diff --git a/CMakeLists.txt b/CMakeLists.txt index 90eaaba09..2087d5db2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,187 +1,191 @@ cmake_minimum_required(VERSION 3.0) project(KDevPlatform) # kdevplatform version set(KDEVPLATFORM_VERSION_MAJOR 5) set(KDEVPLATFORM_VERSION_MINOR 1) set(KDEVPLATFORM_VERSION_PATCH 40) set(KDEVPLATFORM_VERSION "${KDEVPLATFORM_VERSION_MAJOR}.${KDEVPLATFORM_VERSION_MINOR}.${KDEVPLATFORM_VERSION_PATCH}") # plugin versions listed in the .desktop files set(KDEV_PLUGIN_VERSION 29) # Increase this to reset incompatible item-repositories set(KDEV_ITEMREPOSITORY_VERSION 87) # library version / SO version set(KDEVPLATFORM_LIB_SOVERSION 10) # we need some parts of the ECM CMake helpers find_package (ECM "5.14.0" REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${KDevPlatform_SOURCE_DIR}/cmake/modules ${ECM_MODULE_PATH}) include(KDECompilerSettings NO_POLICY_SCOPE) include(ECMAddTests) include(ECMOptionalAddSubdirectory) include(ECMInstallIcons) include(ECMSetupVersion) include(ECMMarkAsTest) include(ECMMarkNonGuiExecutable) include(ECMGenerateHeaders) include(ECMQtDeclareLoggingCategory) include(GenerateExportHeader) include(FeatureSummary) include(WriteBasicConfigVersionFile) include(CheckFunctionExists) include(CMakePackageConfigHelpers) include(KDEInstallDirs) include(KDECMakeSettings) include(KDevPlatformMacros) set(QT_MIN_VERSION "5.5.0") find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED Core DBus Widgets Concurrent Test) +if(BUILD_TESTING) + find_package(Qt5Test ${QT_MIN_VERSION} CONFIG REQUIRED) +endif() find_package(Qt5QuickWidgets ${QT_MIN_VERSION} CONFIG) set_package_properties(Qt5QuickWidgets PROPERTIES PURPOSE "Qt5 QuickWidgets library (part of Qt >=5.3). Required for the Welcome Page plugin." + TYPE RECOMMENDED ) set(KF5_DEP_VERSION "5.18.0") # need KAboutData::fromPluginMetaData in kcoreaddons find_package(KF5 ${KF5_DEP_VERSION} REQUIRED COMPONENTS Archive Config GuiAddons WidgetsAddons IconThemes I18n ItemModels ItemViews JobWidgets KCMUtils KIO NewStuff Notifications NotifyConfig Parts Service Sonnet TextEditor ThreadWeaver WindowSystem Declarative XmlGui ) find_package(Grantlee5 CONFIG) set_package_properties(Grantlee5 PROPERTIES PURPOSE "Grantlee templating library, needed for file templates" URL "http://www.grantlee.org/" TYPE RECOMMENDED) set(Boost_ADDITIONAL_VERSIONS 1.39.0 1.39) find_package(Boost 1.35.0) set_package_properties(Boost PROPERTIES PURPOSE "Boost libraries for enabling the classbrowser" URL "http://www.boost.org" TYPE REQUIRED) add_definitions( -DQT_DEPRECATED_WARNINGS -DQT_DISABLE_DEPRECATED_BEFORE=0x050400 -DQT_NO_SIGNALS_SLOTS_KEYWORDS -DQT_NO_URL_CAST_FROM_STRING -DQT_STRICT_ITERATORS -DQT_USE_FAST_CONCATENATION -DQT_USE_FAST_OPERATOR_PLUS ) function(add_compile_flag_if_supported _flag) unset(_have_flag CACHE) string(REGEX REPLACE "[-=]" "_" _varname ${_flag}) string(TOUPPER ${_varname} _varname) set(_varname "HAVE${_varname}") check_cxx_compiler_flag("${_flag}" "${_varname}") if (${${_varname}}) add_compile_options(${_flag}) endif() endfunction() # Turn off missing-field-initializers warning for GCC to avoid noise from false positives with empty {} # See discussion: http://mail.kde.org/pipermail/kdevelop-devel/2014-February/046910.html add_compile_flag_if_supported(-Wno-missing-field-initializers) add_compile_flag_if_supported(-Werror=undefined-bool-conversion) add_compile_flag_if_supported(-Werror=tautological-undefined-compare) if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") add_compile_flag_if_supported(-Wdocumentation) # This warning is triggered by every call to qCDebug() add_compile_flag_if_supported(-Wno-gnu-zero-variadic-macro-arguments) endif() if (CMAKE_COMPILER_CXX_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") add_compile_flag_if_supported(-pedantic) endif() configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/config-kdevplatform.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kdevplatform.h ) include_directories(${KDevPlatform_SOURCE_DIR} ${KDevPlatform_BINARY_DIR}) string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_TOLOWER) if(CMAKE_BUILD_TYPE_TOLOWER MATCHES "debug" OR CMAKE_BUILD_TYPE_TOLOWER STREQUAL "") set(COMPILER_OPTIMIZATIONS_DISABLED TRUE) else() set(COMPILER_OPTIMIZATIONS_DISABLED FALSE) endif() add_subdirectory(sublime) add_subdirectory(interfaces) add_subdirectory(project) add_subdirectory(language) add_subdirectory(shell) add_subdirectory(util) add_subdirectory(outputview) add_subdirectory(vcs) add_subdirectory(pics) add_subdirectory(debugger) add_subdirectory(documentation) add_subdirectory(serialization) add_subdirectory(template) -add_subdirectory(tests) +if(BUILD_TESTING) + add_subdirectory(tests) +endif() add_subdirectory(plugins) set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KDevPlatform") configure_package_config_file("${CMAKE_CURRENT_SOURCE_DIR}/KDevPlatformConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/KDevPlatformConfig.cmake" INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR} ) ecm_setup_version(${KDEVPLATFORM_VERSION_MAJOR}.${KDEVPLATFORM_VERSION_MINOR}.${KDEVPLATFORM_VERSION_PATCH} VARIABLE_PREFIX KDEVPLATFORM VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/kdevplatform_version.h" PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KDevPlatformConfigVersion.cmake" SOVERSION ${KDEVPLATFORM_LIB_SOVERSION}) install( FILES "${KDevPlatform_BINARY_DIR}/kdevplatform_version.h" "${KDevPlatform_BINARY_DIR}/config-kdevplatform.h" DESTINATION "${KDE_INSTALL_INCLUDEDIR}/kdevplatform" ) install( FILES "${KDevPlatform_BINARY_DIR}/KDevPlatformConfig.cmake" "${KDevPlatform_BINARY_DIR}/KDevPlatformConfigVersion.cmake" cmake/modules/KDevPlatformMacros.cmake DESTINATION "${CMAKECONFIG_INSTALL_DIR}" ) install( EXPORT KDevPlatformTargets DESTINATION "${CMAKECONFIG_INSTALL_DIR}" NAMESPACE KDev:: FILE KDevPlatformTargets.cmake ) # kdebugsettings file install( FILES kdevplatform.categories DESTINATION ${KDE_INSTALL_CONFDIR} ) -include(CTest) - # CTestCustom.cmake has to be in the CTEST_BINARY_DIR. # in the KDE build system, this is the same as CMAKE_BINARY_DIR. configure_file(${CMAKE_SOURCE_DIR}/CTestCustom.cmake ${CMAKE_BINARY_DIR}/CTestCustom.cmake) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/language/backgroundparser/backgroundparser.cpp b/language/backgroundparser/backgroundparser.cpp index 27e945abd..f76cb612e 100644 --- a/language/backgroundparser/backgroundparser.cpp +++ b/language/backgroundparser/backgroundparser.cpp @@ -1,917 +1,941 @@ /* * This file is part of KDevelop * * Copyright 2006 Adam Treat * Copyright 2007 Kris Wong * Copyright 2007-2008 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "backgroundparser.h" #include "qtcompat_p.h" - +#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "parsejob.h" using namespace KDevelop; namespace { const bool separateThreadForHighPriority = true; /** * Elides string in @p path, e.g. "VEEERY/LONG/PATH" -> ".../LONG/PATH" * - probably much faster than QFontMetrics::elidedText() * - we do not need a widget context * - takes path separators into account * * @p width Maximum number of characters * * TODO: Move to kdevutil? */ QString elidedPathLeft(const QString& path, int width) { static const QChar separator = QDir::separator(); static const QString placeholder = QStringLiteral("..."); if (path.size() <= width) { return path; } int start = (path.size() - width) + placeholder.size(); int pos = path.indexOf(separator, start); if (pos == -1) { pos = start; // no separator => just cut off the path at the beginning } Q_ASSERT(path.size() - pos >= 0 && path.size() - pos <= width); QStringRef elidedText = path.rightRef(path.size() - pos); QString result = placeholder; result.append(elidedText); return result; } /** * @return true if @p url is non-empty, valid and has a clean path, false otherwise. */ inline bool isValidURL(const IndexedString& url) { if (url.isEmpty()) { return false; } QUrl original = url.toUrl(); if (!original.isValid() || original.isRelative() || (original.fileName().isEmpty() && original.isLocalFile())) { qCWarning(LANGUAGE) << "INVALID URL ENCOUNTERED:" << url << original; return false; } QUrl cleaned = original.adjusted(QUrl::NormalizePathSegments); return original == cleaned; } } struct DocumentParseTarget { QPointer notifyWhenReady; int priority; TopDUContext::Features features; ParseJob::SequentialProcessingFlags sequentialProcessingFlags; bool operator==(const DocumentParseTarget& rhs) const { return notifyWhenReady == rhs.notifyWhenReady && priority == rhs.priority && features == rhs.features; } }; inline uint qHash(const DocumentParseTarget& target) { return target.features * 7 + target.priority * 13 + target.sequentialProcessingFlags * 17 + reinterpret_cast(target.notifyWhenReady.data()); }; struct DocumentParsePlan { QSet targets; ParseJob::SequentialProcessingFlags sequentialProcessingFlags() const { //Pick the strictest possible flags ParseJob::SequentialProcessingFlags ret = ParseJob::IgnoresSequentialProcessing; foreach(const DocumentParseTarget &target, targets) { ret |= target.sequentialProcessingFlags; } return ret; } int priority() const { //Pick the best priority int ret = BackgroundParser::WorstPriority; foreach(const DocumentParseTarget &target, targets) { if(target.priority < ret) { ret = target.priority; } } return ret; } TopDUContext::Features features() const { //Pick the best features TopDUContext::Features ret = (TopDUContext::Features)0; foreach(const DocumentParseTarget &target, targets) { ret = (TopDUContext::Features) (ret | target.features); } return ret; } QList > notifyWhenReady() const { QList > ret; foreach(const DocumentParseTarget &target, targets) { if(target.notifyWhenReady) ret << target.notifyWhenReady; } return ret; } }; Q_DECLARE_TYPEINFO(DocumentParseTarget, Q_MOVABLE_TYPE); Q_DECLARE_TYPEINFO(DocumentParsePlan, Q_MOVABLE_TYPE); class KDevelop::BackgroundParserPrivate { public: BackgroundParserPrivate(BackgroundParser *parser, ILanguageController *languageController) :m_parser(parser), m_languageController(languageController), m_shuttingDown(false), m_mutex(QMutex::Recursive) { parser->d = this; //Set this so we can safely call back BackgroundParser from within loadSettings() m_timer.setSingleShot(true); m_progressTimer.setSingleShot(true); m_progressTimer.setInterval(500); ThreadWeaver::setDebugLevel(true, 1); QObject::connect(&m_timer, &QTimer::timeout, m_parser, &BackgroundParser::parseDocuments); QObject::connect(&m_progressTimer, &QTimer::timeout, m_parser, &BackgroundParser::updateProgressBar); } void startTimerThreadSafe(int delay) { QMetaObject::invokeMethod(m_parser, "startTimer", Qt::QueuedConnection, Q_ARG(int, delay)); } ~BackgroundParserPrivate() { m_weaver.resume(); m_weaver.finish(); } // Non-mutex guarded functions, only call with m_mutex acquired. int currentBestRunningPriority() const { int bestRunningPriority = BackgroundParser::WorstPriority; for (const auto* decorator : m_parseJobs) { const ParseJob* parseJob = dynamic_cast(decorator->job()); Q_ASSERT(parseJob); if (parseJob->respectsSequentialProcessing() && parseJob->parsePriority() < bestRunningPriority) { bestRunningPriority = parseJob->parsePriority(); } } return bestRunningPriority; } IndexedString nextDocumentToParse() const { // Before starting a new job, first wait for all higher-priority ones to finish. // That way, parse job priorities can be used for dependency handling. const int bestRunningPriority = currentBestRunningPriority(); for (auto it1 = m_documentsForPriority.begin(); it1 != m_documentsForPriority.end(); ++it1 ) { const auto priority = it1.key(); if(priority > m_neededPriority) break; //The priority is not good enough to be processed right now if (m_parseJobs.count() >= m_threads && priority > BackgroundParser::NormalPriority && !specialParseJob) { break; //The additional parsing thread is reserved for higher priority parsing } for (const auto& url : it1.value()) { // When a document is scheduled for parsing while it is being parsed, it will be parsed // again once the job finished, but not now. if (m_parseJobs.contains(url)) { continue; } Q_ASSERT(m_documents.contains(url)); const auto& parsePlan = m_documents[url]; // If the current job requires sequential processing, but not all jobs with a better priority have been // completed yet, it will not be created now. if ( parsePlan.sequentialProcessingFlags() & ParseJob::RequiresSequentialProcessing && parsePlan.priority() > bestRunningPriority ) { continue; } return url; } } return {}; } /** * Create a single delayed parse job * * E.g. jobs for documents which have been changed by the user, but also to * handle initial startup where we parse all project files. */ void parseDocumentsInternal() { if(m_shuttingDown) return; //Only create parse-jobs for up to thread-count * 2 documents, so we don't fill the memory unnecessarily if (m_parseJobs.count() >= m_threads+1 || (m_parseJobs.count() >= m_threads && !separateThreadForHighPriority)) { return; } const auto& url = nextDocumentToParse(); if (!url.isEmpty()) { qCDebug(LANGUAGE) << "creating parse-job" << url << "new count of active parse-jobs:" << m_parseJobs.count() + 1; const QString elidedPathString = elidedPathLeft(url.str(), 70); emit m_parser->showMessage(m_parser, i18n("Parsing: %1", elidedPathString)); ThreadWeaver::QObjectDecorator* decorator = nullptr; { // copy shared data before unlocking the mutex const auto parsePlanConstIt = m_documents.constFind(url); const DocumentParsePlan parsePlan = *parsePlanConstIt; // we must not lock the mutex while creating a parse job // this could in turn lock e.g. the DUChain and then // we have a classic lock order inversion (since, usually, // we lock first the duchain and then our background parser // mutex) // see also: https://bugs.kde.org/show_bug.cgi?id=355100 m_mutex.unlock(); decorator = createParseJob(url, parsePlan); m_mutex.lock(); } // iterator might get invalid during the time we didn't have the lock // search again const auto parsePlanIt = m_documents.find(url); if (parsePlanIt != m_documents.end()) { // Remove all mentions of this document. for (const auto& target : qAsConst(parsePlanIt->targets)) { m_documentsForPriority[target.priority].remove(url); } m_documents.erase(parsePlanIt); } else { qCWarning(LANGUAGE) << "Document got removed during parse job creation:" << url; } if (decorator) { if(m_parseJobs.count() == m_threads+1 && !specialParseJob) specialParseJob = decorator; //This parse-job is allocated into the reserved thread m_parseJobs.insert(url, decorator); m_weaver.enqueue(ThreadWeaver::JobPointer(decorator)); } else { --m_maxParseJobs; } if (!m_documents.isEmpty()) { // Only try creating one parse-job at a time, else we might iterate through thousands of files // without finding a language-support, and block the UI for a long time. QMetaObject::invokeMethod(m_parser, "parseDocuments", Qt::QueuedConnection); } else { // make sure we cleaned up properly // TODO: also empty m_documentsForPriority when m_documents is empty? or do we want to keep capacity? Q_ASSERT(std::none_of(m_documentsForPriority.constBegin(), m_documentsForPriority.constEnd(), [] (const QSet& docs) { return !docs.isEmpty(); })); } } m_parser->updateProgressData(); } // NOTE: you must not access any of the data structures that are protected by any of the // background parser internal mutexes in this method // see also: https://bugs.kde.org/show_bug.cgi?id=355100 ThreadWeaver::QObjectDecorator* createParseJob(const IndexedString& url, const DocumentParsePlan& parsePlan) { ///FIXME: use IndexedString in the other APIs as well! Esp. for createParseJob! QUrl qUrl = url.toUrl(); const auto languages = m_languageController->languagesForUrl(qUrl); const auto& notifyWhenReady = parsePlan.notifyWhenReady(); for (const auto language : languages) { if (!language) { qCWarning(LANGUAGE) << "got zero language for" << qUrl; continue; } ParseJob* job = language->createParseJob(url); if (!job) { continue; // Language part did not produce a valid ParseJob. } job->setParsePriority(parsePlan.priority()); job->setMinimumFeatures(parsePlan.features()); job->setNotifyWhenReady(notifyWhenReady); job->setSequentialProcessingFlags(parsePlan.sequentialProcessingFlags()); ThreadWeaver::QObjectDecorator* decorator = new ThreadWeaver::QObjectDecorator(job); QObject::connect(decorator, &ThreadWeaver::QObjectDecorator::done, m_parser, &BackgroundParser::parseComplete); QObject::connect(decorator, &ThreadWeaver::QObjectDecorator::failed, m_parser, &BackgroundParser::parseComplete); QObject::connect(job, &ParseJob::progress, m_parser, &BackgroundParser::parseProgress, Qt::QueuedConnection); // TODO more thinking required here to support multiple parse jobs per url (where multiple language plugins want to parse) return decorator; } if (languages.isEmpty()) qCDebug(LANGUAGE) << "found no languages for url" << qUrl; else qCDebug(LANGUAGE) << "could not create parse-job for url" << qUrl; //Notify that we failed for (const auto& n : notifyWhenReady) { if (!n) { continue; } QMetaObject::invokeMethod(n.data(), "updateReady", Qt::QueuedConnection, Q_ARG(KDevelop::IndexedString, url), Q_ARG(KDevelop::ReferencedTopDUContext, ReferencedTopDUContext())); } return nullptr; } void loadSettings() { ///@todo re-load settings when they have been changed! Q_ASSERT(ICore::self()->activeSession()); KConfigGroup config(ICore::self()->activeSession()->config(), "Background Parser"); // stay backwards compatible KConfigGroup oldConfig(KSharedConfig::openConfig(), "Background Parser"); #define BACKWARDS_COMPATIBLE_ENTRY(entry, default) \ config.readEntry(entry, oldConfig.readEntry(entry, default)) m_delay = BACKWARDS_COMPATIBLE_ENTRY("Delay", 500); m_timer.setInterval(m_delay); m_threads = 0; if (qEnvironmentVariableIsSet("KDEV_BACKGROUNDPARSER_MAXTHREADS")) { m_parser->setThreadCount(qgetenv("KDEV_BACKGROUNDPARSER_MAXTHREADS").toInt()); } else { m_parser->setThreadCount(BACKWARDS_COMPATIBLE_ENTRY("Number of Threads", QThread::idealThreadCount())); } resume(); if (BACKWARDS_COMPATIBLE_ENTRY("Enabled", true)) { m_parser->enableProcessing(); } else { m_parser->disableProcessing(); } } void suspend() { qCDebug(LANGUAGE) << "Suspending background parser"; bool s = m_weaver.state()->stateId() == ThreadWeaver::Suspended || m_weaver.state()->stateId() == ThreadWeaver::Suspending; if (s) { // Already suspending qCWarning(LANGUAGE) << "Already suspended or suspending"; return; } m_timer.stop(); m_weaver.suspend(); } void resume() { bool s = m_weaver.state()->stateId() == ThreadWeaver::Suspended || m_weaver.state()->stateId() == ThreadWeaver::Suspending; if (m_timer.isActive() && !s) { // Not suspending return; } m_timer.start(m_delay); m_weaver.resume(); } BackgroundParser *m_parser; ILanguageController* m_languageController; //Current parse-job that is executed in the additional thread QPointer specialParseJob; QTimer m_timer; int m_delay = 500; int m_threads = 1; bool m_shuttingDown; // A list of documents that are planned to be parsed, and their priority QHash m_documents; // The documents ordered by priority QMap > m_documentsForPriority; // Currently running parse jobs QHash m_parseJobs; // The url for each managed document. Those may temporarily differ from the real url. QHash m_managedTextDocumentUrls; // Projects currently in progress of loading QSet m_loadingProjects; ThreadWeaver::Queue m_weaver; // generic high-level mutex QMutex m_mutex; // local mutex only protecting m_managed QMutex m_managedMutex; // A change tracker for each managed document QHash m_managed; int m_maxParseJobs = 0; int m_doneParseJobs = 0; QHash m_jobProgress; /// The minimum priority needed for processed jobs int m_neededPriority = BackgroundParser::WorstPriority; int m_progressMax = 0; int m_progressDone = 0; QTimer m_progressTimer; }; BackgroundParser::BackgroundParser(ILanguageController *languageController) : QObject(languageController), d(new BackgroundParserPrivate(this, languageController)) { Q_ASSERT(ICore::self()->documentController()); connect(ICore::self()->documentController(), &IDocumentController::documentLoaded, this, &BackgroundParser::documentLoaded); connect(ICore::self()->documentController(), &IDocumentController::documentUrlChanged, this, &BackgroundParser::documentUrlChanged); connect(ICore::self()->documentController(), &IDocumentController::documentClosed, this, &BackgroundParser::documentClosed); connect(ICore::self(), &ICore::aboutToShutdown, this, &BackgroundParser::aboutToQuit); bool connected = QObject::connect(ICore::self()->projectController(), &IProjectController::projectAboutToBeOpened, this, &BackgroundParser::projectAboutToBeOpened); Q_ASSERT(connected); connected = QObject::connect(ICore::self()->projectController(), &IProjectController::projectOpened, this, &BackgroundParser::projectOpened); Q_ASSERT(connected); connected = QObject::connect(ICore::self()->projectController(), &IProjectController::projectOpeningAborted, this, &BackgroundParser::projectOpeningAborted); Q_ASSERT(connected); Q_UNUSED(connected); } void BackgroundParser::aboutToQuit() { d->m_shuttingDown = true; } BackgroundParser::~BackgroundParser() { delete d; } QString BackgroundParser::statusName() const { return i18n("Background Parser"); } void BackgroundParser::loadSettings() { d->loadSettings(); } void BackgroundParser::parseProgress(KDevelop::ParseJob* job, float value, QString text) { Q_UNUSED(text) d->m_jobProgress[job] = value; updateProgressData(); } void BackgroundParser::revertAllRequests(QObject* notifyWhenReady) { QMutexLocker lock(&d->m_mutex); for (auto it = d->m_documents.begin(); it != d->m_documents.end(); ) { d->m_documentsForPriority[it.value().priority()].remove(it.key()); foreach ( const DocumentParseTarget& target, (*it).targets ) { if ( notifyWhenReady && target.notifyWhenReady.data() == notifyWhenReady ) { (*it).targets.remove(target); } } if((*it).targets.isEmpty()) { it = d->m_documents.erase(it); --d->m_maxParseJobs; continue; } d->m_documentsForPriority[it.value().priority()].insert(it.key()); ++it; } } void BackgroundParser::addDocument(const IndexedString& url, TopDUContext::Features features, int priority, QObject* notifyWhenReady, ParseJob::SequentialProcessingFlags flags, int delay) { // qCDebug(LANGUAGE) << "BackgroundParser::addDocument" << url.toUrl(); Q_ASSERT(isValidURL(url)); QMutexLocker lock(&d->m_mutex); { DocumentParseTarget target; target.priority = priority; target.features = features; target.sequentialProcessingFlags = flags; target.notifyWhenReady = QPointer(notifyWhenReady); auto it = d->m_documents.find(url); if (it != d->m_documents.end()) { //Update the stored plan d->m_documentsForPriority[it.value().priority()].remove(url); it.value().targets << target; d->m_documentsForPriority[it.value().priority()].insert(url); }else{ // qCDebug(LANGUAGE) << "BackgroundParser::addDocument: queuing" << cleanedUrl; d->m_documents[url].targets << target; d->m_documentsForPriority[d->m_documents[url].priority()].insert(url); ++d->m_maxParseJobs; //So the progress-bar waits for this document } if ( delay == ILanguageSupport::DefaultDelay ) { delay = d->m_delay; } d->startTimerThreadSafe(delay); } } void BackgroundParser::removeDocument(const IndexedString& url, QObject* notifyWhenReady) { Q_ASSERT(isValidURL(url)); QMutexLocker lock(&d->m_mutex); if(d->m_documents.contains(url)) { d->m_documentsForPriority[d->m_documents[url].priority()].remove(url); foreach(const DocumentParseTarget& target, d->m_documents[url].targets) { if(target.notifyWhenReady.data() == notifyWhenReady) { d->m_documents[url].targets.remove(target); } } if(d->m_documents[url].targets.isEmpty()) { d->m_documents.remove(url); --d->m_maxParseJobs; }else{ //Insert with an eventually different priority d->m_documentsForPriority[d->m_documents[url].priority()].insert(url); } } } void BackgroundParser::parseDocuments() { if (!d->m_loadingProjects.empty()) { startTimer(d->m_delay); return; } QMutexLocker lock(&d->m_mutex); d->parseDocumentsInternal(); } void BackgroundParser::parseComplete(const ThreadWeaver::JobPointer& job) { auto decorator = dynamic_cast(job.data()); Q_ASSERT(decorator); ParseJob* parseJob = dynamic_cast(decorator->job()); Q_ASSERT(parseJob); emit parseJobFinished(parseJob); { QMutexLocker lock(&d->m_mutex); d->m_parseJobs.remove(parseJob->document()); d->m_jobProgress.remove(parseJob); ++d->m_doneParseJobs; updateProgressData(); } //Continue creating more parse-jobs QMetaObject::invokeMethod(this, "parseDocuments", Qt::QueuedConnection); } void BackgroundParser::disableProcessing() { setNeededPriority(BestPriority); } void BackgroundParser::enableProcessing() { setNeededPriority(WorstPriority); } int BackgroundParser::priorityForDocument(const IndexedString& url) const { Q_ASSERT(isValidURL(url)); QMutexLocker lock(&d->m_mutex); return d->m_documents[url].priority(); } bool BackgroundParser::isQueued(const IndexedString& url) const { Q_ASSERT(isValidURL(url)); QMutexLocker lock(&d->m_mutex); return d->m_documents.contains(url); } int BackgroundParser::queuedCount() const { QMutexLocker lock(&d->m_mutex); return d->m_documents.count(); } bool BackgroundParser::isIdle() const { QMutexLocker lock(&d->m_mutex); return d->m_documents.isEmpty() && d->m_weaver.isIdle(); } void BackgroundParser::setNeededPriority(int priority) { QMutexLocker lock(&d->m_mutex); d->m_neededPriority = priority; d->startTimerThreadSafe(d->m_delay); } void BackgroundParser::abortAllJobs() { qCDebug(LANGUAGE) << "Aborting all parse jobs"; d->m_weaver.requestAbort(); } void BackgroundParser::suspend() { d->suspend(); emit hideProgress(this); } void BackgroundParser::resume() { d->resume(); updateProgressData(); } void BackgroundParser::updateProgressData() { if (d->m_doneParseJobs >= d->m_maxParseJobs) { if(d->m_doneParseJobs > d->m_maxParseJobs) { qCDebug(LANGUAGE) << "m_doneParseJobs larger than m_maxParseJobs:" << d->m_doneParseJobs << d->m_maxParseJobs; } d->m_doneParseJobs = 0; d->m_maxParseJobs = 0; } else { float additionalProgress = 0; for (auto it = d->m_jobProgress.constBegin(); it != d->m_jobProgress.constEnd(); ++it) { additionalProgress += *it; } d->m_progressMax = d->m_maxParseJobs*1000; d->m_progressDone = (additionalProgress + d->m_doneParseJobs)*1000; if (!d->m_progressTimer.isActive()) { d->m_progressTimer.start(); } } // Cancel progress updating and hide progress-bar when parsing is done. if(d->m_doneParseJobs == d->m_maxParseJobs || (d->m_neededPriority == BackgroundParser::BestPriority && d->m_weaver.queueLength() == 0)) { if (d->m_progressTimer.isActive()) { d->m_progressTimer.stop(); } emit d->m_parser->hideProgress(d->m_parser); } } ParseJob* BackgroundParser::parseJobForDocument(const IndexedString& document) const { Q_ASSERT(isValidURL(document)); QMutexLocker lock(&d->m_mutex); auto decorator = d->m_parseJobs.value(document); return decorator ? dynamic_cast(decorator->job()) : nullptr; } void BackgroundParser::setThreadCount(int threadCount) { if (d->m_threads != threadCount) { d->m_threads = threadCount; d->m_weaver.setMaximumNumberOfThreads(d->m_threads+1); //1 Additional thread for high-priority parsing } } int BackgroundParser::threadCount() const { return d->m_threads; } void BackgroundParser::setDelay(int milliseconds) { if (d->m_delay != milliseconds) { d->m_delay = milliseconds; d->m_timer.setInterval(d->m_delay); } } QList< IndexedString > BackgroundParser::managedDocuments() { QMutexLocker l(&d->m_managedMutex); return d->m_managed.keys(); } + +bool BackgroundParser::waitForIdle() const +{ + QList runningParseJobsUrls; + forever { + { + QMutexLocker lock(&d->m_mutex); + if (d->m_parseJobs.isEmpty()) { + qCDebug(LANGUAGE) << "All parse jobs done" << d->m_parseJobs.keys(); + return true; + } + + if (d->m_parseJobs.size() != runningParseJobsUrls.size()) { + runningParseJobsUrls = d->m_parseJobs.keys(); + qCDebug(LANGUAGE) << "Waiting for background parser to get in idle state... -- the following parse jobs are still running:" << runningParseJobsUrls; + } + } + + QCoreApplication::processEvents(); + QThread::msleep(100); + } + return false; +} + DocumentChangeTracker* BackgroundParser::trackerForUrl(const KDevelop::IndexedString& url) const { if (url.isEmpty()) { // this happens e.g. when setting the final location of a problem that is not // yet associated with a top ctx. return nullptr; } if ( !isValidURL(url) ) { qCWarning(LANGUAGE) << "Tracker requested for invalild URL:" << url.toUrl(); } Q_ASSERT(isValidURL(url)); QMutexLocker l(&d->m_managedMutex); return d->m_managed.value(url, nullptr); } void BackgroundParser::documentClosed(IDocument* document) { QMutexLocker l(&d->m_mutex); if(document->textDocument()) { KTextEditor::Document* textDocument = document->textDocument(); if(!d->m_managedTextDocumentUrls.contains(textDocument)) return; // Probably the document had an invalid url, and thus it wasn't added to the background parser Q_ASSERT(d->m_managedTextDocumentUrls.contains(textDocument)); IndexedString url(d->m_managedTextDocumentUrls[textDocument]); QMutexLocker l2(&d->m_managedMutex); Q_ASSERT(d->m_managed.contains(url)); qCDebug(LANGUAGE) << "removing" << url.str() << "from background parser"; delete d->m_managed[url]; d->m_managedTextDocumentUrls.remove(textDocument); d->m_managed.remove(url); } } void BackgroundParser::documentLoaded( IDocument* document ) { QMutexLocker l(&d->m_mutex); if(document->textDocument() && document->textDocument()->url().isValid()) { KTextEditor::Document* textDocument = document->textDocument(); IndexedString url(document->url()); // Some debugging because we had issues with this QMutexLocker l2(&d->m_managedMutex); if(d->m_managed.contains(url) && d->m_managed[url]->document() == textDocument) { qCDebug(LANGUAGE) << "Got redundant documentLoaded from" << document->url() << textDocument; return; } qCDebug(LANGUAGE) << "Creating change tracker for " << document->url(); Q_ASSERT(!d->m_managed.contains(url)); Q_ASSERT(!d->m_managedTextDocumentUrls.contains(textDocument)); d->m_managedTextDocumentUrls[textDocument] = url; d->m_managed.insert(url, new DocumentChangeTracker(textDocument)); }else{ qCDebug(LANGUAGE) << "NOT creating change tracker for" << document->url(); } } void BackgroundParser::documentUrlChanged(IDocument* document) { documentClosed(document); // Only call documentLoaded if the file wasn't renamed to a filename that is already tracked. if(document->textDocument() && !trackerForUrl(IndexedString(document->textDocument()->url()))) documentLoaded(document); } void BackgroundParser::startTimer(int delay) { d->m_timer.start(delay); } void BackgroundParser::projectAboutToBeOpened(IProject* project) { d->m_loadingProjects.insert(project); } void BackgroundParser::projectOpened(IProject* project) { d->m_loadingProjects.remove(project); } void BackgroundParser::projectOpeningAborted(IProject* project) { d->m_loadingProjects.remove(project); } void BackgroundParser::updateProgressBar() { emit showProgress(this, 0, d->m_progressMax, d->m_progressDone); } diff --git a/language/backgroundparser/backgroundparser.h b/language/backgroundparser/backgroundparser.h index 57bb6d5d1..faf6c7061 100644 --- a/language/backgroundparser/backgroundparser.h +++ b/language/backgroundparser/backgroundparser.h @@ -1,239 +1,241 @@ /* * This file is part of KDevelop * * Copyright 2006 Adam Treat * Copyright 2007 Kris Wong * Copyright 2007-2008 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_BACKGROUNDPARSER_H #define KDEVPLATFORM_BACKGROUNDPARSER_H #include #include #include #include #include "parsejob.h" namespace ThreadWeaver { class Job; class QObjectDecorator; class Weaver; } namespace KDevelop { class DocumentChangeTracker; class IDocument; class IProject; class ILanguageController; class ParserDependencyPolicy; /** * This class handles the creation of parse jobs for given file URLs. * * For performance reasons you must always use clean, canonical URLs. If you do not do that, * issues might arise (and the debug build will assert). */ class KDEVPLATFORMLANGUAGE_EXPORT BackgroundParser : public QObject, public IStatus { Q_OBJECT Q_INTERFACES( KDevelop::IStatus ) public: explicit BackgroundParser(ILanguageController *languageController); ~BackgroundParser() override; QString statusName() const override; enum { BestPriority = -10000, ///Best possible job-priority. No jobs should actually have this. NormalPriority = 0, ///Standard job-priority. This priority is used for parse-jobs caused by document-editing/opening. ///There is an additional parsing-thread reserved for jobs with this and better priority, to improve responsiveness. InitialParsePriority = 10000, ///Priority used when adding file on project loading WorstPriority = 100000 ///Worst possible job-priority. }; /** * Queries the background parser as to whether there is currently * a parse job for @p document, and if so, returns it. * * This may not contain all of the parse jobs that are intended * unless you call in from your job's ThreadWeaver::Job::aboutToBeQueued() * function. */ ParseJob* parseJobForDocument(const IndexedString& document) const; /** * Set how many ThreadWeaver threads the background parser should set up and use. */ void setThreadCount(int threadCount); /** * Return how many ThreadWeaver threads the background parser should set up and use. */ int threadCount() const; /** * Set the delay in milliseconds before the background parser starts parsing. */ void setDelay(int milliseconds); /** * Returns all documents that were added through addManagedTopRange. This is typically the currently * open documents. */ QList managedDocuments(); /** * Returns the tracker for the given url if the document is being tracked, else returns zero. * This function is thread-safe, but the returned object also isn't, so you must not use it * when you're in a background thread without the foreground lock acquired. * */ DocumentChangeTracker* trackerForUrl(const IndexedString& url) const; + bool waitForIdle() const; + Q_SIGNALS: /** * Emitted whenever a document parse-job has finished. * The job contains the du-chain(if one was created) etc. * * The job is deleted after this signal has been emitted. Receivers should not hold * references to it. * * Note that if you want to be get updated for all DUChain updates, use * DUChain::updateReady instead, as a single ParseJob may update multiple * DUChain top contexts. * * @sa DUChain::updateReady */ void parseJobFinished(KDevelop::ParseJob* job); // Implementations of IStatus signals void clearMessage( KDevelop::IStatus* ) override; void showMessage( KDevelop::IStatus*, const QString & message, int timeout = 0) override; void hideProgress( KDevelop::IStatus* ) override; void showProgress( KDevelop::IStatus*, int minimum, int maximum, int value) override; void showErrorMessage( const QString&, int ) override; public Q_SLOTS: /** * Aborts all parse jobs */ void abortAllJobs(); /** * Suspends execution of the background parser */ void suspend(); /** * Resumes execution of the background parser */ void resume(); /// Reverts all requests that were made for the given notification-target. /// priorities and requested features will be reverted as well. /// When @p notifyWhenReady is set to a nullptr, all requests will be reverted. void revertAllRequests(QObject* notifyWhenReady); /** * Queues up the @p url to be parsed. * @p features The minimum features that should be computed for this top-context * @p priority A value that manages the order of parsing. Documents with lowest priority are parsed first. * @param notifyWhenReady An optional pointer to a QObject that should contain a slot * "void updateReady(KDevelop::IndexedString url, KDevelop::ReferencedTopDUContext topContext)". * The notification is guaranteed to be called once for each call to addDocument. The given top-context * may be invalid if the update failed. * @param flags Flags indicating how the document should be treated in the queue * @param delay_ms The delay in milliseconds to add the job with, or one of the values of the * ILanguageSupport::ReparseDelaySpecialValues enum. */ void addDocument(const IndexedString& url, TopDUContext::Features features = TopDUContext::VisibleDeclarationsAndContexts, int priority = 0, QObject* notifyWhenReady = nullptr, ParseJob::SequentialProcessingFlags flags = ParseJob::IgnoresSequentialProcessing, int delay_ms = ILanguageSupport::DefaultDelay); /** * Removes the @p url that is registered for the given notification from the url. * * @param notifyWhenReady Notifier the document was added with. */ void removeDocument(const IndexedString& url, QObject* notifyWhenReady = nullptr); /** * Forces the current queue to be parsed. */ void parseDocuments(); void updateProgressData(); /// Disables processing for all jobs that have a worse priority than @p priority /// This can be used to temporarily limit the processing to only the most important jobs. /// To only enable processing for important jobs, call setNeededPriority(0). /// This should only be used to temporarily alter the processing. A progress-bar /// will still be shown for the not yet processed jobs. void setNeededPriority(int priority); /// Disables all processing of new jobs, equivalent to setNeededPriority(BestPriority) void disableProcessing(); /// Enables all processing of new jobs, equivalent to setNeededPriority(WorstPriority) void enableProcessing(); /// Returns true if the given url is queued for parsing bool isQueued(const IndexedString& url) const; /// Retrieve the current priority for the given URL. /// You need to check whether @param url is queued before calling this function. int priorityForDocument(const IndexedString& url) const; /// Returns the number of queued jobs (not yet running nor submitted to ThreadWeaver) int queuedCount() const; /// Returns true if there are no jobs running nor queued anywhere bool isIdle() const; void documentClosed(KDevelop::IDocument*); void documentLoaded(KDevelop::IDocument*); void documentUrlChanged(KDevelop::IDocument*); void loadSettings(); protected Q_SLOTS: void parseComplete(const ThreadWeaver::JobPointer& job); void parseProgress(KDevelop::ParseJob*, float value, QString text); void startTimer(int delay); void aboutToQuit(); void updateProgressBar(); private: friend class BackgroundParserPrivate; class BackgroundParserPrivate *d; private Q_SLOTS: /// Tracking of projects in state of loading. void projectAboutToBeOpened(KDevelop::IProject* project); void projectOpened(KDevelop::IProject* project); void projectOpeningAborted(KDevelop::IProject* project); }; } #endif diff --git a/language/duchain/ducontext.cpp b/language/duchain/ducontext.cpp index 843f3f538..b7bd55aca 100644 --- a/language/duchain/ducontext.cpp +++ b/language/duchain/ducontext.cpp @@ -1,1707 +1,1707 @@ /* This is part of KDevelop Copyright 2006 Hamish Rodda Copyright 2007-2009 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "ducontext.h" #include #include #include #include #include "ducontextdata.h" #include "declaration.h" #include "duchain.h" #include "duchainlock.h" #include "use.h" #include "identifier.h" #include "topducontext.h" #include "persistentsymboltable.h" #include "aliasdeclaration.h" #include "namespacealiasdeclaration.h" #include "abstractfunctiondeclaration.h" #include "duchainregister.h" #include "topducontextdynamicdata.h" #include "importers.h" #include "uses.h" #include "navigation/abstractdeclarationnavigationcontext.h" #include "navigation/abstractnavigationwidget.h" #include "ducontextdynamicdata.h" #include // maximum depth for DUContext::findDeclarationsInternal searches const uint maxParentDepth = 20; using namespace KTextEditor; #ifndef NDEBUG #define ENSURE_CAN_WRITE_(x) {if(x->inDUChain()) { ENSURE_CHAIN_WRITE_LOCKED }} #define ENSURE_CAN_READ_(x) {if(x->inDUChain()) { ENSURE_CHAIN_READ_LOCKED }} #else #define ENSURE_CAN_WRITE_(x) #define ENSURE_CAN_READ_(x) #endif QDebug operator<<(QDebug dbg, const KDevelop::DUContext::Import& import) { QDebugStateSaver saver(dbg); dbg.nospace() << "Import(" << import.indexedContext().data() << ')'; return dbg; } namespace KDevelop { DEFINE_LIST_MEMBER_HASH(DUContextData, m_childContexts, LocalIndexedDUContext) DEFINE_LIST_MEMBER_HASH(DUContextData, m_importers, IndexedDUContext) DEFINE_LIST_MEMBER_HASH(DUContextData, m_importedContexts, DUContext::Import) DEFINE_LIST_MEMBER_HASH(DUContextData, m_localDeclarations, LocalIndexedDeclaration) DEFINE_LIST_MEMBER_HASH(DUContextData, m_uses, Use) REGISTER_DUCHAIN_ITEM(DUContext); DUChainVisitor::~DUChainVisitor() { } /** * We leak here, to prevent a possible crash during destruction, as the destructor * of Identifier is not safe to be called after the duchain has been destroyed */ const Identifier& globalImportIdentifier() { static const Identifier globalImportIdentifierObject(QStringLiteral("{...import...}")); return globalImportIdentifierObject; } const Identifier& globalAliasIdentifier() { static const Identifier globalAliasIdentifierObject(QStringLiteral("{...alias...}")); return globalAliasIdentifierObject; } const IndexedIdentifier& globalIndexedImportIdentifier() { static const IndexedIdentifier id(globalImportIdentifier()); return id; } const IndexedIdentifier& globalIndexedAliasIdentifier() { static const IndexedIdentifier id(globalAliasIdentifier()); return id; } void DUContext::rebuildDynamicData(DUContext* parent, uint ownIndex) { Q_ASSERT(!parent || ownIndex); m_dynamicData->m_topContext = parent ? parent->topContext() : static_cast(this); m_dynamicData->m_indexInTopContext = ownIndex; m_dynamicData->m_parentContext = DUContextPointer(parent); m_dynamicData->m_context = this; m_dynamicData->m_childContexts.clear(); m_dynamicData->m_childContexts.reserve(d_func()->m_childContextsSize()); FOREACH_FUNCTION(const LocalIndexedDUContext& ctx, d_func()->m_childContexts) { m_dynamicData->m_childContexts << ctx.data(m_dynamicData->m_topContext); } m_dynamicData->m_localDeclarations.clear(); m_dynamicData->m_localDeclarations.reserve(d_func()->m_localDeclarationsSize()); FOREACH_FUNCTION(const LocalIndexedDeclaration& idx, d_func()->m_localDeclarations) { auto declaration = idx.data(m_dynamicData->m_topContext); if (!declaration) { qCWarning(LANGUAGE) << "child declaration number" << idx.localIndex() << "of" << d_func_dynamic()->m_localDeclarationsSize() << "is invalid"; continue; } m_dynamicData->m_localDeclarations << declaration; } DUChainBase::rebuildDynamicData(parent, ownIndex); } DUContextData::DUContextData() : m_inSymbolTable(false) , m_anonymousInParent(false) , m_propagateDeclarations(false) { initializeAppendedLists(); } DUContextData::~DUContextData() { freeAppendedLists(); } DUContextData::DUContextData(const DUContextData& rhs) : DUChainBaseData(rhs) , m_inSymbolTable(rhs.m_inSymbolTable) , m_anonymousInParent(rhs.m_anonymousInParent) , m_propagateDeclarations(rhs.m_propagateDeclarations) { initializeAppendedLists(); copyListsFrom(rhs); m_scopeIdentifier = rhs.m_scopeIdentifier; m_contextType = rhs.m_contextType; m_owner = rhs.m_owner; } DUContextDynamicData::DUContextDynamicData(DUContext* d) : m_topContext(nullptr) , m_indexInTopContext(0) , m_context(d) { } void DUContextDynamicData::scopeIdentifier(bool includeClasses, QualifiedIdentifier& target) const { if (m_parentContext) m_parentContext->m_dynamicData->scopeIdentifier(includeClasses, target); if (includeClasses || d_func()->m_contextType != DUContext::Class) target += d_func()->m_scopeIdentifier; } bool DUContextDynamicData::imports(const DUContext* context, const TopDUContext* source, QSet* recursionGuard) const { if( this == context->m_dynamicData ) return true; if (recursionGuard->contains(this)) { return false; } recursionGuard->insert(this); FOREACH_FUNCTION( const DUContext::Import& ctx, d_func()->m_importedContexts ) { DUContext* import = ctx.context(source); if(import == context || (import && import->m_dynamicData->imports(context, source, recursionGuard))) return true; } return false; } inline bool isContextTemporary(uint index) { return index > (0xffffffff/2); } void DUContextDynamicData::addDeclaration( Declaration * newDeclaration ) { // The definition may not have its identifier set when it's assigned... // allow dupes here, TODO catch the error elsewhere //If this context is temporary, added declarations should be as well, and viceversa Q_ASSERT(isContextTemporary(m_indexInTopContext) == isContextTemporary(newDeclaration->ownIndex())); CursorInRevision start = newDeclaration->range().start; bool inserted = false; ///@todo Do binary search to find the position for (int i = m_localDeclarations.size() - 1; i >= 0; --i) { Declaration* child = m_localDeclarations[i]; Q_ASSERT(d_func()->m_localDeclarations()[i].data(m_topContext) == child); if(child == newDeclaration) return; //TODO: All declarations in a macro will have the same empty range, and just get appended //that may not be Good Enough in complex cases. if (start >= child->range().start) { m_localDeclarations.insert(i + 1, newDeclaration); d_func_dynamic()->m_localDeclarationsList().insert(i+1, newDeclaration); Q_ASSERT(d_func()->m_localDeclarations()[i+1].data(m_topContext) == newDeclaration); inserted = true; break; } } if (!inserted) { // We haven't found any child that is before this one, so prepend it m_localDeclarations.insert(0, newDeclaration); d_func_dynamic()->m_localDeclarationsList().insert(0, newDeclaration); Q_ASSERT(d_func()->m_localDeclarations()[0].data(m_topContext) == newDeclaration); } } bool DUContextDynamicData::removeDeclaration(Declaration* declaration) { const int idx = m_localDeclarations.indexOf(declaration); if (idx != -1) { Q_ASSERT(d_func()->m_localDeclarations()[idx].data(m_topContext) == declaration); m_localDeclarations.remove(idx); d_func_dynamic()->m_localDeclarationsList().remove(idx); return true; } else { Q_ASSERT(d_func_dynamic()->m_localDeclarationsList().indexOf(LocalIndexedDeclaration(declaration)) == -1); return false; } } void DUContextDynamicData::addChildContext( DUContext * context ) { // Internal, don't need to assert a lock Q_ASSERT(!context->m_dynamicData->m_parentContext || context->m_dynamicData->m_parentContext.data()->m_dynamicData == this ); LocalIndexedDUContext indexed(context->m_dynamicData->m_indexInTopContext); //If this context is temporary, added declarations should be as well, and viceversa Q_ASSERT(isContextTemporary(m_indexInTopContext) == isContextTemporary(indexed.localIndex())); bool inserted = false; int childCount = m_childContexts.size(); for (int i = childCount-1; i >= 0; --i) {///@todo Do binary search to find the position DUContext* child = m_childContexts[i]; Q_ASSERT(d_func_dynamic()->m_childContexts()[i] == LocalIndexedDUContext(child)); if (context == child) return; if (context->range().start >= child->range().start) { m_childContexts.insert(i+1, context); d_func_dynamic()->m_childContextsList().insert(i+1, indexed); context->m_dynamicData->m_parentContext = m_context; inserted = true; break; } } if( !inserted ) { m_childContexts.insert(0, context); d_func_dynamic()->m_childContextsList().insert(0, indexed); context->m_dynamicData->m_parentContext = m_context; } } bool DUContextDynamicData::removeChildContext( DUContext* context ) { // ENSURE_CAN_WRITE const int idx = m_childContexts.indexOf(context); if (idx != -1) { m_childContexts.remove(idx); Q_ASSERT(d_func()->m_childContexts()[idx] == LocalIndexedDUContext(context)); d_func_dynamic()->m_childContextsList().remove(idx); return true; } else { Q_ASSERT(d_func_dynamic()->m_childContextsList().indexOf(LocalIndexedDUContext(context)) == -1); return false; } } void DUContextDynamicData::addImportedChildContext( DUContext * context ) { // ENSURE_CAN_WRITE DUContext::Import import(m_context, context); if(import.isDirect()) { //Direct importers are registered directly within the data if(d_func_dynamic()->m_importersList().contains(IndexedDUContext(context))) { qCDebug(LANGUAGE) << m_context->scopeIdentifier(true).toString() << "importer added multiple times:" << context->scopeIdentifier(true).toString(); return; } d_func_dynamic()->m_importersList().append(context); }else{ //Indirect importers are registered separately Importers::self().addImporter(import.indirectDeclarationId(), IndexedDUContext(context)); } } //Can also be called with a context that is not in the list void DUContextDynamicData::removeImportedChildContext( DUContext * context ) { // ENSURE_CAN_WRITE DUContext::Import import(m_context, context); if(import.isDirect()) { d_func_dynamic()->m_importersList().removeOne(IndexedDUContext(context)); }else{ //Indirect importers are registered separately Importers::self().removeImporter(import.indirectDeclarationId(), IndexedDUContext(context)); } } int DUContext::depth() const { { if (!parentContext()) return 0; return parentContext()->depth() + 1; } } DUContext::DUContext(DUContextData& data) : DUChainBase(data) , m_dynamicData(new DUContextDynamicData(this)) { } DUContext::DUContext(const RangeInRevision& range, DUContext* parent, bool anonymous) : DUChainBase(*new DUContextData(), range) , m_dynamicData(new DUContextDynamicData(this)) { d_func_dynamic()->setClassId(this); if(parent) m_dynamicData->m_topContext = parent->topContext(); else m_dynamicData->m_topContext = static_cast(this); d_func_dynamic()->setClassId(this); DUCHAIN_D_DYNAMIC(DUContext); d->m_contextType = Other; m_dynamicData->m_parentContext = nullptr; d->m_anonymousInParent = anonymous; d->m_inSymbolTable = false; if (parent) { m_dynamicData->m_indexInTopContext = parent->topContext()->m_dynamicData->allocateContextIndex(this, parent->isAnonymous() || anonymous); Q_ASSERT(m_dynamicData->m_indexInTopContext); if( !anonymous ) parent->m_dynamicData->addChildContext(this); else m_dynamicData->m_parentContext = parent; } if(parent && !anonymous && parent->inSymbolTable()) setInSymbolTable(true); } bool DUContext::isAnonymous() const { return d_func()->m_anonymousInParent || (m_dynamicData->m_parentContext && m_dynamicData->m_parentContext->isAnonymous()); } DUContext::DUContext( DUContextData& dd, const RangeInRevision& range, DUContext * parent, bool anonymous ) : DUChainBase(dd, range) , m_dynamicData(new DUContextDynamicData(this)) { if(parent) m_dynamicData->m_topContext = parent->topContext(); else m_dynamicData->m_topContext = static_cast(this); DUCHAIN_D_DYNAMIC(DUContext); d->m_contextType = Other; m_dynamicData->m_parentContext = nullptr; d->m_inSymbolTable = false; d->m_anonymousInParent = anonymous; if (parent) { m_dynamicData->m_indexInTopContext = parent->topContext()->m_dynamicData->allocateContextIndex(this, parent->isAnonymous() || anonymous); if( !anonymous ) parent->m_dynamicData->addChildContext(this); else m_dynamicData->m_parentContext = parent; } } DUContext::DUContext(DUContext& useDataFrom) : DUChainBase(useDataFrom) , m_dynamicData(useDataFrom.m_dynamicData) { } DUContext::~DUContext( ) { TopDUContext* top = topContext(); if(!top->deleting() || !top->isOnDisk()) { DUCHAIN_D_DYNAMIC(DUContext); if(d->m_owner.declaration()) d->m_owner.declaration()->setInternalContext(nullptr); while( d->m_importersSize() != 0 ) { if(d->m_importers()[0].data()) d->m_importers()[0].data()->removeImportedParentContext(this); else { qCDebug(LANGUAGE) << "importer disappeared"; d->m_importersList().removeOne(d->m_importers()[0]); } } clearImportedParentContexts(); } deleteChildContextsRecursively(); if(!topContext()->deleting() || !topContext()->isOnDisk()) deleteUses(); deleteLocalDeclarations(); //If the top-context is being delete, we don't need to spend time rebuilding the inner structure. //That's expensive, especially when the data is not dynamic. if(!top->deleting() || !top->isOnDisk()) { if (m_dynamicData->m_parentContext) m_dynamicData->m_parentContext->m_dynamicData->removeChildContext(this); } top->m_dynamicData->clearContextIndex(this); Q_ASSERT(d_func()->isDynamic() == (!top->deleting() || !top->isOnDisk() || top->m_dynamicData->isTemporaryContextIndex(m_dynamicData->m_indexInTopContext))); delete m_dynamicData; } QVector< DUContext * > DUContext::childContexts( ) const { ENSURE_CAN_READ return m_dynamicData->m_childContexts; } Declaration* DUContext::owner() const { ENSURE_CAN_READ return d_func()->m_owner.declaration(); } void DUContext::setOwner(Declaration* owner) { ENSURE_CAN_WRITE DUCHAIN_D_DYNAMIC(DUContext); if( owner == d->m_owner.declaration() ) return; Declaration* oldOwner = d->m_owner.declaration(); d->m_owner = owner; //Q_ASSERT(!oldOwner || oldOwner->internalContext() == this); if( oldOwner && oldOwner->internalContext() == this ) oldOwner->setInternalContext(nullptr); //The context set as internal context should always be the last opened context if( owner ) owner->setInternalContext(this); } DUContext* DUContext::parentContext( ) const { //ENSURE_CAN_READ Commented out for performance reasons return m_dynamicData->m_parentContext.data(); } void DUContext::setPropagateDeclarations(bool propagate) { ENSURE_CAN_WRITE DUCHAIN_D_DYNAMIC(DUContext); if(propagate == d->m_propagateDeclarations) return; d->m_propagateDeclarations = propagate; } bool DUContext::isPropagateDeclarations() const { return d_func()->m_propagateDeclarations; } QList DUContext::findLocalDeclarations( const IndexedIdentifier& identifier, const CursorInRevision& position, const TopDUContext* topContext, const AbstractType::Ptr& dataType, SearchFlags flags ) const { ENSURE_CAN_READ DeclarationList ret; findLocalDeclarationsInternal(identifier, position.isValid() ? position : range().end, dataType, ret, topContext ? topContext : this->topContext(), flags); return ret; } QList DUContext::findLocalDeclarations( const Identifier& identifier, const CursorInRevision& position, const TopDUContext* topContext, const AbstractType::Ptr& dataType, SearchFlags flags ) const { return findLocalDeclarations(IndexedIdentifier(identifier), position, topContext, dataType, flags); } namespace { bool contextIsChildOrEqual(const DUContext* childContext, const DUContext* context) { if(childContext == context) return true; if(childContext->parentContext()) return contextIsChildOrEqual(childContext->parentContext(), context); else return false; } struct Checker { Checker(DUContext::SearchFlags flags, const AbstractType::Ptr& dataType, const CursorInRevision & position, DUContext::ContextType ownType) : m_flags(flags) , m_dataType(dataType) , m_position(position) , m_ownType(ownType) { } Declaration* check(Declaration* declaration) const { ///@todo This is C++-specific if (m_ownType != DUContext::Class && m_ownType != DUContext::Template && m_position.isValid() && m_position <= declaration->range().start) { return nullptr; } if (declaration->kind() == Declaration::Alias && !(m_flags & DUContext::DontResolveAliases)) { //Apply alias declarations AliasDeclaration* alias = static_cast(declaration); if (alias->aliasedDeclaration().isValid()) { declaration = alias->aliasedDeclaration().declaration(); } else { qCDebug(LANGUAGE) << "lost aliased declaration"; } } if (declaration->kind() == Declaration::NamespaceAlias && !(m_flags & DUContext::NoFiltering)) { return nullptr; } if ((m_flags & DUContext::OnlyFunctions) && !declaration->isFunctionDeclaration()) { return nullptr; } if (m_dataType && m_dataType->indexed() != declaration->indexedType()) { return nullptr; } return declaration; } DUContext::SearchFlags m_flags; const AbstractType::Ptr m_dataType; const CursorInRevision m_position; DUContext::ContextType m_ownType; }; } void DUContext::findLocalDeclarationsInternal(const Identifier& identifier, const CursorInRevision& position, const AbstractType::Ptr& dataType, DeclarationList& ret, const TopDUContext* source, SearchFlags flags) const { return findLocalDeclarationsInternal(IndexedIdentifier(identifier), position, dataType, ret, source, flags); } void DUContext::findLocalDeclarationsInternal( const IndexedIdentifier& identifier, const CursorInRevision & position, const AbstractType::Ptr& dataType, DeclarationList& ret, const TopDUContext* /*source*/, SearchFlags flags ) const { Checker checker(flags, dataType, position, type()); DUCHAIN_D(DUContext); if (d->m_inSymbolTable && !d->m_scopeIdentifier.isEmpty() && !identifier.isEmpty()) { //This context is in the symbol table, use the symbol-table to speed up the search QualifiedIdentifier id(scopeIdentifier(true) + identifier); TopDUContext* top = topContext(); uint count; const IndexedDeclaration* declarations; PersistentSymbolTable::self().declarations(id, count, declarations); for (uint a = 0; a < count; ++a) { ///@todo Eventually do efficient iteration-free filtering if (declarations[a].topContextIndex() == top->ownIndex()) { Declaration* decl = declarations[a].declaration(); if (decl && contextIsChildOrEqual(decl->context(), this)) { Declaration* checked = checker.check(decl); if (checked) { ret.append(checked); } } } } } else { //Iterate through all declarations DUContextDynamicData::VisibleDeclarationIterator it(m_dynamicData); while (it) { Declaration* declaration = *it; if (declaration && declaration->indexedIdentifier() == identifier) { Declaration* checked = checker.check(declaration); if (checked) ret.append(checked); } ++it; } } } bool DUContext::foundEnough( const DeclarationList& ret, SearchFlags flags ) const { if( !ret.isEmpty() && !(flags & DUContext::NoFiltering)) return true; else return false; } bool DUContext::findDeclarationsInternal( const SearchItem::PtrList & baseIdentifiers, const CursorInRevision & position, const AbstractType::Ptr& dataType, DeclarationList& ret, const TopDUContext* source, SearchFlags flags, uint depth ) const { if (depth > maxParentDepth) { qCDebug(LANGUAGE) << "maximum depth reached in" << scopeIdentifier(true); return false; } DUCHAIN_D(DUContext); if (d->m_contextType != Namespace) { // If we're in a namespace, delay all the searching into the top-context, because only that has the overview to pick the correct declarations. for (int a = 0; a < baseIdentifiers.size(); ++a) { if (!baseIdentifiers[a]->isExplicitlyGlobal && baseIdentifiers[a]->next.isEmpty()) { // It makes no sense searching locally for qualified identifiers findLocalDeclarationsInternal(baseIdentifiers[a]->identifier, position, dataType, ret, source, flags); } } if (foundEnough(ret, flags)) { return true; } } ///Step 1: Apply namespace-aliases and -imports SearchItem::PtrList aliasedIdentifiers; //Because of namespace-imports and aliases, this identifier may need to be searched under multiple names applyAliases(baseIdentifiers, aliasedIdentifiers, position, false, type() != DUContext::Namespace && type() != DUContext::Global); if (d->m_importedContextsSize() != 0) { ///Step 2: Give identifiers that are not marked as explicitly-global to imported contexts(explicitly global ones are treatead in TopDUContext) SearchItem::PtrList nonGlobalIdentifiers; foreach (const SearchItem::Ptr& identifier, aliasedIdentifiers) { if (!identifier->isExplicitlyGlobal) { nonGlobalIdentifiers << identifier; } } if (!nonGlobalIdentifiers.isEmpty()) { const auto& url = this->url(); for(int import = d->m_importedContextsSize()-1; import >= 0; --import ) { if (position.isValid() && d->m_importedContexts()[import].position.isValid() && position < d->m_importedContexts()[import].position) { continue; } DUContext* context = d->m_importedContexts()[import].context(source); if (!context) { continue; } else if (context == this) { qCDebug(LANGUAGE) << "resolved self as import:" << scopeIdentifier(true); continue; } if (!context->findDeclarationsInternal(nonGlobalIdentifiers, url == context->url() ? position : context->range().end, dataType, ret, source, flags | InImportedParentContext, depth+1)) { return false; } } } } if (foundEnough(ret, flags)) { return true; } ///Step 3: Continue search in parent-context if (!(flags & DontSearchInParent) && shouldSearchInParent(flags) && m_dynamicData->m_parentContext) { applyUpwardsAliases(aliasedIdentifiers, source); return m_dynamicData->m_parentContext->findDeclarationsInternal(aliasedIdentifiers, url() == m_dynamicData->m_parentContext->url() ? position : m_dynamicData->m_parentContext->range().end, dataType, ret, source, flags, depth); } return true; } QList< QualifiedIdentifier > DUContext::fullyApplyAliases(const QualifiedIdentifier& id, const TopDUContext* source) const { ENSURE_CAN_READ if(!source) source = topContext(); SearchItem::PtrList identifiers; identifiers << SearchItem::Ptr(new SearchItem(id)); const DUContext* current = this; while(current) { SearchItem::PtrList aliasedIdentifiers; current->applyAliases(identifiers, aliasedIdentifiers, CursorInRevision::invalid(), true, false); current->applyUpwardsAliases(identifiers, source); current = current->parentContext(); } QList ret; foreach (const SearchItem::Ptr& item, identifiers) ret += item->toList(); return ret; } QList DUContext::findDeclarations( const QualifiedIdentifier & identifier, const CursorInRevision & position, const AbstractType::Ptr& dataType, const TopDUContext* topContext, SearchFlags flags) const { ENSURE_CAN_READ DeclarationList ret; - SearchItem::PtrList identifiers; // optimize: we don't want to allocate the top node always // so create it on stack but ref it so its not deleted by the smart pointer SearchItem item(identifier); item.ref.ref(); - identifiers << SearchItem::Ptr(&item); + + SearchItem::PtrList identifiers{SearchItem::Ptr(&item)}; findDeclarationsInternal(identifiers, position.isValid() ? position : range().end, dataType, ret, topContext ? topContext : this->topContext(), flags, 0); return ret; } bool DUContext::imports(const DUContext* origin, const CursorInRevision& /*position*/ ) const { ENSURE_CAN_READ QSet recursionGuard; recursionGuard.reserve(8); return m_dynamicData->imports(origin, topContext(), &recursionGuard); } bool DUContext::addIndirectImport(const DUContext::Import& import) { ENSURE_CAN_WRITE DUCHAIN_D_DYNAMIC(DUContext); for(unsigned int a = 0; a < d->m_importedContextsSize(); ++a) { if(d->m_importedContexts()[a] == import) { d->m_importedContextsList()[a].position = import.position; return true; } } ///Do not sort the imported contexts by their own line-number, it makes no sense. ///Contexts added first, aka template-contexts, should stay in first place, so they are searched first. d->m_importedContextsList().append(import); return false; } void DUContext::addImportedParentContext( DUContext * context, const CursorInRevision& position, bool anonymous, bool /*temporary*/ ) { ENSURE_CAN_WRITE if(context == this) { qCDebug(LANGUAGE) << "Tried to import self"; return; } if(!context) { qCDebug(LANGUAGE) << "Tried to import invalid context"; return; } Import import(context, this, position); if(addIndirectImport(import)) return; if( !anonymous ) { ENSURE_CAN_WRITE_(context) context->m_dynamicData->addImportedChildContext(this); } } void DUContext::removeImportedParentContext( DUContext * context ) { ENSURE_CAN_WRITE DUCHAIN_D_DYNAMIC(DUContext); Import import(context, this, CursorInRevision::invalid()); for(unsigned int a = 0; a < d->m_importedContextsSize(); ++a) { if(d->m_importedContexts()[a] == import) { d->m_importedContextsList().remove(a); break; } } if( !context ) return; context->m_dynamicData->removeImportedChildContext(this); } KDevVarLengthArray DUContext::indexedImporters() const { KDevVarLengthArray ret; if(owner()) ret = Importers::self().importers(owner()->id()); //Add indirect importers to the list FOREACH_FUNCTION(const IndexedDUContext& ctx, d_func()->m_importers) ret.append(ctx); return ret; } QVector DUContext::importers() const { ENSURE_CAN_READ QVector ret; FOREACH_FUNCTION(const IndexedDUContext& ctx, d_func()->m_importers) ret << ctx.context(); if(owner()) { //Add indirect importers to the list KDevVarLengthArray indirect = Importers::self().importers(owner()->id()); foreach (const IndexedDUContext ctx, indirect) { ret << ctx.context(); } } return ret; } DUContext * DUContext::findContext( const CursorInRevision& position, DUContext* parent) const { ENSURE_CAN_READ if (!parent) parent = const_cast(this); foreach (DUContext* context, parent->m_dynamicData->m_childContexts) { if (context->range().contains(position)) { DUContext* ret = findContext(position, context); if (!ret) { ret = context; } return ret; } } return nullptr; } bool DUContext::parentContextOf(DUContext* context) const { if (this == context) return true; foreach (DUContext* child, m_dynamicData->m_childContexts) { if (child->parentContextOf(context)) { return true; } } return false; } QList< QPair > DUContext::allDeclarations(const CursorInRevision& position, const TopDUContext* topContext, bool searchInParents) const { ENSURE_CAN_READ QList< QPair > ret; QHash hadContexts; // Iterate back up the chain mergeDeclarationsInternal(ret, position, hadContexts, topContext ? topContext : this->topContext(), searchInParents); return ret; } QVector DUContext::localDeclarations(const TopDUContext* source) const { ENSURE_CAN_READ // TODO: remove this parameter once we kill old-cpp Q_UNUSED(source); return m_dynamicData->m_localDeclarations; } void DUContext::mergeDeclarationsInternal(QList< QPair >& definitions, const CursorInRevision& position, QHash& hadContexts, const TopDUContext* source, bool searchInParents, int currentDepth) const { ENSURE_CAN_READ if((currentDepth > 300 && currentDepth < 1000) || currentDepth > 1300) { qCDebug(LANGUAGE) << "too much depth"; return; } DUCHAIN_D(DUContext); if(hadContexts.contains(this) && !searchInParents) return; if(!hadContexts.contains(this)) { hadContexts[this] = true; if( (type() == DUContext::Namespace || type() == DUContext::Global) && currentDepth < 1000 ) currentDepth += 1000; { DUContextDynamicData::VisibleDeclarationIterator it(m_dynamicData); while(it) { Declaration* decl = *it; if ( decl && (!position.isValid() || decl->range().start <= position) ) definitions << qMakePair(decl, currentDepth); ++it; } } for(int a = d->m_importedContextsSize()-1; a >= 0; --a) { const Import* import(&d->m_importedContexts()[a]); DUContext* context = import->context(source); while( !context && a > 0 ) { --a; import = &d->m_importedContexts()[a]; context = import->context(source); } if( !context ) break; if(context == this) { qCDebug(LANGUAGE) << "resolved self as import:" << scopeIdentifier(true); continue; } if( position.isValid() && import->position.isValid() && position < import->position ) continue; context->mergeDeclarationsInternal(definitions, CursorInRevision::invalid(), hadContexts, source, searchInParents && context->shouldSearchInParent(InImportedParentContext) && context->parentContext()->type() == DUContext::Helper, currentDepth+1); } } ///Only respect the position if the parent-context is not a class(@todo this is language-dependent) if (parentContext() && searchInParents ) parentContext()->mergeDeclarationsInternal(definitions, parentContext()->type() == DUContext::Class ? parentContext()->range().end : position, hadContexts, source, searchInParents, currentDepth+1); } void DUContext::deleteLocalDeclarations() { ENSURE_CAN_WRITE // It may happen that the deletion of one declaration triggers the deletion of another one // Therefore we copy the list of indexed declarations and work on those. Indexed declarations // will return zero for already deleted declarations. KDevVarLengthArray indexedLocal; if (d_func()->m_localDeclarations()) { indexedLocal.append(d_func()->m_localDeclarations(), d_func()->m_localDeclarationsSize()); } foreach (const LocalIndexedDeclaration& indexed, m_dynamicData->m_localDeclarations) { delete indexed.data(topContext()); } m_dynamicData->m_localDeclarations.clear(); } void DUContext::deleteChildContextsRecursively() { ENSURE_CAN_WRITE // note: don't use qDeleteAll here because child ctx deletion changes m_dynamicData->m_childContexts // also note: foreach iterates on a copy, so this is safe foreach (DUContext* ctx, m_dynamicData->m_childContexts) { delete ctx; } m_dynamicData->m_childContexts.clear(); } QVector DUContext::clearLocalDeclarations( ) { auto copy = m_dynamicData->m_localDeclarations; foreach (Declaration* dec, copy) { dec->setContext(nullptr); } return copy; } QualifiedIdentifier DUContext::scopeIdentifier(bool includeClasses) const { ENSURE_CAN_READ QualifiedIdentifier ret; m_dynamicData->scopeIdentifier(includeClasses, ret); return ret; } bool DUContext::equalScopeIdentifier(const DUContext* rhs) const { ENSURE_CAN_READ const DUContext* left = this; const DUContext* right = rhs; while(left || right) { if(!left || !right) return false; if(!(left->d_func()->m_scopeIdentifier == right->d_func()->m_scopeIdentifier)) return false; left = left->parentContext(); right = right->parentContext(); } return true; } void DUContext::setLocalScopeIdentifier(const QualifiedIdentifier & identifier) { ENSURE_CAN_WRITE bool wasInSymbolTable = inSymbolTable(); setInSymbolTable(false); d_func_dynamic()->m_scopeIdentifier = identifier; setInSymbolTable(wasInSymbolTable); } QualifiedIdentifier DUContext::localScopeIdentifier() const { //ENSURE_CAN_READ Commented out for performance reasons return d_func()->m_scopeIdentifier; } IndexedQualifiedIdentifier DUContext::indexedLocalScopeIdentifier() const { return d_func()->m_scopeIdentifier; } DUContext::ContextType DUContext::type() const { //ENSURE_CAN_READ This is disabled, because type() is called very often while searching, and it costs us performance return d_func()->m_contextType; } void DUContext::setType(ContextType type) { ENSURE_CAN_WRITE d_func_dynamic()->m_contextType = type; } QList DUContext::findDeclarations(const Identifier& identifier, const CursorInRevision& position, const TopDUContext* topContext, SearchFlags flags) const { return findDeclarations(IndexedIdentifier(identifier), position, topContext, flags); } QList DUContext::findDeclarations(const IndexedIdentifier& identifier, const CursorInRevision& position, const TopDUContext* topContext, SearchFlags flags) const { ENSURE_CAN_READ DeclarationList ret; SearchItem::PtrList identifiers; identifiers << SearchItem::Ptr(new SearchItem(false, identifier, SearchItem::PtrList())); findDeclarationsInternal(identifiers, position.isValid() ? position : range().end, AbstractType::Ptr(), ret, topContext ? topContext : this->topContext(), flags, 0); return ret; } void DUContext::deleteUse(int index) { ENSURE_CAN_WRITE DUCHAIN_D_DYNAMIC(DUContext); d->m_usesList().remove(index); } void DUContext::deleteUses() { ENSURE_CAN_WRITE DUCHAIN_D_DYNAMIC(DUContext); d->m_usesList().clear(); } void DUContext::deleteUsesRecursively() { deleteUses(); foreach (DUContext* childContext, m_dynamicData->m_childContexts) { childContext->deleteUsesRecursively(); } } bool DUContext::inDUChain() const { if( d_func()->m_anonymousInParent || !m_dynamicData->m_parentContext) return false; TopDUContext* top = topContext(); return top && top->inDUChain(); } DUContext* DUContext::specialize(const IndexedInstantiationInformation& /*specialization*/, const TopDUContext* topContext, int /*upDistance*/) { if(!topContext) return nullptr; return this; } CursorInRevision DUContext::importPosition(const DUContext* target) const { ENSURE_CAN_READ DUCHAIN_D(DUContext); Import import(const_cast(target), this, CursorInRevision::invalid()); for(unsigned int a = 0; a < d->m_importedContextsSize(); ++a) if(d->m_importedContexts()[a] == import) return d->m_importedContexts()[a].position; return CursorInRevision::invalid(); } QVector DUContext::importedParentContexts() const { ENSURE_CAN_READ QVector ret; ret.reserve(d_func()->m_importedContextsSize()); FOREACH_FUNCTION(const DUContext::Import& import, d_func()->m_importedContexts) ret << import; return ret; } void DUContext::applyAliases(const SearchItem::PtrList& baseIdentifiers, SearchItem::PtrList& identifiers, const CursorInRevision& position, bool canBeNamespace, bool onlyImports) const { DeclarationList imports; findLocalDeclarationsInternal(globalIndexedImportIdentifier(), position, AbstractType::Ptr(), imports, topContext(), DUContext::NoFiltering); if(imports.isEmpty() && onlyImports) { identifiers = baseIdentifiers; return; } for ( const SearchItem::Ptr& identifier : baseIdentifiers ) { bool addUnmodified = true; if( !identifier->isExplicitlyGlobal ) { if( !imports.isEmpty() ) { //We have namespace-imports. foreach ( Declaration* importDecl, imports ) { //Search for the identifier with the import-identifier prepended if(dynamic_cast(importDecl)) { NamespaceAliasDeclaration* alias = static_cast(importDecl); identifiers.append( SearchItem::Ptr( new SearchItem( alias->importIdentifier(), identifier ) ) ) ; }else{ qCDebug(LANGUAGE) << "Declaration with namespace alias identifier has the wrong type" << importDecl->url().str() << importDecl->range().castToSimpleRange(); } } } if( !identifier->isEmpty() && (identifier->hasNext() || canBeNamespace) ) { DeclarationList aliases; findLocalDeclarationsInternal(identifier->identifier, position, AbstractType::Ptr(), imports, nullptr, DUContext::NoFiltering); if(!aliases.isEmpty()) { //The first part of the identifier has been found as a namespace-alias. //In c++, we only need the first alias. However, just to be correct, follow them all for now. foreach ( Declaration* aliasDecl, aliases ) { if(!dynamic_cast(aliasDecl)) continue; addUnmodified = false; //The un-modified identifier can be ignored, because it will be replaced with the resolved alias NamespaceAliasDeclaration* alias = static_cast(aliasDecl); //Create an identifier where namespace-alias part is replaced with the alias target identifiers.append( SearchItem::Ptr( new SearchItem( alias->importIdentifier(), identifier->next ) ) ) ; } } } } if( addUnmodified ) identifiers.append(identifier); } } void DUContext::applyUpwardsAliases(SearchItem::PtrList& identifiers, const TopDUContext* /*source*/) const { if(type() == Namespace) { if(d_func()->m_scopeIdentifier.isEmpty()) return; //Make sure we search for the items in all namespaces of the same name, by duplicating each one with the namespace-identifier prepended. //We do this by prepending items to the current identifiers that equal the local scope identifier. SearchItem::Ptr newItem( new SearchItem(d_func()->m_scopeIdentifier.identifier()) ); //This will exclude explictly global identifiers newItem->addToEachNode( identifiers ); if(!newItem->next.isEmpty()) { //Prepend the full scope before newItem DUContext* parent = m_dynamicData->m_parentContext.data(); while(parent) { newItem = SearchItem::Ptr( new SearchItem(parent->d_func()->m_scopeIdentifier, newItem) ); parent = parent->m_dynamicData->m_parentContext.data(); } newItem->isExplicitlyGlobal = true; identifiers.insert(0, newItem); } } } bool DUContext::shouldSearchInParent(SearchFlags flags) const { return (parentContext() && parentContext()->type() == DUContext::Helper && (flags & InImportedParentContext)) || !(flags & InImportedParentContext); } const Use* DUContext::uses() const { ENSURE_CAN_READ return d_func()->m_uses(); } bool DUContext::declarationHasUses(Declaration* decl) { return DUChain::uses()->hasUses(decl->id()); } int DUContext::usesCount() const { return d_func()->m_usesSize(); } bool usesRangeLessThan(const Use& left, const Use& right) { return left.m_range.start < right.m_range.start; } int DUContext::createUse(int declarationIndex, const RangeInRevision& range, int insertBefore) { DUCHAIN_D_DYNAMIC(DUContext); ENSURE_CAN_WRITE Use use(range, declarationIndex); if(insertBefore == -1) { //Find position where to insert const unsigned int size = d->m_usesSize(); const Use* uses = d->m_uses(); const Use* lowerBound = std::lower_bound(uses, uses + size, use, usesRangeLessThan); insertBefore = lowerBound - uses; // comment out to test this: /* unsigned int a = 0; for(; a < size && range.start > uses[a].m_range.start; ++a) { } Q_ASSERT(a == insertBefore); */ } d->m_usesList().insert(insertBefore, use); return insertBefore; } void DUContext::changeUseRange(int useIndex, const RangeInRevision& range) { ENSURE_CAN_WRITE d_func_dynamic()->m_usesList()[useIndex].m_range = range; } void DUContext::setUseDeclaration(int useNumber, int declarationIndex) { ENSURE_CAN_WRITE d_func_dynamic()->m_usesList()[useNumber].m_declarationIndex = declarationIndex; } DUContext * DUContext::findContextAt(const CursorInRevision & position, bool includeRightBorder) const { ENSURE_CAN_READ // qCDebug(LANGUAGE) << "searchign" << position << "in:" << scopeIdentifier(true).toString() << range() << includeRightBorder; if (!range().contains(position) && (!includeRightBorder || range().end != position)) { // qCDebug(LANGUAGE) << "mismatch"; return nullptr; } const auto childContexts = m_dynamicData->m_childContexts; for(int a = childContexts.size() - 1; a >= 0; --a) { if (DUContext* specific = childContexts[a]->findContextAt(position, includeRightBorder)) { return specific; } } return const_cast(this); } Declaration * DUContext::findDeclarationAt(const CursorInRevision & position) const { ENSURE_CAN_READ if (!range().contains(position)) return nullptr; foreach (Declaration* child, m_dynamicData->m_localDeclarations) { if (child->range().contains(position)) { return child; } } return nullptr; } DUContext* DUContext::findContextIncluding(const RangeInRevision& range) const { ENSURE_CAN_READ if (!this->range().contains(range)) return nullptr; foreach (DUContext* child, m_dynamicData->m_childContexts) { if (DUContext* specific = child->findContextIncluding(range)) { return specific; } } return const_cast(this); } int DUContext::findUseAt(const CursorInRevision & position) const { ENSURE_CAN_READ if (!range().contains(position)) return -1; for(unsigned int a = 0; a < d_func()->m_usesSize(); ++a) if (d_func()->m_uses()[a].m_range.contains(position)) return a; return -1; } bool DUContext::inSymbolTable() const { return d_func()->m_inSymbolTable; } void DUContext::setInSymbolTable(bool inSymbolTable) { d_func_dynamic()->m_inSymbolTable = inSymbolTable; } void DUContext::clearImportedParentContexts() { ENSURE_CAN_WRITE DUCHAIN_D_DYNAMIC(DUContext); while( d->m_importedContextsSize() != 0 ) { DUContext* ctx = d->m_importedContexts()[0].context(nullptr, false); if(ctx) ctx->m_dynamicData->removeImportedChildContext(this); d->m_importedContextsList().removeOne(d->m_importedContexts()[0]); } } void DUContext::cleanIfNotEncountered(const QSet& encountered) { ENSURE_CAN_WRITE // It may happen that the deletion of one declaration triggers the deletion of another one // Therefore we copy the list of indexed declarations and work on those. Indexed declarations // will return zero for already deleted declarations. KDevVarLengthArray indexedLocal; if (d_func()->m_localDeclarations()) { indexedLocal.append(d_func()->m_localDeclarations(), d_func()->m_localDeclarationsSize()); } foreach (const LocalIndexedDeclaration& indexed, m_dynamicData->m_localDeclarations) { auto dec = indexed.data(topContext()); if (dec && !encountered.contains(dec) && (!dec->isAutoDeclaration() || !dec->hasUses())) { delete dec; } } foreach (DUContext* childContext, m_dynamicData->m_childContexts) { if (!encountered.contains(childContext)) { delete childContext; } } } TopDUContext* DUContext::topContext() const { return m_dynamicData->m_topContext; } QWidget* DUContext::createNavigationWidget(Declaration* decl, TopDUContext* topContext, const QString& htmlPrefix, const QString& htmlSuffix, AbstractNavigationWidget::DisplayHints hints) const { if (decl) { AbstractNavigationWidget* widget = new AbstractNavigationWidget; widget->setDisplayHints(hints); AbstractDeclarationNavigationContext* context = new AbstractDeclarationNavigationContext(DeclarationPointer(decl), TopDUContextPointer(topContext)); context->setPrefixSuffix(htmlPrefix, htmlSuffix); widget->setContext(NavigationContextPointer(context)); return widget; } else { return nullptr; } } QList allUses(DUContext* context, int declarationIndex, bool noEmptyUses) { QList ret; for(int a = 0; a < context->usesCount(); ++a) if(context->uses()[a].m_declarationIndex == declarationIndex) if(!noEmptyUses || !context->uses()[a].m_range.isEmpty()) ret << context->uses()[a].m_range; foreach(DUContext* child, context->childContexts()) ret += allUses(child, declarationIndex, noEmptyUses); return ret; } DUContext::SearchItem::SearchItem(const QualifiedIdentifier& id, const Ptr& nextItem, int start) : isExplicitlyGlobal(start == 0 ? id.explicitlyGlobal() : false) { if(!id.isEmpty()) { if(id.count() > start) identifier = id.indexedAt(start); if(id.count() > start+1) addNext(Ptr( new SearchItem(id, nextItem, start+1) )); else if(nextItem) next.append(nextItem); }else if(nextItem) { ///If there is no prefix, just copy nextItem isExplicitlyGlobal = nextItem->isExplicitlyGlobal; identifier = nextItem->identifier; next = nextItem->next; } } DUContext::SearchItem::SearchItem(const QualifiedIdentifier& id, const PtrList& nextItems, int start) : isExplicitlyGlobal(start == 0 ? id.explicitlyGlobal() : false) { if(id.count() > start) identifier = id.indexedAt(start); if(id.count() > start+1) addNext(Ptr( new SearchItem(id, nextItems, start+1) )); else next = nextItems; } DUContext::SearchItem::SearchItem(bool explicitlyGlobal, const IndexedIdentifier& id, const PtrList& nextItems) : isExplicitlyGlobal(explicitlyGlobal) , identifier(id) , next(nextItems) { } DUContext::SearchItem::SearchItem(bool explicitlyGlobal, const IndexedIdentifier& id, const Ptr& nextItem) : isExplicitlyGlobal(explicitlyGlobal) , identifier(id) { next.append(nextItem); } bool DUContext::SearchItem::match(const QualifiedIdentifier& id, int offset) const { if(id.isEmpty()) { if(identifier.isEmpty() && next.isEmpty()) return true; else return false; } if(id.at(offset) != identifier) //The identifier is different return false; if(offset == id.count()-1) { if(next.isEmpty()) return true; //match else return false; //id is too short } for(int a = 0; a < next.size(); ++a) if(next[a]->match(id, offset+1)) return true; return false; } bool DUContext::SearchItem::isEmpty() const { return identifier.isEmpty(); } bool DUContext::SearchItem::hasNext() const { return !next.isEmpty(); } QList DUContext::SearchItem::toList(const QualifiedIdentifier& prefix) const { QList ret; QualifiedIdentifier id = prefix; if(id.isEmpty()) id.setExplicitlyGlobal(isExplicitlyGlobal); if(!identifier.isEmpty()) id.push(identifier); if(next.isEmpty()) { ret << id; } else { for(int a = 0; a < next.size(); ++a) ret += next[a]->toList(id); } return ret; } void DUContext::SearchItem::addNext(const SearchItem::Ptr& other) { next.append(other); } void DUContext::SearchItem::addToEachNode(const SearchItem::Ptr& other) { if(other->isExplicitlyGlobal) return; next.append(other); for(int a = 0; a < next.size()-1; ++a) next[a]->addToEachNode(other); } void DUContext::SearchItem::addToEachNode(const SearchItem::PtrList& other) { int added = 0; for (const SearchItem::Ptr& o : other) { if(!o->isExplicitlyGlobal) { next.append(o); ++added; } } for(int a = 0; a < next.size()-added; ++a) next[a]->addToEachNode(other); } DUContext::Import::Import(DUContext* _context, const DUContext* importer, const CursorInRevision& _position) : position(_position) { if(_context && _context->owner() && (_context->owner()->specialization().index() || (importer && importer->topContext() != _context->topContext()))) { m_declaration = _context->owner()->id(); }else{ m_context = _context; } } DUContext::Import::Import(const DeclarationId& id, const CursorInRevision& _position) : position(_position) , m_declaration(id) { } DUContext* DUContext::Import::context(const TopDUContext* topContext, bool instantiateIfRequired) const { if(m_declaration.isValid()) { Declaration* decl = m_declaration.getDeclaration(topContext, instantiateIfRequired); //This first case rests on the assumption that no context will ever import a function's expression context //More accurately, that no specialized or cross-topContext imports will, but if the former assumption fails the latter will too if (AbstractFunctionDeclaration *functionDecl = dynamic_cast(decl)) { if (functionDecl->internalFunctionContext()) { return functionDecl->internalFunctionContext(); } else { qCWarning(LANGUAGE) << "Import of function declaration without internal function context encountered!"; } } if(decl) return decl->logicalInternalContext(topContext); else return nullptr; }else{ return m_context.data(); } } bool DUContext::Import::isDirect() const { return m_context.isValid(); } void DUContext::visit(DUChainVisitor& visitor) { ENSURE_CAN_READ visitor.visit(this); foreach (Declaration* decl, m_dynamicData->m_localDeclarations) { visitor.visit(decl); } foreach (DUContext* childContext, m_dynamicData->m_childContexts) { childContext->visit(visitor); } } static bool sortByRange(const DUChainBase* lhs, const DUChainBase* rhs) { return lhs->range() < rhs->range(); } void DUContext::resortLocalDeclarations() { ENSURE_CAN_WRITE std::sort(m_dynamicData->m_localDeclarations.begin(), m_dynamicData->m_localDeclarations.end(), sortByRange); auto top = topContext(); auto& declarations = d_func_dynamic()->m_localDeclarationsList(); std::sort(declarations.begin(), declarations.end(), [top] (const LocalIndexedDeclaration& lhs, const LocalIndexedDeclaration& rhs) { return lhs.data(top)->range() < rhs.data(top)->range(); }); } void DUContext::resortChildContexts() { ENSURE_CAN_WRITE std::sort(m_dynamicData->m_childContexts.begin(), m_dynamicData->m_childContexts.end(), sortByRange); auto top = topContext(); auto& contexts = d_func_dynamic()->m_childContextsList(); std::sort(contexts.begin(), contexts.end(), [top] (const LocalIndexedDUContext& lhs, const LocalIndexedDUContext& rhs) { return lhs.data(top)->range() < rhs.data(top)->range(); }); } } diff --git a/language/duchain/types/typeregister.cpp b/language/duchain/types/typeregister.cpp index 3c6857243..c107920d9 100644 --- a/language/duchain/types/typeregister.cpp +++ b/language/duchain/types/typeregister.cpp @@ -1,87 +1,104 @@ /* This file is part of KDevelop Copyright 2008 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "typeregister.h" +#include + namespace KDevelop { AbstractType* TypeSystem::create(AbstractTypeData* data) const { - if (!isFactoryLoaded(*data)) { + Q_ASSERT(data); + if (!ensureFactoryLoaded(*data)) { return nullptr; } return m_factories.value(data->typeClassId)->create(data); } void TypeSystem::callDestructor(AbstractTypeData* data) const { - if (!isFactoryLoaded(*data)) { + Q_ASSERT(data); + if (!ensureFactoryLoaded(*data)) { return; } return m_factories.value(data->typeClassId)->callDestructor(data); } uint TypeSystem::dynamicSize(const AbstractTypeData& data) const { - if (!isFactoryLoaded(data)) { + if (!ensureFactoryLoaded(data)) { return 0; } return m_factories.value(data.typeClassId)->dynamicSize(data); } uint TypeSystem::dataClassSize(const AbstractTypeData& data) const { Q_ASSERT(m_dataClassSizes.contains(data.typeClassId)); return m_dataClassSizes.value(data.typeClassId); } bool TypeSystem::isFactoryLoaded(const AbstractTypeData& data) const { return m_factories.contains(data.typeClassId); } +bool TypeSystem::ensureFactoryLoaded(const AbstractTypeData& data) const +{ + if (!m_factories.contains(data.typeClassId)) { + qCWarning(LANGUAGE) << "Factory for this type not loaded:" << data.typeClassId; + Q_ASSERT(false); + return false; + } + return true; +} + void TypeSystem::copy(const AbstractTypeData& from, AbstractTypeData& to, bool constant) const { //Shouldn't try to copy an unknown type - Q_ASSERT(isFactoryLoaded(from)); + ensureFactoryLoaded(from); return m_factories.value(from.typeClassId)->copy(from, to, constant); } TypeSystem& TypeSystem::self() { static TypeSystem system; return system; } void TypeSystem::registerTypeClassInternal(AbstractTypeFactory* repo, uint dataClassSize, uint identity) { + qCDebug(LANGUAGE) << "Registering type class" << identity; + Q_ASSERT(repo); Q_ASSERT(!m_factories.contains(identity)); m_factories.insert(identity, repo); Q_ASSERT(!m_dataClassSizes.contains(identity)); m_dataClassSizes.insert(identity, dataClassSize); } void TypeSystem::unregisterTypeClassInternal(uint identity) { + qCDebug(LANGUAGE) << "Unregistering type class" << identity; AbstractTypeFactory* repo = m_factories.take(identity); Q_ASSERT(repo); delete repo; int removed = m_dataClassSizes.remove(identity); Q_ASSERT(removed); Q_UNUSED(removed); } } diff --git a/language/duchain/types/typeregister.h b/language/duchain/types/typeregister.h index b94f5165d..99793e2b4 100644 --- a/language/duchain/types/typeregister.h +++ b/language/duchain/types/typeregister.h @@ -1,203 +1,205 @@ /* This file is part of KDevelop Copyright 2008 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_TYPEREGISTER_H #define KDEVPLATFORM_TYPEREGISTER_H #include #include "abstracttype.h" #include "typesystemdata.h" namespace KDevelop { /** * \short A factory class for type data. * * This class provides an interface for creating private data * structures for classes. */ class KDEVPLATFORMLANGUAGE_EXPORT AbstractTypeFactory { public: /** * Create a new type for the given \a data. * * \param data Data to assign to the new type. The data type must match the class type. */ virtual AbstractType* create(AbstractTypeData* data) const = 0; /** * Call the destructor of the data-type. */ virtual void callDestructor(AbstractTypeData* data) const = 0; /** * Copy contents of type-data \a from one location \a to another. * * \param from data to copy from * \param to data to copy to. This data must not be initialized yet * (the constructor must not have been called yet) * \param constant set to true if \a to is to be a static unchangeable * data type (eg. in the type-repository), or false if * \a to is to be a dynamic changeable type data. */ virtual void copy(const AbstractTypeData& from, AbstractTypeData& to, bool constant) const = 0; /** * Return the memory size of the given private \a data, including dynamic data. * * \param data data structure * \returns the size in memory of the data. */ virtual uint dynamicSize(const AbstractTypeData& data) const = 0; /// Destructor. virtual ~AbstractTypeFactory() { } }; /** * Template class to implement factories for each AbstractType subclass you want * to instantiate. */ template class TypeFactory : public AbstractTypeFactory { public: AbstractType* create(AbstractTypeData* data) const override { /* if(!m_reUseTypes.isEmpty()) { return new (m_reUseTypes.pop()) T(*static_cast(data)); }else{*/ return new T(*static_cast(data)); // } } void copy(const AbstractTypeData& from, AbstractTypeData& to, bool constant) const override { Q_ASSERT(from.typeClassId == T::Identity); if((bool)from.m_dynamic == (bool)!constant) { //We have a problem, "from" and "to" should both be either dynamic or constant. We must copy once more. Data* temp = &AbstractType::copyDataDirectly(static_cast(from)); new (&to) Data(*temp); //Call the copy constructor to initialize the target Q_ASSERT((bool)to.m_dynamic == (bool)!constant); destroyData(temp); }else{ new (&to) Data(static_cast(from)); //Call the copy constructor to initialize the target } } void destroyData(AbstractTypeData* data) const { callDestructor(data); delete[] (char*)data; } void callDestructor(AbstractTypeData* data) const override { Q_ASSERT(data->typeClassId == T::Identity); static_cast(data)->~Data(); } uint dynamicSize(const AbstractTypeData& data) const override { Q_ASSERT(data.typeClassId == T::Identity); return static_cast(data).dynamicSize(); } }; /** * \short A class which registers data types and creates factories for them. * * TypeSystem is a global static class which allows you to register new * AbstractType subclasses for creation. */ class KDEVPLATFORMLANGUAGE_EXPORT TypeSystem { public: /** * Register a new AbstractType subclass. */ template void registerTypeClass() { registerTypeClassInternal(new TypeFactory(), sizeof(Data), T::Identity); } /** * Unregister an AbstractType subclass. */ template void unregisterTypeClass() { unregisterTypeClassInternal(T::Identity); } /** * Create an AbstractType for the given data. The returned type must be put into a AbstractType::Ptr immediately. * Can return null if no type-factory is available for the given data (for example when a language-part is not loaded) */ AbstractType* create(AbstractTypeData* data) const; /** * This just calls the correct constructor on the target. The target must be big enough to hold all the data. */ void copy(const AbstractTypeData& from, AbstractTypeData& to, bool constant) const; ///Calls the dynamicSize(..) member on the given data, in the most special class. Since we cannot use virtual functions, this is the only way. uint dynamicSize(const AbstractTypeData& data) const; ///Returns the size of the derived class, not including dynamic data. ///Returns zero if the class is not known. uint dataClassSize(const AbstractTypeData& data) const; ///Calls the destructor, but does not delete anything. This is needed because the data classes must not contain virtual members. void callDestructor(AbstractTypeData* data) const; ///Returns true if the factory for this data type is loaded. ///If false is returned, then any of the other calls will fail. bool isFactoryLoaded(const AbstractTypeData& data) const; /// Access the static TypeSystem instance. static TypeSystem& self(); private: void registerTypeClassInternal(AbstractTypeFactory* repo, uint dataClassSize, uint identity); void unregisterTypeClassInternal(uint identity); + bool ensureFactoryLoaded(const AbstractTypeData& data) const; + QHash m_factories; QHash m_dataClassSizes; }; /// Helper class to register an AbstractType subclass. /// /// Just use the REGISTER_TYPE(YourTypeClass) macro in your code, and you're done. template struct TypeSystemRegistrator { TypeSystemRegistrator() { TypeSystem::self().registerTypeClass(); } ~TypeSystemRegistrator() { TypeSystem::self().unregisterTypeClass(); } }; ///You must add this into your source-files for every AbstractType based class ///For this to work, the class must have an "Identity" enumerator, that is unique among all types. ///It should be a unique value, but as small as possible, because a buffer at least as big as that number is created internally. #define REGISTER_TYPE(Class) TypeSystemRegistrator register ## Class } #endif diff --git a/plugins/filetemplates/CMakeLists.txt b/plugins/filetemplates/CMakeLists.txt index 419ae5aea..9d3e72d0e 100644 --- a/plugins/filetemplates/CMakeLists.txt +++ b/plugins/filetemplates/CMakeLists.txt @@ -1,99 +1,100 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kdevfiletemplates\") set(kdevfiletemplates_PART_SRCS templatepreview.cpp templatepreviewtoolview.cpp filetemplatesplugin.cpp ipagefocus.cpp classidentifierpage.cpp classmemberspage.cpp defaultcreateclasshelper.cpp licensepage.cpp outputpage.cpp overridespage.cpp templateclassassistant.cpp templateoptionspage.cpp templateselectionpage.cpp testcasespage.cpp ) ecm_qt_declare_logging_category(kdevfiletemplates_PART_SRCS HEADER debug.h IDENTIFIER PLUGIN_FILETEMPLATES CATEGORY_NAME "kdevplatform.plugins.filetemplates" ) ki18n_wrap_ui(kdevfiletemplates_PART_SRCS templatepreviewtoolview.ui ui/licensechooser.ui ui/newclass.ui ui/outputlocation.ui ui/overridevirtuals.ui ui/templateselection.ui ui/testcases.ui ) qt5_add_resources(kdevfiletemplates_PART_SRCS kdevfiletemplates.qrc) kdevplatform_add_plugin(kdevfiletemplates JSON kdevfiletemplates.json SOURCES ${kdevfiletemplates_PART_SRCS}) target_link_libraries(kdevfiletemplates KDev::Interfaces KDev::Language KDev::Project KDev::Util KF5::NewStuff KF5::TextEditor KF5::ItemModels ) ########### install files ############### install(FILES licenses/GPL\ v2 licenses/GPL\ v3 licenses/LGPL\ v2 licenses/LGPL\ v3 licenses/Apache\ v2 licenses/BSD licenses/Boost licenses/MIT\ X11 licenses/Mozilla\ v1.1 "licenses/LGPL v2+ (KDE)" "licenses/GPL v2+ (KDE)" DESTINATION ${KDE_INSTALL_DATADIR}/kdevcodegen/licenses ) ################ set(test_srcs main.cpp templatepreview.cpp ipagefocus.cpp classidentifierpage.cpp classmemberspage.cpp defaultcreateclasshelper.cpp licensepage.cpp outputpage.cpp overridespage.cpp templateclassassistant.cpp templateoptionspage.cpp templateselectionpage.cpp testcasespage.cpp debug.cpp ) -add_executable(testfiletemplates ${test_srcs}) -add_dependencies(testfiletemplates kdevfiletemplates) +if(BUILD_TESTING) + add_executable(testfiletemplates ${test_srcs}) + add_dependencies(testfiletemplates kdevfiletemplates) + target_link_libraries(testfiletemplates + KDev::Interfaces + KDev::Language + KDev::Project + KDev::Util + KDev::Tests + KF5::NewStuff + ) -target_link_libraries(testfiletemplates - KDev::Interfaces - KDev::Language - KDev::Project - KDev::Util - KDev::Tests - KF5::NewStuff -) - -add_subdirectory(tests) + add_subdirectory(tests) +endif() diff --git a/plugins/patchreview/patchhighlighter.cpp b/plugins/patchreview/patchhighlighter.cpp index d6f0e5411..14265b136 100644 --- a/plugins/patchreview/patchhighlighter.cpp +++ b/plugins/patchreview/patchhighlighter.cpp @@ -1,692 +1,692 @@ /*************************************************************************** Copyright 2006 David Nolden ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "patchhighlighter.h" #include #include #include "patchreview.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; namespace { QPointer currentTooltip; KTextEditor::MovingRange* currentTooltipMark; QSize sizeHintForHtml( QString html, QSize maxSize ) { QTextDocument doc; doc.setHtml( html ); QSize ret; if( doc.idealWidth() > maxSize.width() ) { doc.setPageSize( QSize( maxSize.width(), 30 ) ); ret.setWidth( maxSize.width() ); }else{ ret.setWidth( doc.idealWidth() ); } ret.setHeight( doc.size().height() ); if( ret.height() > maxSize.height() ) ret.setHeight( maxSize.height() ); return ret; } } const unsigned int PatchHighlighter::m_allmarks = KTextEditor::MarkInterface::markType22 | KTextEditor::MarkInterface::markType23 | KTextEditor::MarkInterface::markType24 | KTextEditor::MarkInterface::markType25 | KTextEditor::MarkInterface::markType26 | KTextEditor::MarkInterface::markType27; void PatchHighlighter::showToolTipForMark(const QPoint& pos, KTextEditor::MovingRange* markRange) { if( currentTooltipMark == markRange && currentTooltip ) return; delete currentTooltip; //Got the difference Diff2::Difference* diff = m_ranges[markRange]; QString html; #if 0 if( diff->hasConflict() ) html += i18n( "Conflict
" ); #endif Diff2::DifferenceStringList lines; html += QLatin1String(""); if( diff->applied() ) { if( !m_plugin->patch()->isAlreadyApplied() ) html += i18n( "Applied.
" ); if( isInsertion( diff ) ) { html += i18n( "Insertion
" ); } else { if( isRemoval( diff ) ) html += i18n( "Removal
" ); html += i18n( "Previous:
" ); lines = diff->sourceLines(); } } else { if( m_plugin->patch()->isAlreadyApplied() ) html += i18n( "Reverted.
" ); if( isRemoval( diff ) ) { html += i18n( "Removal
" ); } else { if( isInsertion( diff ) ) html += i18n( "Insertion
" ); html += i18n( "Alternative:
" ); lines = diff->destinationLines(); } } html += QLatin1String("
"); for( int a = 0; a < lines.size(); ++a ) { Diff2::DifferenceString* line = lines[a]; uint currentPos = 0; QString string = line->string(); Diff2::MarkerList markers = line->markerList(); for( int b = 0; b < markers.size(); ++b ) { QString spanText = string.mid( currentPos, markers[b]->offset() - currentPos ).toHtmlEscaped(); if( markers[b]->type() == Diff2::Marker::End && ( currentPos != 0 || markers[b]->offset() != static_cast( string.size() ) ) ) { html += "" + spanText + ""; }else{ html += spanText; } currentPos = markers[b]->offset(); } html += string.mid( currentPos, string.length()-currentPos ).toHtmlEscaped(); html += QLatin1String("
"); } auto browser = new QTextBrowser; browser->setPalette( QApplication::palette() ); browser->setHtml( html ); int maxHeight = 500; browser->setMinimumSize( sizeHintForHtml( html, QSize( ( ICore::self()->uiController()->activeMainWindow()->width()*2 )/3, maxHeight ) ) ); browser->setMaximumSize( browser->minimumSize() + QSize( 10, 10 ) ); if( browser->minimumHeight() != maxHeight ) browser->setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); QVBoxLayout* layout = new QVBoxLayout; layout->setMargin( 0 ); layout->addWidget( browser ); KDevelop::ActiveToolTip* tooltip = new KDevelop::ActiveToolTip( ICore::self()->uiController()->activeMainWindow(), pos + QPoint( 5, -browser->sizeHint().height() - 30 ) ); tooltip->setLayout( layout ); tooltip->resize( tooltip->sizeHint() + QSize( 10, 10 ) ); tooltip->move( pos - QPoint( 0, 20 + tooltip->height() ) ); tooltip->setHandleRect( QRect( pos - QPoint( 15, 15 ), pos + QPoint( 15, 15 ) ) ); currentTooltip = tooltip; currentTooltipMark = markRange; ActiveToolTip::showToolTip( tooltip ); } void PatchHighlighter::markClicked( KTextEditor::Document* doc, const KTextEditor::Mark& mark, bool& handled ) { if( handled || !(mark.type & m_allmarks) ) return; auto range_diff = rangeForMark(mark); m_applying = true; if (range_diff.first) { handled = true; KTextEditor::MovingRange *&range = range_diff.first; Diff2::Difference *&diff = range_diff.second; QString currentText = doc->text( range->toRange() ); removeLineMarker( range ); QString sourceText; QString targetText; for( int a = 0; a < diff->sourceLineCount(); ++a ) { sourceText += diff->sourceLineAt( a )->string(); if( !sourceText.endsWith( '\n' ) ) sourceText += '\n'; } for( int a = 0; a < diff->destinationLineCount(); ++a ) { targetText += diff->destinationLineAt( a )->string(); if( !targetText.endsWith( '\n' ) ) targetText += '\n'; } bool applied = diff->applied(); QString &replace(applied ? targetText : sourceText); QString &replaceWith(applied ? sourceText : targetText); if( currentText.simplified() != replace.simplified() ) { KMessageBox::error( ICore::self()->uiController()->activeMainWindow(), i18n( "Could not apply the change: Text should be \"%1\", but is \"%2\".", replace, currentText ) ); m_applying = false; return; } diff->apply(!applied); KTextEditor::Cursor start = range->start().toCursor(); range->document()->replaceText( range->toRange(), replaceWith ); uint replaceWithLines = replaceWith.count( '\n' ); KTextEditor::Range newRange( start, KTextEditor::Cursor(start.line() + replaceWithLines, start.column()) ); range->setRange( newRange ); addLineMarker( range, diff ); { // After applying the change, show the tooltip again, mainly to update an old tooltip delete currentTooltip; currentTooltip = nullptr; bool h = false; markToolTipRequested( doc, mark, QCursor::pos(), h ); } } m_applying = false; } QPair PatchHighlighter::rangeForMark( const KTextEditor::Mark &mark ) { if (!m_applying) { for( QMap::const_iterator it = m_ranges.constBegin(); it != m_ranges.constEnd(); ++it ) { if (it.value() && it.key()->start().line() <= mark.line && mark.line <= it.key()->end().line()) { return qMakePair(it.key(), it.value()); } } } return qMakePair(nullptr, nullptr); } void PatchHighlighter::markToolTipRequested( KTextEditor::Document*, const KTextEditor::Mark& mark, QPoint pos, bool& handled ) { if( handled ) return; if( mark.type & m_allmarks ) { //There is a mark in this line. Show the old text. auto range = rangeForMark(mark); if( range.first ) { showToolTipForMark( pos, range.first ); handled = true; } } } bool PatchHighlighter::isInsertion( Diff2::Difference* diff ) { return diff->sourceLineCount() == 0; } bool PatchHighlighter::isRemoval( Diff2::Difference* diff ) { return diff->destinationLineCount() == 0; } void PatchHighlighter::performContentChange( KTextEditor::Document* doc, const QStringList& oldLines, const QStringList& newLines, int editLineNumber ) { QPair, QList > diffChange = m_model->linesChanged( oldLines, newLines, editLineNumber ); QList inserted = diffChange.first; QList removed = diffChange.second; foreach(Diff2::Difference* d, removed) { foreach(Diff2::DifferenceString* s, d->sourceLines()) qCDebug(PLUGIN_PATCHREVIEW) << "removed source" << s->string(); foreach(Diff2::DifferenceString* s, d->destinationLines()) qCDebug(PLUGIN_PATCHREVIEW) << "removed destination" << s->string(); } foreach(Diff2::Difference* d, inserted) { foreach(Diff2::DifferenceString* s, d->sourceLines()) qCDebug(PLUGIN_PATCHREVIEW) << "inserted source" << s->string(); foreach(Diff2::DifferenceString* s, d->destinationLines()) qCDebug(PLUGIN_PATCHREVIEW) << "inserted destination" << s->string(); } // Remove all ranges that are in the same line (the line markers) for (auto it = m_ranges.begin(); it != m_ranges.end();) { if (removed.contains(it.value())) { KTextEditor::MovingRange* r = it.key(); removeLineMarker(r); // is altering m_ranges it = m_ranges.erase(it); delete r; } else { ++it; } } qDeleteAll(removed); KTextEditor::MovingInterface* moving = dynamic_cast( doc ); if ( !moving ) return; foreach( Diff2::Difference* diff, inserted ) { int lineStart = diff->destinationLineNumber(); if ( lineStart > 0 ) { --lineStart; } int lineEnd = diff->destinationLineEnd(); if ( lineEnd > 0 ) { --lineEnd; } KTextEditor::Range newRange( lineStart, 0, lineEnd, 0 ); KTextEditor::MovingRange * r = moving->newMovingRange( newRange ); m_ranges[r] = diff; addLineMarker( r, diff ); } } void PatchHighlighter::textRemoved( KTextEditor::Document* doc, const KTextEditor::Range& range, const QString& oldText ) { if ( m_applying ) { // Do not interfere with patch application return; } qCDebug(PLUGIN_PATCHREVIEW) << "removal range" << range; qCDebug(PLUGIN_PATCHREVIEW) << "removed text" << oldText; KTextEditor::Cursor cursor = range.start(); int startLine = cursor.line(); QStringList removedLines; QStringList remainingLines; if (startLine > 0) { QString above = doc->line(--startLine); removedLines << above; remainingLines << above; } QString changed = doc->line(cursor.line()) + '\n'; removedLines << changed.mid(0, cursor.column()) + oldText + changed.mid(cursor.column()); remainingLines << changed; if (doc->documentRange().end().line() > cursor.line()) { QString below = doc->line(cursor.line() + 1); removedLines << below; remainingLines << below; } performContentChange(doc, removedLines, remainingLines, startLine + 1); } void PatchHighlighter::newlineRemoved(KTextEditor::Document* doc, int line) { if ( m_applying ) { // Do not interfere with patch application return; } qCDebug(PLUGIN_PATCHREVIEW) << "remove newline" << line; KTextEditor::Cursor cursor = m_doc->cursorPosition(); int startLine = line - 1; QStringList removedLines; QStringList remainingLines; if (startLine > 0) { QString above = doc->line(--startLine); removedLines << above; remainingLines << above; } QString changed = doc->line(line - 1); if (cursor.line() == line - 1) { removedLines << changed.mid(0, cursor.column()); removedLines << changed.mid(cursor.column()); } else { removedLines << changed; removedLines << QString(); } remainingLines << changed; if (doc->documentRange().end().line() >= line) { QString below = doc->line(line); removedLines << below; remainingLines << below; } performContentChange(doc, removedLines, remainingLines, startLine + 1); } void PatchHighlighter::documentReloaded(KTextEditor::Document* doc) { qCDebug(PLUGIN_PATCHREVIEW) << "re-doing"; //The document was loaded / reloaded if ( !m_model->differences() ) return; KTextEditor::MovingInterface* moving = dynamic_cast( doc ); if ( !moving ) return; KTextEditor::MarkInterface* markIface = dynamic_cast( doc ); if( !markIface ) return; clear(); KColorScheme scheme( QPalette::Active ); QImage tintedInsertion = QIcon::fromTheme( QStringLiteral("insert-text") ).pixmap( 16, 16 ).toImage(); KIconEffect::colorize( tintedInsertion, scheme.foreground( KColorScheme::NegativeText ).color(), 1.0 ); QImage tintedRemoval = QIcon::fromTheme( QStringLiteral("edit-delete") ).pixmap( 16, 16 ).toImage(); KIconEffect::colorize( tintedRemoval, scheme.foreground( KColorScheme::NegativeText ).color(), 1.0 ); QImage tintedChange = QIcon::fromTheme( QStringLiteral("text-field") ).pixmap( 16, 16 ).toImage(); KIconEffect::colorize( tintedChange, scheme.foreground( KColorScheme::NegativeText ).color(), 1.0 ); markIface->setMarkDescription( KTextEditor::MarkInterface::markType22, i18n( "Insertion" ) ); markIface->setMarkPixmap( KTextEditor::MarkInterface::markType22, QPixmap::fromImage( tintedInsertion ) ); markIface->setMarkDescription( KTextEditor::MarkInterface::markType23, i18n( "Removal" ) ); markIface->setMarkPixmap( KTextEditor::MarkInterface::markType23, QPixmap::fromImage( tintedRemoval ) ); markIface->setMarkDescription( KTextEditor::MarkInterface::markType24, i18n( "Change" ) ); markIface->setMarkPixmap( KTextEditor::MarkInterface::markType24, QPixmap::fromImage( tintedChange ) ); markIface->setMarkDescription( KTextEditor::MarkInterface::markType25, i18n( "Insertion" ) ); markIface->setMarkPixmap( KTextEditor::MarkInterface::markType25, QIcon::fromTheme( QStringLiteral("insert-text") ).pixmap( 16, 16 ) ); markIface->setMarkDescription( KTextEditor::MarkInterface::markType26, i18n( "Removal" ) ); markIface->setMarkPixmap( KTextEditor::MarkInterface::markType26, QIcon::fromTheme( QStringLiteral("edit-delete") ).pixmap( 16, 16 ) ); markIface->setMarkDescription( KTextEditor::MarkInterface::markType27, i18n( "Change" ) ); markIface->setMarkPixmap( KTextEditor::MarkInterface::markType27, QIcon::fromTheme( QStringLiteral("text-field") ).pixmap( 16, 16 ) ); for ( Diff2::DifferenceList::const_iterator it = m_model->differences()->constBegin(); it != m_model->differences()->constEnd(); ++it ) { Diff2::Difference* diff = *it; int line, lineCount; Diff2::DifferenceStringList lines; if( diff->applied() ) { line = diff->destinationLineNumber(); lineCount = diff->destinationLineCount(); lines = diff->destinationLines(); } else { line = diff->sourceLineNumber(); lineCount = diff->sourceLineCount(); lines = diff->sourceLines(); } if ( line > 0 ) line -= 1; KTextEditor::Cursor c( line, 0 ); KTextEditor::Cursor endC( line + lineCount, 0 ); if ( doc->lines() <= c.line() ) c.setLine( doc->lines() - 1 ); if ( doc->lines() <= endC.line() ) endC.setLine( doc->lines() ); if ( endC.isValid() && c.isValid() ) { KTextEditor::MovingRange * r = moving->newMovingRange( KTextEditor::Range( c, endC ) ); m_ranges[r] = diff; addLineMarker( r, diff ); } } } void PatchHighlighter::textInserted(KTextEditor::Document* doc, const KTextEditor::Cursor& cursor, const QString& text) { if ( m_applying ) { // Do not interfere with patch application return; } int startLine = cursor.line(); int endColumn = cursor.column() + text.length(); qCDebug(PLUGIN_PATCHREVIEW) << "insertion range" << KTextEditor::Range(cursor, KTextEditor::Cursor(startLine, endColumn)); qCDebug(PLUGIN_PATCHREVIEW) << "inserted text" << text; QStringList removedLines; QStringList insertedLines; if (startLine > 0) { QString above = doc->line(--startLine) + '\n'; removedLines << above; insertedLines << above; } QString changed = doc->line(cursor.line()) + '\n'; removedLines << changed.mid(0, cursor.column()) + changed.mid(endColumn); insertedLines << changed; if (doc->documentRange().end().line() > cursor.line()) { QString below = doc->line(cursor.line() + 1) + '\n'; removedLines << below; insertedLines << below; } performContentChange(doc, removedLines, insertedLines, startLine + 1); } void PatchHighlighter::newlineInserted(KTextEditor::Document* doc, const KTextEditor::Cursor& cursor) { if ( m_applying ) { // Do not interfere with patch application return; } qCDebug(PLUGIN_PATCHREVIEW) << "newline range" << KTextEditor::Range(cursor, KTextEditor::Cursor(cursor.line() + 1, 0)); int startLine = cursor.line(); QStringList removedLines; QStringList insertedLines; if (startLine > 0) { QString above = doc->line(--startLine) + '\n'; removedLines << above; insertedLines << above; } insertedLines << QString('\n'); if (doc->documentRange().end().line() > cursor.line()) { QString below = doc->line(cursor.line() + 1) + '\n'; removedLines << below; insertedLines << below; } performContentChange(doc, removedLines, insertedLines, startLine + 1); } -PatchHighlighter::PatchHighlighter( Diff2::DiffModel* model, IDocument* kdoc, PatchReviewPlugin* plugin, bool updatePatchFromEdits ) throw( QString ) +PatchHighlighter::PatchHighlighter( Diff2::DiffModel* model, IDocument* kdoc, PatchReviewPlugin* plugin, bool updatePatchFromEdits ) : m_doc( kdoc ), m_plugin( plugin ), m_model( model ), m_applying( false ) { KTextEditor::Document* doc = kdoc->textDocument(); // connect( kdoc, SIGNAL(destroyed(QObject*)), this, SLOT(documentDestroyed()) ); if (updatePatchFromEdits) { connect(doc, &KTextEditor::Document::textInserted, this, &PatchHighlighter::textInserted); connect(doc, &KTextEditor::Document::lineWrapped, this, &PatchHighlighter::newlineInserted); connect(doc, &KTextEditor::Document::textRemoved, this, &PatchHighlighter::textRemoved); connect(doc, &KTextEditor::Document::lineUnwrapped, this, &PatchHighlighter::newlineRemoved); } connect(doc, &KTextEditor::Document::reloaded, this, &PatchHighlighter::documentReloaded); connect(doc, &KTextEditor::Document::destroyed, this, &PatchHighlighter::documentDestroyed); if ( doc->lines() == 0 ) return; if (qobject_cast(doc)) { //can't use new signal/slot syntax here, MarkInterface is not a QObject connect(doc, SIGNAL(markToolTipRequested(KTextEditor::Document*,KTextEditor::Mark,QPoint,bool&)), this, SLOT(markToolTipRequested(KTextEditor::Document*,KTextEditor::Mark,QPoint,bool&))); connect(doc, SIGNAL(markClicked(KTextEditor::Document*,KTextEditor::Mark,bool&)), this, SLOT(markClicked(KTextEditor::Document*,KTextEditor::Mark,bool&))); } if (qobject_cast(doc)) { //can't use new signal/slot syntax here, MovingInterface is not a QObject connect(doc, SIGNAL(aboutToDeleteMovingInterfaceContent(KTextEditor::Document*)), this, SLOT(aboutToDeleteMovingInterfaceContent(KTextEditor::Document*))); connect(doc, SIGNAL(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document*)), this, SLOT(aboutToDeleteMovingInterfaceContent(KTextEditor::Document*))); } documentReloaded(doc); } void PatchHighlighter::removeLineMarker( KTextEditor::MovingRange* range ) { KTextEditor::MovingInterface* moving = dynamic_cast( range->document() ); if ( !moving ) return; KTextEditor::MarkInterface* markIface = dynamic_cast( range->document() ); if( !markIface ) return; for (int line = range->start().line(); line <= range->end().line(); ++line) { markIface->removeMark(line, m_allmarks); } // Remove all ranges that are in the same line (the line markers) for (auto it = m_ranges.begin(); it != m_ranges.end();) { if (it.key() != range && range->overlaps(it.key()->toRange())) { delete it.key(); it = m_ranges.erase(it); } else { ++it; } } } void PatchHighlighter::addLineMarker( KTextEditor::MovingRange* range, Diff2::Difference* diff ) { KTextEditor::MovingInterface* moving = dynamic_cast( range->document() ); if ( !moving ) return; KTextEditor::MarkInterface* markIface = dynamic_cast( range->document() ); if( !markIface ) return; KTextEditor::Attribute::Ptr t( new KTextEditor::Attribute() ); bool isOriginalState = diff->applied() == m_plugin->patch()->isAlreadyApplied(); if( isOriginalState ) { t->setProperty( QTextFormat::BackgroundBrush, QBrush( ColorCache::self()->blendBackground( QColor( 0, 255, 255 ), 20 ) ) ); }else{ t->setProperty( QTextFormat::BackgroundBrush, QBrush( ColorCache::self()->blendBackground( QColor( 255, 0, 255 ), 20 ) ) ); } range->setAttribute( t ); range->setZDepth( -500 ); KTextEditor::MarkInterface::MarkTypes mark; if( isOriginalState ) { mark = KTextEditor::MarkInterface::markType27; if( isInsertion( diff ) ) mark = KTextEditor::MarkInterface::markType25; if( isRemoval( diff ) ) mark = KTextEditor::MarkInterface::markType26; }else{ mark = KTextEditor::MarkInterface::markType24; if( isInsertion( diff ) ) mark = KTextEditor::MarkInterface::markType22; if( isRemoval( diff ) ) mark = KTextEditor::MarkInterface::markType23; } markIface->addMark( range->start().line(), mark ); Diff2::DifferenceStringList lines; if( diff->applied() ) lines = diff->destinationLines(); else lines = diff->sourceLines(); for( int a = 0; a < lines.size(); ++a ) { Diff2::DifferenceString* line = lines[a]; int currentPos = 0; QString string = line->string(); Diff2::MarkerList markers = line->markerList(); for( int b = 0; b < markers.size(); ++b ) { if( markers[b]->type() == Diff2::Marker::End ) { if( currentPos != 0 || markers[b]->offset() != static_cast( string.size() ) ) { KTextEditor::MovingRange * r2 = moving->newMovingRange( KTextEditor::Range( KTextEditor::Cursor( a + range->start().line(), currentPos ), KTextEditor::Cursor( a + range->start().line(), markers[b]->offset() ) ) ); m_ranges[r2] = nullptr; KTextEditor::Attribute::Ptr t( new KTextEditor::Attribute() ); t->setProperty( QTextFormat::BackgroundBrush, QBrush( ColorCache::self()->blendBackground( QColor( 255, 0, 0 ), 70 ) ) ); r2->setAttribute( t ); r2->setZDepth( -600 ); } } currentPos = markers[b]->offset(); } } } void PatchHighlighter::clear() { if( m_ranges.empty() ) return; KTextEditor::MovingInterface* moving = dynamic_cast( m_doc->textDocument() ); if ( !moving ) return; KTextEditor::MarkInterface* markIface = dynamic_cast( m_doc->textDocument() ); if( !markIface ) return; foreach( int line, markIface->marks().keys() ) { markIface->removeMark( line, m_allmarks ); } // Diff is taking care of its own objects (except removed ones) qDeleteAll( m_ranges.keys() ); m_ranges.clear(); } PatchHighlighter::~PatchHighlighter() { clear(); } IDocument* PatchHighlighter::doc() { return m_doc; } void PatchHighlighter::documentDestroyed() { qCDebug(PLUGIN_PATCHREVIEW) << "document destroyed"; m_ranges.clear(); } void PatchHighlighter::aboutToDeleteMovingInterfaceContent( KTextEditor::Document* ) { qCDebug(PLUGIN_PATCHREVIEW) << "about to delete"; clear(); } QList< KTextEditor::MovingRange* > PatchHighlighter::ranges() const { return m_ranges.keys(); } diff --git a/plugins/patchreview/patchhighlighter.h b/plugins/patchreview/patchhighlighter.h index a2ce62b4d..0ddfef0bd 100644 --- a/plugins/patchreview/patchhighlighter.h +++ b/plugins/patchreview/patchhighlighter.h @@ -1,85 +1,85 @@ /*************************************************************************** Copyright 2006 David Nolden ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef KDEVPLATFORM_PLUGIN_PATCHHIGHLIGHTER_H #define KDEVPLATFORM_PLUGIN_PATCHHIGHLIGHTER_H #include #include #include #include #include #include namespace Diff2 { class Difference; class DiffModel; } class PatchReviewPlugin; namespace KDevelop { class IDocument; } namespace KTextEditor { class Document; class Range; class Cursor; class Mark; class MovingRange; } ///Delete itself when the document(or textDocument), or Diff-Model is deleted. class PatchHighlighter : public QObject { Q_OBJECT public: - PatchHighlighter( Diff2::DiffModel* model, KDevelop::IDocument* doc, PatchReviewPlugin* plugin, bool updatePatchFromEdits ) throw( QString ); + PatchHighlighter( Diff2::DiffModel* model, KDevelop::IDocument* doc, PatchReviewPlugin* plugin, bool updatePatchFromEdits ); ~PatchHighlighter() override; KDevelop::IDocument* doc(); QList< KTextEditor::MovingRange* > ranges() const; private Q_SLOTS: void documentReloaded( KTextEditor::Document* ); void documentDestroyed(); void aboutToDeleteMovingInterfaceContent( KTextEditor::Document* ); private: void addLineMarker( KTextEditor::MovingRange* arg1, Diff2::Difference* arg2 ); void removeLineMarker( KTextEditor::MovingRange* range ); void performContentChange( KTextEditor::Document* doc, const QStringList& oldLines, const QStringList& newLines, int editLineNumber ); QPair rangeForMark(const KTextEditor::Mark &mark); void clear(); QMap< KTextEditor::MovingRange*, Diff2::Difference* > m_ranges; KDevelop::IDocument* m_doc; PatchReviewPlugin* m_plugin; Diff2::DiffModel* m_model; bool m_applying; static const unsigned int m_allmarks; public Q_SLOTS: void markToolTipRequested( KTextEditor::Document*, const KTextEditor::Mark&, QPoint, bool & ); void showToolTipForMark(const QPoint& arg1, KTextEditor::MovingRange* arg2); bool isRemoval( Diff2::Difference* ); bool isInsertion( Diff2::Difference* ); void markClicked( KTextEditor::Document*, const KTextEditor::Mark&, bool& ); void textInserted(KTextEditor::Document* doc, const KTextEditor::Cursor& cursor, const QString& text); void newlineInserted(KTextEditor::Document* doc, const KTextEditor::Cursor& cursor); void textRemoved( KTextEditor::Document*, const KTextEditor::Range&, const QString& oldText ); void newlineRemoved(KTextEditor::Document*, int line); }; #endif diff --git a/plugins/quickopen/tests/CMakeLists.txt b/plugins/quickopen/tests/CMakeLists.txt index a85b6c92f..24d586dc4 100644 --- a/plugins/quickopen/tests/CMakeLists.txt +++ b/plugins/quickopen/tests/CMakeLists.txt @@ -1,12 +1,15 @@ -add_library(quickopentestbase STATIC - quickopentestbase.cpp - ../projectfilequickopen.cpp) +if(BUILD_TESTING) + add_library(quickopentestbase STATIC + quickopentestbase.cpp + ../projectfilequickopen.cpp) + + target_link_libraries(quickopentestbase PUBLIC + Qt5::Test KF5::IconThemes KDev::Tests KDev::Project KDev::Language) +endif() -target_link_libraries(quickopentestbase PUBLIC - Qt5::Test KF5::IconThemes KDev::Tests KDev::Project KDev::Language) ecm_add_test(test_quickopen.cpp LINK_LIBRARIES quickopentestbase) if(NOT COMPILER_OPTIMIZATIONS_DISABLED) ecm_add_test(bench_quickopen.cpp LINK_LIBRARIES quickopentestbase) set_tests_properties(bench_quickopen PROPERTIES TIMEOUT 30) endif() diff --git a/plugins/subversion/kdevsvncpp/client.hpp b/plugins/subversion/kdevsvncpp/client.hpp index a648cd57c..7a0734ec2 100644 --- a/plugins/subversion/kdevsvncpp/client.hpp +++ b/plugins/subversion/kdevsvncpp/client.hpp @@ -1,739 +1,739 @@ /* * ==================================================================== * Copyright (c) 2002-2009 The RapidSvn Group. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program (in the file GPL.txt. * If not, see . * * This software consists of voluntary contributions made by many * individuals. For exact contribution history, see the revision * history and logs, available at http://rapidsvn.tigris.org/. * ==================================================================== */ #ifndef _SVNCPP_CLIENT_H_ #define _SVNCPP_CLIENT_H_ // Ignore MSVC 6 compiler warning #if defined (_MSC_VER) && _MSC_VER <= 1200 // debug symbol truncated #pragma warning (disable: 4786) // C++ exception specification #pragma warning (disable: 4290) #endif // Ignore MSVC 7,8,9 compiler warnings #if defined (_MSC_VER) && _MSC_VER > 1200 && _MSC_VER <= 1500 // C++ exception specification #pragma warning (disable: 4290) #endif // stl #include "kdevsvncpp/vector_wrapper.hpp" #include "kdevsvncpp/utility_wrapper.hpp" #include "kdevsvncpp/map_wrapper.hpp" // svncpp #include "kdevsvncpp/context.hpp" #include "kdevsvncpp/exception.hpp" #include "kdevsvncpp/path.hpp" #include "kdevsvncpp/entry.hpp" #include "kdevsvncpp/revision.hpp" #include "kdevsvncpp/log_entry.hpp" #include "kdevsvncpp/annotate_line.hpp" namespace svn { // forward declarations class Context; class DirEntry; class Info; class Status; class Targets; typedef std::vector AnnotatedFile; typedef std::vector DirEntries; typedef std::vector InfoVector; typedef std::vector LogEntries; typedef std::vector StatusEntries; // map of property names to values typedef std::map PropertiesMap; // pair of path, PropertiesMap typedef std::pair PathPropertiesMapEntry; // vector of path, Properties pairs typedef std::vector PathPropertiesMapList; /** * These flags can be passed to the status function to filter * the files * * @see status */ struct StatusFilter { public: bool showUnversioned; bool showUnmodified; bool showModified; ///< this includes @a showConflicted as well bool showConflicted; bool showIgnored; bool showExternals; StatusFilter() : showUnversioned(false), showUnmodified(false), showModified(false), showConflicted(false), showExternals(false) { } }; /** * Subversion client API. */ class Client { public: /** * Initializes the primary memory pool. */ Client(Context * context = nullptr); virtual ~Client(); /** * @return returns the Client context */ const Context * getContext() const; /** * @return returns the Client context */ Context * getContext(); /** * sets the client context * you have to make sure the old context * is de-allocated * * @param context new context to use */ void setContext(Context * context = NULL); /** * Enumerates all files/dirs at a given path. * * Throws an exception if an error occurs * * @param path Path to explore. * @param descend Recurse into subdirectories if existant. * @param get_all Return all entries, not just the interesting ones. * @param update Query the repository for updates. * @param no_ignore Disregard default and svn:ignore property ignores. * @param ignore_externals Disregard external files. * @return vector with Status entries. */ StatusEntries status(const char * path, const bool descend = false, const bool get_all = true, const bool update = false, const bool no_ignore = false, - const bool ignore_externals = false) throw(ClientException); + const bool ignore_externals = false); /** * Enumerates all files/dirs matchin the parameter @a filter * at @a path and returns them in the vector @a statusEntries * * Throws an exception if an error occurs * * @since New in 0.9.7 * * @param path Path to explore. * @param filter use a combination of the @a SHOW_* values to filter the * output * @param descend Recurse into subdirectories if existant. * @param update Query the repository for updates. * @param entries vector with Status entries * * @return current revnum */ svn_revnum_t status(const char * path, const StatusFilter & filter, const bool descend, const bool update, - StatusEntries & entries) throw(ClientException); + StatusEntries & entries); /** * Executes a revision checkout. * @param moduleName name of the module to checkout. * @param destPath destination directory for checkout. * @param revision the revision number to checkout. If the number is -1 * then it will checkout the latest revision. * @param recurse whether you want it to checkout files recursively. * @param ignore_externals whether you want get external resources too. * @param peg_revision peg revision to checkout, by default current. * @exception ClientException */ svn_revnum_t checkout(const char * moduleName, const Path & destPath, const Revision & revision, bool recurse, bool ignore_externals = false, - const Revision & peg_revision = Revision::UNSPECIFIED) throw(ClientException); + const Revision & peg_revision = Revision::UNSPECIFIED); /** * relocate wc @a from to @a to * @exception ClientException */ void relocate(const Path & path, const char *from_url, - const char *to_url, bool recurse) throw(ClientException); + const char *to_url, bool recurse); /** * Sets a single file for deletion. * @exception ClientException */ void - remove(const Path & path, bool force) throw(ClientException); + remove(const Path & path, bool force); /** * Sets files for deletion. * * @param targets targets to delete * @param force force if files are locally modified * @exception ClientException */ void remove(const Targets & targets, - bool force) throw(ClientException); + bool force); /** * Sets files to lock. * * @param targets targets to lock * @param force force setting/stealing lock * @param comment writing comment about lock setting is necessary * @exception ClientException */ void lock(const Targets & targets, bool force, - const char * comment) throw(ClientException); + const char * comment); /** * Sets files to unlock. * * @param targets targets to unlock * @param force force unlock even if lock belongs to another user * @exception ClientException */ void - unlock(const Targets & targets, bool force) throw(ClientException); + unlock(const Targets & targets, bool force); /** * Reverts a couple of files to a pristiner state. * @exception ClientException */ void - revert(const Targets & targets, bool recurse) throw(ClientException); + revert(const Targets & targets, bool recurse); /** * Adds a file to the repository. * @exception ClientException */ void - add(const Path & path, bool recurse) throw(ClientException); + add(const Path & path, bool recurse); /** * Updates the file or directory. * @param targets target files. * @param revision the revision number to checkout. * Revision::HEAD will checkout the * latest revision. * @param recurse recursively update. * @param ignore_externals don't affect external destinations. * @exception ClientException * * @return a vector with resulting revisions */ std::vector update(const Targets & targets, const Revision & revision, bool recurse, - bool ignore_externals) throw(ClientException); + bool ignore_externals); svn_revnum_t update(const Path & path, const Revision & revision, bool recurse, - bool ignore_externals) throw(ClientException); + bool ignore_externals); /** * Retrieves the contents for a specific @a revision of * a @a path * * @param path path of file or directory * @param revision revision to retrieve * @param peg_revision peg revision to retrieve, * by default is the latest one * @return contents of the file */ std::string cat(const Path & path, const Revision & revision, - const Revision & peg_revision = Revision::UNSPECIFIED) throw(ClientException); + const Revision & peg_revision = Revision::UNSPECIFIED); /** * Retrieves the contents for a specific @a revision of * a @a path and saves it to the destination file @a dstPath. * * If @a dstPath is empty (""), then this path will be * constructed from the temporary directory on this system * and the filename in @a path. @a dstPath will still have * the file extension from @a path and uniqueness of the * temporary filename will be ensured. * * @param dstPath Filename in which the contents * of the file file will be safed. * @param path path or url * @param peg_revision peg revision to retrieve, by default is the latest one */ void get(Path & dstPath, const Path & path, const Revision & revision, - const Revision & peg_revision = Revision::UNSPECIFIED) throw(ClientException); + const Revision & peg_revision = Revision::UNSPECIFIED); /** * Retrieves the contents for a specific @a revision of * a @a path * * @param path path of file or directory * @param revisionStart revision to retrieve * @param revisionEnd revision to retrieve * @return contents of the file */ AnnotatedFile * annotate(const Path & path, const Revision & revisionStart, - const Revision & revisionEnd) throw(ClientException); + const Revision & revisionEnd); /** * Commits changes to the repository. This usually requires * authentication, see Auth. * @return Returns a long representing the revision. It returns a * -1 if the revision number is invalid. * @param targets files to commit. * @param message log message. * @param recurse whether the operation should be done recursively. * @param keep_locks whether to preserve locks or to release them after commit * @exception ClientException */ svn_revnum_t commit(const Targets & targets, const char * message, bool recurse, - bool keep_locks = false) throw(ClientException); + bool keep_locks = false); /** * Copies a versioned file with the history preserved. * @exception ClientException */ void copy(const Path & srcPath, const Revision & srcRevision, - const Path & destPath) throw(ClientException); + const Path & destPath); /** * Moves or renames a file. * @exception ClientException */ void move(const Path & srcPath, const Revision & srcRevision, const Path & destPath, - bool force) throw(ClientException); + bool force); /** * Creates a directory directly in a repository or creates a * directory on disk and schedules it for addition. If path * is a URL then authentication is usually required, see Auth. * * @exception ClientException */ void - mkdir(const Path & path) throw(ClientException); + mkdir(const Path & path); void - mkdir(const Targets & targets) throw(ClientException); + mkdir(const Targets & targets); /** * Recursively cleans up a local directory, finishing any * incomplete operations, removing lockfiles, etc. * @param path a local directory. * @exception ClientException */ void - cleanup(const Path & path) throw(ClientException); + cleanup(const Path & path); /** * Removes the 'conflicted' state on a file. * @exception ClientException */ void - resolved(const Path & path, bool recurse) throw(ClientException); + resolved(const Path & path, bool recurse); /** * Export into file or directory TO_PATH from local or remote FROM_PATH * @param from_path path to import * @param to_path where to import * @param revision revision of files in source repository or working copy * @param overwrite overwrite existing files in to_path * @param ignore_externals whether to ignore external sources in from_path * @param native_eol which EOL to use when exporting, usually different for * different OSs * @exception ClientException */ void doExport(const Path & from_path, const Path & to_path, const Revision & revision, bool overwrite = false, const Revision & peg_revision = Revision::UNSPECIFIED, bool ignore_externals = false, bool recurse = true, - const char * native_eol = NULL) throw(ClientException); + const char * native_eol = NULL); /** * Update local copy to mirror a new url. This excapsulates the * svn_client_switch() client method. * @exception ClientException */ svn_revnum_t doSwitch(const Path & path, const char * url, const Revision & revision, - bool recurse) throw(ClientException); + bool recurse); /** * Import file or directory PATH into repository directory URL at * head. This usually requires authentication, see Auth. * @param path path to import * @param message log message. * @exception ClientException */ void import(const Path & path, const char * url, const char * message, - bool recurse) throw(ClientException); + bool recurse); void import(const Path & path, const Path & url, const char * message, - bool recurse) throw(ClientException); + bool recurse); /** * Merge changes from two paths into a new local path. * @exception ClientException */ void merge(const Path & path1, const Revision & revision1, const Path & path2, const Revision & revision2, const Path & localPath, bool force, bool recurse, bool notice_ancestry = false, - bool dry_run = false) throw(ClientException); + bool dry_run = false); /** * retrieve information about the given path * or URL * * @see Client::status * @see Info */ InfoVector info(const Path & pathOrUrl, bool recurse=false, const Revision & revision = Revision::UNSPECIFIED, - const Revision & pegRevision = Revision::UNSPECIFIED) throw(ClientException); + const Revision & pegRevision = Revision::UNSPECIFIED); /** * Retrieve log information for the given path * Loads the log messages result set. The first * entry is the youngest revision. * * You can use the constants Revision::START and * Revision::HEAD * @return a vector with log entries */ const LogEntries * log(const char * path, const Revision & revisionStart, const Revision & revisionEnd, bool discoverChangedPaths = false, - bool strictNodeHistory = true) throw(ClientException); + bool strictNodeHistory = true); /** * Produce diff output which describes the delta between * @a path/@a revision1 and @a path/@a revision2. @a path * can be either a working-copy path or a URL. * * A ClientException will be thrown if either @a revision1 or * @a revision2 has an `unspecified' or unrecognized `kind'. * * @param tmpPath prefix for a temporary directory needed by diff. * Filenames will have ".tmp" and similar added to this prefix in * order to ensure uniqueness. * @param path path of the file. * @param revision1 one of the revisions to check. * @param revision2 the other revision. * @param recurse whether the operation should be done recursively. * @param ignoreAncestry whether the files will be checked for * relatedness. * @param noDiffDeleted if true, no diff output will be generated * on deleted files. * @return delta between the files * @exception ClientException */ std::string diff(const Path & tmpPath, const Path & path, const Revision & revision1, const Revision & revision2, const bool recurse, const bool ignoreAncestry, - const bool noDiffDeleted) throw(ClientException); + const bool noDiffDeleted); /** * Produce diff output which describes the delta between * @a path1/@a revision1 and @a path2/@a revision2. @a path1, * @a path2 can be either a working-copy path or a URL. * * A ClientException will be thrown if either @a revision1 or * @a revision2 has an `unspecified' or unrecognized `kind'. * * @param tmpPath prefix for a temporary directory needed by diff. * Filenames will have ".tmp" and similar added to this prefix in * order to ensure uniqueness. * @param path1 path of the first file corresponding to @a revision1. * @param path2 path of the first file corresponding to @a revision2. * @param revision1 one of the revisions to check. * @param revision2 the other revision. * @param recurse whether the operation should be done recursively. * @param ignoreAncestry whether the files will be checked for * relatedness. * @param noDiffDeleted if true, no diff output will be generated * on deleted files. * @return delta between the files * @exception ClientException */ std::string diff(const Path & tmpPath, const Path & path1, const Path & path2, const Revision & revision1, const Revision & revision2, const bool recurse, const bool ignoreAncestry, - const bool noDiffDeleted) throw(ClientException); + const bool noDiffDeleted); /** * Produce diff output which describes the delta of * @a path/@a pegRevision between @a revision1 and @a revision2. * @a path can be either a working-copy path or a URL. * * A ClientException will be thrown if either @a revision1 or * @a revision2 has an `unspecified' or unrecognized `kind'. * * @param tmpPath prefix for a temporary directory needed by diff. * Filenames will have ".tmp" and similar added to this prefix in * order to ensure uniqueness. * @param path path of the file. * @param pegRevision the peg revision to identify the path. * @param revision1 one of the revisions to check. * @param revision2 the other revision. * @param recurse whether the operation should be done recursively. * @param ignoreAncestry whether the files will be checked for * relatedness. * @param noDiffDeleted if true, no diff output will be generated * on deleted files. * @return delta between the files * @exception ClientException */ std::string diff(const Path & tmpPath, const Path & path, const Revision & pegRevision, const Revision & revision1, const Revision & revision2, const bool recurse, const bool ignoreAncestry, const bool noDiffDeleted) - throw(ClientException); + ; /** * lists entries in @a pathOrUrl no matter whether local or * repository * * @return a vector of directory entries, each with * a relative path (only filename) */ DirEntries list(const char * pathOrUrl, svn_opt_revision_t * revision, - bool recurse) throw(ClientException); + bool recurse); /** * lists properties in @a path no matter whether local or * repository * * @return PropertiesList */ PathPropertiesMapList proplist(const Path &path, const Revision &revision, bool recurse = false); /** * lists one property in @a path no matter whether local or * repository * * @return PathPropertiesMapList */ PathPropertiesMapList propget(const char * propName, const Path & path, const Revision & revision, bool recurse = false); /** * This method is deprecated, please use * @a Property.set * set property in @a path no matter whether local or * repository * * @deprecated */ void propset(const char * propName, const char * propValue, const Path & path, const Revision & revision, bool recurse = false, bool skip_checks = true); /** * delete property in @a path no matter whether local or * repository * */ void propdel(const char * propName, const Path & path, const Revision & revision, bool recurse = false); /** * lists revision properties in @a path no matter whether local or * repository * * @return PropertiesList */ std::pair revproplist(const Path & path, const Revision & revision); /** * lists one revision property in @a path no matter whether local or * repository * * @return PropertiesList */ std::pair revpropget(const char * propName, const Path & path, const Revision & revision); /** * set revision property in @a path no matter whether local or * repository * * @return Revision */ svn_revnum_t revpropset(const char * propName, const char * propValue, const Path & path, const Revision & revision, bool force = false); /** * delete revision property in @a path no matter whether local or * repository * * @return Revision */ svn_revnum_t revpropdel(const char * propName, const Path & path, const Revision & revision, bool force = false); /** * Add a single file into ignore list. * * @param path path to the file * @exception ClientException * @see svn:ignore property description */ void - ignore(const Path & path) throw(ClientException); + ignore(const Path & path); /** * Add files into ignore list. * * @param targets targets to treat as ignored * @exception ClientException * @see svn:ignore property description */ void - ignore(const Targets & targets) throw(ClientException); + ignore(const Targets & targets); private: Context * m_context; /** * disallow assignment operator */ Client & operator= (const Client &); /** * disallow copy constructor */ Client(const Client &); }; } #endif /* ----------------------------------------------------------------- * local variables: * eval: (load-file "../../rapidsvn-dev.el") * end: */ diff --git a/plugins/subversion/kdevsvncpp/client_annotate.cpp b/plugins/subversion/kdevsvncpp/client_annotate.cpp index 7e2e5f619..4e3b242a6 100644 --- a/plugins/subversion/kdevsvncpp/client_annotate.cpp +++ b/plugins/subversion/kdevsvncpp/client_annotate.cpp @@ -1,81 +1,81 @@ /* * ==================================================================== * Copyright (c) 2002-2009 The RapidSvn Group. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program (in the file GPL.txt. * If not, see . * * This software consists of voluntary contributions made by many * individuals. For exact contribution history, see the revision * history and logs, available at http://rapidsvn.tigris.org/. * ==================================================================== */ #if defined( _MSC_VER) && _MSC_VER <= 1200 #pragma warning( disable: 4786 )// debug symbol truncated #endif // Subversion api #include "svn_client.h" // svncpp #include "kdevsvncpp/client.hpp" namespace svn { static svn_error_t * annotateReceiver(void *baton, apr_int64_t line_no, svn_revnum_t revision, const char *author, const char *date, const char *line, apr_pool_t * /*pool*/) { AnnotatedFile * entries = (AnnotatedFile *) baton; entries->push_back( AnnotateLine(line_no, revision, author?author:"unknown", date?date:"unknown date", line?line:"???")); return nullptr; } AnnotatedFile * Client::annotate(const Path & path, const Revision & revisionStart, - const Revision & revisionEnd) throw(ClientException) + const Revision & revisionEnd) { Pool pool; AnnotatedFile * entries = new AnnotatedFile; svn_error_t *error; error = svn_client_blame( path.c_str(), revisionStart.revision(), revisionEnd.revision(), annotateReceiver, entries, *m_context, // client ctx pool); if (error != nullptr) { delete entries; throw ClientException(error); } return entries; } } diff --git a/plugins/subversion/kdevsvncpp/client_cat.cpp b/plugins/subversion/kdevsvncpp/client_cat.cpp index 9c4b4a6c7..8cf3208a3 100644 --- a/plugins/subversion/kdevsvncpp/client_cat.cpp +++ b/plugins/subversion/kdevsvncpp/client_cat.cpp @@ -1,178 +1,177 @@ /* * ==================================================================== * Copyright (c) 2002-2009 The RapidSvn Group. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program (in the file GPL.txt. * If not, see . * * This software consists of voluntary contributions made by many * individuals. For exact contribution history, see the revision * history and logs, available at http://rapidsvn.tigris.org/. * ==================================================================== */ #if defined( _MSC_VER) && _MSC_VER <= 1200 #pragma warning( disable: 4786 )// debug symbol truncated #endif // stl #include // Subversion api #include "svn_client.h" // svncpp #include "kdevsvncpp/client.hpp" #include "kdevsvncpp/exception.hpp" #include "kdevsvncpp/pool.hpp" #include "kdevsvncpp/status.hpp" #include "m_check.hpp" namespace svn { std::string Client::cat(const Path & path, const Revision & revision, - const Revision & peg_revision) throw(ClientException) + const Revision & peg_revision) { Pool pool; svn_stringbuf_t * stringbuf = svn_stringbuf_create("", pool); svn_stream_t * stream = svn_stream_from_stringbuf(stringbuf, pool); svn_error_t * error; error = svn_client_cat2(stream, path.c_str(), peg_revision.revision(), revision.revision(), *m_context, pool); if (error != nullptr) throw ClientException(error); return std::string(stringbuf->data, stringbuf->len); } /** * Create a new temporary file in @a dstPath. If @a dstPath * is empty (""), then construct the temporary filename * from the temporary directory and the filename component * of @a path. The file-extension of @a path will be transformed * to @a dstPath and @a dstPath will be a unique filename * * @param dstPath path to temporary file. Will be constructed * from @a path and temporary dir (and unique elements) * if empty string * @param path existing filename. Necessary only for construction * of @a dstPath * @param pool pool to use * @return open file */ static apr_file_t * openTempFile(Path & dstPath, const Path & path, const Revision & revision, Pool & pool) - throw(ClientException) { apr_file_t * file = nullptr; if (dstPath.length() > 0) { apr_status_t status = apr_file_open(&file, dstPath.c_str(), APR_WRITE | APR_CREATE | APR_TRUNCATE | APR_BINARY, APR_OS_DEFAULT, pool); if (status != 0) throw ClientException(status); } else { // split the path into its components std::string dir, filename, ext; path.split(dir, filename, ext); // add the revision number to the filename char revstring[20]; if (revision.kind() == revision.HEAD) strcpy(revstring, "HEAD"); else sprintf(revstring, "%" SVN_REVNUM_T_FMT, revision.revnum()); filename += '-'; filename += revstring; // replace the dir component with tempdir Path tempPath = Path::getTempDir(); tempPath.addComponent(filename); const char * unique_name; svn_error_t * error = svn_io_open_unique_file( &file, &unique_name, tempPath.c_str(), // path ext.c_str(), // suffix 0, // do not delete on close pool); if (error != nullptr) throw ClientException(error); dstPath = unique_name; } return file; } void Client::get(Path & dstPath, const Path & path, const Revision & revision, - const Revision & peg_revision) throw(ClientException) + const Revision & peg_revision) { Pool pool; // create a new file and suppose we only want // this users to be able to read and write the file apr_file_t * file = openTempFile(dstPath, path, revision, pool); // now create a stream and let svn_client_cat write to the // stream svn_stream_t * stream = svn_stream_from_aprfile(file, pool); if (stream != nullptr) { svn_error_t * error = svn_client_cat2( stream, path.c_str(), peg_revision.revision() , revision.revision(), *m_context, pool); if (error != nullptr) throw ClientException(error); svn_stream_close(stream); } // finalize stuff apr_file_close(file); } } /* ----------------------------------------------------------------- * local variables: * eval: (load-file "../../rapidsvn-dev.el") * end: */ diff --git a/plugins/subversion/kdevsvncpp/client_diff.cpp b/plugins/subversion/kdevsvncpp/client_diff.cpp index ed21fb4c1..ae6b6f569 100644 --- a/plugins/subversion/kdevsvncpp/client_diff.cpp +++ b/plugins/subversion/kdevsvncpp/client_diff.cpp @@ -1,340 +1,339 @@ /* * ==================================================================== * Copyright (c) 2002-2009 The RapidSvn Group. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program (in the file GPL.txt. * If not, see . * * This software consists of voluntary contributions made by many * individuals. For exact contribution history, see the revision * history and logs, available at http://rapidsvn.tigris.org/. * ==================================================================== */ #if defined( _MSC_VER) && _MSC_VER <= 1200 #pragma warning( disable: 4786 )// debug symbol truncated #endif // Subversion api #include "svn_client.h" // svncpp #include "kdevsvncpp/client.hpp" #include "kdevsvncpp/exception.hpp" #include "kdevsvncpp/pool.hpp" #include "kdevsvncpp/status.hpp" namespace svn { /** * a quick way to create error messages */ static void fail(apr_pool_t *pool, apr_status_t status, const char *fmt, ...) { va_list ap; char *msg; svn_error_t * error; va_start(ap, fmt); msg = apr_pvsprintf(pool, fmt, ap); va_end(ap); error = svn_error_create(status, nullptr, msg); throw ClientException(error); } /** * closes and deletes temporary files that diff has been using */ static void diffCleanup(apr_file_t * outfile, const char * outfileName, apr_file_t * errfile, const char * errfileName, apr_pool_t *pool) { if (outfile != nullptr) apr_file_close(outfile); if (errfile != nullptr) apr_file_close(errfile); if (outfileName != nullptr) svn_error_clear(svn_io_remove_file(outfileName, pool)); if (errfileName != nullptr) svn_error_clear(svn_io_remove_file(errfileName, pool)); } std::string Client::diff(const Path & tmpPath, const Path & path, const Revision & revision1, const Revision & revision2, const bool recurse, const bool ignoreAncestry, - const bool noDiffDeleted) throw(ClientException) + const bool noDiffDeleted) { Pool pool; svn_error_t * error; apr_status_t status; apr_file_t * outfile = nullptr; const char * outfileName = nullptr; apr_file_t * errfile = nullptr; const char * errfileName = nullptr; apr_array_header_t * options; svn_stringbuf_t * stringbuf; // svn_client_diff needs an options array, even if it is empty options = apr_array_make(pool, 0, 0); // svn_client_diff needs a temporary file to write diff output to error = svn_io_open_unique_file(&outfile, &outfileName, tmpPath.c_str(), ".tmp", false, pool); if (error != nullptr) { diffCleanup(outfile, outfileName, errfile, errfileName, pool); throw ClientException(error); } // and another one to write errors to error = svn_io_open_unique_file(&errfile, &errfileName, tmpPath.c_str(), ".tmp", false, pool); if (error != nullptr) { diffCleanup(outfile, outfileName, errfile, errfileName, pool); throw ClientException(error); } // run diff error = svn_client_diff(options, path.c_str(), revision1.revision(), path.c_str(), revision2.revision(), recurse, ignoreAncestry, noDiffDeleted, outfile, errfile, *m_context, pool); if (error != nullptr) { diffCleanup(outfile, outfileName, errfile, errfileName, pool); throw ClientException(error); } // then we reopen outfile for reading status = apr_file_close(outfile); if (status) { diffCleanup(outfile, outfileName, errfile, errfileName, pool); fail(pool, status, "failed to close '%s'", outfileName); } status = apr_file_open(&outfile, outfileName, APR_READ, APR_OS_DEFAULT, pool); if (status) { diffCleanup(outfile, outfileName, errfile, errfileName, pool); fail(pool, status, "failed to open '%s'", outfileName); } // now we can read the diff output from outfile and return that error = svn_stringbuf_from_aprfile(&stringbuf, outfile, pool); if (error != nullptr) { diffCleanup(outfile, outfileName, errfile, errfileName, pool); throw ClientException(error); } diffCleanup(outfile, outfileName, errfile, errfileName, pool); return stringbuf->data; } std::string Client::diff(const Path & tmpPath, const Path & path1, const Path & path2, const Revision & revision1, const Revision & revision2, const bool recurse, const bool ignoreAncestry, const bool noDiffDeleted) - throw(ClientException) { Pool pool; svn_error_t * error; apr_status_t status; apr_file_t * outfile = nullptr; const char * outfileName = nullptr; apr_file_t * errfile = nullptr; const char * errfileName = nullptr; apr_array_header_t * options; svn_stringbuf_t * stringbuf; // svn_client_diff needs an options array, even if it is empty options = apr_array_make(pool, 0, 0); // svn_client_diff needs a temporary file to write diff output to error = svn_io_open_unique_file(&outfile, &outfileName, tmpPath.c_str(), ".tmp", false, pool); if (error != nullptr) { diffCleanup(outfile, outfileName, errfile, errfileName, pool); throw ClientException(error); } // and another one to write errors to error = svn_io_open_unique_file(&errfile, &errfileName, tmpPath.c_str(), ".tmp", false, pool); if (error != nullptr) { diffCleanup(outfile, outfileName, errfile, errfileName, pool); throw ClientException(error); } // run diff error = svn_client_diff(options, path1.c_str(), revision1.revision(), path2.c_str(), revision2.revision(), recurse, ignoreAncestry, noDiffDeleted, outfile, errfile, *m_context, pool); if (error != nullptr) { diffCleanup(outfile, outfileName, errfile, errfileName, pool); throw ClientException(error); } // then we reopen outfile for reading status = apr_file_close(outfile); if (status) { diffCleanup(outfile, outfileName, errfile, errfileName, pool); fail(pool, status, "failed to close '%s'", outfileName); } status = apr_file_open(&outfile, outfileName, APR_READ, APR_OS_DEFAULT, pool); if (status) { diffCleanup(outfile, outfileName, errfile, errfileName, pool); fail(pool, status, "failed to open '%s'", outfileName); } // now we can read the diff output from outfile and return that error = svn_stringbuf_from_aprfile(&stringbuf, outfile, pool); if (error != nullptr) { diffCleanup(outfile, outfileName, errfile, errfileName, pool); throw ClientException(error); } diffCleanup(outfile, outfileName, errfile, errfileName, pool); return stringbuf->data; } std::string Client::diff(const Path & tmpPath, const Path & path, const Revision & pegRevision, const Revision & revision1, const Revision & revision2, const bool recurse, const bool ignoreAncestry, - const bool noDiffDeleted) throw(ClientException) + const bool noDiffDeleted) { Pool pool; svn_error_t * error; apr_status_t status; apr_file_t * outfile = nullptr; const char * outfileName = nullptr; apr_file_t * errfile = nullptr; const char * errfileName = nullptr; apr_array_header_t * options; svn_stringbuf_t * stringbuf; // svn_client_diff needs an options array, even if it is empty options = apr_array_make(pool, 0, 0); // svn_client_diff needs a temporary file to write diff output to error = svn_io_open_unique_file(&outfile, &outfileName, tmpPath.c_str(), ".tmp", false, pool); if (error != nullptr) { diffCleanup(outfile, outfileName, errfile, errfileName, pool); throw ClientException(error); } // and another one to write errors to error = svn_io_open_unique_file(&errfile, &errfileName, tmpPath.c_str(), ".tmp", false, pool); if (error != nullptr) { diffCleanup(outfile, outfileName, errfile, errfileName, pool); throw ClientException(error); } // run diff error = svn_client_diff_peg(options, path.c_str(), pegRevision.revision(), revision1.revision(), revision2.revision(), recurse, ignoreAncestry, noDiffDeleted, outfile, errfile, *m_context, pool); if (error != nullptr) { diffCleanup(outfile, outfileName, errfile, errfileName, pool); throw ClientException(error); } // then we reopen outfile for reading status = apr_file_close(outfile); if (status) { diffCleanup(outfile, outfileName, errfile, errfileName, pool); fail(pool, status, "failed to close '%s'", outfileName); } status = apr_file_open(&outfile, outfileName, APR_READ, APR_OS_DEFAULT, pool); if (status) { diffCleanup(outfile, outfileName, errfile, errfileName, pool); fail(pool, status, "failed to open '%s'", outfileName); } // now we can read the diff output from outfile and return that error = svn_stringbuf_from_aprfile(&stringbuf, outfile, pool); if (error != nullptr) { diffCleanup(outfile, outfileName, errfile, errfileName, pool); throw ClientException(error); } diffCleanup(outfile, outfileName, errfile, errfileName, pool); return stringbuf->data; } } /* ----------------------------------------------------------------- * local variables: * eval: (load-file "../../rapidsvn-dev.el") * end: */ diff --git a/plugins/subversion/kdevsvncpp/client_ls.cpp b/plugins/subversion/kdevsvncpp/client_ls.cpp index 95fd6cb91..c9915c4b6 100644 --- a/plugins/subversion/kdevsvncpp/client_ls.cpp +++ b/plugins/subversion/kdevsvncpp/client_ls.cpp @@ -1,166 +1,166 @@ /* * ==================================================================== * Copyright (c) 2002-2009 The RapidSvn Group. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program (in the file GPL.txt. * If not, see . * * This software consists of voluntary contributions made by many * individuals. For exact contribution history, see the revision * history and logs, available at http://rapidsvn.tigris.org/. * ==================================================================== */ #if defined( _MSC_VER) && _MSC_VER <= 1200 #pragma warning( disable: 4786 )// debug symbol truncated #endif // subversion api #include "svn_client.h" #include "svn_path.h" #include "svn_sorts.h" #include "svn_version.h" //#include "svn_utf.h" // svncpp #include "kdevsvncpp/client.hpp" #include "kdevsvncpp/dirent.hpp" #include "kdevsvncpp/exception.hpp" #if SVN_VER_MAJOR == 1 && SVN_VER_MINOR < 8 static int compare_items_as_paths(const svn_sort__item_t *a, const svn_sort__item_t *b) { return svn_path_compare_paths((const char *)a->key, (const char *)b->key); } namespace svn { DirEntries Client::list(const char * pathOrUrl, svn_opt_revision_t * revision, bool recurse) throw(ClientException) { Pool pool; apr_hash_t * hash; svn_error_t * error = svn_client_ls(&hash, pathOrUrl, revision, recurse, *m_context, pool); if (error != 0) throw ClientException(error); apr_array_header_t * array = svn_sort__hash( hash, compare_items_as_paths, pool); DirEntries entries; for (int i = 0; i < array->nelts; ++i) { const char *entryname; svn_dirent_t *dirent; svn_sort__item_t *item; item = &APR_ARRAY_IDX(array, i, svn_sort__item_t); entryname = static_cast(item->key); dirent = static_cast (apr_hash_get(hash, entryname, item->klen)); entries.push_back(DirEntry(entryname, dirent)); } return entries; } } #else #include static svn_error_t* store_entry( void *baton, const char *path, const svn_dirent_t *dirent, const svn_lock_t *, const char *abs_path, const char *, const char *, apr_pool_t *scratch_pool) { svn::DirEntries *entries = reinterpret_cast(baton); if (path[0] == '\0') { if (dirent->kind == svn_node_file) { // for compatibility with svn_client_ls behaviour, listing a file // stores that file name entries->push_back(svn::DirEntry(svn_path_basename(abs_path, scratch_pool), dirent)); } } else { entries->push_back(svn::DirEntry(path, dirent)); } return SVN_NO_ERROR; } static bool sort_by_path(svn::DirEntry const& a, svn::DirEntry const& b) { return svn_path_compare_paths(a.name(), b.name()) < 0; } namespace svn { DirEntries Client::list(const char * pathOrUrl, svn_opt_revision_t * revision, - bool recurse) throw(ClientException) + bool recurse) { Pool pool; DirEntries entries; svn_error_t * error = svn_client_list3(pathOrUrl, revision, revision, SVN_DEPTH_INFINITY_OR_IMMEDIATES(recurse), SVN_DIRENT_ALL, FALSE, // fetch locks FALSE, // include externals &store_entry, &entries, *m_context, pool); if (error != SVN_NO_ERROR) throw ClientException(error); std::sort(entries.begin(), entries.end(), &sort_by_path); return entries; } } #endif /* ----------------------------------------------------------------- * local variables: * eval: (load-file "../../rapidsvn-dev.el") * end: */ diff --git a/plugins/subversion/kdevsvncpp/client_modify.cpp b/plugins/subversion/kdevsvncpp/client_modify.cpp index c125d4be4..5bb714d4b 100644 --- a/plugins/subversion/kdevsvncpp/client_modify.cpp +++ b/plugins/subversion/kdevsvncpp/client_modify.cpp @@ -1,554 +1,554 @@ /* * ==================================================================== * Copyright (c) 2002-2009 The RapidSvn Group. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program (in the file GPL.txt. * If not, see . * * This software consists of voluntary contributions made by many * individuals. For exact contribution history, see the revision * history and logs, available at http://rapidsvn.tigris.org/. * ==================================================================== */ #if defined( _MSC_VER) && _MSC_VER <= 1200 #pragma warning( disable: 4786 )// debug symbol truncated #endif // subversion api #include "svn_client.h" // svncpp #include "kdevsvncpp/client.hpp" #include "kdevsvncpp/exception.hpp" #include "kdevsvncpp/pool.hpp" #include "kdevsvncpp/targets.hpp" #include "m_check.hpp" namespace svn { svn_revnum_t Client::checkout(const char * url, const Path & destPath, const Revision & revision, bool recurse, bool ignore_externals, - const Revision & peg_revision) throw(ClientException) + const Revision & peg_revision) { Pool subPool; apr_pool_t * apr_pool = subPool.pool(); svn_revnum_t revnum = 0; svn_error_t * error = svn_client_checkout2(&revnum, url, destPath.c_str(), peg_revision.revision(), // peg_revision revision.revision(), // revision recurse, ignore_externals, *m_context, apr_pool); if (error != nullptr) throw ClientException(error); return revnum; } void Client::remove(const Path & path, - bool force) throw(ClientException) + bool force) { Pool pool; Targets targets(path.c_str()); svn_client_commit_info_t *commit_info = nullptr; svn_error_t * error = svn_client_delete(&commit_info, const_cast(targets.array(pool)), force, *m_context, pool); if (error != nullptr) throw ClientException(error); } void Client::remove(const Targets & targets, - bool force) throw(ClientException) + bool force) { Pool pool; svn_client_commit_info_t *commit_info = nullptr; svn_error_t * error = svn_client_delete(&commit_info, const_cast(targets.array(pool)), force, *m_context, pool); if (error != nullptr) throw ClientException(error); } void Client::lock(const Targets & targets, bool force, - const char * comment) throw(ClientException) + const char * comment) { Pool pool; svn_error_t * error = svn_client_lock(const_cast(targets.array(pool)), comment, force, *m_context, pool); if (error != nullptr) throw ClientException(error); } void - Client::unlock(const Targets & targets, bool force) throw(ClientException) + Client::unlock(const Targets & targets, bool force) { Pool pool; svn_error_t * error = svn_client_unlock(const_cast(targets.array(pool)), force, *m_context, pool); if (error != nullptr) throw ClientException(error); } void Client::revert(const Targets & targets, - bool recurse) throw(ClientException) + bool recurse) { Pool pool; svn_error_t * error = svn_client_revert((targets.array(pool)), recurse, *m_context, pool); if (error != nullptr) throw ClientException(error); } void Client::add(const Path & path, - bool recurse) throw(ClientException) + bool recurse) { Pool pool; // we do not need the newer version of this // function "svn_client_add2" or "svn_client_add3" // since RapidSVN doesnt even have a dialog // for adding false svn_error_t * error = svn_client_add(path.c_str(), recurse, *m_context, pool); if (error != nullptr) throw ClientException(error); } std::vector Client::update(const Targets & targets, const Revision & revision, bool recurse, - bool ignore_externals) throw(ClientException) + bool ignore_externals) { Pool pool; apr_array_header_t * result_revs; svn_error_t * error = svn_client_update2(&result_revs, const_cast(targets.array(pool)), revision.revision(), recurse, ignore_externals, *m_context, pool); if (error != nullptr) throw ClientException(error); std::vector revnums; int i; for (i = 0; i < result_revs->nelts; i++) { svn_revnum_t revnum= APR_ARRAY_IDX(result_revs, i, svn_revnum_t); revnums.push_back(revnum); } return revnums; } svn_revnum_t Client::update(const Path & path, const Revision & revision, bool recurse, - bool ignore_externals) throw(ClientException) + bool ignore_externals) { Targets targets(path.c_str()); return update(targets, revision, recurse, ignore_externals)[0]; } svn_revnum_t Client::commit(const Targets & targets, const char * message, bool recurse, - bool keep_locks) throw(ClientException) + bool keep_locks) { Pool pool; m_context->setLogMessage(message); svn_client_commit_info_t *commit_info = nullptr; svn_error_t * error = svn_client_commit2(&commit_info, targets.array(pool), recurse, keep_locks, *m_context, pool); if (error != nullptr) throw ClientException(error); if (commit_info && SVN_IS_VALID_REVNUM(commit_info->revision)) return commit_info->revision; return -1; } void Client::copy(const Path & srcPath, const Revision & srcRevision, - const Path & destPath) throw(ClientException) + const Path & destPath) { Pool pool; svn_client_commit_info_t *commit_info = nullptr; svn_error_t * error = svn_client_copy(&commit_info, srcPath.c_str(), srcRevision.revision(), destPath.c_str(), *m_context, pool); if (error != nullptr) throw ClientException(error); } void Client::move(const Path & srcPath, const Revision & /*srcRevision*/, const Path & destPath, - bool force) throw(ClientException) + bool force) { Pool pool; svn_client_commit_info_t *commit_info = nullptr; svn_error_t * error = svn_client_move2(&commit_info, srcPath.c_str(), destPath.c_str(), force, *m_context, pool); if (error != nullptr) throw ClientException(error); } void - Client::mkdir(const Path & path) throw(ClientException) + Client::mkdir(const Path & path) { Pool pool; Targets targets(path.c_str()); svn_client_commit_info_t *commit_info = nullptr; svn_error_t * error = svn_client_mkdir(&commit_info, const_cast (targets.array(pool)), *m_context, pool); if (error != nullptr) throw ClientException(error); } void - Client::mkdir(const Targets & targets) throw(ClientException) + Client::mkdir(const Targets & targets) { Pool pool; svn_client_commit_info_t *commit_info = nullptr; svn_error_t * error = svn_client_mkdir(&commit_info, const_cast (targets.array(pool)), *m_context, pool); if (error != nullptr) throw ClientException(error); } void - Client::cleanup(const Path & path) throw(ClientException) + Client::cleanup(const Path & path) { Pool subPool; apr_pool_t * apr_pool = subPool.pool(); svn_error_t * error = svn_client_cleanup(path.c_str(), *m_context, apr_pool); if (error != nullptr) throw ClientException(error); } void Client::resolved(const Path & path, - bool recurse) throw(ClientException) + bool recurse) { Pool pool; svn_error_t * error = svn_client_resolved(path.c_str(), recurse, *m_context, pool); if (error != nullptr) throw ClientException(error); } void Client::doExport(const Path & from_path, const Path & to_path, const Revision & revision, bool overwrite, const Revision & peg_revision, bool ignore_externals, bool recurse, - const char * native_eol) throw(ClientException) + const char * native_eol) { Pool pool; svn_revnum_t revnum = 0; svn_error_t * error = svn_client_export3(&revnum, from_path.c_str(), to_path.c_str(), peg_revision.revision(), revision.revision(), overwrite, ignore_externals, recurse, native_eol, *m_context, pool); if (error != nullptr) throw ClientException(error); } svn_revnum_t Client::doSwitch(const Path & path, const char * url, const Revision & revision, - bool recurse) throw(ClientException) + bool recurse) { Pool pool; svn_revnum_t revnum = 0; svn_error_t * error = svn_client_switch(&revnum, path.c_str(), url, revision.revision(), recurse, *m_context, pool); if (error != nullptr) throw ClientException(error); return revnum; } void Client::import(const Path & path, const char * url, const char * message, - bool recurse) throw(ClientException) + bool recurse) { Pool pool; svn_client_commit_info_t *commit_info = nullptr; m_context->setLogMessage(message); svn_error_t * error = svn_client_import(&commit_info, path.c_str(), url, !recurse, *m_context, pool); if (error != nullptr) throw ClientException(error); } void Client::import(const Path & path, const Path & url, const char * message, - bool recurse) throw(ClientException) + bool recurse) { import(path, url.c_str(), message, recurse); } void Client::merge(const Path & path1, const Revision & revision1, const Path & path2, const Revision & revision2, const Path & localPath, bool force, bool recurse, bool notice_ancestry, - bool dry_run) throw(ClientException) + bool dry_run) { Pool pool; svn_error_t * error = svn_client_merge(path1.c_str(), revision1.revision(), path2.c_str(), revision2.revision(), localPath.c_str(), recurse, !notice_ancestry, force, dry_run, *m_context, pool); if (error != nullptr) throw ClientException(error); } void Client::relocate(const Path & path, const char * from_url, const char * to_url, - bool recurse) throw(ClientException) + bool recurse) { Pool pool; svn_error_t * error = svn_client_relocate(path.c_str(), from_url, to_url, recurse, *m_context, pool); if (error != nullptr) throw ClientException(error); } void - Client::ignore(const Path & path) throw(ClientException) + Client::ignore(const Path & path) { static const char s_svnIgnore[] = "svn:ignore"; Pool pool; std::string dirpath, basename; path.split(dirpath, basename); Revision revision; apr_hash_t *props; svn_error_t * error = svn_client_propget(&props, s_svnIgnore, dirpath.c_str(), Revision::UNSPECIFIED.revision(), false, // recursive *m_context, pool); if (error != nullptr) throw ClientException(error); PathPropertiesMapList path_prop_map_list; apr_hash_index_t *hi; for (hi = apr_hash_first(pool, props); hi; hi = apr_hash_next(hi)) { PropertiesMap prop_map; const void *key; void *val; apr_hash_this(hi, &key, nullptr, &val); prop_map [std::string(s_svnIgnore)] = std::string(((const svn_string_t *)val)->data); path_prop_map_list.push_back(PathPropertiesMapEntry((const char *)key, prop_map)); } std::string str = basename; for (PathPropertiesMapList::const_iterator i=path_prop_map_list.begin(), ei=path_prop_map_list.end();i!=ei;++i) { if (dirpath != i->first) continue; for (PropertiesMap::const_iterator j=i->second.begin(), ej=i->second.end(); j != ej; ++j) { if (s_svnIgnore != j->first) continue; str += '\n'+j->second; } } const svn_string_t * propval = svn_string_create(str.c_str(), pool); error = svn_client_propset2(s_svnIgnore, propval, dirpath.c_str(), false, false, *m_context, pool); if (error != nullptr) throw ClientException(error); } void - Client::ignore(const Targets & targets) throw(ClientException) + Client::ignore(const Targets & targets) { // it's slow, but simple for (std::vector::const_iterator i=targets.targets().begin(), e=targets.targets().end();i!=e;++i) { ignore(*i); } } } /* ----------------------------------------------------------------- * local variables: * eval: (load-file "../../rapidsvn-dev.el") * end: */ diff --git a/plugins/subversion/kdevsvncpp/client_status.cpp b/plugins/subversion/kdevsvncpp/client_status.cpp index 954cb420d..4b8b5a7aa 100644 --- a/plugins/subversion/kdevsvncpp/client_status.cpp +++ b/plugins/subversion/kdevsvncpp/client_status.cpp @@ -1,418 +1,418 @@ /* * ==================================================================== * Copyright (c) 2002-2009 The RapidSvn Group. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program (in the file GPL.txt. * If not, see . * * This software consists of voluntary contributions made by many * individuals. For exact contribution history, see the revision * history and logs, available at http://rapidsvn.tigris.org/. * ==================================================================== */ #if defined( _MSC_VER) && _MSC_VER <= 1200 #pragma warning( disable: 4786 )// debug symbol truncated #endif // Stdlib (for strcmp) #include "string.h" // Subversion api #include "svn_client.h" #include "svn_sorts.h" //#include "svn_utf.h" // svncpp #include "kdevsvncpp/client.hpp" #include "kdevsvncpp/dirent.hpp" #include "kdevsvncpp/exception.hpp" #include "kdevsvncpp/info.hpp" #include "kdevsvncpp/pool.hpp" #include "kdevsvncpp/status.hpp" #include "kdevsvncpp/targets.hpp" #include "kdevsvncpp/url.hpp" namespace svn { static svn_error_t * logReceiver(void *baton, apr_hash_t * changedPaths, svn_revnum_t rev, const char *author, const char *date, const char *msg, apr_pool_t * pool) { LogEntries * entries = (LogEntries *) baton; entries->insert(entries->begin(), LogEntry(rev, author, date, msg)); if (changedPaths != nullptr) { LogEntry &entry = entries->front(); for (apr_hash_index_t *hi = apr_hash_first(pool, changedPaths); hi != nullptr; hi = apr_hash_next(hi)) { char *path; void *val; apr_hash_this(hi, (const void **)&path, nullptr, &val); svn_log_changed_path_t *log_item = reinterpret_cast(val); entry.changedPaths.push_back( LogChangePathEntry(path, log_item->action, log_item->copyfrom_path, log_item->copyfrom_rev)); } } return nullptr; } static void statusEntriesFunc(void *baton, const char *path, svn_wc_status2_t *status) { StatusEntries * entries = static_cast(baton); entries->push_back(Status(path, status)); } static StatusEntries localStatus(const char * path, const bool descend, const bool get_all, const bool update, const bool no_ignore, Context * context, const bool ignore_externals) { svn_error_t *error; StatusEntries entries; svn_revnum_t revnum; Revision rev(Revision::HEAD); Pool pool; error = svn_client_status2( &revnum, // revnum path, // path rev, // revision statusEntriesFunc, // status func &entries, // status baton descend, // recurse get_all, update, // need 'update' to be true to get repository lock info no_ignore, ignore_externals, // ignore_externals *context, // client ctx pool); if (error!=nullptr) throw ClientException(error); return entries; } static Status dirEntryToStatus(const char * path, const DirEntry & dirEntry) { Pool pool; svn_wc_entry_t * e = static_cast( apr_pcalloc(pool, sizeof(svn_wc_entry_t))); std::string url(path); url += '/'; url += dirEntry.name(); e->name = dirEntry.name(); e->revision = dirEntry.createdRev(); e->url = url.c_str(); e->kind = dirEntry.kind(); e->schedule = svn_wc_schedule_normal; e->text_time = dirEntry.time(); e->prop_time = dirEntry.time(); e->cmt_rev = dirEntry.createdRev(); e->cmt_date = dirEntry.time(); e->cmt_author = dirEntry.lastAuthor(); svn_wc_status2_t * s = static_cast( apr_pcalloc(pool, sizeof(svn_wc_status2_t))); s->entry = e; s->text_status = svn_wc_status_normal; s->prop_status = svn_wc_status_normal; s->locked = 0; s->switched = 0; s->repos_text_status = svn_wc_status_normal; s->repos_prop_status = svn_wc_status_normal; return Status(url.c_str(), s); } static svn_revnum_t remoteStatus(Client * client, const char * path, const bool descend, StatusEntries & entries, Context * /*context*/) { Revision rev(Revision::HEAD); DirEntries dirEntries = client->list(path, rev, descend); DirEntries::const_iterator it; svn_revnum_t revnum = 0; for (it = dirEntries.begin(); it != dirEntries.end(); ++it) { const DirEntry & dirEntry = *it; entries.push_back(dirEntryToStatus(path, dirEntry)); } if (dirEntries.size() > 0) revnum = dirEntries[0].createdRev(); return revnum; } StatusEntries Client::status(const char * path, const bool descend, const bool get_all, const bool update, const bool no_ignore, - const bool ignore_externals) throw(ClientException) + const bool ignore_externals) { if (Url::isValid(path)) { StatusEntries entries; remoteStatus(this, path, descend, entries, m_context); return entries; } else return localStatus(path, descend, get_all, update, no_ignore, m_context, ignore_externals); } struct StatusFilter; struct StatusBaton { public: const StatusFilter & filter; StatusEntries & entries; StatusBaton(const StatusFilter & filter_, StatusEntries & entries_) : filter(filter_), entries(entries_) { } }; static void filteredStatusFunc(void *baton_, const char *path, svn_wc_status2_t *status) { StatusBaton * baton = static_cast(baton_); // now we have to decide whether to return the entry or not if (nullptr == status) return; bool useStatus = false; bool isUnversioned = nullptr == status->entry; if (isUnversioned) { // unversioned if (baton->filter.showUnversioned) useStatus = true; } else { bool isUnmodified = ((svn_wc_status_normal == status->text_status) && (svn_wc_status_normal == status->prop_status)); if (isUnmodified) { if (baton->filter.showUnmodified) useStatus = true; } else { // so here we know its modified. // what are we interested in? if (baton->filter.showModified) useStatus = true; else if (baton->filter.showConflicted) { if (svn_wc_status_conflicted == status->text_status) useStatus = true; } } } if (useStatus) baton->entries.push_back(Status(path, status)); } static svn_revnum_t localFilteredStatus(const char * path, const StatusFilter & filter, const bool descend, const bool update, StatusEntries & entries, Context * context) { svn_error_t *error; svn_revnum_t revnum; Revision rev(Revision::HEAD); Pool pool; StatusBaton baton(filter, entries); error = svn_client_status2( &revnum, // revnum path, // path rev, // revision filteredStatusFunc, // status func &baton, // status baton descend, // recurse filter.showUnmodified, update, // need 'update' to be true to get repository lock info filter.showIgnored, // no_ignores !filter.showExternals, // ignore_externals *context, // client ctx pool); if (error!=nullptr) throw ClientException(error); return revnum; } svn_revnum_t Client::status(const char * path, const StatusFilter & filter, const bool descend, const bool update, - StatusEntries & entries) throw(ClientException) + StatusEntries & entries) { entries.clear(); if (Url::isValid(path)) return remoteStatus(this, path, descend, entries, m_context); else { // remote URLs only need a subset of the filters: // we do not expect any modified, conflicting, unknown, // ignored entries. And externals arent visible there anyhow return localFilteredStatus( path, filter, descend, update, entries, m_context); } } const LogEntries * Client::log(const char * path, const Revision & revisionStart, const Revision & revisionEnd, bool discoverChangedPaths, - bool strictNodeHistory) throw(ClientException) + bool strictNodeHistory) { Pool pool; Targets target(path); LogEntries * entries = new LogEntries(); svn_error_t *error; int limit = 0; error = svn_client_log2( target.array(pool), revisionStart.revision(), revisionEnd.revision(), limit, discoverChangedPaths ? 1 : 0, strictNodeHistory ? 1 : 0, logReceiver, entries, *m_context, // client ctx pool); if (error != nullptr) { delete entries; throw ClientException(error); } return entries; } /** * callback function for Client::info, will be * called for every entry svn_client_info wants to * return */ static svn_error_t * infoReceiverFunc(void * baton, const char * path, const svn_info_t * info, apr_pool_t * /*pool*/) { InfoVector * infoVector = static_cast(baton); infoVector->push_back(Info(path, info)); return nullptr; } InfoVector Client::info(const Path & pathOrUrl, bool recurse, const Revision & revision, - const Revision & pegRevision) throw(ClientException) + const Revision & pegRevision) { Pool pool; InfoVector infoVector; svn_error_t * error = svn_client_info(pathOrUrl.c_str(), pegRevision.revision(), revision.revision(), infoReceiverFunc, &infoVector, recurse, *m_context, pool); if (error != nullptr) throw ClientException(error); return infoVector; } } /* ----------------------------------------------------------------- * local variables: * eval: (load-file "../../rapidsvn-dev.el") * end: */ diff --git a/plugins/subversion/svnclient.cpp b/plugins/subversion/svnclient.cpp index f40b90815..556704d7b 100644 --- a/plugins/subversion/svnclient.cpp +++ b/plugins/subversion/svnclient.cpp @@ -1,334 +1,331 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2007 Andreas Pakulat * * * * Parts of the file are copied from the RapidSvn C++ library * * Copyright (c) 2002-2006 The RapidSvn Group. All rights reserved. * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "svnclient.h" #include #include extern "C" { #include #include } #include "kdevsvncpp/targets.hpp" #include "kdevsvncpp/pool.hpp" #include #include void fail (apr_pool_t *pool, apr_status_t status, const char *fmt, ...) { va_list ap; char *msg; svn_error_t * error; va_start (ap, fmt); msg = apr_pvsprintf (pool, fmt, ap); va_end (ap); error = svn_error_create (status, nullptr, msg); throw svn::ClientException (error); } void cleanup( apr_file_t* outfile, const char* outfileName, apr_file_t* errfile, const char* errfileName, const svn::Pool& pool ) { if( outfile != nullptr ) { apr_file_close( outfile ); } if( errfile != nullptr ) { apr_file_close( outfile ); } if( outfileName != nullptr ) { svn_error_clear( svn_io_remove_file ( outfileName, pool ) ); } if( errfileName != nullptr ) { svn_error_clear( svn_io_remove_file ( errfileName, pool ) ); } } SvnClient::SvnClient( svn::Context* ctx ) : QObject(nullptr), svn::Client( ctx ), m_ctxt( ctx ) { } QString SvnClient::diff( const svn::Path& src, const svn::Revision& srcRev, const svn::Path& dst, const svn::Revision& dstRev, const bool recurse, const bool ignoreAncestry, const bool noDiffDeleted, const bool ignoreContentType ) - throw (svn::ClientException) { svn::Pool pool; // null options apr_array_header_t *options = svn_cstring_split( "", "\t\r\n", false, pool ); svn_error_t* error; const char* outfileName = nullptr; apr_file_t* outfile = nullptr; const char* errfileName = nullptr; apr_file_t* errfile = nullptr; QByteArray ba = QString(QStandardPaths::writableLocation(QStandardPaths::TempLocation)+"/kdevelop_svn_diff" ).toUtf8(); error = svn_io_open_unique_file( &outfile, &outfileName, ba.data(), ".tmp", false, pool ); if( error != nullptr ) { ::cleanup( outfile, outfileName, errfile, errfileName, pool ); throw svn::ClientException( error ); } error = svn_io_open_unique_file( &errfile, &errfileName, ba.data(), ".tmp", false, pool ); if( error != nullptr ) { ::cleanup( outfile, outfileName, errfile, errfileName, pool ); throw svn::ClientException( error ); } error = svn_client_diff3( options, src.c_str(), srcRev.revision(), dst.c_str(), dstRev.revision(), recurse, ignoreAncestry, noDiffDeleted, ignoreContentType, "UTF-8", outfile, errfile, m_ctxt->ctx(), pool ); if ( error ) { ::cleanup( outfile, outfileName, errfile, errfileName, pool ); throw svn::ClientException(error); } // then we reopen outfile for reading apr_status_t aprstatus = apr_file_close (outfile); if (aprstatus) { ::cleanup (outfile, outfileName, errfile, errfileName, pool); ::fail (pool, aprstatus, "failed to close '%s'", outfileName); } aprstatus = apr_file_open (&outfile, outfileName, APR_READ, APR_OS_DEFAULT, pool); if (aprstatus) { ::cleanup (outfile, outfileName, errfile, errfileName, pool); ::fail (pool, aprstatus, "failed to open '%s'", outfileName); } svn_stringbuf_t* stringbuf; // now we can read the diff output from outfile and return that error = svn_stringbuf_from_aprfile (&stringbuf, outfile, pool); if (error != nullptr) { ::cleanup (outfile, outfileName, errfile, errfileName, pool); throw svn::ClientException (error); } ::cleanup (outfile, outfileName, errfile, errfileName, pool); return QString::fromUtf8( stringbuf->data ); } QString SvnClient::diff( const svn::Path& src, const svn::Revision& pegRev, const svn::Revision& srcRev, const svn::Revision& dstRev, const bool recurse, const bool ignoreAncestry, const bool noDiffDeleted, const bool ignoreContentType ) - throw (svn::ClientException) { svn::Pool pool; // null options apr_array_header_t *options = svn_cstring_split( "", "\t\r\n", false, pool ); svn_error_t* error; const char* outfileName = nullptr; apr_file_t* outfile = nullptr; const char* errfileName = nullptr; apr_file_t* errfile = nullptr; QByteArray ba = QStandardPaths::writableLocation(QStandardPaths::TempLocation).toUtf8(); error = svn_io_open_unique_file( &outfile, &outfileName, ba.data(), ".tmp", false, pool ); if( error != nullptr ) { ::cleanup( outfile, outfileName, errfile, errfileName, pool ); throw svn::ClientException( error ); } error = svn_io_open_unique_file( &errfile, &errfileName, ba.data(), ".tmp", false, pool ); if( error != nullptr ) { ::cleanup( outfile, outfileName, errfile, errfileName, pool ); throw svn::ClientException( error ); } error = svn_client_diff_peg3( options, src.c_str(), pegRev.revision(), srcRev.revision(), dstRev.revision(), recurse, ignoreAncestry, noDiffDeleted, ignoreContentType, "UTF-8", outfile, errfile, m_ctxt->ctx(), pool ); if ( error ) { ::cleanup( outfile, outfileName, errfile, errfileName, pool ); throw svn::ClientException(error); } // then we reopen outfile for reading apr_status_t aprstatus = apr_file_close (outfile); if (aprstatus) { ::cleanup (outfile, outfileName, errfile, errfileName, pool); ::fail (pool, aprstatus, "failed to close '%s'", outfileName); } aprstatus = apr_file_open (&outfile, outfileName, APR_READ, APR_OS_DEFAULT, pool); if (aprstatus) { ::cleanup (outfile, outfileName, errfile, errfileName, pool); ::fail (pool, aprstatus, "failed to open '%s'", outfileName); } svn_stringbuf_t* stringbuf; // now we can read the diff output from outfile and return that error = svn_stringbuf_from_aprfile (&stringbuf, outfile, pool); if (error != nullptr) { ::cleanup (outfile, outfileName, errfile, errfileName, pool); throw svn::ClientException(error); } ::cleanup (outfile, outfileName, errfile, errfileName, pool); return QString::fromUtf8( stringbuf->data ); } static svn_error_t * kdev_logReceiver (void *baton, apr_hash_t * changedPaths, svn_revnum_t rev, const char *author, const char *date, const char *msg, apr_pool_t * pool) { SvnClient* client = (SvnClient *) baton; KDevelop::VcsEvent ev; ev.setAuthor( QString::fromUtf8( author ) ); ev.setDate( QDateTime::fromString( QString::fromUtf8( date ), Qt::ISODate ) ); ev.setMessage( QString::fromUtf8( msg ) ); KDevelop::VcsRevision vcsrev; vcsrev.setRevisionValue( QVariant( qlonglong( rev ) ), KDevelop::VcsRevision::GlobalNumber ); ev.setRevision( vcsrev ); if (changedPaths != nullptr) { for (apr_hash_index_t *hi = apr_hash_first (pool, changedPaths); hi != nullptr; hi = apr_hash_next (hi)) { char *path; void *val; apr_hash_this (hi, (const void **)&path, nullptr, &val); svn_log_changed_path_t *log_item = reinterpret_cast (val); KDevelop::VcsItemEvent iev; iev.setRepositoryLocation( QString::fromUtf8( path ) ); iev.setRepositoryCopySourceLocation( QString::fromUtf8( log_item->copyfrom_path ) ); KDevelop::VcsRevision irev; irev.setRevisionValue( QVariant( qlonglong( log_item->copyfrom_rev ) ), KDevelop::VcsRevision::GlobalNumber ); iev.setRepositoryCopySourceRevision( irev ); switch( log_item->action ) { case 'A': iev.setActions( KDevelop::VcsItemEvent::Added ); break; case 'M': iev.setActions( KDevelop::VcsItemEvent::Modified ); break; case 'D': iev.setActions( KDevelop::VcsItemEvent::Deleted ); break; case 'R': iev.setActions( KDevelop::VcsItemEvent::Replaced ); break; } auto items = ev.items(); items.append( iev ); ev.setItems( items ); } } client->emitLogEventReceived( ev ); return nullptr; } void SvnClient::log( const char* path, const svn::Revision& start, const svn::Revision& end, int limit, bool discoverChangedPaths, bool strictNodeHistory ) - throw (svn::ClientException) { svn::Pool pool; svn::Targets target(path); svn_error_t *error; error = svn_client_log2 ( target.array(pool), start.revision(), end.revision(), limit, discoverChangedPaths ? 1 : 0, strictNodeHistory ? 1 : 0, kdev_logReceiver, this, m_ctxt->ctx(), // client ctx pool); if (error != nullptr) { throw svn::ClientException (error); } } void SvnClient::emitLogEventReceived( const KDevelop::VcsEvent& ev ) { emit logEventReceived( ev ); } diff --git a/plugins/subversion/svnclient.h b/plugins/subversion/svnclient.h index 327bee905..fae3b82d7 100644 --- a/plugins/subversion/svnclient.h +++ b/plugins/subversion/svnclient.h @@ -1,67 +1,64 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2007 Andreas Pakulat * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef KDEVPLATFORM_PLUGIN_SVNCLIENT_H #define KDEVPLATFORM_PLUGIN_SVNCLIENT_H #include #include "kdevsvncpp/path.hpp" #include "kdevsvncpp/revision.hpp" #include "kdevsvncpp/client.hpp" #include class SvnClient : public QObject, public svn::Client { Q_OBJECT public: explicit SvnClient( svn::Context* = nullptr ); QString diff( const svn::Path& src, const svn::Revision& srcRev, const svn::Path& dst, const svn::Revision& dstRev, const bool recurse, const bool ignoreAncestry, - const bool noDiffDeleted, const bool ignoreContentType ) - throw (svn::ClientException); + const bool noDiffDeleted, const bool ignoreContentType ); QString diff( const svn::Path& src, const svn::Revision& pegRev, const svn::Revision& srcRev, const svn::Revision& dstRev, const bool recurse, const bool ignoreAncestry, - const bool noDiffDeleted, const bool ignoreContentType ) - throw (svn::ClientException); + const bool noDiffDeleted, const bool ignoreContentType ); void log( const char* path, const svn::Revision& start, const svn::Revision& end, int limit, bool discoverChangedPaths = false, - bool strictNodeHistory = true ) - throw (svn::ClientException); + bool strictNodeHistory = true ); void emitLogEventReceived( const KDevelop::VcsEvent& ); Q_SIGNALS: void logEventReceived( const KDevelop::VcsEvent& ); private: svn::Context* m_ctxt; }; #endif diff --git a/project/CMakeLists.txt b/project/CMakeLists.txt index 2e6fe7dbd..7a69523b8 100644 --- a/project/CMakeLists.txt +++ b/project/CMakeLists.txt @@ -1,71 +1,73 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kdevplatform\") set(KDevPlatformProject_LIB_SRCS projectutils.cpp projectmodel.cpp projectchangesmodel.cpp projectconfigskeleton.cpp importprojectjob.cpp builderjob.cpp projectbuildsetmodel.cpp projectitemlineedit.cpp helper.cpp debug.cpp projectproxymodel.cpp abstractfilemanagerplugin.cpp filemanagerlistjob.cpp projectfiltermanager.cpp interfaces/iprojectbuilder.cpp interfaces/iprojectfilemanager.cpp interfaces/ibuildsystemmanager.cpp interfaces/iprojectfilter.cpp interfaces/iprojectfilterprovider.cpp widgets/dependencieswidget.cpp ) ki18n_wrap_ui( KDevPlatformProject_LIB_SRCS widgets/dependencieswidget.ui) kdevplatform_add_library(KDevPlatformProject SOURCES ${KDevPlatformProject_LIB_SRCS}) target_link_libraries(KDevPlatformProject LINK_PUBLIC KDev::Interfaces KDev::Util # util/path.h LINK_PRIVATE KDev::Interfaces KDev::Serialization KDev::Vcs KF5::KIOWidgets Qt5::Concurrent ) -add_subdirectory(tests) +if(BUILD_TESTING) + add_subdirectory(tests) +endif() install(FILES interfaces/iprojectbuilder.h interfaces/iprojectfilemanager.h interfaces/ibuildsystemmanager.h interfaces/iprojectfilter.h interfaces/iprojectfilterprovider.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/kdevplatform/project/interfaces COMPONENT Devel ) install(FILES projectutils.h importprojectjob.h projectchangesmodel.h projectconfigskeleton.h projectmodel.h projectconfigpage.h projectitemlineedit.h projectbuildsetmodel.h builderjob.h helper.h abstractfilemanagerplugin.h projectfiltermanager.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/kdevplatform/project COMPONENT Devel ) install(FILES widgets/dependencieswidget.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/kdevplatform/project/widgets COMPONENT Devel ) diff --git a/shell/core.cpp b/shell/core.cpp index 5b2d40c55..3db20bf95 100644 --- a/shell/core.cpp +++ b/shell/core.cpp @@ -1,605 +1,609 @@ /*************************************************************************** * Copyright 2007 Alexander Dymo * * Copyright 2007 Kris Wong * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "core.h" #include "core_p.h" #include #include #include #include #include #include "mainwindow.h" #include "sessioncontroller.h" #include "uicontroller.h" #include "plugincontroller.h" #include "projectcontroller.h" #include "partcontroller.h" #include "languagecontroller.h" #include "documentcontroller.h" #include "runcontroller.h" #include "session.h" #include "documentationcontroller.h" #include "sourceformattercontroller.h" #include "progresswidget/progressmanager.h" #include "selectioncontroller.h" #include "debugcontroller.h" #include "kdevplatform_version.h" #include "workingsetcontroller.h" #include "testcontroller.h" #include "runtimecontroller.h" #include "debug.h" #include #include #include namespace { void shutdownGracefully(int sig) { static volatile std::sig_atomic_t handlingSignal = 0; if ( !handlingSignal ) { handlingSignal = 1; qCDebug(SHELL) << "signal " << sig << " received, shutting down gracefully"; QCoreApplication* app = QCoreApplication::instance(); if (QApplication* guiApp = qobject_cast(app)) { guiApp->closeAllWindows(); } app->quit(); return; } // re-raise signal with default handler and trigger program termination std::signal(sig, SIG_DFL); std::raise(sig); } void installSignalHandler() { #ifdef SIGHUP std::signal(SIGHUP, shutdownGracefully); #endif #ifdef SIGINT std::signal(SIGINT, shutdownGracefully); #endif #ifdef SIGTERM std::signal(SIGTERM, shutdownGracefully); #endif } } namespace KDevelop { Core *Core::m_self = nullptr; KAboutData createAboutData() { KAboutData aboutData( QStringLiteral("kdevplatform"), i18n("KDevelop Platform"), QStringLiteral(KDEVPLATFORM_VERSION_STRING), i18n("Development Platform for IDE-like Applications"), KAboutLicense::LGPL_V2, i18n("Copyright 2004-2017, The KDevelop developers"), QString(), QStringLiteral("https://www.kdevelop.org/")); aboutData.addAuthor( i18n("Andreas Pakulat"), i18n( "Architecture, VCS Support, Project Management Support, QMake Projectmanager" ), QStringLiteral("apaku@gmx.de") ); aboutData.addAuthor( i18n("Alexander Dymo"), i18n( "Architecture, Sublime UI, Ruby support" ), QStringLiteral("adymo@kdevelop.org") ); aboutData.addAuthor( i18n("David Nolden"), i18n( "Definition-Use Chain, C++ Support" ), QStringLiteral("david.nolden.kdevelop@art-master.de") ); aboutData.addAuthor( i18n("Aleix Pol Gonzalez"), i18n( "CMake Support, Run Support, Kross Support" ), QStringLiteral("aleixpol@kde.org") ); aboutData.addAuthor( i18n("Vladimir Prus"), i18n( "GDB integration" ), QStringLiteral("ghost@cs.msu.su") ); aboutData.addAuthor( i18n("Hamish Rodda"), i18n( "Text editor integration, definition-use chain" ), QStringLiteral("rodda@kde.org") ); aboutData.addCredit( i18n("Matt Rogers"), QString(), QStringLiteral("mattr@kde.org")); aboutData.addCredit( i18n("Cédric Pasteur"), i18n("astyle and indent support"), QStringLiteral("cedric.pasteur@free.fr") ); aboutData.addCredit( i18n("Evgeniy Ivanov"), i18n("Distributed VCS, Git, Mercurial"), QStringLiteral("powerfox@kde.ru") ); //Veritas is outside in playground currently. //aboutData.addCredit( i18n("Manuel Breugelmanns"), i18n( "Veritas, QTest integraton"), "mbr.nxi@gmail.com" ); aboutData.addCredit( i18n("Robert Gruber") , i18n( "SnippetPart, debugger and usability patches" ), QStringLiteral("rgruber@users.sourceforge.net") ); aboutData.addCredit( i18n("Dukju Ahn"), i18n( "Subversion plugin, Custom Make Manager, Overall improvements" ), QStringLiteral("dukjuahn@gmail.com") ); aboutData.addAuthor( i18n("Niko Sams"), i18n( "GDB integration, Webdevelopment Plugins" ), QStringLiteral("niko.sams@gmail.com") ); aboutData.addAuthor( i18n("Milian Wolff"), i18n( "Generic manager, Webdevelopment Plugins, Snippets, Performance" ), QStringLiteral("mail@milianw.de") ); aboutData.addAuthor( i18n("Kevin Funk"), i18n( "Co-maintainer, C++/Clang, QA, Windows Support, Performance, Website" ), QStringLiteral("kfunk@kde.org") ); aboutData.addAuthor( i18n("Sven Brauch"), i18n( "Co-maintainer, AppImage, Python Support, User Interface improvements" ), QStringLiteral("svenbrauch@gmx.de") ); return aboutData; } CorePrivate::CorePrivate(Core *core): m_aboutData( createAboutData() ), m_core(core), m_cleanedUp(false), m_shuttingDown(false) { } bool CorePrivate::initialize(Core::Setup mode, QString session ) { m_mode=mode; qCDebug(SHELL) << "Creating controllers"; if( !sessionController ) { sessionController = new SessionController(m_core); } if( !workingSetController && !(mode & Core::NoUi) ) { workingSetController = new WorkingSetController(); } qCDebug(SHELL) << "Creating ui controller"; if( !uiController ) { uiController = new UiController(m_core); } qCDebug(SHELL) << "Creating plugin controller"; if( !pluginController ) { pluginController = new PluginController(m_core); const auto pluginInfos = pluginController->allPluginInfos(); if (pluginInfos.isEmpty()) { QMessageBox::critical(nullptr, i18n("Could not find any plugins"), i18n("

Could not find any plugins during startup.
" "Please make sure QT_PLUGIN_PATH is set correctly.

" "Refer to this article for more information."), QMessageBox::Abort, QMessageBox::Abort); qCWarning(SHELL) << "Could not find any plugins, aborting"; return false; } } if( !partController && !(mode & Core::NoUi)) { partController = new PartController(m_core, uiController.data()->defaultMainWindow()); } if( !projectController ) { projectController = new ProjectController(m_core); } if( !documentController ) { documentController = new DocumentController(m_core); } if( !languageController ) { // Must be initialized after documentController, because the background parser depends // on the document controller. languageController = new LanguageController(m_core); } if( !runController ) { runController = new RunController(m_core); } if( !sourceFormatterController ) { sourceFormatterController = new SourceFormatterController(m_core); } if ( !progressController) { progressController = ProgressManager::instance(); } if( !selectionController ) { selectionController = new SelectionController(m_core); } if( !documentationController && !(mode & Core::NoUi) ) { documentationController = new DocumentationController(m_core); } if( !runtimeController ) { runtimeController = new RuntimeController(m_core); } if( !debugController ) { debugController = new DebugController(m_core); } if( !testController ) { testController = new TestController(m_core); } qCDebug(SHELL) << "Done creating controllers"; qCDebug(SHELL) << "Initializing controllers"; sessionController.data()->initialize( session ); if( !sessionController.data()->activeSessionLock() ) { return false; } // TODO: Is this early enough, or should we put the loading of the session into // the controller construct DUChain::initialize(); if (!(mode & Core::NoUi)) { uiController.data()->initialize(); } languageController.data()->initialize(); if (partController) { partController.data()->initialize(); } projectController.data()->initialize(); documentController.data()->initialize(); /* This is somewhat messy. We want to load the areas before loading the plugins, so that when each plugin is loaded we know if an area wants some of the tool view from that plugin. OTOH, loading of areas creates documents, and some documents might require that a plugin is already loaded. Probably, the best approach would be to plugins to just add tool views to a list of available tool view, and then grab those tool views when loading an area. */ qCDebug(SHELL) << "Initializing plugin controller (loading session plugins)"; pluginController.data()->initialize(); qCDebug(SHELL) << "Initializing working set controller"; if(!(mode & Core::NoUi)) { workingSetController.data()->initialize(); /* Need to do this after everything else is loaded. It's too hard to restore position of views, and toolbars, and whatever that are not created yet. */ uiController.data()->loadAllAreas(KSharedConfig::openConfig()); uiController.data()->defaultMainWindow()->show(); } qCDebug(SHELL) << "Initializing remaining controllers"; runController.data()->initialize(); sourceFormatterController.data()->initialize(); selectionController.data()->initialize(); if (documentationController) { documentationController.data()->initialize(); } debugController.data()->initialize(); testController.data()->initialize(); runtimeController.data()->initialize(); installSignalHandler(); qCDebug(SHELL) << "Done initializing controllers"; return true; } CorePrivate::~CorePrivate() { delete selectionController.data(); delete projectController.data(); delete languageController.data(); delete pluginController.data(); delete uiController.data(); delete partController.data(); delete documentController.data(); delete runController.data(); delete sessionController.data(); delete sourceFormatterController.data(); delete documentationController.data(); delete debugController.data(); delete workingSetController.data(); delete testController.data(); delete runtimeController.data(); selectionController.clear(); projectController.clear(); languageController.clear(); pluginController.clear(); uiController.clear(); partController.clear(); documentController.clear(); runController.clear(); sessionController.clear(); sourceFormatterController.clear(); documentationController.clear(); debugController.clear(); workingSetController.clear(); testController.clear(); runtimeController.clear(); } bool Core::initialize(QObject* splash, Setup mode, const QString& session ) { if (splash) { QTimer::singleShot( 200, splash, &QObject::deleteLater ); } return initialize(mode, session); } bool Core::initialize(Setup mode, const QString& session) { if (m_self) return true; m_self = new Core(); bool ret = m_self->d->initialize(mode, session); if(ret) emit m_self->initializationCompleted(); return ret; } Core *KDevelop::Core::self() { return m_self; } Core::Core(QObject *parent) : ICore(parent) { d = new CorePrivate(this); connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, &Core::shutdown); } Core::Core(CorePrivate* dd, QObject* parent) : ICore(parent), d(dd) { connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, &Core::shutdown); } Core::~Core() { qCDebug(SHELL); //Cleanup already called before mass destruction of GUI delete d; m_self = nullptr; } Core::Setup Core::setupFlags() const { return d->m_mode; } void Core::shutdown() { qCDebug(SHELL); if (!d->m_shuttingDown) { cleanup(); deleteLater(); } qCDebug(SHELL) << "Shutdown done"; } bool Core::shuttingDown() const { return d->m_shuttingDown; } void Core::cleanup() { qCDebug(SHELL); d->m_shuttingDown = true; emit aboutToShutdown(); if (!d->m_cleanedUp) { - // first of all: stop background jobs + // first of all: request stop of all background parser jobs d->languageController->backgroundParser()->abortAllJobs(); d->languageController->backgroundParser()->suspend(); d->debugController.data()->cleanup(); d->selectionController.data()->cleanup(); // Save the layout of the ui here, so run it first d->uiController.data()->cleanup(); if (d->workingSetController) d->workingSetController.data()->cleanup(); /* Must be called before projectController.data()->cleanup(). */ // Closes all documents (discards, as already saved if the user wished earlier) d->documentController.data()->cleanup(); d->runController.data()->cleanup(); if (d->partController) { d->partController->cleanup(); } d->projectController.data()->cleanup(); d->sourceFormatterController.data()->cleanup(); + + // before unloading language plugins, we need to make sure all parse jobs are done + d->languageController->backgroundParser()->waitForIdle(); d->pluginController.data()->cleanup(); + d->sessionController.data()->cleanup(); d->testController.data()->cleanup(); //Disable the functionality of the language controller d->languageController.data()->cleanup(); DUChain::self()->shutdown(); } d->m_cleanedUp = true; emit shutdownCompleted(); } KAboutData Core::aboutData() const { return d->m_aboutData; } IUiController *Core::uiController() { return d->uiController.data(); } ISession* Core::activeSession() { return sessionController()->activeSession(); } ISessionLock::Ptr Core::activeSessionLock() { return sessionController()->activeSessionLock(); } SessionController *Core::sessionController() { return d->sessionController.data(); } UiController *Core::uiControllerInternal() { return d->uiController.data(); } IPluginController *Core::pluginController() { return d->pluginController.data(); } PluginController *Core::pluginControllerInternal() { return d->pluginController.data(); } IProjectController *Core::projectController() { return d->projectController.data(); } ProjectController *Core::projectControllerInternal() { return d->projectController.data(); } IPartController *Core::partController() { return d->partController.data(); } PartController *Core::partControllerInternal() { return d->partController.data(); } ILanguageController *Core::languageController() { return d->languageController.data(); } LanguageController *Core::languageControllerInternal() { return d->languageController.data(); } IDocumentController *Core::documentController() { return d->documentController.data(); } DocumentController *Core::documentControllerInternal() { return d->documentController.data(); } IRunController *Core::runController() { return d->runController.data(); } RunController *Core::runControllerInternal() { return d->runController.data(); } ISourceFormatterController* Core::sourceFormatterController() { return d->sourceFormatterController.data(); } SourceFormatterController* Core::sourceFormatterControllerInternal() { return d->sourceFormatterController.data(); } ProgressManager *Core::progressController() { return d->progressController.data(); } ISelectionController* Core::selectionController() { return d->selectionController.data(); } IDocumentationController* Core::documentationController() { return d->documentationController.data(); } DocumentationController* Core::documentationControllerInternal() { return d->documentationController.data(); } IRuntimeController* Core::runtimeController() { return d->runtimeController.data(); } RuntimeController* Core::runtimeControllerInternal() { return d->runtimeController.data(); } IDebugController* Core::debugController() { return d->debugController.data(); } DebugController* Core::debugControllerInternal() { return d->debugController.data(); } ITestController* Core::testController() { return d->testController.data(); } TestController* Core::testControllerInternal() { return d->testController.data(); } WorkingSetController* Core::workingSetControllerInternal() { return d->workingSetController.data(); } QString Core::version() { return QStringLiteral(KDEVPLATFORM_VERSION_STRING); } } diff --git a/sublime/mainwindow_p.cpp b/sublime/mainwindow_p.cpp index 29575d4e7..961c89134 100644 --- a/sublime/mainwindow_p.cpp +++ b/sublime/mainwindow_p.cpp @@ -1,815 +1,816 @@ /*************************************************************************** * Copyright 2006-2009 Alexander Dymo * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "mainwindow_p.h" #include #include #include #include #include #include #include #include #include #include "area.h" #include "view.h" #include "areaindex.h" #include "document.h" #include "container.h" #include "controller.h" #include "mainwindow.h" #include "idealcontroller.h" #include "holdupdates.h" #include "idealbuttonbarwidget.h" #include class IdealToolBar : public QToolBar { Q_OBJECT public: explicit IdealToolBar(const QString& title, bool hideWhenEmpty, Sublime::IdealButtonBarWidget* buttons, QMainWindow* parent) : QToolBar(title, parent) , m_buttons(buttons) , m_hideWhenEmpty(hideWhenEmpty) { setMovable(false); setFloatable(false); setObjectName(title); layout()->setMargin(0); addWidget(m_buttons); if (m_hideWhenEmpty) { connect(m_buttons, &Sublime::IdealButtonBarWidget::emptyChanged, this, &IdealToolBar::updateVisibilty); } } private Q_SLOTS: void updateVisibilty() { setVisible(!m_buttons->isEmpty()); } private: Sublime::IdealButtonBarWidget* m_buttons; const bool m_hideWhenEmpty; }; namespace Sublime { MainWindowPrivate::MainWindowPrivate(MainWindow *w, Controller* controller) :controller(controller), area(nullptr), activeView(nullptr), activeToolView(nullptr), bgCentralWidget(nullptr), ignoreDockShown(false), autoAreaSettingsSave(false), m_mainWindow(w) { KActionCollection *ac = m_mainWindow->actionCollection(); m_concentrationModeAction = new QAction(i18n("Concentration Mode"), this); m_concentrationModeAction->setIcon(QIcon::fromTheme(QStringLiteral("page-zoom"))); m_concentrationModeAction->setToolTip(i18n("Removes most of the controls so you can focus on what matters.")); m_concentrationModeAction->setCheckable(true); m_concentrationModeAction->setChecked(false); ac->setDefaultShortcut(m_concentrationModeAction, Qt::META | Qt::Key_C); connect(m_concentrationModeAction, &QAction::toggled, this, &MainWindowPrivate::restoreConcentrationMode); ac->addAction(QStringLiteral("toggle_concentration_mode"), m_concentrationModeAction); QAction* action = new QAction(i18n("Show Left Dock"), this); action->setCheckable(true); ac->setDefaultShortcut(action, Qt::META | Qt::CTRL | Qt::Key_Left); connect(action, &QAction::toggled, this, &MainWindowPrivate::showLeftDock); ac->addAction(QStringLiteral("show_left_dock"), action); action = new QAction(i18n("Show Right Dock"), this); action->setCheckable(true); ac->setDefaultShortcut(action, Qt::META | Qt::CTRL | Qt::Key_Right); connect(action, &QAction::toggled, this, &MainWindowPrivate::showRightDock); ac->addAction(QStringLiteral("show_right_dock"), action); action = new QAction(i18n("Show Bottom Dock"), this); action->setCheckable(true); ac->setDefaultShortcut(action, Qt::META | Qt::CTRL | Qt::Key_Down); connect(action, &QAction::toggled, this, &MainWindowPrivate::showBottomDock); ac->addAction(QStringLiteral("show_bottom_dock"), action); action = new QAction(i18nc("@action", "Focus Editor"), this); ac->setDefaultShortcut(action, Qt::META | Qt::CTRL | Qt::Key_E); connect(action, &QAction::triggered, this, &MainWindowPrivate::focusEditor); ac->addAction(QStringLiteral("focus_editor"), action); action = new QAction(i18n("Hide/Restore Docks"), this); ac->setDefaultShortcut(action, Qt::META | Qt::CTRL | Qt::Key_Up); connect(action, &QAction::triggered, this, &MainWindowPrivate::toggleDocksShown); ac->addAction(QStringLiteral("hide_all_docks"), action); action = new QAction(i18n("Next Tool View"), this); ac->setDefaultShortcut(action, Qt::META | Qt::CTRL | Qt::Key_N); action->setIcon(QIcon::fromTheme(QStringLiteral("go-next"))); connect(action, &QAction::triggered, this, &MainWindowPrivate::selectNextDock); ac->addAction(QStringLiteral("select_next_dock"), action); action = new QAction(i18n("Previous Tool View"), this); ac->setDefaultShortcut(action, Qt::META | Qt::CTRL | Qt::Key_P); action->setIcon(QIcon::fromTheme(QStringLiteral("go-previous"))); connect(action, &QAction::triggered, this, &MainWindowPrivate::selectPreviousDock); ac->addAction(QStringLiteral("select_previous_dock"), action); action = new KActionMenu(i18n("Tool Views"), this); ac->addAction(QStringLiteral("docks_submenu"), action); idealController = new IdealController(m_mainWindow); m_leftToolBar = new IdealToolBar(i18n("Left Button Bar"), true, idealController->leftBarWidget, m_mainWindow); m_mainWindow->addToolBar(Qt::LeftToolBarArea, m_leftToolBar); m_rightToolBar = new IdealToolBar(i18n("Right Button Bar"), true, idealController->rightBarWidget, m_mainWindow); m_mainWindow->addToolBar(Qt::RightToolBarArea, m_rightToolBar); m_bottomToolBar = new IdealToolBar(i18n("Bottom Button Bar"), false, idealController->bottomBarWidget, m_mainWindow); m_mainWindow->addToolBar(Qt::BottomToolBarArea, m_bottomToolBar); // adymo: intentionally do not add a toolbar for top buttonbar // this doesn't work well with toolbars added via xmlgui centralWidget = new QWidget; centralWidget->setObjectName(QStringLiteral("centralWidget")); QVBoxLayout* layout = new QVBoxLayout(centralWidget); layout->setMargin(0); centralWidget->setLayout(layout); splitterCentralWidget = new QSplitter(centralWidget); // take as much space as possible splitterCentralWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); layout->addWidget(splitterCentralWidget, 2); // this view bar container is used for the ktexteditor integration to show // all view bars at a central place, esp. for split view configurations viewBarContainer = new QWidget; viewBarContainer->setObjectName(QStringLiteral("viewBarContainer")); // hide by default viewBarContainer->setVisible(false); // only take as much as needed viewBarContainer->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); layout->addWidget(viewBarContainer); m_mainWindow->setCentralWidget(centralWidget); connect(idealController, &IdealController::dockShown, this, &MainWindowPrivate::slotDockShown); connect(idealController, &IdealController::widgetResized, this, &MainWindowPrivate::widgetResized); connect(idealController, &IdealController::dockBarContextMenuRequested, m_mainWindow, &MainWindow::dockBarContextMenuRequested); } MainWindowPrivate::~MainWindowPrivate() { delete m_leftTabbarCornerWidget.data(); m_leftTabbarCornerWidget.clear(); } void MainWindowPrivate::disableConcentrationMode() { m_concentrationModeAction->setChecked(false); restoreConcentrationMode(); } void MainWindowPrivate::restoreConcentrationMode() { const bool concentrationModeOn = m_concentrationModeAction->isChecked(); QWidget* cornerWidget = nullptr; if (m_concentrateToolBar) { QLayout* l = m_concentrateToolBar->layout(); QLayoutItem* li = l->takeAt(1); //ensure the cornerWidget isn't destroyed with the toolbar if (li) { cornerWidget = li->widget(); delete li; } m_concentrateToolBar->deleteLater(); } m_mainWindow->menuBar()->setVisible(!concentrationModeOn); m_bottomToolBar->setVisible(!concentrationModeOn); m_leftToolBar->setVisible(!concentrationModeOn); m_rightToolBar->setVisible(!concentrationModeOn); if (concentrationModeOn) { m_concentrateToolBar = new QToolBar(m_mainWindow); m_concentrateToolBar->setObjectName(QStringLiteral("concentrateToolBar")); m_concentrateToolBar->addAction(m_concentrationModeAction); + m_concentrateToolBar->toggleViewAction()->setVisible(false); QWidgetAction *action = new QWidgetAction(this); action->setDefaultWidget(m_mainWindow->menuBar()->cornerWidget(Qt::TopRightCorner)); m_concentrateToolBar->addAction(action); m_concentrateToolBar->setMovable(false); m_mainWindow->addToolBar(Qt::TopToolBarArea, m_concentrateToolBar); m_mainWindow->menuBar()->setCornerWidget(nullptr, Qt::TopRightCorner); } else if (cornerWidget) { m_mainWindow->menuBar()->setCornerWidget(cornerWidget, Qt::TopRightCorner); cornerWidget->show(); } if (concentrationModeOn) { m_mainWindow->installEventFilter(this); } else { m_mainWindow->removeEventFilter(this); } } bool MainWindowPrivate::eventFilter(QObject* obj, QEvent* event) { Q_ASSERT(m_mainWindow == obj); if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) { const auto ev = static_cast(event); Qt::KeyboardModifiers modifiers = ev->modifiers(); //QLineEdit banned mostly so that alt navigation can be used from QuickOpen const bool visible = modifiers == Qt::AltModifier && ev->type() == QEvent::KeyPress && !qApp->focusWidget()->inherits("QLineEdit"); m_mainWindow->menuBar()->setVisible(visible); } return false; } void MainWindowPrivate::showLeftDock(bool b) { idealController->showLeftDock(b); } void MainWindowPrivate::showBottomDock(bool b) { idealController->showBottomDock(b); } void MainWindowPrivate::showRightDock(bool b) { idealController->showRightDock(b); } void MainWindowPrivate::setBackgroundCentralWidget(QWidget* w) { delete bgCentralWidget; QLayout* l=m_mainWindow->centralWidget()->layout(); l->addWidget(w); bgCentralWidget=w; setBackgroundVisible(area->views().isEmpty()); } void MainWindowPrivate::setBackgroundVisible(bool v) { if(!bgCentralWidget) return; bgCentralWidget->setVisible(v); splitterCentralWidget->setVisible(!v); } void MainWindowPrivate::focusEditor() { if (View* view = m_mainWindow->activeView()) if (view->hasWidget()) view->widget()->setFocus(Qt::ShortcutFocusReason); } void MainWindowPrivate::toggleDocksShown() { idealController->toggleDocksShown(); } void MainWindowPrivate::selectNextDock() { idealController->goPrevNextDock(IdealController::NextDock); } void MainWindowPrivate::selectPreviousDock() { idealController->goPrevNextDock(IdealController::PrevDock); } Area::WalkerMode MainWindowPrivate::IdealToolViewCreator::operator() (View *view, Sublime::Position position) { if (!d->docks.contains(view)) { d->docks << view; //add view d->idealController->addView(d->positionToDockArea(position), view); } return Area::ContinueWalker; } Area::WalkerMode MainWindowPrivate::ViewCreator::operator() (AreaIndex *index) { QSplitter *splitter = d->m_indexSplitters.value(index); if (!splitter) { //no splitter - we shall create it and populate with views if (!index->parent()) { qCDebug(SUBLIME) << "reconstructing root area"; //this is root area splitter = d->splitterCentralWidget; d->m_indexSplitters[index] = splitter; } else { if (!d->m_indexSplitters.value(index->parent())) { // can happen in working set code, as that adds a view to a child index first // hence, recursively reconstruct the parent indizes first operator()(index->parent()); } QSplitter *parent = d->m_indexSplitters.value(index->parent()); splitter = new QSplitter(parent); d->m_indexSplitters[index] = splitter; if(index == index->parent()->first()) parent->insertWidget(0, splitter); else parent->addWidget(splitter); } Q_ASSERT(splitter); } if (index->isSplit()) //this is a visible splitter splitter->setOrientation(index->orientation()); else { Container *container = nullptr; while(splitter->count() && qobject_cast(splitter->widget(0))) { // After unsplitting, we might have to remove old splitters QWidget* widget = splitter->widget(0); qCDebug(SUBLIME) << "deleting" << widget; widget->setParent(nullptr); delete widget; } if (!splitter->widget(0)) { //we need to create view container container = new Container(splitter); connect(container, &Container::activateView, d->m_mainWindow, &MainWindow::activateViewAndFocus); connect(container, &Container::tabDoubleClicked, d->m_mainWindow, &MainWindow::tabDoubleClicked); connect(container, &Container::tabContextMenuRequested, d->m_mainWindow, &MainWindow::tabContextMenuRequested); connect(container, &Container::tabToolTipRequested, d->m_mainWindow, &MainWindow::tabToolTipRequested); connect(container, static_cast(&Container::requestClose), d, &MainWindowPrivate::widgetCloseRequest, Qt::QueuedConnection); connect(container, &Container::newTabRequested, d->m_mainWindow, &MainWindow::newTabRequested); splitter->addWidget(container); } else container = qobject_cast(splitter->widget(0)); container->show(); int position = 0; bool hadActiveView = false; Sublime::View* activeView = d->activeView; foreach (View *view, index->views()) { QWidget *widget = view->widget(container); if (widget) { if(!container->hasWidget(widget)) { container->addWidget(view, position); d->viewContainers[view] = container; d->widgetToView[widget] = view; } if(activeView == view) { hadActiveView = true; container->setCurrentWidget(widget); }else if(topViews.contains(view) && !hadActiveView) container->setCurrentWidget(widget); } position++; } } return Area::ContinueWalker; } void MainWindowPrivate::reconstructViews(QList topViews) { ViewCreator viewCreator(this, topViews); area->walkViews(viewCreator, area->rootIndex()); setBackgroundVisible(area->views().isEmpty()); } void MainWindowPrivate::reconstruct() { if(m_leftTabbarCornerWidget) { m_leftTabbarCornerWidget->hide(); m_leftTabbarCornerWidget->setParent(nullptr); } IdealToolViewCreator toolViewCreator(this); area->walkToolViews(toolViewCreator, Sublime::AllPositions); reconstructViews(); { QSignalBlocker blocker(m_mainWindow); qCDebug(SUBLIME) << "RECONSTRUCT" << area << area->shownToolViews(Sublime::Left); foreach (View *view, area->toolViews()) { QString id = view->document()->documentSpecifier(); if (!id.isEmpty()) { Sublime::Position pos = area->toolViewPosition(view); if (area->shownToolViews(pos).contains(id)) idealController->raiseView(view, IdealController::GroupWithOtherViews); } } } setTabBarLeftCornerWidget(m_leftTabbarCornerWidget.data()); } void MainWindowPrivate::clearArea() { if(m_leftTabbarCornerWidget) m_leftTabbarCornerWidget->setParent(nullptr); //reparent toolview widgets to 0 to prevent their deletion together with dockwidgets foreach (View *view, area->toolViews()) { // FIXME should we really delete here?? bool nonDestructive = true; idealController->removeView(view, nonDestructive); if (view->hasWidget()) view->widget()->setParent(nullptr); } docks.clear(); //reparent all view widgets to 0 to prevent their deletion together with central //widget. this reparenting is necessary when switching areas inside the same mainwindow foreach (View *view, area->views()) { if (view->hasWidget()) view->widget()->setParent(nullptr); } cleanCentralWidget(); m_mainWindow->setActiveView(nullptr); m_indexSplitters.clear(); area = nullptr; viewContainers.clear(); setTabBarLeftCornerWidget(m_leftTabbarCornerWidget.data()); } void MainWindowPrivate::cleanCentralWidget() { while(splitterCentralWidget->count()) delete splitterCentralWidget->widget(0); setBackgroundVisible(true); } struct ShownToolViewFinder { ShownToolViewFinder() {} Area::WalkerMode operator()(View *v, Sublime::Position /*position*/) { if (v->hasWidget() && v->widget()->isVisible()) views << v; return Area::ContinueWalker; } QList views; }; void MainWindowPrivate::slotDockShown(Sublime::View* /*view*/, Sublime::Position pos, bool /*shown*/) { if (ignoreDockShown) return; ShownToolViewFinder finder; m_mainWindow->area()->walkToolViews(finder, pos); QStringList ids; foreach (View *v, finder.views) { ids << v->document()->documentSpecifier(); } area->setShownToolViews(pos, ids); } void MainWindowPrivate::viewRemovedInternal(AreaIndex* index, View* view) { Q_UNUSED(index); Q_UNUSED(view); setBackgroundVisible(area->views().isEmpty()); } void MainWindowPrivate::viewAdded(Sublime::AreaIndex *index, Sublime::View *view) { if(m_leftTabbarCornerWidget) { m_leftTabbarCornerWidget->hide(); m_leftTabbarCornerWidget->setParent(nullptr); } // Remove container objects in the hierarchy from the parents, // because they are not needed anymore, and might lead to broken splitter hierarchy and crashes. for(Sublime::AreaIndex* current = index; current; current = current->parent()) { QSplitter *splitter = m_indexSplitters[current]; if (current->isSplit() && splitter) { // Also update the orientation splitter->setOrientation(current->orientation()); for(int w = 0; w < splitter->count(); ++w) { Container *container = qobject_cast(splitter->widget(w)); //we need to remove extra container before reconstruction //first reparent widgets in container so that they are not deleted if(container) { while (container->count()) { container->widget(0)->setParent(nullptr); } //and then delete the container delete container; } } } } ViewCreator viewCreator(this); area->walkViews(viewCreator, index); emit m_mainWindow->viewAdded( view ); setTabBarLeftCornerWidget(m_leftTabbarCornerWidget.data()); setBackgroundVisible(false); } void Sublime::MainWindowPrivate::raiseToolView(Sublime::View * view) { idealController->raiseView(view); } void MainWindowPrivate::aboutToRemoveView(Sublime::AreaIndex *index, Sublime::View *view) { QSplitter *splitter = m_indexSplitters[index]; if (!splitter) return; qCDebug(SUBLIME) << "index " << index << " root " << area->rootIndex(); qCDebug(SUBLIME) << "splitter " << splitter << " container " << splitter->widget(0); qCDebug(SUBLIME) << "structure: " << index->print() << " whole structure: " << area->rootIndex()->print(); //find the container for the view and remove the widget Container *container = qobject_cast(splitter->widget(0)); if (!container) { qCWarning(SUBLIME) << "Splitter does not have a left widget!"; return; } emit m_mainWindow->aboutToRemoveView( view ); if (view->widget()) widgetToView.remove(view->widget()); viewContainers.remove(view); const bool wasActive = m_mainWindow->activeView() == view; if (container->count() > 1) { //container is not empty or this is a root index //just remove a widget if( view->widget() ) { container->removeWidget(view->widget()); view->widget()->setParent(nullptr); //activate what is visible currently in the container if the removed view was active if (wasActive) return m_mainWindow->setActiveView(container->viewForWidget(container->currentWidget())); } } else { if(m_leftTabbarCornerWidget) { m_leftTabbarCornerWidget->hide(); m_leftTabbarCornerWidget->setParent(nullptr); } // We've about to remove the last view of this container. It will // be empty, so have to delete it, as well. // If we have a container, then it should be the only child of // the splitter. Q_ASSERT(splitter->count() == 1); container->removeWidget(view->widget()); if (view->widget()) view->widget()->setParent(nullptr); else qCWarning(SUBLIME) << "View does not have a widget!"; Q_ASSERT(container->count() == 0); // We can be called from signal handler of container // (which is tab widget), so defer deleting it. container->deleteLater(); container->setParent(nullptr); /* If we're not at the top level, we get to collapse split views. */ if (index->parent()) { /* The splitter used to have container as the only child, now it's time to get rid of it. Make sure deleting splitter does not delete container -- per above comment, we'll delete it later. */ container->setParent(nullptr); m_indexSplitters.remove(index); delete splitter; AreaIndex *parent = index->parent(); QSplitter *parentSplitter = m_indexSplitters[parent]; AreaIndex *sibling = parent->first() == index ? parent->second() : parent->first(); QSplitter *siblingSplitter = m_indexSplitters[sibling]; if(siblingSplitter) { HoldUpdates du(parentSplitter); //save sizes and orientation of the sibling splitter parentSplitter->setOrientation(siblingSplitter->orientation()); QList sizes = siblingSplitter->sizes(); /* Parent has two children -- 'index' that we've deleted and 'sibling'. We move all children of 'sibling' into parent, and delete 'sibling'. sibling either contains a single Container instance, or a bunch of further QSplitters. */ while (siblingSplitter->count() > 0) { //reparent contents into parent splitter QWidget *siblingWidget = siblingSplitter->widget(0); siblingWidget->setParent(parentSplitter); parentSplitter->addWidget(siblingWidget); } m_indexSplitters.remove(sibling); delete siblingSplitter; parentSplitter->setSizes(sizes); } qCDebug(SUBLIME) << "after deleation " << parent << " has " << parentSplitter->count() << " elements"; //find the container somewhere to activate Container *containerToActivate = parentSplitter->findChild(); //activate the current view there if (containerToActivate) { m_mainWindow->setActiveView(containerToActivate->viewForWidget(containerToActivate->currentWidget())); setTabBarLeftCornerWidget(m_leftTabbarCornerWidget.data()); return; } } } setTabBarLeftCornerWidget(m_leftTabbarCornerWidget.data()); if ( wasActive ) { m_mainWindow->setActiveView(nullptr); } } void MainWindowPrivate::toolViewAdded(Sublime::View* /*toolView*/, Sublime::Position position) { IdealToolViewCreator toolViewCreator(this); area->walkToolViews(toolViewCreator, position); } void MainWindowPrivate::aboutToRemoveToolView(Sublime::View *toolView, Sublime::Position /*position*/) { if (!docks.contains(toolView)) return; idealController->removeView(toolView); // TODO are Views unique? docks.removeAll(toolView); } void MainWindowPrivate::toolViewMoved( Sublime::View *toolView, Sublime::Position position) { if (!docks.contains(toolView)) return; idealController->moveView(toolView, positionToDockArea(position)); } Qt::DockWidgetArea MainWindowPrivate::positionToDockArea(Position position) { switch (position) { case Sublime::Left: return Qt::LeftDockWidgetArea; case Sublime::Right: return Qt::RightDockWidgetArea; case Sublime::Bottom: return Qt::BottomDockWidgetArea; case Sublime::Top: return Qt::TopDockWidgetArea; default: return Qt::LeftDockWidgetArea; } } void MainWindowPrivate::switchToArea(QAction *action) { qCDebug(SUBLIME) << "for" << action; controller->showArea(m_actionAreas.value(action), m_mainWindow); } void MainWindowPrivate::updateAreaSwitcher(Sublime::Area *area) { QAction* action = m_areaActions.value(area); if (action) action->setChecked(true); } void MainWindowPrivate::activateFirstVisibleView() { QList views = area->views(); if (views.count() > 0) m_mainWindow->activateView(views.first()); } void MainWindowPrivate::widgetResized(Qt::DockWidgetArea /*dockArea*/, int /*thickness*/) { //TODO: adymo: remove all thickness business } void MainWindowPrivate::widgetCloseRequest(QWidget* widget) { if (View *view = widgetToView.value(widget)) { area->closeView(view); } } void MainWindowPrivate::setTabBarLeftCornerWidget(QWidget* widget) { if(widget != m_leftTabbarCornerWidget.data()) { delete m_leftTabbarCornerWidget.data(); m_leftTabbarCornerWidget.clear(); } m_leftTabbarCornerWidget = widget; if(!widget || !area || viewContainers.isEmpty()) return; AreaIndex* putToIndex = area->rootIndex(); QSplitter* splitter = m_indexSplitters[putToIndex]; while(putToIndex->isSplit()) { putToIndex = putToIndex->first(); splitter = m_indexSplitters[putToIndex]; } // Q_ASSERT(splitter || putToIndex == area->rootIndex()); Container* c = nullptr; if(splitter) { c = qobject_cast(splitter->widget(0)); }else{ c = *viewContainers.constBegin(); } Q_ASSERT(c); c->setLeftCornerWidget(widget); } } #include "mainwindow_p.moc" #include "moc_mainwindow_p.cpp" diff --git a/tests/autotestshell.cpp b/tests/autotestshell.cpp index 9fa71732b..8fcaa6a3e 100644 --- a/tests/autotestshell.cpp +++ b/tests/autotestshell.cpp @@ -1,34 +1,36 @@ /*************************************************************************** * Copyright 2013 Kevin Funk