diff --git a/CMakeLists.txt b/CMakeLists.txt index 9cea56b744..4f64b5ccb3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,194 +1,196 @@ cmake_minimum_required(VERSION 3.0) project(KDevelop VERSION 5.3.40) # KDevelop SOVERSION # E.g. for KDevelop 5.2.0 => SOVERSION 52 (we only promise ABI compatibility between patch version updates) set(KDEVELOP_SOVERSION 54) # plugin version as used e.g. in plugin installation path set(KDEV_PLUGIN_VERSION 32) # we need some parts of the ECM CMake helpers find_package (ECM 5.14.0 REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${KDevelop_SOURCE_DIR}/cmake/modules ${ECM_MODULE_PATH}) include(KDECompilerSettings NO_POLICY_SCOPE) # needs to be first, as set policies influence following macros include(ECMOptionalAddSubdirectory) include(ECMInstallIcons) include(ECMAddAppIcon) include(ECMSetupVersion) include(ECMAddTests) include(ECMMarkNonGuiExecutable) include(ECMGenerateHeaders) include(ECMQtDeclareLoggingCategory) include(GenerateExportHeader) include(CMakePackageConfigHelpers) include(FeatureSummary) include(WriteBasicConfigVersionFile) include(CheckFunctionExists) include(KDEInstallDirs) include(KDECMakeSettings) include(KDevelopMacrosInternal) if(POLICY CMP0071) # CMake 3.10 generates warnings when projects combine AUTOMOC with qt5_wrap_ui() or qt5_add_resources() # Avoid that by setting this policy (cf. https://bugreports.qt.io/browse/QTBUG-63442) # Note: Once we depend on a Qt which has this fix (likely Qt 5.9.4+), remove this cmake_policy(SET CMP0071 OLD) endif() set(QT_MIN_VERSION "5.5.0") find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED Widgets Concurrent Quick QuickWidgets) if(BUILD_TESTING) find_package(Qt5Test ${QT_MIN_VERSION} CONFIG REQUIRED) endif() set(KF5_DEP_VERSION "5.15.0") # we need KCrash::initialize find_package(KF5 ${KF5_DEP_VERSION} REQUIRED COMPONENTS Config Declarative DocTools IconThemes I18n ItemModels ItemViews JobWidgets KCMUtils KIO NewStuff NotifyConfig Parts Service TextEditor ThreadWeaver XmlGui WindowSystem Crash GuiAddons Archive Notifications ) find_package(KF5SysGuard CONFIG) set_package_properties(KF5SysGuard PROPERTIES PURPOSE "Framework for process listing. Required for the 'Attach to Process' feature" TYPE RECOMMENDED ) find_package(KDevelop-PG-Qt 1.90.90 CONFIG) set_package_properties(KDevelop-PG-Qt PROPERTIES PURPOSE "KDevelop parser generator library. Required for the QMake Builder/Manager plugin." TYPE RECOMMENDED ) find_package(SharedMimeInfo REQUIRED) if(NOT CMAKE_VERSION VERSION_LESS "3.10.0" AND KF5_VERSION VERSION_LESS "5.42.0") # CMake 3.9+ warns about automoc on files without Q_OBJECT, and doesn't know about other macros. # 3.10+ lets us provide more macro names that require automoc. # KF5 >= 5.42 takes care itself of adding its macros in its cmake config files list(APPEND CMAKE_AUTOMOC_MACRO_NAMES "K_PLUGIN_FACTORY_WITH_JSON" "K_EXPORT_PLASMA_DATAENGINE_WITH_JSON" "K_EXPORT_PLASMA_RUNNER") endif() if(NOT CMAKE_VERSION VERSION_LESS "3.9.0" AND KF5_VERSION VERSION_LESS "5.44.0") # CMake's automoc needs help to find names of plugin metadata files in case Q_PLUGIN_METADATA # is indirectly used via other C++ preprocessor macros # 3.9+ lets us provide some filter rule pairs (keyword, regexp) to match the names of such files # in the plain text of the sources. See AUTOMOC_DEPEND_FILTERS docs for details. list(APPEND CMAKE_AUTOMOC_DEPEND_FILTERS "K_PLUGIN_FACTORY_WITH_JSON" "[\n^][ \t]*K_PLUGIN_FACTORY_WITH_JSON[ \t\n]*\\([^,]*,[ \t\n]*\"([^\"]+)\"" "K_EXPORT_PLASMA_DATAENGINE_WITH_JSON" "[\n^][ \t]*K_EXPORT_PLASMA_DATAENGINE_WITH_JSON[ \t\n]*\\([^,]*,[^,]*,[ \t\n]*\"([^\"]+)\"" ) endif() add_definitions( -DQT_DEPRECATED_WARNINGS -DQT_DISABLE_DEPRECATED_BEFORE=0x050500 -DQT_NO_SIGNALS_SLOTS_KEYWORDS -DQT_NO_URL_CAST_FROM_STRING -DQT_NO_CAST_FROM_ASCII -DQT_NO_CAST_TO_ASCII -DQT_NO_CAST_FROM_BYTEARRAY -DQT_STRICT_ITERATORS -DQT_USE_QSTRINGBUILDER -DQT_NO_NARROWING_CONVERSIONS_IN_CONNECT ) # 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=switch) add_compile_flag_if_supported(-Werror=undefined-bool-conversion) add_compile_flag_if_supported(-Werror=tautological-undefined-compare) +add_compile_flag_if_supported(-Werror=implicit-fallthrough) # Use Q_FALLTHROUGH for false positives. if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") add_compile_flag_if_supported(-Wdocumentation) add_compile_flag_if_supported(-Wcovered-switch-default) + add_compile_flag_if_supported(-Wunreachable-code-break) # This warning is triggered by every call to qCDebug() add_compile_flag_if_supported(-Wno-gnu-zero-variadic-macro-arguments) endif() if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") add_compile_flag_if_supported(-pedantic) add_compile_flag_if_supported(-Wzero-as-null-pointer-constant CXX_ONLY) endif() if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") add_compile_flag_if_supported(-Wsuggest-override CXX_ONLY) endif() include_directories(${KDevelop_SOURCE_DIR} ${KDevelop_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() # create config-kdevelop.h configure_file(config-kdevelop.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kdevelop.h) add_subdirectory(kdevplatform) add_subdirectory(plugins) add_subdirectory(pics) add_subdirectory(app) add_subdirectory(app_templates) add_subdirectory(file_templates) add_subdirectory(shortcuts) add_subdirectory(doc) set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KDevelop") configure_package_config_file("${CMAKE_CURRENT_SOURCE_DIR}/KDevelopConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/KDevelopConfig.cmake" INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR} ) ecm_setup_version(${KDevelop_VERSION_MAJOR}.${KDevelop_VERSION_MINOR}.${KDevelop_VERSION_PATCH} VARIABLE_PREFIX KDEVELOP VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/kdevelop_version.h" PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KDevelopConfigVersion.cmake" ) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/kdevelop_version.h" DESTINATION "${KDE_INSTALL_INCLUDEDIR}/kdevelop") install(FILES "${CMAKE_CURRENT_BINARY_DIR}/KDevelopConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/KDevelopConfigVersion.cmake" DESTINATION "${CMAKECONFIG_INSTALL_DIR}" ) install(EXPORT KDevelopTargets DESTINATION "${CMAKECONFIG_INSTALL_DIR}" NAMESPACE KDev:: FILE KDevelopTargets.cmake) # kdebugsettings file install(FILES kdevelop.categories DESTINATION ${KDE_INSTALL_CONFDIR}) # 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) install(FILES org.kde.kdevelop.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR}) # Make it possible to use the po files fetched by the fetch-translations step ki18n_install(po) feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/kdevplatform/language/codecompletion/normaldeclarationcompletionitem.cpp b/kdevplatform/language/codecompletion/normaldeclarationcompletionitem.cpp index 2c877053b9..6ea2fb4b76 100644 --- a/kdevplatform/language/codecompletion/normaldeclarationcompletionitem.cpp +++ b/kdevplatform/language/codecompletion/normaldeclarationcompletionitem.cpp @@ -1,231 +1,230 @@ /* * KDevelop Generic Code Completion Support * * 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 "normaldeclarationcompletionitem.h" #include "codecompletionmodel.h" #include "../duchain/duchainlock.h" #include "../duchain/duchain.h" #include "../duchain/classfunctiondeclaration.h" #include "../duchain/types/functiontype.h" #include "../duchain/types/enumeratortype.h" #include "../duchain/duchainutils.h" #include "../duchain/navigation/abstractdeclarationnavigationcontext.h" #include #include #include namespace KDevelop { const int NormalDeclarationCompletionItem::normalBestMatchesCount = 5; //If this is true, the return-values of argument-hints will be just written as "..." if they are too long const bool NormalDeclarationCompletionItem::shortenArgumentHintReturnValues = true; const int NormalDeclarationCompletionItem::maximumArgumentHintReturnValueLength = 30; const int NormalDeclarationCompletionItem::desiredTypeLength = 20; NormalDeclarationCompletionItem::NormalDeclarationCompletionItem(const KDevelop::DeclarationPointer& decl, const QExplicitlySharedDataPointer& context, int inheritanceDepth) : m_completionContext(context), m_declaration(decl), m_inheritanceDepth(inheritanceDepth) { } KDevelop::DeclarationPointer NormalDeclarationCompletionItem::declaration() const { return m_declaration; } QExplicitlySharedDataPointer< KDevelop::CodeCompletionContext > NormalDeclarationCompletionItem::completionContext() const { return m_completionContext; } int NormalDeclarationCompletionItem::inheritanceDepth() const { return m_inheritanceDepth; } int NormalDeclarationCompletionItem::argumentHintDepth() const { if( m_completionContext ) return m_completionContext->depth(); else return 0; } QString NormalDeclarationCompletionItem::declarationName() const { if (!m_declaration) { return QStringLiteral(""); } QString ret = m_declaration->identifier().toString(); if (ret.isEmpty()) return QStringLiteral(""); else return ret; } void NormalDeclarationCompletionItem::execute(KTextEditor::View* view, const KTextEditor::Range& word) { if( m_completionContext && m_completionContext->depth() != 0 ) return; //Do not replace any text when it is an argument-hint KTextEditor::Document* document = view->document(); QString newText; { KDevelop::DUChainReadLocker lock(KDevelop::DUChain::lock()); if(m_declaration) { newText = declarationName(); } else { qCDebug(LANGUAGE) << "Declaration disappeared"; return; } } document->replaceText(word, newText); KTextEditor::Range newRange = word; newRange.setEnd(KTextEditor::Cursor(newRange.end().line(), newRange.start().column() + newText.length())); executed(view, newRange); } QWidget* NormalDeclarationCompletionItem::createExpandingWidget(const KDevelop::CodeCompletionModel* model) const { Q_UNUSED(model); return nullptr; } bool NormalDeclarationCompletionItem::createsExpandingWidget() const { return false; } QString NormalDeclarationCompletionItem::shortenedTypeString(const KDevelop::DeclarationPointer& decl, int desiredTypeLength) const { Q_UNUSED(desiredTypeLength); return decl->abstractType()->toString(); } void NormalDeclarationCompletionItem::executed(KTextEditor::View* view, const KTextEditor::Range& word) { Q_UNUSED(view); Q_UNUSED(word); } QVariant NormalDeclarationCompletionItem::data(const QModelIndex& index, int role, const KDevelop::CodeCompletionModel* model) const { DUChainReadLocker lock(DUChain::lock(), 500); if(!lock.locked()) { qCDebug(LANGUAGE) << "Failed to lock the du-chain in time"; return QVariant(); } if(!m_declaration) return QVariant(); switch (role) { case Qt::DisplayRole: if (index.column() == CodeCompletionModel::Name) { return declarationName(); } else if(index.column() == CodeCompletionModel::Postfix) { if (FunctionType::Ptr functionType = m_declaration->type()) { // Retrieve const/volatile string return functionType->AbstractType::toString(); } } else if(index.column() == CodeCompletionModel::Prefix) { if(m_declaration->kind() == Declaration::Namespace) return QStringLiteral("namespace"); if (m_declaration->abstractType()) { if(EnumeratorType::Ptr enumerator = m_declaration->type()) { if(m_declaration->context()->owner() && m_declaration->context()->owner()->abstractType()) { if(!m_declaration->context()->owner()->identifier().isEmpty()) return shortenedTypeString(DeclarationPointer(m_declaration->context()->owner()), desiredTypeLength); else return QStringLiteral("enum"); } } if (FunctionType::Ptr functionType = m_declaration->type()) { ClassFunctionDeclaration* funDecl = dynamic_cast(m_declaration.data()); if (functionType->returnType()) { QString ret = shortenedTypeString(m_declaration, desiredTypeLength); if(shortenArgumentHintReturnValues && argumentHintDepth() && ret.length() > maximumArgumentHintReturnValueLength) return QStringLiteral("..."); else return ret; }else if(argumentHintDepth()) { return QString();//Don't show useless prefixes in the argument-hints }else if(funDecl && funDecl->isConstructor() ) return QStringLiteral(""); else if(funDecl && funDecl->isDestructor() ) return QStringLiteral(""); else return QStringLiteral(""); } else { return shortenedTypeString(m_declaration, desiredTypeLength); } } else { return QStringLiteral(""); } } else if (index.column() == CodeCompletionModel::Arguments) { if (m_declaration->isFunctionDeclaration()) { auto functionType = declaration()->type(); return functionType ? functionType->partToString(FunctionType::SignatureArguments) : QVariant(); } } break; case CodeCompletionModel::BestMatchesCount: return QVariant(normalBestMatchesCount); - break; case CodeCompletionModel::IsExpandable: return QVariant(createsExpandingWidget()); case CodeCompletionModel::ExpandingWidget: { QWidget* nav = createExpandingWidget(model); Q_ASSERT(nav); QVariant v; v.setValue(nav); return v; } case CodeCompletionModel::ScopeIndex: return static_cast(reinterpret_cast(m_declaration->context())); case CodeCompletionModel::CompletionRole: return (int)completionProperties(); case CodeCompletionModel::ItemSelected: { NavigationContextPointer ctx(new AbstractDeclarationNavigationContext(DeclarationPointer(m_declaration), TopDUContextPointer())); return ctx->html(true); } case Qt::DecorationRole: { if( index.column() == CodeCompletionModel::Icon ) { CodeCompletionModel::CompletionProperties p = completionProperties(); lock.unlock(); return DUChainUtils::iconForProperties(p); } break; } } return QVariant(); } } diff --git a/kdevplatform/language/duchain/navigation/abstractnavigationcontext.cpp b/kdevplatform/language/duchain/navigation/abstractnavigationcontext.cpp index 2bc91d9341..21ba04e522 100644 --- a/kdevplatform/language/duchain/navigation/abstractnavigationcontext.cpp +++ b/kdevplatform/language/duchain/navigation/abstractnavigationcontext.cpp @@ -1,562 +1,562 @@ /* Copyright 2007 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 "abstractnavigationcontext.h" #include #include #include "abstractdeclarationnavigationcontext.h" #include "abstractnavigationwidget.h" #include "usesnavigationcontext.h" #include "../../../interfaces/icore.h" #include "../../../interfaces/idocumentcontroller.h" #include "../functiondeclaration.h" #include "../namespacealiasdeclaration.h" #include "../types/functiontype.h" #include "../types/structuretype.h" #include #include #include #include namespace KDevelop { class AbstractNavigationContextPrivate { public: QVector m_children; //Used to keep alive all children until this is deleted int m_selectedLink = 0; //The link currently selected NavigationAction m_selectedLinkAction; //Target of the currently selected link bool m_shorten = false; //A counter used while building the html-code to count the used links. int m_linkCount = -1; //Something else than -1 if the current position is represented by a line-number, not a link. int m_currentLine = 0; int m_currentPositionLine = 0; QMap m_links; QMap m_linkLines; //Holds the line for each link QMap m_intLinks; AbstractNavigationContext* m_previousContext; QString m_prefix, m_suffix; TopDUContextPointer m_topContext; QString m_currentText; //Here the text is built }; void AbstractNavigationContext::setTopContext(const TopDUContextPointer& context) { d->m_topContext = context; } TopDUContextPointer AbstractNavigationContext::topContext() const { return d->m_topContext; } AbstractNavigationContext::AbstractNavigationContext(const TopDUContextPointer& topContext, AbstractNavigationContext* previousContext) : d(new AbstractNavigationContextPrivate) { d->m_previousContext = previousContext; d->m_topContext = topContext; qRegisterMetaType("KTextEditor::Cursor"); qRegisterMetaType("IDocumentation::Ptr"); } AbstractNavigationContext::~AbstractNavigationContext() { } void AbstractNavigationContext::addExternalHtml( const QString& text ) { int lastPos = 0; int pos = 0; QString fileMark = QStringLiteral("KDEV_FILE_LINK{"); while( pos < text.length() && (pos = text.indexOf( fileMark, pos)) != -1 ) { modifyHtml() += text.mid(lastPos, pos-lastPos); pos += fileMark.length(); if( pos != text.length() ) { int fileEnd = text.indexOf(QLatin1Char('}'), pos); if( fileEnd != -1 ) { QString file = text.mid( pos, fileEnd - pos ); pos = fileEnd + 1; const QUrl url = QUrl::fromUserInput(file); makeLink( url.fileName(), file, NavigationAction( url, KTextEditor::Cursor() ) ); } } lastPos = pos; } modifyHtml() += text.mid(lastPos, text.length()-lastPos); } void AbstractNavigationContext::makeLink(const QString& name, const DeclarationPointer& declaration, NavigationAction::Type actionType) { NavigationAction action( declaration, actionType ); makeLink(name, QString(), action); } QString AbstractNavigationContext::createLink(const QString& name, const QString&, const NavigationAction& action) { if(d->m_shorten) { //Do not create links in shortened mode, it's only for viewing return typeHighlight(name.toHtmlEscaped()); } // NOTE: Since the by definition in the HTML standard some uri components // are case-insensitive, we define a new lowercase link-id for each // link. Otherwise Qt 5 seems to mess up the casing and the link // cannot be matched when it's executed. QString hrefId = QStringLiteral("link_%1").arg(d->m_links.count()); d->m_links[ hrefId ] = action; d->m_intLinks[ d->m_linkCount ] = action; d->m_linkLines[ d->m_linkCount ] = d->m_currentLine; if(d->m_currentPositionLine == d->m_currentLine) { d->m_currentPositionLine = -1; d->m_selectedLink = d->m_linkCount; } QString str = name.toHtmlEscaped(); if( d->m_linkCount == d->m_selectedLink ) str = QLatin1String("") + str + QLatin1String(""); QString ret = QLatin1String("m_linkCount == d->m_selectedLink && d->m_currentPositionLine == -1) ? QStringLiteral(" name = \"currentPosition\"") : QString()) + QLatin1Char('>') + str + QLatin1String(""); if( d->m_selectedLink == d->m_linkCount ) d->m_selectedLinkAction = action; ++d->m_linkCount; return ret; } void AbstractNavigationContext::makeLink(const QString& name, const QString& targetId, const NavigationAction& action) { modifyHtml() += createLink(name, targetId, action); } void AbstractNavigationContext::clear() { d->m_linkCount = 0; d->m_currentLine = 0; d->m_currentText.clear(); d->m_links.clear(); d->m_intLinks.clear(); d->m_linkLines.clear(); } void AbstractNavigationContext::executeLink(const QString& link) { if(!d->m_links.contains(link)) return; execute(d->m_links[link]); } NavigationContextPointer AbstractNavigationContext::executeKeyAction(const QString& key) { Q_UNUSED(key); return NavigationContextPointer(this); } NavigationContextPointer AbstractNavigationContext::execute(const NavigationAction& action) { if(action.targetContext) return NavigationContextPointer(action.targetContext); if(action.type == NavigationAction::ExecuteKey) return executeKeyAction(action.key); if( !action.decl && (action.type != NavigationAction::JumpToSource || action.document.isEmpty()) ) { qCDebug(LANGUAGE) << "Navigation-action has invalid declaration" << endl; return NavigationContextPointer(this); } switch( action.type ) { case NavigationAction::ExecuteKey: break; case NavigationAction::None: qCDebug(LANGUAGE) << "Tried to execute an invalid action in navigation-widget" << endl; break; case NavigationAction::NavigateDeclaration: { auto ctx = dynamic_cast(d->m_previousContext); if( ctx && ctx->declaration() == action.decl ) return NavigationContextPointer(d->m_previousContext); return registerChild(action.decl); - } break; + } case NavigationAction::NavigateUses: { IContextBrowser* browser = ICore::self()->pluginController()->extensionForPlugin(); if (browser) { browser->showUses(action.decl); return NavigationContextPointer(this); } Q_FALLTHROUGH(); } case NavigationAction::ShowUses: { return registerChild(new UsesNavigationContext(action.decl.data(), this)); } case NavigationAction::JumpToSource: { QUrl doc = action.document; KTextEditor::Cursor cursor = action.cursor; { DUChainReadLocker lock(DUChain::lock()); if(action.decl) { if(doc.isEmpty()) { doc = action.decl->url().toUrl(); /* if(action.decl->internalContext()) cursor = action.decl->internalContext()->range().start() + KTextEditor::Cursor(0, 1); else*/ cursor = action.decl->rangeInCurrentRevision().start(); } action.decl->activateSpecialization(); } } //This is used to execute the slot delayed in the event-loop, so crashes are avoided QMetaObject::invokeMethod( ICore::self()->documentController(), "openDocument", Qt::QueuedConnection, Q_ARG(QUrl, doc), Q_ARG(KTextEditor::Cursor, cursor) ); break; } case NavigationAction::ShowDocumentation: { auto doc = ICore::self()->documentationController()->documentationForDeclaration(action.decl.data()); // This is used to execute the slot delayed in the event-loop, so crashes are avoided // which can happen e.g. due to focus change events resulting in tooltip destruction and thus this object QMetaObject::invokeMethod(ICore::self()->documentationController(), "showDocumentation", Qt::QueuedConnection, Q_ARG(IDocumentation::Ptr, doc)); } break; } return NavigationContextPointer( this ); } AbstractNavigationContext* AbstractNavigationContext::previousContext() const { return d->m_previousContext; } void AbstractNavigationContext::setPreviousContext(AbstractNavigationContext* previous) { d->m_previousContext = previous; } NavigationContextPointer AbstractNavigationContext::registerChild(AbstractNavigationContext* context) { d->m_children << NavigationContextPointer(context); return d->m_children.last(); } NavigationContextPointer AbstractNavigationContext::registerChild(const DeclarationPointer& declaration) { //We create a navigation-widget here, and steal its context.. evil ;) QScopedPointer navigationWidget(declaration->context()->createNavigationWidget(declaration.data())); if (AbstractNavigationWidget* abstractNavigationWidget = dynamic_cast(navigationWidget.data()) ) { NavigationContextPointer ret = abstractNavigationWidget->context(); ret->setPreviousContext(this); d->m_children << ret; return ret; } else { return NavigationContextPointer(this); } } const int lineJump = 3; void AbstractNavigationContext::down() { //Make sure link-count is valid if( d->m_linkCount == -1 ) { DUChainReadLocker lock; html(); } int fromLine = d->m_currentPositionLine; if(d->m_selectedLink >= 0 && d->m_selectedLink < d->m_linkCount) { if(fromLine == -1) fromLine = d->m_linkLines[d->m_selectedLink]; for(int newSelectedLink = d->m_selectedLink+1; newSelectedLink < d->m_linkCount; ++newSelectedLink) { if(d->m_linkLines[newSelectedLink] > fromLine && d->m_linkLines[newSelectedLink] - fromLine <= lineJump) { d->m_selectedLink = newSelectedLink; d->m_currentPositionLine = -1; return; } } } if(fromLine == -1) fromLine = 0; d->m_currentPositionLine = fromLine + lineJump; if(d->m_currentPositionLine > d->m_currentLine) d->m_currentPositionLine = d->m_currentLine; } void AbstractNavigationContext::up() { //Make sure link-count is valid if( d->m_linkCount == -1 ) { DUChainReadLocker lock; html(); } int fromLine = d->m_currentPositionLine; if(d->m_selectedLink >= 0 && d->m_selectedLink < d->m_linkCount) { if(fromLine == -1) fromLine = d->m_linkLines[d->m_selectedLink]; for(int newSelectedLink = d->m_selectedLink-1; newSelectedLink >= 0; --newSelectedLink) { if(d->m_linkLines[newSelectedLink] < fromLine && fromLine - d->m_linkLines[newSelectedLink] <= lineJump) { d->m_selectedLink = newSelectedLink; d->m_currentPositionLine = -1; return; } } } if(fromLine == -1) fromLine = d->m_currentLine; d->m_currentPositionLine = fromLine - lineJump; if(d->m_currentPositionLine < 0) d->m_currentPositionLine = 0; } void AbstractNavigationContext::nextLink() { //Make sure link-count is valid if( d->m_linkCount == -1 ) { DUChainReadLocker lock; html(); } d->m_currentPositionLine = -1; if( d->m_linkCount > 0 ) d->m_selectedLink = (d->m_selectedLink+1) % d->m_linkCount; } void AbstractNavigationContext::previousLink() { //Make sure link-count is valid if( d->m_linkCount == -1 ) { DUChainReadLocker lock; html(); } d->m_currentPositionLine = -1; if( d->m_linkCount > 0 ) { --d->m_selectedLink; if( d->m_selectedLink < 0 ) d->m_selectedLink += d->m_linkCount; } Q_ASSERT(d->m_selectedLink >= 0); } int AbstractNavigationContext::linkCount() const { return d->m_linkCount; } QString AbstractNavigationContext::prefix() const { return d->m_prefix; } QString AbstractNavigationContext::suffix() const { return d->m_suffix; } void AbstractNavigationContext::setPrefixSuffix( const QString& prefix, const QString& suffix ) { d->m_prefix = prefix; d->m_suffix = suffix; } NavigationContextPointer AbstractNavigationContext::back() { if(d->m_previousContext) return NavigationContextPointer(d->m_previousContext); else return NavigationContextPointer(this); } NavigationContextPointer AbstractNavigationContext::accept() { if( d->m_selectedLink >= 0 && d->m_selectedLink < d->m_linkCount ) { NavigationAction action = d->m_intLinks[d->m_selectedLink]; return execute(action); } return NavigationContextPointer(this); } NavigationContextPointer AbstractNavigationContext::accept(IndexedDeclaration decl) { if(decl.data()) { NavigationAction action(DeclarationPointer(decl.data()), NavigationAction::NavigateDeclaration); return execute(action); }else{ return NavigationContextPointer(this); } } NavigationContextPointer AbstractNavigationContext::acceptLink(const QString& link) { if( !d->m_links.contains(link) ) { qCDebug(LANGUAGE) << "Executed unregistered link " << link << endl; return NavigationContextPointer(this); } return execute(d->m_links[link]); } NavigationAction AbstractNavigationContext::currentAction() const { return d->m_selectedLinkAction; } QString AbstractNavigationContext::declarationKind(const DeclarationPointer& decl) { const AbstractFunctionDeclaration* function = dynamic_cast(decl.data()); QString kind; if( decl->isTypeAlias() ) kind = i18n("Typedef"); else if( decl->kind() == Declaration::Type ) { if( decl->type() ) kind = i18n("Class"); } else if( decl->kind() == Declaration::Instance ) { kind = i18n("Variable"); } else if ( decl->kind() == Declaration::Namespace ) { kind = i18n("Namespace"); } if( NamespaceAliasDeclaration* alias = dynamic_cast(decl.data()) ) { if( alias->identifier().isEmpty() ) kind = i18n("Namespace import"); else kind = i18n("Namespace alias"); } if(function) kind = i18n("Function"); if( decl->isForwardDeclaration() ) kind = i18n("Forward Declaration"); return kind; } QString AbstractNavigationContext::html(bool shorten) { d->m_shorten = shorten; return QString(); } bool AbstractNavigationContext::alreadyComputed() const { return !d->m_currentText.isEmpty(); } bool AbstractNavigationContext::isWidgetMaximized() const { return true; } QWidget* AbstractNavigationContext::widget() const { return nullptr; } ///Splits the string by the given regular expression, but keeps the split-matches at the end of each line static QStringList splitAndKeep(QString str, const QRegExp& regExp) { QStringList ret; int place = regExp.indexIn(str); while(place != -1) { ret << str.left(place + regExp.matchedLength()); str.remove(0, place + regExp.matchedLength()); place = regExp.indexIn(str); } ret << str; return ret; } void AbstractNavigationContext::addHtml(const QString& html) { QRegExp newLineRegExp(QStringLiteral("
|
")); foreach(const QString& line, splitAndKeep(html, newLineRegExp)) { d->m_currentText += line; if(line.indexOf(newLineRegExp) != -1) { ++d->m_currentLine; if(d->m_currentLine == d->m_currentPositionLine) { d->m_currentText += QStringLiteral(" <-> "); // ><-> is <-> } } } } QString AbstractNavigationContext::currentHtml() const { return d->m_currentText; } QString AbstractNavigationContext::fontSizePrefix(bool /*shorten*/) const { return QString(); } QString AbstractNavigationContext::fontSizeSuffix(bool /*shorten*/) const { return QString(); } QString Colorizer::operator() ( const QString& str ) const { QString ret = QLatin1String("") + str + QLatin1String(""); if( m_formatting & Fixed ) ret = QLatin1String("") + ret + QLatin1String(""); if ( m_formatting & Bold ) ret = QLatin1String("") + ret + QLatin1String(""); if ( m_formatting & Italic ) ret = QLatin1String("") + ret + QLatin1String(""); return ret; } const Colorizer AbstractNavigationContext::typeHighlight(QStringLiteral("006000")); const Colorizer AbstractNavigationContext::errorHighlight(QStringLiteral("990000")); const Colorizer AbstractNavigationContext::labelHighlight(QStringLiteral("000000")); const Colorizer AbstractNavigationContext::codeHighlight(QStringLiteral("005000")); const Colorizer AbstractNavigationContext::propertyHighlight(QStringLiteral("009900")); const Colorizer AbstractNavigationContext::navigationHighlight(QStringLiteral("000099")); const Colorizer AbstractNavigationContext::importantHighlight(QStringLiteral("000000"), Colorizer::Bold | Colorizer::Italic); const Colorizer AbstractNavigationContext::commentHighlight(QStringLiteral("303030")); const Colorizer AbstractNavigationContext::nameHighlight(QStringLiteral("000000"), Colorizer::Bold); } diff --git a/kdevplatform/language/duchain/stringhelpers.cpp b/kdevplatform/language/duchain/stringhelpers.cpp index fc093775d4..9fbcf5a4f0 100644 --- a/kdevplatform/language/duchain/stringhelpers.cpp +++ b/kdevplatform/language/duchain/stringhelpers.cpp @@ -1,596 +1,594 @@ /* Copyright 2007 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 "stringhelpers.h" #include "safetycounter.h" #include #include #include #include namespace { template int strip_impl(const T& str, T& from) { if( str.isEmpty() ) return 0; int i = 0; int ip = 0; int s = from.length(); for( int a = 0; a < s; a++ ) { if( QChar(from[a]).isSpace() ) { continue; } else { if( from[a] == str[i] ) { i++; ip = a+1; if( i == (int)str.length() ) break; } else { break; } } } if( ip ) { from.remove(0, ip); } return s - from.length(); } template int rStrip_impl(const T& str, T& from) { if( str.isEmpty() ) return 0; int i = 0; int ip = from.length(); int s = from.length(); for( int a = s-1; a >= 0; a-- ) { if( QChar( from[a] ).isSpace() ) { ///@todo Check whether this can cause problems in utf-8, as only one real character is treated! continue; } else { if( from[a] == str[i] ) { i++; ip = a; if( i == (int)str.length() ) break; } else { break; } } } if( ip != (int)from.length() ) { from = from.left( ip ); } return s - from.length(); } template T formatComment_impl(const T& comment) { T ret; QList lines = comment.split('\n'); if ( !lines.isEmpty() ) { auto it = lines.begin(); auto eit = lines.end(); // remove common leading chars from the beginning of lines for( ; it != eit; ++it ) { // don't trigger repeated temporary allocations here static const T tripleSlash("///"); static const T doubleSlash("//"); static const T doubleStar("**"); static const T slashDoubleStar("/**"); strip_impl( tripleSlash, *it ); strip_impl( doubleSlash, *it ); strip_impl( doubleStar, *it ); rStrip_impl( slashDoubleStar, *it ); } // TODO add method with QStringList specialisation foreach(const T& line, lines) { if(!ret.isEmpty()) ret += '\n'; ret += line; } } return ret.trimmed(); } } namespace KDevelop { class ParamIteratorPrivate { public: QString m_prefix; QString m_source; QString m_parens; int m_cur; int m_curEnd; int m_end; int next() const { return findCommaOrEnd( m_source, m_cur, m_parens[ 1 ] ); } }; bool parenFits( QChar c1, QChar c2 ) { if (c1 == QLatin1Char('<') && c2 == QLatin1Char('>')) return true; else if (c1 == QLatin1Char('(') && c2 == QLatin1Char(')')) return true; else if (c1 == QLatin1Char('[') && c2 == QLatin1Char(']')) return true; else if (c1 == QLatin1Char('{') && c2 == QLatin1Char('}')) return true; else return false; } int findClose( const QString& str , int pos ) { int depth = 0; QList st; QChar last = QLatin1Char(' '); for( int a = pos; a < (int)str.length(); a++) { switch(str[a].unicode()) { case '<': case '(': case '[': case '{': st.push_front( str[a] ); depth++; break; case '>': if (last == QLatin1Char('-')) break; Q_FALLTHROUGH(); case ')': case ']': case '}': if( !st.isEmpty() && parenFits(st.front(), str[a]) ) { depth--; st.pop_front(); } break; case '"': last = str[a]; a++; while (a < (int)str.length() && (str[a] != QLatin1Char('"') || last == QLatin1Char('\\'))) { last = str[a]; a++; } continue; - break; case '\'': last = str[a]; a++; while( a < (int)str.length() && (str[a] != QLatin1Char('\'') || last == QLatin1Char('\\'))) { last = str[a]; a++; } continue; - break; } last = str[a]; if( depth == 0 ) { return a; } } return -1; } int findCommaOrEnd( const QString& str , int pos, QChar validEnd) { for( int a = pos; a < (int)str.length(); a++) { switch(str[a].unicode()) { case '"': case '(': case '[': case '{': case '<': a = findClose( str, a ); if( a == -1 ) return str.length(); break; case ')': case ']': case '}': case '>': if( validEnd != QLatin1Char(' ') && validEnd != str[a] ) continue; Q_FALLTHROUGH(); case ',': return a; } } return str.length(); } QString reverse( const QString& str ) { QString ret; int len = str.length(); ret.reserve(len); for( int a = len-1; a >= 0; --a ) { switch(str[a].unicode()) { case '(': ret += QLatin1Char(')'); continue; case '[': ret += QLatin1Char(']'); continue; case '{': ret += QLatin1Char('}'); continue; case '<': ret += QLatin1Char('>'); continue; case ')': ret += QLatin1Char('('); continue; case ']': ret += QLatin1Char('['); continue; case '}': ret += QLatin1Char('{'); continue; case '>': ret += QLatin1Char('<'); continue; default: ret += str[a]; continue; } } return ret; } ///@todo this hackery sucks QString escapeForBracketMatching(QString str) { str.replace(QStringLiteral("<<"), QStringLiteral("$&")); str.replace(QStringLiteral(">>"), QStringLiteral("$$")); str.replace(QStringLiteral("\\\""), QStringLiteral("$!")); str.replace(QStringLiteral("->"), QStringLiteral("$?")); return str; } QString escapeFromBracketMatching(QString str) { str.replace(QStringLiteral("$&"), QStringLiteral("<<")); str.replace(QStringLiteral("$$"), QStringLiteral(">>")); str.replace(QStringLiteral("$!"), QStringLiteral("\\\"")); str.replace(QStringLiteral("$?"), QStringLiteral("->")); return str; } void skipFunctionArguments(const QString& str_, QStringList& skippedArguments, int& argumentsStart) { QString withStrings = escapeForBracketMatching(str_); QString str = escapeForBracketMatching(clearStrings(str_)); //Blank out everything that can confuse the bracket-matching algorithm QString reversed = reverse( str.left(argumentsStart) ); QString withStringsReversed = reverse( withStrings.left(argumentsStart) ); //Now we should decrease argumentStart at the end by the count of steps we go right until we find the beginning of the function SafetyCounter s( 1000 ); int pos = 0; int len = reversed.length(); //we are searching for an opening-brace, but the reversion has also reversed the brace while( pos < len && s ) { int lastPos = pos; pos = KDevelop::findCommaOrEnd( reversed, pos ) ; if( pos > lastPos ) { QString arg = reverse( withStringsReversed.mid(lastPos, pos-lastPos) ).trimmed(); if( !arg.isEmpty() ) skippedArguments.push_front( escapeFromBracketMatching(arg) ); //We are processing the reversed reverseding, so push to front } if( reversed[pos] == QLatin1Char(')') || reversed[pos] == QLatin1Char('>') ) break; else ++pos; } if( !s ) { qCDebug(LANGUAGE) << "skipFunctionArguments: Safety-counter triggered"; } argumentsStart -= pos; } QString reduceWhiteSpace(const QString& str_) { const QString str = str_.trimmed(); QString ret; const int len = str.length(); ret.reserve(len); QChar spaceChar = QLatin1Char(' '); bool hadSpace = false; for (int a = 0; a < len; ++a) { if( str[a].isSpace() ) { hadSpace = true; } else { if( hadSpace ) { hadSpace = false; ret += spaceChar; } ret += str[a]; } } ret.squeeze(); return ret; } void fillString( QString& str, int start, int end, QChar replacement ) { for( int a = start; a < end; a++) str[a] = replacement; } QString stripFinalWhitespace(const QString& str) { for( int a = str.length() - 1; a >= 0; --a ) { if( !str[a].isSpace() ) return str.left( a+1 ); } return QString(); } QString clearComments(const QString& str_, QChar replacement) { QString str(str_); QString withoutStrings = clearStrings(str, '$'); int pos = -1, newlinePos = -1, endCommentPos = -1, nextPos = -1, dest = -1; while ( (pos = str.indexOf(QLatin1Char('/'), pos + 1)) != -1 ) { newlinePos = withoutStrings.indexOf('\n', pos); if (withoutStrings[pos + 1] == QLatin1Char('/')) { //C style comment dest = newlinePos == -1 ? str.length() : newlinePos; fillString(str, pos, dest, replacement); pos = dest; } else if (withoutStrings[pos + 1] == QLatin1Char('*')) { //CPP style comment endCommentPos = withoutStrings.indexOf(QStringLiteral("*/"), pos + 2); if (endCommentPos != -1) endCommentPos += 2; dest = endCommentPos == -1 ? str.length() : endCommentPos; while (pos < dest) { nextPos = (dest > newlinePos && newlinePos != -1) ? newlinePos : dest; fillString(str, pos, nextPos, replacement); pos = nextPos; if (pos == newlinePos) { ++pos; //Keep newlines intact, skip them newlinePos = withoutStrings.indexOf(QLatin1Char('\n'), pos + 1); } } } } return str; } QString clearStrings(const QString& str_, QChar replacement) { QString str(str_); bool inString = false; for(int pos = 0; pos < str.length(); ++pos) { //Skip cpp comments if(!inString && pos + 1 < str.length() && str[pos] == QLatin1Char('/') && str[pos+1] == QLatin1Char('*')) { pos += 2; while(pos + 1 < str.length()) { if (str[pos] == '*' && str[pos + 1] == QLatin1Char('/')) { ++pos; break; } ++pos; } } //Skip cstyle comments if(!inString && pos + 1 < str.length() && str[pos] == QLatin1Char('/') && str[pos+1] == QLatin1Char('/')) { pos += 2; while(pos < str.length() && str[pos] != QLatin1Char('\n')) { ++pos; } } //Skip a character a la 'b' if (!inString && str[pos] == QLatin1Char('\'') && pos + 3 <= str.length()) { //skip the opening ' str[pos] = replacement; ++pos; if (str[pos] == QLatin1Char('\\')) { //Skip an escape character str[pos] = replacement; ++pos; } //Skip the actual character str[pos] = replacement; ++pos; //Skip the closing ' if (pos < str.length() && str[pos] == QLatin1Char('\'')) { str[pos] = replacement; } continue; } bool intoString = false; if (str[pos] == QLatin1Char('"') && !inString) intoString = true; if(inString || intoString) { if(inString) { if(str[pos] == QLatin1Char('"')) inString = false; }else{ inString = true; } bool skip = false; if (str[pos] == QLatin1Char('\\')) skip = true; str[pos] = replacement; if(skip) { ++pos; if(pos < str.length()) str[pos] = replacement; } } } return str; } int strip(const QByteArray& str, QByteArray& from) { return strip_impl(str, from); } int rStrip(const QByteArray& str, QByteArray& from) { return rStrip_impl(str, from); } QByteArray formatComment(const QByteArray& comment) { return formatComment_impl(comment); } QString formatComment(const QString& comment) { return formatComment_impl(comment); } ParamIterator::~ParamIterator() = default; ParamIterator::ParamIterator(const QString& parens, const QString& source, int offset) : d(new ParamIteratorPrivate) { d->m_source = source; d->m_parens = parens; d->m_cur = offset; d->m_curEnd = offset; d->m_end = d->m_source.length(); ///The whole search should be stopped when: A) The end-sign is found on the top-level B) A closing-brace of parameters was found int parenBegin = d->m_source.indexOf( parens[ 0 ], offset ); //Search for an interrupting end-sign that comes before the found paren-begin int foundEnd = -1; if( parens.length() > 2 ) { foundEnd = d->m_source.indexOf( parens[2], offset ); if( foundEnd > parenBegin && parenBegin != -1 ) foundEnd = -1; } if( foundEnd != -1 ) { //We have to stop the search, because we found an interrupting end-sign before the opening-paren d->m_prefix = d->m_source.mid( offset, foundEnd - offset ); d->m_curEnd = d->m_end = d->m_cur = foundEnd; } else { if( parenBegin != -1 ) { //We have a valid prefix before an opening-paren. Take the prefix, and start iterating parameters. d->m_prefix = d->m_source.mid( offset, parenBegin - offset ); d->m_cur = parenBegin + 1; d->m_curEnd = d->next(); if( d->m_curEnd == d->m_source.length() ) { //The paren was not closed. It might be an identifier like "operator<", so count everything as prefix. d->m_prefix = d->m_source.mid(offset); d->m_curEnd = d->m_end = d->m_cur = d->m_source.length(); } } else { //We have neither found an ending-character, nor an opening-paren, so take the whole input and end d->m_prefix = d->m_source.mid(offset); d->m_curEnd = d->m_end = d->m_cur = d->m_source.length(); } } } ParamIterator& ParamIterator::operator ++() { if( d->m_source[d->m_curEnd] == d->m_parens[1] ) { //We have reached the end-paren. Stop iterating. d->m_cur = d->m_end = d->m_curEnd + 1; } else { //Iterate on through parameters d->m_cur = d->m_curEnd + 1; if ( d->m_cur < ( int ) d->m_source.length() ) { d->m_curEnd = d->next(); } } return *this; } QString ParamIterator::operator *() { return d->m_source.mid( d->m_cur, d->m_curEnd - d->m_cur ).trimmed(); } ParamIterator::operator bool() const { return d->m_cur < ( int ) d->m_end; } QString ParamIterator::prefix() const { return d->m_prefix; } uint ParamIterator::position() const { return (uint)d->m_cur; } } diff --git a/kdevplatform/outputview/outputmodel.cpp b/kdevplatform/outputview/outputmodel.cpp index 15693fdb20..c7c23f8f3a 100644 --- a/kdevplatform/outputview/outputmodel.cpp +++ b/kdevplatform/outputview/outputmodel.cpp @@ -1,472 +1,467 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2007 Andreas Pakulat * * Copyright 2010 Aleix Pol Gonzalez * * Copyright (C) 2012 Morten Danielsen Volden mvolden2@gmail.com * * * * 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 "outputmodel.h" #include "filtereditem.h" #include "outputfilteringstrategies.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include Q_DECLARE_METATYPE(QVector) namespace KDevelop { /** * Number of lines that are processed in one go before we notify the GUI thread * about the result. It is generally faster to add multiple items to a model * in one go compared to adding each item independently. */ static const int BATCH_SIZE = 50; /** * Time in ms that we wait in the parse worker for new incoming lines before * actually processing them. If we already have enough for one batch though * we process immediately. */ static const int BATCH_AGGREGATE_TIME_DELAY = 50; class ParseWorker : public QObject { Q_OBJECT public: ParseWorker() : QObject(nullptr) , m_filter(new NoFilterStrategy) , m_timer(new QTimer(this)) { m_timer->setInterval(BATCH_AGGREGATE_TIME_DELAY); m_timer->setSingleShot(true); connect(m_timer, &QTimer::timeout, this, &ParseWorker::process); } public Q_SLOTS: void changeFilterStrategy( KDevelop::IFilterStrategy* newFilterStrategy ) { m_filter = QSharedPointer( newFilterStrategy ); } void addLines( const QStringList& lines ) { m_cachedLines << lines; if (m_cachedLines.size() >= BATCH_SIZE) { // if enough lines were added, process immediately m_timer->stop(); process(); } else if (!m_timer->isActive()) { m_timer->start(); } } void flushBuffers() { m_timer->stop(); process(); emit allDone(); } Q_SIGNALS: void parsedBatch(const QVector& filteredItems); void progress(const KDevelop::IFilterStrategy::Progress& progress); void allDone(); private Q_SLOTS: /** * Process *all* cached lines, emit parsedBatch for each batch */ void process() { QVector filteredItems; filteredItems.reserve(qMin(BATCH_SIZE, m_cachedLines.size())); // apply pre-filtering functions std::transform(m_cachedLines.constBegin(), m_cachedLines.constEnd(), m_cachedLines.begin(), &KDevelop::stripAnsiSequences); // apply filtering strategy foreach(const QString& line, m_cachedLines) { FilteredItem item = m_filter->errorInLine(line); if( item.type == FilteredItem::InvalidItem ) { item = m_filter->actionInLine(line); } filteredItems << item; auto progress = m_filter->progressInLine(line); if (progress.percent >= 0 && m_progress.percent != progress.percent) { m_progress = progress; emit this->progress(m_progress); } if( filteredItems.size() == BATCH_SIZE ) { emit parsedBatch(filteredItems); filteredItems.clear(); filteredItems.reserve(qMin(BATCH_SIZE, m_cachedLines.size())); } } // Make sure to emit the rest as well if( !filteredItems.isEmpty() ) { emit parsedBatch(filteredItems); } m_cachedLines.clear(); } private: QSharedPointer m_filter; QStringList m_cachedLines; QTimer* m_timer; IFilterStrategy::Progress m_progress; }; class ParsingThread { public: ParsingThread() { m_thread.setObjectName(QStringLiteral("OutputFilterThread")); } virtual ~ParsingThread() { if (m_thread.isRunning()) { m_thread.quit(); m_thread.wait(); } } void addWorker(ParseWorker* worker) { if (!m_thread.isRunning()) { m_thread.start(); } worker->moveToThread(&m_thread); } private: QThread m_thread; }; Q_GLOBAL_STATIC(ParsingThread, s_parsingThread) class OutputModelPrivate { public: explicit OutputModelPrivate( OutputModel* model, const QUrl& builddir = QUrl() ); ~OutputModelPrivate(); bool isValidIndex( const QModelIndex&, int currentRowCount ) const; OutputModel* model; ParseWorker* worker; QVector m_filteredItems; // We use std::set because that is ordered std::set m_errorItems; // Indices of all items that we want to move to using previous and next QUrl m_buildDir; void linesParsed(const QVector& items) { model->beginInsertRows( QModelIndex(), model->rowCount(), model->rowCount() + items.size() - 1); m_filteredItems.reserve(m_filteredItems.size() + items.size()); for (const FilteredItem& item : items) { if( item.type == FilteredItem::ErrorItem ) { m_errorItems.insert(m_filteredItems.size()); } m_filteredItems << item; } model->endInsertRows(); } }; OutputModelPrivate::OutputModelPrivate( OutputModel* model_, const QUrl& builddir) : model(model_) , worker(new ParseWorker ) , m_buildDir( builddir ) { qRegisterMetaType >(); qRegisterMetaType(); qRegisterMetaType(); s_parsingThread->addWorker(worker); model->connect(worker, &ParseWorker::parsedBatch, model, [=] (const QVector& items) { linesParsed(items); }); model->connect(worker, &ParseWorker::allDone, model, &OutputModel::allDone); model->connect(worker, &ParseWorker::progress, model, &OutputModel::progress); } bool OutputModelPrivate::isValidIndex( const QModelIndex& idx, int currentRowCount ) const { return ( idx.isValid() && idx.row() >= 0 && idx.row() < currentRowCount && idx.column() == 0 ); } OutputModelPrivate::~OutputModelPrivate() { worker->deleteLater(); } OutputModel::OutputModel( const QUrl& builddir, QObject* parent ) : QAbstractListModel(parent) , d( new OutputModelPrivate( this, builddir ) ) { } OutputModel::OutputModel( QObject* parent ) : QAbstractListModel(parent) , d( new OutputModelPrivate( this ) ) { } OutputModel::~OutputModel() = default; QVariant OutputModel::data(const QModelIndex& idx , int role ) const { if( d->isValidIndex(idx, rowCount()) ) { switch( role ) { case Qt::DisplayRole: return d->m_filteredItems.at( idx.row() ).originalLine; - break; case OutputModel::OutputItemTypeRole: return static_cast(d->m_filteredItems.at( idx.row() ).type); - break; case Qt::FontRole: return QFontDatabase::systemFont(QFontDatabase::FixedFont); - break; - default: - break; } } return QVariant(); } int OutputModel::rowCount( const QModelIndex& parent ) const { if( !parent.isValid() ) return d->m_filteredItems.count(); return 0; } QVariant OutputModel::headerData( int, Qt::Orientation, int ) const { return QVariant(); } void OutputModel::activate( const QModelIndex& index ) { if( index.model() != this || !d->isValidIndex(index, rowCount()) ) { return; } qCDebug(OUTPUTVIEW) << "Model activated" << index.row(); FilteredItem item = d->m_filteredItems.at( index.row() ); if( item.isActivatable ) { qCDebug(OUTPUTVIEW) << "activating:" << item.lineNo << item.url; KTextEditor::Cursor range( item.lineNo, item.columnNo ); KDevelop::IDocumentController *docCtrl = KDevelop::ICore::self()->documentController(); QUrl url = item.url; if (item.url.isEmpty()) { qCWarning(OUTPUTVIEW) << "trying to open empty url"; return; } if(url.isRelative()) { url = d->m_buildDir.resolved(url); } Q_ASSERT(!url.isRelative()); docCtrl->openDocument( url, range ); } else { qCDebug(OUTPUTVIEW) << "not an activateable item"; } } QModelIndex OutputModel::firstHighlightIndex() { if( !d->m_errorItems.empty() ) { return index( *d->m_errorItems.begin(), 0, QModelIndex() ); } for( int row = 0; row < rowCount(); ++row ) { if( d->m_filteredItems.at( row ).isActivatable ) { return index( row, 0, QModelIndex() ); } } return QModelIndex(); } QModelIndex OutputModel::nextHighlightIndex( const QModelIndex ¤tIdx ) { int startrow = d->isValidIndex(currentIdx, rowCount()) ? currentIdx.row() + 1 : 0; if( !d->m_errorItems.empty() ) { qCDebug(OUTPUTVIEW) << "searching next error"; // Jump to the next error item std::set< int >::const_iterator next = d->m_errorItems.lower_bound( startrow ); if( next == d->m_errorItems.end() ) next = d->m_errorItems.begin(); return index( *next, 0, QModelIndex() ); } for( int row = 0; row < rowCount(); ++row ) { int currow = (startrow + row) % rowCount(); if( d->m_filteredItems.at( currow ).isActivatable ) { return index( currow, 0, QModelIndex() ); } } return QModelIndex(); } QModelIndex OutputModel::previousHighlightIndex( const QModelIndex ¤tIdx ) { //We have to ensure that startrow is >= rowCount - 1 to get a positive value from the % operation. int startrow = rowCount() + (d->isValidIndex(currentIdx, rowCount()) ? currentIdx.row() : rowCount()) - 1; if(!d->m_errorItems.empty()) { qCDebug(OUTPUTVIEW) << "searching previous error"; // Jump to the previous error item std::set< int >::const_iterator previous = d->m_errorItems.lower_bound( currentIdx.row() ); if( previous == d->m_errorItems.begin() ) previous = d->m_errorItems.end(); --previous; return index( *previous, 0, QModelIndex() ); } for ( int row = 0; row < rowCount(); ++row ) { int currow = (startrow - row) % rowCount(); if( d->m_filteredItems.at( currow ).isActivatable ) { return index( currow, 0, QModelIndex() ); } } return QModelIndex(); } QModelIndex OutputModel::lastHighlightIndex() { if( !d->m_errorItems.empty() ) { return index( *d->m_errorItems.rbegin(), 0, QModelIndex() ); } for( int row = rowCount()-1; row >=0; --row ) { if( d->m_filteredItems.at( row ).isActivatable ) { return index( row, 0, QModelIndex() ); } } return QModelIndex(); } void OutputModel::setFilteringStrategy(const OutputFilterStrategy& currentStrategy) { // TODO: Turn into factory, decouple from OutputModel IFilterStrategy* filter = nullptr; switch( currentStrategy ) { case NoFilter: filter = new NoFilterStrategy; break; case CompilerFilter: filter = new CompilerFilterStrategy( d->m_buildDir ); break; case ScriptErrorFilter: filter = new ScriptErrorFilterStrategy; break; case NativeAppErrorFilter: filter = new NativeAppErrorFilterStrategy; break; case StaticAnalysisFilter: filter = new StaticAnalysisFilterStrategy; break; } if (!filter) { filter = new NoFilterStrategy; } QMetaObject::invokeMethod(d->worker, "changeFilterStrategy", Q_ARG(KDevelop::IFilterStrategy*, filter)); } void OutputModel::setFilteringStrategy(IFilterStrategy* filterStrategy) { QMetaObject::invokeMethod(d->worker, "changeFilterStrategy", Q_ARG(KDevelop::IFilterStrategy*, filterStrategy)); } void OutputModel::appendLines( const QStringList& lines ) { if( lines.isEmpty() ) return; QMetaObject::invokeMethod(d->worker, "addLines", Q_ARG(QStringList, lines)); } void OutputModel::appendLine( const QString& line ) { appendLines( QStringList() << line ); } void OutputModel::ensureAllDone() { QMetaObject::invokeMethod(d->worker, "flushBuffers"); } void OutputModel::clear() { ensureAllDone(); beginResetModel(); d->m_filteredItems.clear(); endResetModel(); } } #include "outputmodel.moc" #include "moc_outputmodel.cpp" diff --git a/kdevplatform/project/projectbuildsetmodel.cpp b/kdevplatform/project/projectbuildsetmodel.cpp index 2983e11741..8df8654b26 100644 --- a/kdevplatform/project/projectbuildsetmodel.cpp +++ b/kdevplatform/project/projectbuildsetmodel.cpp @@ -1,410 +1,406 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2007 Andreas Pakulat * * Copyright 2009 Aleix Pol * * * * 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 "projectbuildsetmodel.h" #include #include #include #include #include #include #include #include "projectmodel.h" #include #include namespace KDevelop { BuildItem::BuildItem() { } BuildItem::BuildItem( const QStringList & itemPath ) : m_itemPath( itemPath ) { } BuildItem::BuildItem( KDevelop::ProjectBaseItem* item ) { initializeFromItem( item ); } BuildItem::BuildItem( const BuildItem& rhs ) : m_itemPath(rhs.itemPath()) { } void BuildItem::initializeFromItem( KDevelop::ProjectBaseItem* item ) { Q_ASSERT(item); KDevelop::ProjectModel* model=KDevelop::ICore::self()->projectController()->projectModel(); m_itemPath = model->pathFromIndex(item->index()); } QString BuildItem::itemName() const { return m_itemPath.last(); } QString BuildItem::itemProject() const { return m_itemPath.first(); } KDevelop::ProjectBaseItem* BuildItem::findItem() const { KDevelop::ProjectModel* model=KDevelop::ICore::self()->projectController()->projectModel(); QModelIndex idx = model->pathToIndex(m_itemPath); return model->itemFromIndex(idx); } bool operator==( const BuildItem& rhs, const BuildItem& lhs ) { return( rhs.itemPath() == lhs.itemPath() ); } BuildItem& BuildItem::operator=( const BuildItem& rhs ) { if( this == &rhs ) return *this; m_itemPath = rhs.itemPath(); return *this; } class ProjectBuildSetModelPrivate { public: QList items; QList orderingCache; }; ProjectBuildSetModel::ProjectBuildSetModel( QObject* parent ) : QAbstractTableModel( parent ) , d(new ProjectBuildSetModelPrivate) { } ProjectBuildSetModel::~ProjectBuildSetModel() = default; void ProjectBuildSetModel::loadFromSession( ISession* session ) { if (!session) { return; } // Load the item ordering cache KConfigGroup sessionBuildSetConfig = session->config()->group( "Buildset" ); const QVariantList sessionBuildItems = KDevelop::stringToQVariant(sessionBuildSetConfig.readEntry("BuildItems", QString())).toList(); d->orderingCache.reserve(d->orderingCache.size() + sessionBuildItems.size()); for (const QVariant& item : sessionBuildItems) { d->orderingCache.append(item.toStringList()); } } void ProjectBuildSetModel::storeToSession( ISession* session ) { if (!session) { return; } // Store the item ordering cache QVariantList sessionBuildItems; sessionBuildItems.reserve(d->orderingCache.size()); foreach (const QStringList& item, d->orderingCache) { sessionBuildItems.append( item ); } KConfigGroup sessionBuildSetConfig = session->config()->group( "Buildset" ); sessionBuildSetConfig.writeEntry("BuildItems", KDevelop::qvariantToString( QVariant( sessionBuildItems ) )); sessionBuildSetConfig.sync(); } int ProjectBuildSetModel::findInsertionPlace( const QStringList& itemPath ) { /* * The ordering cache list is a superset of the build set, and must be ordered in the same way. * Example: * (items) A - B ----- D --------- G * (orderingCache) A - B - C - D - E - F - G * * We scan orderingCache until we find the required item (absent in items: say, F). * In process of scanning we synchronize position in orderingCache with position in items; * so, when we reach F, we have D as last synchronization point and hence return it * as the insertion place (actually, we return the next item's index - here, index of G). * * If an item cannot be found in the ordering list, we append it to the list. */ int insertionIndex = 0; bool found = false; QList::iterator orderingCacheIterator = d->orderingCache.begin(); // Points to the item which is next to last synchronization point. QList::iterator nextItemIterator = d->items.begin(); while (orderingCacheIterator != d->orderingCache.end()) { if( itemPath == *orderingCacheIterator ) { found = true; break; } if (nextItemIterator != d->items.end() && nextItemIterator->itemPath() == *orderingCacheIterator ) { ++insertionIndex; ++nextItemIterator; } ++orderingCacheIterator; } // while if( !found ) { d->orderingCache.append(itemPath); } Q_ASSERT( insertionIndex >= 0 && insertionIndex <= d->items.size() ); return insertionIndex; } void ProjectBuildSetModel::removeItemsWithCache( const QList& itemIndices ) { /* * Removes the items with given indices from both the build set and the ordering cache. * List is given since removing many items together is more efficient than by one. * * Indices in the list shall be sorted. */ QList itemIndicesCopy = itemIndices; beginRemoveRows( QModelIndex(), itemIndices.first(), itemIndices.last() ); for (QList::iterator cacheIterator = d->orderingCache.end() - 1; cacheIterator >= d->orderingCache.begin() && !itemIndicesCopy.isEmpty();) { int index = itemIndicesCopy.back(); Q_ASSERT( index >= 0 && index < d->items.size() ); if (*cacheIterator == d->items.at(index).itemPath()) { cacheIterator = d->orderingCache.erase(cacheIterator); d->items.removeAt(index); itemIndicesCopy.removeLast(); } --cacheIterator; } // for endRemoveRows(); Q_ASSERT( itemIndicesCopy.isEmpty() ); } void ProjectBuildSetModel::insertItemWithCache( const BuildItem& item ) { int insertionPlace = findInsertionPlace( item.itemPath() ); beginInsertRows( QModelIndex(), insertionPlace, insertionPlace ); d->items.insert(insertionPlace, item); endInsertRows(); } void ProjectBuildSetModel::insertItemsOverrideCache( int index, const QList< BuildItem >& items ) { Q_ASSERT( index >= 0 && index <= d->items.size() ); if (index == d->items.size()) { beginInsertRows( QModelIndex(), index, index + items.size() - 1 ); d->items.append(items); d->orderingCache.reserve(d->orderingCache.size() + items.size()); for (const BuildItem& item : items) { d->orderingCache.append(item.itemPath()); } endInsertRows(); } else { int indexInCache = d->orderingCache.indexOf(d->items.at(index).itemPath()); Q_ASSERT( indexInCache >= 0 ); beginInsertRows( QModelIndex(), index, index + items.size() - 1 ); for( int i = 0; i < items.size(); ++i ) { const BuildItem& item = items.at( i ); d->items.insert(index + i, item); d->orderingCache.insert(indexInCache + i, item.itemPath()); } endInsertRows(); } } QVariant ProjectBuildSetModel::data( const QModelIndex& idx, int role ) const { if( !idx.isValid() || idx.row() < 0 || idx.column() < 0 || idx.row() >= rowCount() || idx.column() >= columnCount()) { return QVariant(); } if(role == Qt::DisplayRole) { switch( idx.column() ) { case 0: return d->items.at(idx.row()).itemName(); - break; case 1: return KDevelop::joinWithEscaping(d->items.at(idx.row()).itemPath(), QLatin1Char('/'), QLatin1Char('\\')); - break; } } else if(role == Qt::DecorationRole && idx.column()==0) { KDevelop::ProjectBaseItem* item = d->items.at(idx.row()).findItem(); if( item ) { return QIcon::fromTheme( item->iconName() ); } } return QVariant(); } QVariant ProjectBuildSetModel::headerData( int section, Qt::Orientation orientation, int role ) const { if( section < 0 || section >= columnCount() || orientation != Qt::Horizontal || role != Qt::DisplayRole ) return QVariant(); switch( section ) { case 0: return i18nc("@title:column buildset item name", "Name"); - break; case 1: return i18nc("@title:column buildset item path", "Path"); - break; } return QVariant(); } int ProjectBuildSetModel::rowCount( const QModelIndex& parent ) const { if( parent.isValid() ) return 0; return d->items.count(); } int ProjectBuildSetModel::columnCount( const QModelIndex& parent ) const { if( parent.isValid() ) return 0; return 2; } void ProjectBuildSetModel::addProjectItem( KDevelop::ProjectBaseItem* item ) { BuildItem buildItem( item ); if (d->items.contains(buildItem)) return; insertItemWithCache( buildItem ); } bool ProjectBuildSetModel::removeRows( int row, int count, const QModelIndex& parent ) { if( parent.isValid() || row > rowCount() || row < 0 || (row+count) > rowCount() || count <= 0 ) return false; QList itemsToRemove; itemsToRemove.reserve(count); for( int i = row; i < row+count; i++ ) { itemsToRemove.append( i ); } removeItemsWithCache( itemsToRemove ); return true; } QList ProjectBuildSetModel::items() { return d->items; } void ProjectBuildSetModel::projectClosed( KDevelop::IProject* project ) { for (int i = d->items.count() - 1; i >= 0; --i) { if (d->items.at(i).itemProject() == project->name()) { beginRemoveRows( QModelIndex(), i, i ); d->items.removeAt(i); endRemoveRows(); } } } void ProjectBuildSetModel::saveToProject( KDevelop::IProject* project ) const { QVariantList paths; foreach (const BuildItem& item, d->items) { if( item.itemProject() == project->name() ) paths.append(item.itemPath()); } KConfigGroup base = project->projectConfiguration()->group("Buildset"); base.writeEntry("BuildItems", KDevelop::qvariantToString( QVariant( paths ) )); base.sync(); } void ProjectBuildSetModel::loadFromProject( KDevelop::IProject* project ) { KConfigGroup base = project->projectConfiguration()->group("Buildset"); if (base.hasKey("BuildItems")) { const QVariantList items = KDevelop::stringToQVariant(base.readEntry("BuildItems", QString())).toList(); for (const QVariant& path : items) { insertItemWithCache( BuildItem( path.toStringList() ) ); } } else { // Add project to buildset, but only if there is no configuration for this project yet. addProjectItem( project->projectItem() ); } } void ProjectBuildSetModel::moveRowsDown(int row, int count) { QList items = d->items.mid(row, count); removeRows( row, count ); insertItemsOverrideCache( row + 1, items ); } void ProjectBuildSetModel::moveRowsToBottom(int row, int count) { QList items = d->items.mid(row, count); removeRows( row, count ); insertItemsOverrideCache( rowCount(), items ); } void ProjectBuildSetModel::moveRowsUp(int row, int count) { QList items = d->items.mid(row, count); removeRows( row, count ); insertItemsOverrideCache( row - 1, items ); } void ProjectBuildSetModel::moveRowsToTop(int row, int count) { QList items = d->items.mid(row, count); removeRows( row, count ); insertItemsOverrideCache( 0, items ); } } diff --git a/kdevplatform/shell/sessionlock.cpp b/kdevplatform/shell/sessionlock.cpp index b380fead0b..8b23974bbe 100644 --- a/kdevplatform/shell/sessionlock.cpp +++ b/kdevplatform/shell/sessionlock.cpp @@ -1,226 +1,224 @@ /* * This file is part of KDevelop * Copyright 2013 Milian Wolff * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "sessionlock.h" #include "debug.h" #include "sessioncontroller.h" #include #include #include #include #include using namespace KDevelop; namespace { QString lockFileForSession( const QString& id ) { return SessionController::sessionDirectory( id ) + QLatin1String("/lock"); } QString dBusServiceNameForSession( const QString& id ) { // We remove starting "{" and ending "}" from the string UUID representation // as D-Bus apparently doesn't allow them in service names return QStringLiteral( "org.kdevelop.kdevplatform-lock-" ) + id.midRef( 1, id.size() - 2 ); } /// Force-removes the lock-file. void forceRemoveLockfile(const QString& lockFilename) { if( QFile::exists( lockFilename ) ) { QFile::remove( lockFilename ); } } } TryLockSessionResult SessionLock::tryLockSession(const QString& sessionId, bool doLocking) { ///FIXME: if this is hit, someone tried to lock a non-existing session /// this should be fixed by using a proper data type distinct from /// QString for id's, i.e. QUuid or similar. Q_ASSERT(QFile::exists(SessionController::sessionDirectory( sessionId ))); /* * We've got two locking mechanisms here: D-Bus unique service name (based on the session id) * and a plain lockfile (QLockFile). * The latter is required to get the appname/pid of the locking instance * in case if it's stale/hanging/crashed (to make user know which PID he needs to kill). * D-Bus mechanism is the primary one. * * Since there is a kind of "logic tree", the code is a bit hard. */ const QString service = dBusServiceNameForSession( sessionId ); QDBusConnection connection = QDBusConnection::sessionBus(); QDBusConnectionInterface* connectionInterface = connection.interface(); const QString lockFilename = lockFileForSession( sessionId ); QSharedPointer lockFile(new QLockFile( lockFilename )); const bool haveDBus = connection.isConnected(); const bool canLockDBus = haveDBus && connectionInterface && !connectionInterface->isServiceRegistered( service ); bool lockedDBus = false; // Lock D-Bus if we can and we need to if( doLocking && canLockDBus ) { lockedDBus = connection.registerService( service ); } // Attempt to lock file, despite the possibility to do so and presence of the request (doLocking) // This is required as QLockFile::getLockInfo() works only after QLockFile::lock() is called bool lockResult = lockFile->tryLock(); SessionRunInfo runInfo; if (lockResult) { // Unlock immediately if we shouldn't have locked it if( haveDBus && !lockedDBus ) { lockFile->unlock(); } } else { // If locking failed, retrieve the lock's metadata lockFile->getLockInfo(&runInfo.holderPid, &runInfo.holderHostname, &runInfo.holderApp ); runInfo.isRunning = !haveDBus || !canLockDBus; if( haveDBus && lockedDBus ) { // Since the lock-file is secondary, try to force-lock it if D-Bus locking succeeded forceRemoveLockfile(lockFilename); lockResult = lockFile->tryLock(); Q_ASSERT(lockResult); } } // Set the result by D-Bus status if (doLocking && (haveDBus ? lockedDBus : lockResult)) { return TryLockSessionResult(QSharedPointer(new SessionLock(sessionId, lockFile))); } else { return TryLockSessionResult(runInfo); } } QString SessionLock::id() { return m_sessionId; } SessionLock::SessionLock(const QString& sessionId, const QSharedPointer& lockFile) : m_sessionId(sessionId) , m_lockFile(lockFile) { Q_ASSERT(lockFile->isLocked()); } SessionLock::~SessionLock() { m_lockFile->unlock(); bool unregistered = QDBusConnection::sessionBus().unregisterService( dBusServiceNameForSession(m_sessionId) ); Q_UNUSED(unregistered); } void SessionLock::removeFromDisk() { Q_ASSERT(m_lockFile->isLocked()); // unlock first to prevent warnings: "Could not remove our own lock file ..." m_lockFile->unlock(); QDir(SessionController::sessionDirectory(m_sessionId)).removeRecursively(); } QString SessionLock::handleLockedSession(const QString& sessionName, const QString& sessionId, const SessionRunInfo& runInfo) { if( !runInfo.isRunning ) { return sessionId; } // try to make the locked session active { // The timeout for "ensureVisible" call // Leave it sufficiently low to avoid waiting for hung instances. static const int timeout_ms = 1000; QDBusMessage message = QDBusMessage::createMethodCall( dBusServiceNameForSession(sessionId), QStringLiteral("/kdevelop/MainWindow"), QStringLiteral("org.kdevelop.MainWindow"), QStringLiteral("ensureVisible") ); QDBusMessage reply = QDBusConnection::sessionBus().call( message, QDBus::Block, timeout_ms ); if( reply.type() == QDBusMessage::ReplyMessage ) { QTextStream out(stdout); out << i18nc( "@info:shell", "Running %1 instance (PID: %2) detected, making this one visible instead of starting a new one", runInfo.holderApp, runInfo.holderPid ) << endl; return QString(); } else { qCWarning(SHELL) << i18nc("@info:shell", "Running %1 instance (PID: %2) is apparently hung", runInfo.holderApp, runInfo.holderPid); qCWarning(SHELL) << i18nc("@info:shell", "running %1 instance (PID: %2) is apparently hung", runInfo.holderApp, runInfo.holderPid); } } // otherwise ask the user whether we should retry QString problemDescription = i18nc("@info", "The given application did not respond to a DBUS call, " "it may have crashed or is hanging."); QString problemHeader; if( runInfo.holderPid != -1 ) { problemHeader = i18nc("@info", "Failed to lock the session %1, " "already locked by %2 on %3 (PID %4).", sessionName, runInfo.holderApp, runInfo.holderHostname, runInfo.holderPid); } else { problemHeader = i18nc("@info", "Failed to lock the session %1 (lock-file unavailable).", sessionName); } QString problemResolution = i18nc("@info", "

Please, close the offending application instance " "or choose another session to launch.

"); QString errmsg = QLatin1String("

") + problemHeader + QLatin1String("
") + problemDescription + QLatin1String("

") + problemResolution; KGuiItem retry = KStandardGuiItem::cont(); retry.setText(i18nc("@action:button", "Retry startup")); KGuiItem choose = KStandardGuiItem::configure(); choose.setText(i18nc("@action:button", "Choose another session")); KGuiItem cancel = KStandardGuiItem::quit(); int ret = KMessageBox::warningYesNoCancel(nullptr, errmsg, i18nc("@title:window", "Failed to Lock Session %1", sessionName), retry, choose, cancel); switch( ret ) { case KMessageBox::Yes: return sessionId; - break; case KMessageBox::No: { QString errmsg = i18nc("@info", "The session %1 is already active in another running instance.", sessionName); return SessionController::showSessionChooserDialog(errmsg); - break; } case KMessageBox::Cancel: default: break; } return QString(); } diff --git a/kdevplatform/vcs/vcsrevision.cpp b/kdevplatform/vcs/vcsrevision.cpp index 7f222e6b90..9a51cb86f2 100644 --- a/kdevplatform/vcs/vcsrevision.cpp +++ b/kdevplatform/vcs/vcsrevision.cpp @@ -1,189 +1,175 @@ /*************************************************************************** * 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. * ***************************************************************************/ #include "vcsrevision.h" #include #include #include #include namespace KDevelop { VcsRevision VcsRevision::createSpecialRevision( KDevelop::VcsRevision::RevisionSpecialType _type ) { VcsRevision rev; rev.setRevisionValue( QVariant::fromValue( _type ), VcsRevision::Special ); return rev; } class VcsRevisionPrivate : public QSharedData { public: QVariant value; VcsRevision::RevisionType type; QMap internalValues; }; VcsRevision::VcsRevision() : d(new VcsRevisionPrivate) { d->type = VcsRevision::Invalid; } VcsRevision::VcsRevision( const VcsRevision& rhs ) : d(rhs.d) { } VcsRevision::~VcsRevision() = default; VcsRevision& VcsRevision::operator=( const VcsRevision& rhs) { d = rhs.d; return *this; } void VcsRevision::setRevisionValue( const QVariant& rev, VcsRevision::RevisionType type ) { d->value = rev; d->type = type; } VcsRevision::RevisionType VcsRevision::revisionType() const { return d->type; } VcsRevision::RevisionSpecialType VcsRevision::specialType() const { Q_ASSERT(d->type==Special); return d->value.value(); } QVariant VcsRevision::revisionValue() const { return d->value; } QStringList VcsRevision::keys() const { return d->internalValues.keys(); } QVariant VcsRevision::value(const QString& key) const { if( d->internalValues.contains(key) ) { return d->internalValues[key]; } return QVariant(); } void VcsRevision::setValue( const QString& key, const QVariant& value ) { d->internalValues[key] = value; } void VcsRevision::setType( RevisionType t) { d->type = t; } void VcsRevision::setSpecialType( RevisionSpecialType t) { d->value = QVariant(t); } void VcsRevision::setValue( const QVariant& v ) { d->value = v; } bool VcsRevision::operator==( const KDevelop::VcsRevision& rhs ) const { return ( d->type == rhs.d->type && d->value == rhs.d->value && d->internalValues == rhs.d->internalValues ); } QString VcsRevision::prettyValue() const { switch( revisionType() ) { case GlobalNumber: case FileNumber: return (revisionValue().type() == QVariant::String ? revisionValue().toString() : QString::number(revisionValue().toLongLong())); - break; case Special: switch( revisionValue().value( ) ) { case VcsRevision::Head: return QStringLiteral("Head"); - break; case VcsRevision::Base: return QStringLiteral("Base"); - break; case VcsRevision::Working: return QStringLiteral("Working"); - break; case VcsRevision::Previous: return QStringLiteral("Previous"); - break; case VcsRevision::Start: return QStringLiteral("Start"); - break; default: return QStringLiteral("User"); - break; } - break; case Date: return revisionValue().toDateTime().toString( Qt::LocalDate ); - break; default: return revisionValue().toString(); - break; } } } uint KDevelop::qHash( const KDevelop::VcsRevision& rev) { const auto revisionValue = rev.revisionValue(); switch (rev.revisionType()) { case VcsRevision::GlobalNumber: case VcsRevision::FileNumber: return (revisionValue.type() == QVariant::String ? ::qHash(revisionValue.toString()) : ::qHash(revisionValue.toULongLong())); - break; case VcsRevision::Special: return ::qHash(static_cast(revisionValue.value())); - break; case VcsRevision::Date: return ::qHash(revisionValue.toDateTime()); - break; default: - break; + return ::qHash(revisionValue.toString()); } - return ::qHash(revisionValue.toString()); } diff --git a/plugins/clang/duchain/clangdiagnosticevaluator.cpp b/plugins/clang/duchain/clangdiagnosticevaluator.cpp index 669cd82acf..d106a23a3c 100644 --- a/plugins/clang/duchain/clangdiagnosticevaluator.cpp +++ b/plugins/clang/duchain/clangdiagnosticevaluator.cpp @@ -1,131 +1,128 @@ /* * Copyright 2014 Kevin Funk * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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, see . * */ #include "clangdiagnosticevaluator.h" #include "unknowndeclarationproblem.h" #include "missingincludepathproblem.h" #include "util/clangtypes.h" namespace { /** * Check whether the problem stated in @p diagnostic may be caused by a missing include * * @return True if this may be fixable by adding a include, false otherwise */ bool isDeclarationProblem(const QByteArray& description) { /* libclang does not currently expose an enum or any other way to query * what specific semantic error we're dealing with. Instead, we have to * parse the clang error message and guess if a missing include could be * the reason for the error * * There is no nice way of determining what identifier we're looking at either, * so we have to read that from the diagnostic too. Hopefully libclang will * get these features in the future. * * I have suggested this feature to clang devs. For reference, see: * http://lists.cs.uiuc.edu/pipermail/cfe-dev/2014-March/036036.html */ return description.startsWith( QByteArrayLiteral("use of undeclared identifier") ) || description.startsWith( QByteArrayLiteral("no member named") ) || description.startsWith( QByteArrayLiteral("unknown type name") ) || description.startsWith( QByteArrayLiteral("variable has incomplete type") ) || description.startsWith( QByteArrayLiteral("member access into incomplete type") ); } /// @return true if @p diagnostic says that include file not found bool isIncludeFileNotFound(const QByteArray& description) { return description.endsWith(QByteArrayLiteral("file not found")); } bool isReplaceWithDotProblem(const QByteArray& description) { // TODO: The diagnostic message depends on LibClang version. static const QByteArray diagnosticMessages[] = { QByteArrayLiteral("did you mean to use '.'?"), QByteArrayLiteral("maybe you meant to use '.'?") }; for (const auto& diagnStr : diagnosticMessages) { if (description.endsWith(diagnStr)) { return true; } } return false; } bool isReplaceWithArrowProblem(const QByteArray& description) { // TODO: The diagnostic message depends on LibClang version. static const QByteArray diagnosticMessages[] = { QByteArrayLiteral("did you mean to use '->'?"), QByteArrayLiteral("maybe you meant to use '->'?") }; for (const auto& diagnStr : diagnosticMessages) { if (description.endsWith(diagnStr)) { return true; } } return false; } } ClangDiagnosticEvaluator::DiagnosticType ClangDiagnosticEvaluator::diagnosticType(CXDiagnostic diagnostic) { const ClangString str(clang_getDiagnosticSpelling(diagnostic)); const auto description = QByteArray::fromRawData(str.c_str(), qstrlen(str.c_str())); if (isDeclarationProblem(description)) { return UnknownDeclarationProblem; } else if (isIncludeFileNotFound(description)) { return IncludeFileNotFoundProblem; } else if (isReplaceWithDotProblem(description)) { return ReplaceWithDotProblem; } else if (isReplaceWithArrowProblem(description)) { return ReplaceWithArrowProblem; } return Unknown; } ClangProblem* ClangDiagnosticEvaluator::createProblem(CXDiagnostic diagnostic, CXTranslationUnit unit) { switch (diagnosticType(diagnostic)) { case IncludeFileNotFoundProblem: return new MissingIncludePathProblem(diagnostic, unit); - break; case UnknownDeclarationProblem: return new class UnknownDeclarationProblem(diagnostic, unit); - break; default: return new ClangProblem(diagnostic, unit); - break; } } diff --git a/plugins/clang/duchain/clangproblem.cpp b/plugins/clang/duchain/clangproblem.cpp index aa1005e7c0..9fecf076bd 100644 --- a/plugins/clang/duchain/clangproblem.cpp +++ b/plugins/clang/duchain/clangproblem.cpp @@ -1,271 +1,270 @@ /* * Copyright 2014 Kevin Funk * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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, see . * */ #include "clangproblem.h" #include #include #include "util/clangtypes.h" #include "util/clangdebug.h" #include #include #include #include using namespace KDevelop; namespace { IProblem::Severity diagnosticSeverityToSeverity(CXDiagnosticSeverity severity, const QString& optionName) { switch (severity) { case CXDiagnostic_Fatal: case CXDiagnostic_Error: return IProblem::Error; case CXDiagnostic_Warning: if (optionName.startsWith(QLatin1String("-Wunused-"))) { return IProblem::Hint; } return IProblem::Warning; - break; default: return IProblem::Hint; } } /** * Clang diagnostic messages always start with a lowercase character * * @return Prettified version, starting with uppercase character */ inline QString prettyDiagnosticSpelling(const QString& str) { QString ret = str; if (ret.isEmpty()) { return {}; } ret[0] = ret[0].toUpper(); return ret; } ClangFixits fixitsForDiagnostic(CXDiagnostic diagnostic) { ClangFixits fixits; auto numFixits = clang_getDiagnosticNumFixIts(diagnostic); fixits.reserve(numFixits); for (uint i = 0; i < numFixits; ++i) { CXSourceRange range; const QString replacementText = ClangString(clang_getDiagnosticFixIt(diagnostic, i, &range)).toString(); const auto docRange = ClangRange(range).toDocumentRange(); auto doc = KDevelop::ICore::self()->documentController()->documentForUrl(docRange.document.toUrl()); const QString original = doc ? doc->text(docRange) : QString{}; fixits << ClangFixit{replacementText, docRange, QString(), original}; } return fixits; } } QDebug operator<<(QDebug debug, const ClangFixit& fixit) { debug.nospace() << "ClangFixit[" << "replacementText=" << fixit.replacementText << ", range=" << fixit.range << ", description=" << fixit.description << "]"; return debug; } ClangProblem::ClangProblem(CXDiagnostic diagnostic, CXTranslationUnit unit) { const QString diagnosticOption = ClangString(clang_getDiagnosticOption(diagnostic, nullptr)).toString(); auto severity = diagnosticSeverityToSeverity(clang_getDiagnosticSeverity(diagnostic), diagnosticOption); setSeverity(severity); QString description = ClangString(clang_getDiagnosticSpelling(diagnostic)).toString(); if (!diagnosticOption.isEmpty()) { description.append(QLatin1String(" [") + diagnosticOption + QLatin1Char(']')); } setDescription(prettyDiagnosticSpelling(description)); ClangLocation location(clang_getDiagnosticLocation(diagnostic)); CXFile diagnosticFile; clang_getFileLocation(location, &diagnosticFile, nullptr, nullptr, nullptr); const ClangString fileName(clang_getFileName(diagnosticFile)); DocumentRange docRange(IndexedString(QUrl::fromLocalFile(fileName.toString()).adjusted(QUrl::NormalizePathSegments)), KTextEditor::Range(location, location)); const uint numRanges = clang_getDiagnosticNumRanges(diagnostic); for (uint i = 0; i < numRanges; ++i) { auto range = ClangRange(clang_getDiagnosticRange(diagnostic, i)).toRange(); if(!range.isValid()){ continue; } if (range.start() < docRange.start()) { docRange.setStart(range.start()); } if (range.end() > docRange.end()) { docRange.setEnd(range.end()); } } if (docRange.isEmpty()) { // try to find a bigger range for the given location by using the token at the given location CXFile file = nullptr; unsigned line = 0; unsigned column = 0; clang_getExpansionLocation(location, &file, &line, &column, nullptr); // just skip ahead some characters, hoping that it's sufficient to encompass // a token we can use for building the range auto nextLocation = clang_getLocation(unit, file, line, column + 100); auto rangeToTokenize = clang_getRange(location, nextLocation); const ClangTokens tokens(unit, rangeToTokenize); if (tokens.size()) { docRange.setRange(ClangRange(clang_getTokenExtent(unit, tokens.at(0))).toRange()); } } setFixits(fixitsForDiagnostic(diagnostic)); setFinalLocation(docRange); setSource(IProblem::SemanticAnalysis); QVector diagnostics; auto childDiagnostics = clang_getChildDiagnostics(diagnostic); auto numChildDiagnostics = clang_getNumDiagnosticsInSet(childDiagnostics); diagnostics.reserve(numChildDiagnostics); for (uint j = 0; j < numChildDiagnostics; ++j) { auto childDiagnostic = clang_getDiagnosticInSet(childDiagnostics, j); ClangProblem::Ptr problem(new ClangProblem(childDiagnostic, unit)); diagnostics << ProblemPointer(problem.data()); } setDiagnostics(diagnostics); } IAssistant::Ptr ClangProblem::solutionAssistant() const { if (allFixits().isEmpty()) { return {}; } return IAssistant::Ptr(new ClangFixitAssistant(allFixits())); } ClangFixits ClangProblem::fixits() const { return m_fixits; } void ClangProblem::setFixits(const ClangFixits& fixits) { m_fixits = fixits; } ClangFixits ClangProblem::allFixits() const { ClangFixits result; result << m_fixits; const auto& diagnostics = this->diagnostics(); for (const IProblem::Ptr& diagnostic : diagnostics) { const Ptr problem(dynamic_cast(diagnostic.data())); Q_ASSERT(problem); result << problem->allFixits(); } return result; } ClangFixitAssistant::ClangFixitAssistant(const ClangFixits& fixits) : m_title(i18n("Fix-it Hints")) , m_fixits(fixits) { } ClangFixitAssistant::ClangFixitAssistant(const QString& title, const ClangFixits& fixits) : m_title(title) , m_fixits(fixits) { } QString ClangFixitAssistant::title() const { return m_title; } void ClangFixitAssistant::createActions() { KDevelop::IAssistant::createActions(); for (const ClangFixit& fixit : qAsConst(m_fixits)) { addAction(IAssistantAction::Ptr(new ClangFixitAction(fixit))); } } ClangFixits ClangFixitAssistant::fixits() const { return m_fixits; } ClangFixitAction::ClangFixitAction(const ClangFixit& fixit) : m_fixit(fixit) { } QString ClangFixitAction::description() const { if (!m_fixit.description.isEmpty()) return m_fixit.description; const auto range = m_fixit.range; if (range.start() == range.end()) { return i18n("Insert \"%1\" at line: %2, column: %3", m_fixit.replacementText, range.start().line()+1, range.start().column()+1); } else if (range.start().line() == range.end().line()) { if (m_fixit.currentText.isEmpty()) { return i18n("Replace text at line: %1, column: %2 with: \"%3\"", range.start().line()+1, range.start().column()+1, m_fixit.replacementText); } else return i18n("Replace \"%1\" with: \"%2\"", m_fixit.currentText, m_fixit.replacementText); } else { return i18n("Replace multiple lines starting at line: %1, column: %2 with: \"%3\"", range.start().line()+1, range.start().column()+1, m_fixit.replacementText); } } void ClangFixitAction::execute() { DocumentChangeSet changes; { DUChainReadLocker lock; DocumentChange change(m_fixit.range.document, m_fixit.range, m_fixit.currentText, m_fixit.replacementText); change.m_ignoreOldText = !m_fixit.currentText.isEmpty(); changes.addChange(change); } changes.setReplacementPolicy(DocumentChangeSet::WarnOnFailedChange); changes.applyAllChanges(); emit executed(this); } diff --git a/plugins/custom-definesandincludes/kcm_widget/projectpathsmodel.cpp b/plugins/custom-definesandincludes/kcm_widget/projectpathsmodel.cpp index 1f8c4bc27a..478afabfb1 100644 --- a/plugins/custom-definesandincludes/kcm_widget/projectpathsmodel.cpp +++ b/plugins/custom-definesandincludes/kcm_widget/projectpathsmodel.cpp @@ -1,238 +1,230 @@ /************************************************************************ * * * Copyright 2010 Andreas Pakulat * * * * 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 or 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; if not, see . * ************************************************************************/ #include "projectpathsmodel.h" #include #include #include #include using namespace KDevelop; ProjectPathsModel::ProjectPathsModel( QObject* parent ) : QAbstractListModel( parent ) { } void ProjectPathsModel::setProject(IProject* w_project) { project = w_project; } QVariant ProjectPathsModel::data( const QModelIndex& index, int role ) const { if( !index.isValid() || index.row() < 0 || index.row() >= rowCount() || index.column() != 0 ) { return QVariant(); } const ConfigEntry& pathConfig = projectPaths.at( index.row() ); switch( role ) { case IncludesDataRole: return pathConfig.includes; - break; case DefinesDataRole: return QVariant::fromValue(pathConfig.defines); - break; case Qt::EditRole: return sanitizePath( pathConfig.path, true, false ); - break; case Qt::DisplayRole: { const QString& path = pathConfig.path; return (path == QLatin1String(".")) ? QStringLiteral("(project root)") : path; - break; } case FullUrlDataRole: return QVariant::fromValue(QUrl::fromUserInput( sanitizePath( pathConfig.path, true, false ) )); - break; case CompilerDataRole: return QVariant::fromValue(pathConfig.compiler); - break; case ParserArgumentsRole: return QVariant::fromValue(pathConfig.parserArguments); - break; default: break; } return QVariant(); } int ProjectPathsModel::rowCount( const QModelIndex& parent ) const { if( parent.isValid() ) { return 0; } return projectPaths.count(); } bool ProjectPathsModel::setData( const QModelIndex& index, const QVariant& value, int role ) { if( !index.isValid() || index.row() < 0 || index.row() >= rowCount() || index.column() != 0 ) { return false; } // Do not allow to change path of the first entry; instead, add a new one in that case if( index.row() == 0 && ( role == Qt::EditRole || role == Qt::DisplayRole || role == FullUrlDataRole ) ) { QString addedPath = sanitizePath( value.toString(), false ); // Do not allow duplicates foreach( const ConfigEntry& existingConfig, projectPaths ) { if( addedPath == existingConfig.path ) { return false; } } projectPaths.insert( 1, ConfigEntry(sanitizePath( value.toString(), false ) )); emit dataChanged( this->index( 1, 0 ), this->index( projectPaths.count() - 1, 0 ) ); return true; } ConfigEntry& pathConfig = projectPaths[ index.row() ]; switch( role ) { case IncludesDataRole: pathConfig.includes = value.toStringList(); break; case DefinesDataRole: pathConfig.defines = value.value(); break; case Qt::EditRole: pathConfig.path = sanitizePath( value.toString(), false ); break; case Qt::DisplayRole: pathConfig.path = sanitizePath( value.toString(), true ); break; case FullUrlDataRole: pathConfig.path = sanitizeUrl(value.toUrl()); break; case CompilerDataRole: pathConfig.compiler = value.value(); break; case ParserArgumentsRole: pathConfig.parserArguments = value.value(); break; default: return false; - break; } emit dataChanged( index, index ); return true; } Qt::ItemFlags ProjectPathsModel::flags( const QModelIndex& index ) const { if( !index.isValid() ) { return Qt::NoItemFlags; } if( index.row() == 0 ) { return Qt::ItemFlags( Qt::ItemIsSelectable | Qt::ItemIsEnabled ); } return Qt::ItemFlags( Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled ); } QVector< ConfigEntry > ProjectPathsModel::paths() const { return projectPaths; } void ProjectPathsModel::setPaths(const QVector< ConfigEntry >& paths ) { beginResetModel(); projectPaths.clear(); for (const ConfigEntry& existingPathConfig : paths) { // Sanitize the path of loaded config ConfigEntry config = existingPathConfig; bool rootPath = config.path == QLatin1String(".") ? true : false; config.path = sanitizePath(rootPath ? QString() : config.path ); addPathInternal(config, rootPath); } addPathInternal( ConfigEntry(sanitizePath( QString() )), true ); // add an empty "root" config entry if one does not exist endResetModel(); } bool ProjectPathsModel::removeRows( int row, int count, const QModelIndex& parent ) { if( row >= 0 && count > 0 && row < rowCount() ) { beginRemoveRows( parent, row, row + count - 1 ); for( int i = 0; i < count; ++i ) { if( projectPaths.at(row).path == QLatin1String(".") ) { continue; // we won't remove the root item } projectPaths.removeAt(row); } endRemoveRows(); return true; } return false; } void ProjectPathsModel::addPath( const QUrl &url ) { if( !project->path().isParentOf(KDevelop::Path(url)) ) { return; } beginInsertRows( QModelIndex(), rowCount(), rowCount() ); addPathInternal( ConfigEntry(sanitizeUrl(url)), false ); endInsertRows(); } void ProjectPathsModel::addPathInternal( const ConfigEntry& config, bool prepend ) { Q_ASSERT(!config.parserArguments.isAnyEmpty()); // Do not allow duplicates foreach( const ConfigEntry& existingConfig, projectPaths ) { if( config.path == existingConfig.path ) { return; } } if( prepend ) { projectPaths.prepend( config ); } else { projectPaths.append( config ); } } QString ProjectPathsModel::sanitizeUrl( const QUrl& url, bool needRelative ) const { Q_ASSERT( project ); if (needRelative) { const auto relativePath = project->path().relativePath(KDevelop::Path(url)); return relativePath.isEmpty() ? QStringLiteral(".") : relativePath; } return url.adjusted(QUrl::StripTrailingSlash | QUrl::NormalizePathSegments).toString(QUrl::PreferLocalFile); } QString ProjectPathsModel::sanitizePath( const QString& path, bool expectRelative, bool needRelative ) const { Q_ASSERT( project ); Q_ASSERT( expectRelative || project->inProject(IndexedString(path)) ); QUrl url; if( expectRelative ) { url = KDevelop::Path(project->path(), path).toUrl(); } else { url = QUrl::fromUserInput(path); } return sanitizeUrl( url, needRelative ); } diff --git a/plugins/ghprovider/ghproviderwidget.cpp b/plugins/ghprovider/ghproviderwidget.cpp index 06978574ba..8751c3087d 100644 --- a/plugins/ghprovider/ghproviderwidget.cpp +++ b/plugins/ghprovider/ghproviderwidget.cpp @@ -1,175 +1,178 @@ /* This file is part of KDevelop * * Copyright (C) 2012-2013 Miquel Sabaté * * 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. * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include + using namespace KDevelop; namespace gh { ProviderWidget::ProviderWidget(QWidget *parent) : IProjectProviderWidget(parent) { setLayout(new QVBoxLayout()); m_projects = new QListView(this); connect(m_projects, &QListView::clicked, this, &ProviderWidget::projectIndexChanged); m_waiting = new QLabel(i18n("Waiting for response"), this); m_waiting->setAlignment(Qt::AlignCenter); m_waiting->hide(); ProviderModel *model = new ProviderModel(this); m_projects->setModel(model); m_projects->setEditTriggers(QAbstractItemView::NoEditTriggers); m_resource = new Resource(this, model); m_account = new Account(m_resource); connect(m_resource, &Resource::reposUpdated, m_waiting, &QLabel::hide); QHBoxLayout *topLayout = new QHBoxLayout(); m_edit = new LineEdit(this); m_edit->setPlaceholderText(i18n("Search")); m_edit->setToolTip(i18n("You can press the Return key if you do not want to wait")); connect(m_edit, &LineEdit::returnPressed, this, &ProviderWidget::searchRepo); topLayout->addWidget(m_edit); m_combo = new QComboBox(this); m_combo->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Fixed); connect(m_combo, static_cast(&QComboBox::currentIndexChanged), this, &ProviderWidget::searchRepo); fillCombo(); topLayout->addWidget(m_combo); QPushButton *settings = new QPushButton(QIcon::fromTheme(QStringLiteral("configure")), QString(), this); settings->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Fixed); settings->setToolTip(i18n("Click this button to configure your GitHub account")); connect(settings, &QPushButton::clicked, this, &ProviderWidget::showSettings); topLayout->addWidget(settings); layout()->addItem(topLayout); layout()->addWidget(m_waiting); layout()->addWidget(m_projects); } KDevelop::VcsJob * ProviderWidget::createWorkingCopy(const QUrl &dest) { QModelIndex pos = m_projects->currentIndex(); if (!pos.isValid()) return nullptr; auto plugin = ICore::self()->pluginController()->pluginForExtension(QStringLiteral("org.kdevelop.IBasicVersionControl"), QStringLiteral("kdevgit")); if (!plugin) { KMessageBox::error(nullptr, i18n("The Git plugin could not be loaded which is required to import a GitHub project."), i18n("GitHub Provider Error")); return nullptr; } QString url = pos.data(ProviderModel::VcsLocationRole).toString(); if (m_account->validAccount()) url = QLatin1String("https://") + m_account->token() + QLatin1Char('@') + url.midRef(8); QUrl real = QUrl(url); VcsLocation loc(real); auto vc = plugin->extension(); Q_ASSERT(vc); return vc->createWorkingCopy(loc, dest); } void ProviderWidget::fillCombo() { m_combo->clear(); m_combo->addItem(i18n("User"), 1); m_combo->addItem(i18n("Organization"), 3); if (m_account->validAccount()) { m_combo->addItem(m_account->name(), 0); m_combo->setCurrentIndex(2); } const QStringList &orgs = m_account->orgs(); for (const QString& org : orgs) m_combo->addItem(org, 2); } bool ProviderWidget::isCorrect() const { return m_projects->currentIndex().isValid(); } void ProviderWidget::projectIndexChanged(const QModelIndex ¤tIndex) { if (currentIndex.isValid()) { QString name = currentIndex.data().toString(); emit changed(name); } } void ProviderWidget::showSettings() { Dialog *dialog = new Dialog(this, m_account); connect(dialog, &Dialog::shouldUpdate, this, &ProviderWidget::fillCombo); dialog->show(); } void ProviderWidget::searchRepo() { bool enabled = true; QString uri, text = m_edit->text(); int idx = m_combo->itemData(m_combo->currentIndex()).toInt(); switch (idx) { case 0: /* Looking for this user's repo */ uri = QStringLiteral("/user/repos"); enabled = false; break; case 1: /* Looking for some user's repo */ if (text == m_account->name()) uri = QStringLiteral("/user/repos"); else uri = QStringLiteral("/users/%1/repos").arg(text); break; case 2: /* Known organization */ text = m_combo->currentText(); enabled = false; + Q_FALLTHROUGH(); default:/* Looking for some organization's repo. */ uri = QStringLiteral("/orgs/%1/repos").arg(text); break; } m_edit->setEnabled(enabled); m_waiting->show(); m_resource->searchRepos(uri, m_account->token()); } } // End of namespace gh diff --git a/plugins/makebuilder/makejob.cpp b/plugins/makebuilder/makejob.cpp index 4e6190f800..03a93c3c9f 100644 --- a/plugins/makebuilder/makejob.cpp +++ b/plugins/makebuilder/makejob.cpp @@ -1,315 +1,314 @@ /* This file is part of KDevelop Copyright 2004 Roberto Raggi Copyright 2007 Andreas Pakulat Copyright 2007 Dukju Ahn Copyright 2008 Hamish Rodda Copyright 2012 Ivan Shapovalov This library 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 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 "makejob.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "makebuilder.h" #include "makebuilderpreferences.h" #include "debug.h" using namespace KDevelop; class MakeJobCompilerFilterStrategy : public CompilerFilterStrategy { public: using CompilerFilterStrategy::CompilerFilterStrategy; IFilterStrategy::Progress progressInLine(const QString& line) override; }; IFilterStrategy::Progress MakeJobCompilerFilterStrategy::progressInLine(const QString& line) { // example string: [ 97%] Built target clang-parser static const QRegularExpression re(QStringLiteral("^\\[([\\d ][\\d ]\\d)%\\] (.*)")); QRegularExpressionMatch match = re.match(line); if (match.hasMatch()) { bool ok; const int percent = match.capturedRef(1).toInt(&ok); if (ok) { // this is output from make, likely const QString action = match.captured(2); return {action, percent}; } } return {}; } MakeJob::MakeJob(QObject* parent, KDevelop::ProjectBaseItem* item, CommandType c, const QStringList& overrideTargets, const MakeVariables& variables ) : OutputExecuteJob(parent) , m_idx(item->index()) , m_command(c) , m_overrideTargets(overrideTargets) , m_variables(variables) { auto bsm = item->project()->buildSystemManager(); auto buildDir = bsm->buildDirectory(item); Q_ASSERT(item && item->model() && m_idx.isValid() && this->item() == item); setCapabilities( Killable ); setFilteringStrategy(new MakeJobCompilerFilterStrategy(buildDir.toUrl())); setProperties( NeedWorkingDirectory | PortableMessages | DisplayStderr | IsBuilderHint ); QString title; if( !m_overrideTargets.isEmpty() ) title = i18n("Make (%1): %2", item->text(), m_overrideTargets.join(QLatin1Char(' '))); else title = i18n("Make (%1)", item->text()); setJobName( title ); setToolTitle( i18n("Make") ); } MakeJob::~MakeJob() { } void MakeJob::start() { ProjectBaseItem* it = item(); qCDebug(KDEV_MAKEBUILDER) << "Building with make" << m_command << m_overrideTargets.join(QLatin1Char(' ')); if (!it) { setError(ItemNoLongerValidError); setErrorText(i18n("Build item no longer available")); emitResult(); return; } if( it->type() == KDevelop::ProjectBaseItem::File ) { setError(IncorrectItemError); setErrorText(i18n("Internal error: cannot build a file item")); emitResult(); return; } setStandardToolView(IOutputView::BuildView); setBehaviours(KDevelop::IOutputView::AllowUserClose | KDevelop::IOutputView::AutoScroll); OutputExecuteJob::start(); } KDevelop::ProjectBaseItem * MakeJob::item() const { return ICore::self()->projectController()->projectModel()->itemFromIndex(m_idx); } MakeJob::CommandType MakeJob::commandType() const { return m_command; } QStringList MakeJob::customTargets() const { return m_overrideTargets; } QUrl MakeJob::workingDirectory() const { ProjectBaseItem* it = item(); if(!it) return QUrl(); KDevelop::IBuildSystemManager *bldMan = it->project()->buildSystemManager(); if( bldMan ) return bldMan->buildDirectory( it ).toUrl(); // the correct build dir else { // Just build in-source, where the build directory equals the one with particular target/source. for( ProjectBaseItem* item = it; item; item = item->parent() ) { switch( item->type() ) { case KDevelop::ProjectBaseItem::Folder: case KDevelop::ProjectBaseItem::BuildFolder: return static_cast(item)->path().toUrl(); - break; case KDevelop::ProjectBaseItem::Target: case KDevelop::ProjectBaseItem::File: break; } } return QUrl(); } } QStringList MakeJob::privilegedExecutionCommand() const { ProjectBaseItem* it = item(); if(!it) return QStringList(); KSharedConfigPtr configPtr = it->project()->projectConfiguration(); KConfigGroup builderGroup( configPtr, "MakeBuilder" ); bool runAsRoot = builderGroup.readEntry( "Install As Root", false ); if ( runAsRoot && m_command == InstallCommand ) { QString suCommand = builderGroup.readEntry( "Su Command", QString() ); bool suCommandIsDigit; QStringList suCommandWithArg; int suCommandNum = suCommand.toInt(&suCommandIsDigit); /* * "if(suCommandIsDigit)" block exists only because of backwards compatibility * reasons, In earlier versions of KDevelop, suCommand's type was * int, if a user upgrades to current version from an older version, * suCommandIsDigit will become "true" and we will set suCommand according * to the stored config entry. */ if(suCommandIsDigit) { switch(suCommandNum) { case 1: { suCommand = QStringLiteral("kdesudo"); break; } case 2: { suCommand = QStringLiteral("sudo"); break; } default: suCommand = QStringLiteral("kdesu"); } builderGroup.writeEntry("Su Command", suCommand); //so that suCommandIsDigit becomes false next time //project is opened. } suCommandWithArg = KShell::splitArgs(suCommand); if( suCommandWithArg.isEmpty() ) { suCommandWithArg = QStringList{QStringLiteral("kdesu"), QStringLiteral("-t")}; } return suCommandWithArg; } return QStringList(); } QStringList MakeJob::commandLine() const { ProjectBaseItem* it = item(); if(!it) return QStringList(); QStringList cmdline; KSharedConfigPtr configPtr = it->project()->projectConfiguration(); KConfigGroup builderGroup( configPtr, "MakeBuilder" ); // TODO: migrate to more generic key term "Make Executable" QString makeBin = builderGroup.readEntry("Make Binary", MakeBuilderPreferences::standardMakeExecutable()); cmdline << makeBin; if( ! builderGroup.readEntry("Abort on First Error", true)) { cmdline << (isNMake(makeBin) ? QStringLiteral("/K") : QStringLiteral("-k")); } // note: nmake does not support the -j flag if (!isNMake(makeBin)) { if (builderGroup.readEntry("Override Number Of Jobs", false)) { int jobCount = builderGroup.readEntry("Number Of Jobs", 1); if (jobCount > 0) { cmdline << QStringLiteral("-j%1").arg(jobCount); } } else { // use the ideal thread count by default cmdline << QStringLiteral("-j%1").arg(QThread::idealThreadCount()); } } if( builderGroup.readEntry("Display Only", false) ) { cmdline << (isNMake(makeBin) ? QStringLiteral("/N") : QStringLiteral("-n")); } QString extraOptions = builderGroup.readEntry("Additional Options", QString()); if( ! extraOptions.isEmpty() ) { foreach(const QString& option, KShell::splitArgs( extraOptions ) ) cmdline << option; } for (MakeVariables::const_iterator it = m_variables.constBegin(); it != m_variables.constEnd(); ++it) { cmdline += QStringLiteral("%1=%2").arg(it->first, it->second); } if( m_overrideTargets.isEmpty() ) { QString target; switch (it->type()) { case KDevelop::ProjectBaseItem::Target: case KDevelop::ProjectBaseItem::ExecutableTarget: case KDevelop::ProjectBaseItem::LibraryTarget: Q_ASSERT(it->target()); cmdline << it->target()->text(); break; case KDevelop::ProjectBaseItem::BuildFolder: target = builderGroup.readEntry("Default Target", QString()); if( !target.isEmpty() ) cmdline << target; break; default: break; } }else { cmdline += m_overrideTargets; } return cmdline; } QString MakeJob::environmentProfile() const { ProjectBaseItem* it = item(); if(!it) return QString(); KSharedConfigPtr configPtr = it->project()->projectConfiguration(); KConfigGroup builderGroup( configPtr, "MakeBuilder" ); return builderGroup.readEntry( "Default Make Environment Profile", QString() ); } bool MakeJob::isNMake(const QString& makeBin) { return !QFileInfo(makeBin).baseName().compare(QStringLiteral("nmake"), Qt::CaseInsensitive); } diff --git a/plugins/perforce/perforceplugin.cpp b/plugins/perforce/perforceplugin.cpp index 9c45a7c3ca..dfa79917b8 100644 --- a/plugins/perforce/perforceplugin.cpp +++ b/plugins/perforce/perforceplugin.cpp @@ -1,689 +1,690 @@ /*************************************************************************** * Copyright 2010 Morten Danielsen Volden * * * * 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. * * * * 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, see . * ***************************************************************************/ #include "perforceplugin.h" #include "ui/perforceimportmetadatawidget.h" #include "debug.h" #include "qtcompat_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; namespace { QString toRevisionName(const KDevelop::VcsRevision& rev, const QString& currentRevision=QString()) { bool ok; int previous = currentRevision.toInt(&ok); previous--; QString tmp; switch(rev.revisionType()) { case VcsRevision::Special: switch(rev.revisionValue().value()) { case VcsRevision::Head: return QStringLiteral("#head"); case VcsRevision::Base: return QStringLiteral("#have"); case VcsRevision::Working: return QStringLiteral("#have"); case VcsRevision::Previous: Q_ASSERT(!currentRevision.isEmpty()); tmp.setNum(previous); tmp.prepend(QLatin1Char('#')); return tmp; case VcsRevision::Start: return QString(); case VcsRevision::UserSpecialType: //Not used Q_ASSERT(false && "i don't know how to do that"); } break; case VcsRevision::GlobalNumber: tmp.append(QLatin1Char('#') + rev.revisionValue().toString()); return tmp; case VcsRevision::Date: case VcsRevision::FileNumber: case VcsRevision::Invalid: case VcsRevision::UserType: Q_ASSERT(false); } return QString(); } VcsItemEvent::Actions actionsFromString(QString const& changeDescription) { if(changeDescription == QLatin1String("add")) return VcsItemEvent::Added; if(changeDescription == QLatin1String("delete")) return VcsItemEvent::Deleted; return VcsItemEvent::Modified; } QDir urlDir(const QUrl& url) { QFileInfo f(url.toLocalFile()); if(f.isDir()) return QDir(url.toLocalFile()); else return f.absoluteDir(); } } PerforcePlugin::PerforcePlugin(QObject* parent, const QVariantList&): KDevelop::IPlugin(QStringLiteral("kdevperforce"), parent) , m_common(new KDevelop::VcsPluginHelper(this, this)) , m_perforceConfigName(QStringLiteral("p4config.txt")) , m_perforceExecutable(QStringLiteral("p4")) , m_edit_action(nullptr) { QProcessEnvironment currentEviron(QProcessEnvironment::systemEnvironment()); QString tmp(currentEviron.value(QStringLiteral("P4CONFIG"))); if (tmp.isEmpty()) { // We require the P4CONFIG variable to be set because the perforce command line client will need it setErrorDescription(i18n("The variable P4CONFIG is not set. Is perforce installed on the system?")); return; } else { m_perforceConfigName = tmp; } qCDebug(PLUGIN_PERFORCE) << "The value of P4CONFIG is : " << tmp; } PerforcePlugin::~PerforcePlugin() { } QString PerforcePlugin::name() const { return i18n("Perforce"); } KDevelop::VcsImportMetadataWidget* PerforcePlugin::createImportMetadataWidget(QWidget* parent) { return new PerforceImportMetadataWidget(parent); } bool PerforcePlugin::isValidRemoteRepositoryUrl(const QUrl& remoteLocation) { Q_UNUSED(remoteLocation); // TODO return false; } bool PerforcePlugin::isValidDirectory(const QUrl & dirPath) { const QFileInfo finfo(dirPath.toLocalFile()); QDir dir = finfo.isDir() ? QDir(dirPath.toLocalFile()) : finfo.absoluteDir(); do { if (dir.exists(m_perforceConfigName)) { return true; } } while (dir.cdUp()); return false; } bool PerforcePlugin::isVersionControlled(const QUrl& localLocation) { QFileInfo fsObject(localLocation.toLocalFile()); if (fsObject.isDir()) { return isValidDirectory(localLocation); } return parseP4fstat(fsObject, KDevelop::OutputJob::Silent); } DVcsJob* PerforcePlugin::p4fstatJob(const QFileInfo& curFile, OutputJob::OutputJobVerbosity verbosity) { DVcsJob* job = new DVcsJob(curFile.absolutePath(), this, verbosity); setEnvironmentForJob(job, curFile); *job << m_perforceExecutable << "fstat" << curFile.fileName(); return job; } bool PerforcePlugin::parseP4fstat(const QFileInfo& curFile, OutputJob::OutputJobVerbosity verbosity) { QScopedPointer job(p4fstatJob(curFile, verbosity)); if (job->exec() && job->status() == KDevelop::VcsJob::JobSucceeded) { qCDebug(PLUGIN_PERFORCE) << "Perforce returned: " << job->output(); if (!job->output().isEmpty()) return true; } return false; } QString PerforcePlugin::getRepositoryName(const QFileInfo& curFile) { static const QString DEPOT_FILE_STR(QStringLiteral("... depotFile ")); QString ret; QScopedPointer job(p4fstatJob(curFile, KDevelop::OutputJob::Silent)); if (job->exec() && job->status() == KDevelop::VcsJob::JobSucceeded) { if (!job->output().isEmpty()) { const QStringList outputLines = job->output().split('\n', QString::SkipEmptyParts); for (const QString& line : outputLines) { int idx(line.indexOf(DEPOT_FILE_STR)); if (idx != -1) { ret = line.mid(DEPOT_FILE_STR.size()); return ret; } } } } return ret; } KDevelop::VcsJob* PerforcePlugin::repositoryLocation(const QUrl& /*localLocation*/) { return nullptr; } KDevelop::VcsJob* PerforcePlugin::add(const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/) { QFileInfo curFile(localLocations.front().toLocalFile()); DVcsJob* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose); setEnvironmentForJob(job, curFile); *job << m_perforceExecutable << "add" << localLocations; return job; } KDevelop::VcsJob* PerforcePlugin::remove(const QList& /*localLocations*/) { return nullptr; } KDevelop::VcsJob* PerforcePlugin::copy(const QUrl& /*localLocationSrc*/, const QUrl& /*localLocationDstn*/) { return nullptr; } KDevelop::VcsJob* PerforcePlugin::move(const QUrl& /*localLocationSrc*/, const QUrl& /*localLocationDst*/) { return nullptr; } KDevelop::VcsJob* PerforcePlugin::status(const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/) { if (localLocations.count() != 1) { KMessageBox::error(nullptr, i18n("Please select only one item for this operation")); return nullptr; } QFileInfo curFile(localLocations.front().toLocalFile()); DVcsJob* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose); setEnvironmentForJob(job, curFile); *job << m_perforceExecutable << "fstat" << curFile.fileName(); connect(job, &DVcsJob::readyForParsing, this, &PerforcePlugin::parseP4StatusOutput); return job; } KDevelop::VcsJob* PerforcePlugin::revert(const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/) { if (localLocations.count() != 1) { KMessageBox::error(nullptr, i18n("Please select only one item for this operation")); return nullptr; } QFileInfo curFile(localLocations.front().toLocalFile()); DVcsJob* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose); setEnvironmentForJob(job, curFile); *job << m_perforceExecutable << "revert" << curFile.fileName(); return job; } KDevelop::VcsJob* PerforcePlugin::update(const QList& localLocations, const KDevelop::VcsRevision& /*rev*/, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/) { QFileInfo curFile(localLocations.front().toLocalFile()); DVcsJob* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose); setEnvironmentForJob(job, curFile); //*job << m_perforceExecutable << "-p" << "127.0.0.1:1666" << "info"; - Let's keep this for now it's very handy for debugging QString fileOrDirectory; if (curFile.isDir()) fileOrDirectory = curFile.absolutePath() + "/..."; else fileOrDirectory = curFile.fileName(); *job << m_perforceExecutable << "sync" << fileOrDirectory; return job; } KDevelop::VcsJob* PerforcePlugin::commit(const QString& message, const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/) { if (localLocations.empty() || message.isEmpty()) return errorsFound(i18n("No files or message specified")); QFileInfo curFile(localLocations.front().toLocalFile()); DVcsJob* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose); setEnvironmentForJob(job, curFile); *job << m_perforceExecutable << "submit" << "-d" << message << localLocations; return job; } KDevelop::VcsJob* PerforcePlugin::diff(const QUrl& fileOrDirectory, const KDevelop::VcsRevision& srcRevision, const KDevelop::VcsRevision& dstRevision, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/) { QFileInfo curFile(fileOrDirectory.toLocalFile()); QString depotSrcFileName = getRepositoryName(curFile); QString depotDstFileName = depotSrcFileName; depotSrcFileName.append(toRevisionName(srcRevision, dstRevision.prettyValue())); // dstRevision acutally contains the number that we want to take previous of DVcsJob* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose); setEnvironmentForJob(job, curFile); switch (dstRevision.revisionType()) { case VcsRevision::FileNumber: case VcsRevision::GlobalNumber: depotDstFileName.append(QLatin1Char('#') + dstRevision.prettyValue()); *job << m_perforceExecutable << "diff2" << "-u" << depotSrcFileName << depotDstFileName; break; case VcsRevision::Special: switch (dstRevision.revisionValue().value()) { case VcsRevision::Working: *job << m_perforceExecutable << "diff" << "-du" << depotSrcFileName; break; case VcsRevision::Start: case VcsRevision::UserSpecialType: default: break; } + break; default: break; } connect(job, &DVcsJob::readyForParsing, this, &PerforcePlugin::parseP4DiffOutput); return job; } KDevelop::VcsJob* PerforcePlugin::log(const QUrl& localLocation, const KDevelop::VcsRevision& rev, long unsigned int limit) { static QString lastSeenChangeList; QFileInfo curFile(localLocation.toLocalFile()); QString localLocationAndRevStr = localLocation.toLocalFile(); DVcsJob* job = new DVcsJob(urlDir(localLocation), this, KDevelop::OutputJob::Verbose); setEnvironmentForJob(job, curFile); *job << m_perforceExecutable << "filelog" << "-lit"; if(limit > 0) *job << QStringLiteral("-m %1").arg(limit); if (curFile.isDir()) { localLocationAndRevStr.append("/..."); } QString revStr = toRevisionName(rev, QString()); if(!revStr.isEmpty()) { // This is not too nice, but perforce argument for restricting output from filelog does not Work :-( // So putting this in so we do not end up in infinite loop calling log, if(revStr == lastSeenChangeList) { localLocationAndRevStr.append("#none"); lastSeenChangeList.clear(); } else { localLocationAndRevStr.append(revStr); lastSeenChangeList = revStr; } } *job << localLocationAndRevStr; qCDebug(PLUGIN_PERFORCE) << "Issuing the following command to p4: " << job->dvcsCommand(); connect(job, &DVcsJob::readyForParsing, this, &PerforcePlugin::parseP4LogOutput); return job; } KDevelop::VcsJob* PerforcePlugin::log(const QUrl& localLocation, const KDevelop::VcsRevision& /*rev*/, const KDevelop::VcsRevision& /*limit*/) { QFileInfo curFile(localLocation.toLocalFile()); if (curFile.isDir()) { KMessageBox::error(nullptr, i18n("Please select a file for this operation")); return errorsFound(i18n("Directory not supported for this operation")); } DVcsJob* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose); setEnvironmentForJob(job, curFile); *job << m_perforceExecutable << "filelog" << "-lit" << localLocation; connect(job, &DVcsJob::readyForParsing, this , &PerforcePlugin::parseP4LogOutput); return job; } KDevelop::VcsJob* PerforcePlugin::annotate(const QUrl& localLocation, const KDevelop::VcsRevision& /*rev*/) { QFileInfo curFile(localLocation.toLocalFile()); if (curFile.isDir()) { KMessageBox::error(nullptr, i18n("Please select a file for this operation")); return errorsFound(i18n("Directory not supported for this operation")); } DVcsJob* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose); setEnvironmentForJob(job, curFile); *job << m_perforceExecutable << "annotate" << "-qi" << localLocation; connect(job, &DVcsJob::readyForParsing, this , &PerforcePlugin::parseP4AnnotateOutput); return job; } KDevelop::VcsJob* PerforcePlugin::resolve(const QList& /*localLocations*/, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/) { return nullptr; } KDevelop::VcsJob* PerforcePlugin::createWorkingCopy(const KDevelop::VcsLocation& /*sourceRepository*/, const QUrl& /*destinationDirectory*/, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/) { return nullptr; } KDevelop::VcsLocationWidget* PerforcePlugin::vcsLocation(QWidget* parent) const { return new StandardVcsLocationWidget(parent); } KDevelop::VcsJob* PerforcePlugin::edit(const QList& localLocations) { QFileInfo curFile(localLocations.front().toLocalFile()); DVcsJob* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose); setEnvironmentForJob(job, curFile); *job << m_perforceExecutable << "edit" << localLocations; return job; } KDevelop::VcsJob* PerforcePlugin::edit(const QUrl& /*localLocation*/) { return nullptr; } KDevelop::VcsJob* PerforcePlugin::unedit(const QUrl& /*localLocation*/) { return nullptr; } KDevelop::VcsJob* PerforcePlugin::localRevision(const QUrl& /*localLocation*/, KDevelop::VcsRevision::RevisionType) { return nullptr; } KDevelop::VcsJob* PerforcePlugin::import(const QString& /*commitMessage*/, const QUrl& /*sourceDirectory*/, const KDevelop::VcsLocation& /*destinationRepository*/) { return nullptr; } KDevelop::ContextMenuExtension PerforcePlugin::contextMenuExtension(KDevelop::Context* context, QWidget* parent) { m_common->setupFromContext(context); const QList & ctxUrlList = m_common->contextUrlList(); bool hasVersionControlledEntries = false; for( const QUrl& url : ctxUrlList) { if (isValidDirectory(url)) { hasVersionControlledEntries = true; break; } } if (!hasVersionControlledEntries) return IPlugin::contextMenuExtension(context, parent); QMenu * perforceMenu = m_common->commonActions(parent); perforceMenu->addSeparator(); perforceMenu->addSeparator(); if (!m_edit_action) { m_edit_action = new QAction(i18n("Edit"), this); connect(m_edit_action, &QAction::triggered, this, & PerforcePlugin::ctxEdit); } perforceMenu->addAction(m_edit_action); ContextMenuExtension menuExt; menuExt.addAction(ContextMenuExtension::VcsGroup, perforceMenu->menuAction()); return menuExt; } void PerforcePlugin::ctxEdit() { QList const & ctxUrlList = m_common->contextUrlList(); KDevelop::ICore::self()->runController()->registerJob(edit(ctxUrlList)); } void PerforcePlugin::setEnvironmentForJob(DVcsJob* job, const QFileInfo& curFile) { KProcess* jobproc = job->process(); jobproc->setEnv(QStringLiteral("P4CONFIG"), m_perforceConfigName); if (curFile.isDir()) { jobproc->setEnv(QStringLiteral("PWD"), curFile.filePath()); } else { jobproc->setEnv(QStringLiteral("PWD"), curFile.absolutePath()); } } QList PerforcePlugin::getQvariantFromLogOutput(QStringList const& outputLines) { static const QString LOGENTRY_START(QStringLiteral("... #")); static const QString DEPOTMESSAGE_START(QStringLiteral("... .")); QMap changes; QList commits; QString currentFileName; QString changeNumberStr, author,changeDescription, commitMessage; VcsEvent currentVcsEvent; VcsItemEvent currentRepoFile; VcsRevision rev; int indexofAt; int changeNumber = 0; for (const QString& line : outputLines) { if (!line.startsWith(LOGENTRY_START) && !line.startsWith(DEPOTMESSAGE_START) && !line.startsWith('\t')) { currentFileName = line; } if(line.indexOf(LOGENTRY_START) != -1) { // expecting the Logentry line to be of the form: //... #5 change 10 edit on 2010/12/06 12:07:31 by mvo@testbed (text) changeNumberStr = line.section(' ', 3, 3 ); // We use global change number changeNumber = changeNumberStr.toInt(); author = line.section(' ', 9, 9); changeDescription = line.section(' ' , 4, 4 ); indexofAt = author.indexOf('@'); author.remove(indexofAt, author.size()); // Only keep the username itself rev.setRevisionValue(changeNumberStr, KDevelop::VcsRevision::GlobalNumber); changes[changeNumber].setRevision(rev); changes[changeNumber].setAuthor(author); changes[changeNumber].setDate(QDateTime::fromString(line.section(' ', 6, 7), QStringLiteral("yyyy/MM/dd hh:mm:ss"))); currentRepoFile.setRepositoryLocation(currentFileName); currentRepoFile.setActions( actionsFromString(changeDescription) ); changes[changeNumber].addItem(currentRepoFile); commitMessage.clear(); // We have a new entry, clear message } if (line.startsWith('\t') || line.startsWith(DEPOTMESSAGE_START)) { commitMessage += line.trimmed() + '\n'; changes[changeNumber].setMessage(commitMessage); } } for(const auto& item : qAsConst(changes)) { commits.prepend(QVariant::fromValue(item)); } return commits; } void PerforcePlugin::parseP4StatusOutput(DVcsJob* job) { const QStringList outputLines = job->output().split('\n', QString::SkipEmptyParts); QVariantList statuses; static const QString ACTION_STR(QStringLiteral("... action ")); static const QString CLIENT_FILE_STR(QStringLiteral("... clientFile ")); VcsStatusInfo status; status.setState(VcsStatusInfo::ItemUserState); for (const QString& line : outputLines) { int idx(line.indexOf(ACTION_STR)); if (idx != -1) { QString curr = line.mid(ACTION_STR.size()); if (curr == QLatin1String("edit")) { status.setState(VcsStatusInfo::ItemModified); } else if (curr == QLatin1String("add")) { status.setState(VcsStatusInfo::ItemAdded); } else { status.setState(VcsStatusInfo::ItemUserState); } continue; } idx = line.indexOf(CLIENT_FILE_STR); if (idx != -1) { QUrl fileUrl = QUrl::fromLocalFile(line.mid(CLIENT_FILE_STR.size())); status.setUrl(fileUrl); } } statuses.append(qVariantFromValue(status)); job->setResults(statuses); } void PerforcePlugin::parseP4LogOutput(KDevelop::DVcsJob* job) { QList commits(getQvariantFromLogOutput(job->output().split('\n', QString::SkipEmptyParts))); job->setResults(commits); } void PerforcePlugin::parseP4DiffOutput(DVcsJob* job) { VcsDiff diff; diff.setDiff(job->output()); QDir dir(job->directory()); do { if (dir.exists(m_perforceConfigName)) { break; } } while (dir.cdUp()); diff.setBaseDiff(QUrl::fromLocalFile(dir.absolutePath())); job->setResults(qVariantFromValue(diff)); } void PerforcePlugin::parseP4AnnotateOutput(DVcsJob *job) { QVariantList results; /// First get the changelists for this file QStringList strList(job->dvcsCommand()); QString localLocation(strList.last()); /// ASSUMPTION WARNING - localLocation is the last in the annotate command KDevelop::VcsRevision dummyRev; QScopedPointer logJob(new DVcsJob(job->directory(), this, OutputJob::Silent)); QFileInfo curFile(localLocation); setEnvironmentForJob(logJob.data(), curFile); *logJob << m_perforceExecutable << "filelog" << "-lit" << localLocation; QList commits; if (logJob->exec() && logJob->status() == KDevelop::VcsJob::JobSucceeded) { if (!job->output().isEmpty()) { commits = getQvariantFromLogOutput(logJob->output().split('\n', QString::SkipEmptyParts)); } } VcsEvent item; QMap globalCommits; /// Move the VcsEvents to a more suitable data strucure for (QList::const_iterator commitsIt = commits.constBegin(), commitsEnd = commits.constEnd(); commitsIt != commitsEnd; ++commitsIt) { if(commitsIt->canConvert()) { item = commitsIt->value(); } globalCommits.insert(item.revision().revisionValue().toLongLong(), item); } QStringList lines = job->output().split('\n'); int lineNumber = 0; QMap::iterator currentEvent; bool convertToIntOk(false); int globalRevisionInt(0); QString globalRevision; for (QStringList::const_iterator it = lines.constBegin(), itEnd = lines.constEnd(); it != itEnd; ++it) { if (it->isEmpty()) { continue; } globalRevision = it->left(it->indexOf(':')); VcsAnnotationLine annotation; annotation.setLineNumber(lineNumber); VcsRevision rev; rev.setRevisionValue(globalRevision, KDevelop::VcsRevision::GlobalNumber); annotation.setRevision(rev); // Find the other info in the commits list globalRevisionInt = globalRevision.toLongLong(&convertToIntOk); if(convertToIntOk) { currentEvent = globalCommits.find(globalRevisionInt); annotation.setAuthor(currentEvent->author()); annotation.setCommitMessage(currentEvent->message()); annotation.setDate(currentEvent->date()); } results += qVariantFromValue(annotation); ++lineNumber; } job->setResults(results); } KDevelop::VcsJob* PerforcePlugin::errorsFound(const QString& error, KDevelop::OutputJob::OutputJobVerbosity verbosity) { DVcsJob* j = new DVcsJob(QDir::temp(), this, verbosity); *j << "echo" << i18n("error: %1", error) << "-n"; return j; }