diff --git a/CMakeLists.txt b/CMakeLists.txt index de27c3469a..c73cd569da 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,180 +1,182 @@ cmake_minimum_required(VERSION 3.0) project(KDevelop VERSION 5.2.40) # KDevelop SOVERSION # E.g. for KDevelop 5.2.0 => SOVERSION 52 (we only promise ABI compatibility between patch version updates) set(KDEVELOP_SOVERSION 53) # plugin version as used e.g. in plugin installation path set(KDEV_PLUGIN_VERSION 31) # 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}) if(NOT CMAKE_VERSION VERSION_LESS "3.10.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. # Remove this when depending on a Frameworks or ECM version that solves this upstream. list(APPEND CMAKE_AUTOMOC_MACRO_NAMES "K_PLUGIN_FACTORY_WITH_JSON" "K_EXPORT_PLASMA_DATAENGINE_WITH_JSON" "K_EXPORT_PLASMA_RUNNER") endif() 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) 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) add_definitions( -DQT_DEPRECATED_WARNINGS -DQT_DISABLE_DEPRECATED_BEFORE=0x050500 -DQT_NO_SIGNALS_SLOTS_KEYWORDS -DQT_NO_URL_CAST_FROM_STRING -DQT_STRICT_ITERATORS -DQT_USE_QSTRINGBUILDER ) function(add_compile_flag_if_supported _flag) unset(_have_flag CACHE) string(REGEX REPLACE "[-=]" "_" _varname ${_flag}) string(TOUPPER ${_varname} _varname) set(_varname "HAVE${_varname}") check_cxx_compiler_flag("${_flag}" "${_varname}") if (${${_varname}}) add_compile_options(${_flag}) endif() endfunction() # Turn off missing-field-initializers warning for GCC to avoid noise from false positives with empty {} # See discussion: http://mail.kde.org/pipermail/kdevelop-devel/2014-February/046910.html add_compile_flag_if_supported(-Wno-missing-field-initializers) +add_compile_flag_if_supported(-Werror=switch) add_compile_flag_if_supported(-Werror=undefined-bool-conversion) add_compile_flag_if_supported(-Werror=tautological-undefined-compare) if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") add_compile_flag_if_supported(-Wdocumentation) + add_compile_flag_if_supported(-Wcovered-switch-default) # This warning is triggered by every call to qCDebug() add_compile_flag_if_supported(-Wno-gnu-zero-variadic-macro-arguments) endif() if (CMAKE_COMPILER_CXX_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") add_compile_flag_if_supported(-pedantic) endif() 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/debugger/variable/variablecollection.cpp b/kdevplatform/debugger/variable/variablecollection.cpp index 165507fb4c..62f1d5bc5a 100644 --- a/kdevplatform/debugger/variable/variablecollection.cpp +++ b/kdevplatform/debugger/variable/variablecollection.cpp @@ -1,552 +1,552 @@ /* * KDevelop Debugger Support * * Copyright 2007 Hamish Rodda * Copyright 2008 Vladimir Prus * Copyright 2009 Niko Sams * * 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 "variablecollection.h" #include #include #include #include #include #include #include #include "../../interfaces/icore.h" #include "../../interfaces/idocumentcontroller.h" #include "../../interfaces/iuicontroller.h" #include "../../sublime/controller.h" #include "../../sublime/view.h" #include "../../interfaces/idebugcontroller.h" #include "../interfaces/idebugsession.h" #include "../interfaces/ivariablecontroller.h" #include #include "util/texteditorhelpers.h" #include "variabletooltip.h" #include namespace KDevelop { IDebugSession* currentSession() { return ICore::self()->debugController()->currentSession(); } IDebugSession::DebuggerState currentSessionState() { if (!currentSession()) return IDebugSession::NotStartedState; return currentSession()->state(); } bool hasStartedSession() { IDebugSession::DebuggerState s = currentSessionState(); return s != IDebugSession::NotStartedState && s != IDebugSession::EndedState; } Variable::Variable(TreeModel* model, TreeItem* parent, const QString& expression, const QString& display) : TreeItem(model, parent) , m_expression(expression) , m_inScope(true) , m_topLevel(true) , m_changed(false) , m_showError(false) , m_format(Natural) { // FIXME: should not duplicate the data, instead overload 'data' // and return expression_ directly. if (display.isEmpty()) setData(QVector() << expression << QString() << QString()); else setData(QVector() << display << QString() << QString()); } QString Variable::expression() const { return m_expression; } bool Variable::inScope() const { return m_inScope; } void Variable::setValue(const QString& v) { itemData[VariableCollection::ValueColumn] = v; reportChange(); } QString Variable::value() const { return itemData[VariableCollection::ValueColumn].toString(); } void Variable::setType(const QString& type) { itemData[VariableCollection::TypeColumn] = type; reportChange(); } QString Variable::type() const { return itemData[VariableCollection::TypeColumn].toString(); } void Variable::setTopLevel(bool v) { m_topLevel = v; } void Variable::setInScope(bool v) { m_inScope = v; for (int i=0; i < childCount(); ++i) { if (Variable *var = qobject_cast(child(i))) { var->setInScope(v); } } reportChange(); } void Variable::setShowError (bool v) { m_showError = v; reportChange(); } bool Variable::showError() { return m_showError; } Variable::~Variable() { } void Variable::die() { removeSelf(); deleteLater(); } void Variable::setChanged(bool c) { m_changed=c; reportChange(); } void Variable::resetChanged() { setChanged(false); for (int i=0; i(childItem)) { static_cast(childItem)->resetChanged(); } } } Variable::format_t Variable::str2format(const QString& str) { if(str==QLatin1String("Binary") || str==QLatin1String("binary")) return Binary; if(str==QLatin1String("Octal") || str==QLatin1String("octal")) return Octal; if(str==QLatin1String("Decimal") || str==QLatin1String("decimal")) return Decimal; if(str==QLatin1String("Hexadecimal") || str==QLatin1String("hexadecimal"))return Hexadecimal; return Natural; // maybe most reasonable default } QString Variable::format2str(format_t format) { switch(format) { case Natural: return QStringLiteral("natural"); case Binary: return QStringLiteral("binary"); case Octal: return QStringLiteral("octal"); case Decimal: return QStringLiteral("decimal"); case Hexadecimal: return QStringLiteral("hexadecimal"); - default: return QString(); } + return QString(); } void Variable::setFormat(Variable::format_t format) { if (m_format != format) { m_format = format; formatChanged(); } } void Variable::formatChanged() { } bool Variable::isPotentialProblematicValue() const { const auto value = data(VariableCollection::ValueColumn, Qt::DisplayRole).toString(); return value == QLatin1String("0x0"); } QVariant Variable::data(int column, int role) const { if (m_showError) { if (role == Qt::FontRole) { QVariant ret = TreeItem::data(column, role); QFont font = ret.value(); font.setStyle(QFont::StyleItalic); return font; } else if (column == 1 && role == Qt::DisplayRole) { return i18n("Error"); } } if (column == 1 && role == Qt::TextColorRole) { KColorScheme scheme(QPalette::Active); if (!m_inScope) { return scheme.foreground(KColorScheme::InactiveText).color(); } else if (isPotentialProblematicValue()) { return scheme.foreground(KColorScheme::NegativeText).color(); } else if (m_changed) { return scheme.foreground(KColorScheme::NeutralText).color(); } } if (role == Qt::ToolTipRole) { return TreeItem::data(column, Qt::DisplayRole); } return TreeItem::data(column, role); } Watches::Watches(TreeModel* model, TreeItem* parent) : TreeItem(model, parent), finishResult_(nullptr) { setData(QVector() << i18n("Auto") << QString()); } Variable* Watches::add(const QString& expression) { if (!hasStartedSession()) return nullptr; Variable* v = currentSession()->variableController()->createVariable( model(), this, expression); appendChild(v); v->attachMaybe(); if (childCount() == 1 && !isExpanded()) { setExpanded(true); } return v; } Variable *Watches::addFinishResult(const QString& convenienceVarible) { if( finishResult_ ) { removeFinishResult(); } finishResult_ = currentSession()->variableController()->createVariable( model(), this, convenienceVarible, QStringLiteral("$ret")); appendChild(finishResult_); finishResult_->attachMaybe(); if (childCount() == 1 && !isExpanded()) { setExpanded(true); } return finishResult_; } void Watches::removeFinishResult() { if (finishResult_) { finishResult_->die(); finishResult_ = nullptr; } } void Watches::resetChanged() { for (int i=0; i(childItem)) { static_cast(childItem)->resetChanged(); } } } QVariant Watches::data(int column, int role) const { #if 0 if (column == 0 && role == Qt::FontRole) { /* FIXME: is creating font again and agian efficient? */ QFont f = font(); f.setBold(true); return f; } #endif return TreeItem::data(column, role); } void Watches::reinstall() { for (int i = 0; i < childItems.size(); ++i) { Variable* v = static_cast(child(i)); v->attachMaybe(); } } Locals::Locals(TreeModel* model, TreeItem* parent, const QString &name) : TreeItem(model, parent) { setData(QVector() << name << QString()); } QList Locals::updateLocals(QStringList locals) { QSet existing, current; for (int i = 0; i < childItems.size(); i++) { Q_ASSERT(qobject_cast(child(i))); Variable* var= static_cast(child(i)); existing << var->expression(); } foreach (const QString& var, locals) { current << var; // If we currently don't display this local var, add it. if( !existing.contains( var ) ) { // FIXME: passing variableCollection this way is awkward. // In future, variableCollection probably should get a // method to create variable. Variable* v = currentSession()->variableController()->createVariable( ICore::self()->debugController()->variableCollection(), this, var ); appendChild( v, false ); } } for (int i = 0; i < childItems.size(); ++i) { KDevelop::Variable* v = static_cast(child(i)); if (!current.contains(v->expression())) { removeChild(i); --i; // FIXME: check that -var-delete is sent. delete v; } } if (hasMore()) { setHasMore(false); } // Variables which changed just value are updated by a call to -var-update. // Variables that changed type -- likewise. QList ret; foreach (TreeItem *i, childItems) { Q_ASSERT(qobject_cast(i)); ret << static_cast(i); } return ret; } void Locals::resetChanged() { for (int i=0; i(childItem)) { static_cast(childItem)->resetChanged(); } } } VariablesRoot::VariablesRoot(TreeModel* model) : TreeItem(model) , m_watches(new Watches(model, this)) { appendChild(m_watches, true); } Locals* VariablesRoot::locals(const QString& name) { if (!m_locals.contains(name)) { m_locals[name] = new Locals(model(), this, name); appendChild(m_locals[name]); } return m_locals[name]; } QHash VariablesRoot::allLocals() const { return m_locals; } void VariablesRoot::resetChanged() { m_watches->resetChanged(); foreach (Locals *l, m_locals) { l->resetChanged(); } } VariableCollection::VariableCollection(IDebugController* controller) : TreeModel({i18n("Name"), i18n("Value"), i18n("Type")}, controller) , m_widgetVisible(false) , m_textHintProvider(this) { m_universe = new VariablesRoot(this); setRootItem(m_universe); //new ModelTest(this); connect (ICore::self()->documentController(), &IDocumentController::textDocumentCreated, this, &VariableCollection::textDocumentCreated ); connect(controller, &IDebugController::currentSessionChanged, this, &VariableCollection::updateAutoUpdate); // Qt5 signal slot syntax does not support default arguments auto callUpdateAutoUpdate = [&] { updateAutoUpdate(); }; connect(locals(), &Locals::expanded, this, callUpdateAutoUpdate); connect(locals(), &Locals::collapsed, this, callUpdateAutoUpdate); connect(watches(), &Watches::expanded, this, callUpdateAutoUpdate); connect(watches(), &Watches::collapsed, this, callUpdateAutoUpdate); } void VariableCollection::variableWidgetHidden() { m_widgetVisible = false; updateAutoUpdate(); } void VariableCollection::variableWidgetShown() { m_widgetVisible = true; updateAutoUpdate(); } void VariableCollection::updateAutoUpdate(IDebugSession* session) { if (!session) session = currentSession(); qCDebug(DEBUGGER) << session; if (!session) return; if (!m_widgetVisible) { session->variableController()->setAutoUpdate(IVariableController::UpdateNone); } else { QFlags t = IVariableController::UpdateNone; if (locals()->isExpanded()) t |= IVariableController::UpdateLocals; if (watches()->isExpanded()) t |= IVariableController::UpdateWatches; session->variableController()->setAutoUpdate(t); } } VariableCollection::~ VariableCollection() { } void VariableCollection::textDocumentCreated(IDocument* doc) { connect( doc->textDocument(), &KTextEditor::Document::viewCreated, this, &VariableCollection::viewCreated ); foreach( KTextEditor::View* view, doc->textDocument()->views() ) viewCreated( doc->textDocument(), view ); } void VariableCollection::viewCreated(KTextEditor::Document* doc, KTextEditor::View* view) { Q_UNUSED(doc); using namespace KTextEditor; TextHintInterface *iface = dynamic_cast(view); if( !iface ) return; iface->registerTextHintProvider(&m_textHintProvider); } Locals* VariableCollection::locals(const QString &name) const { return m_universe->locals(name.isEmpty() ? i18n("Locals") : name); } VariableProvider::VariableProvider(VariableCollection* collection) : KTextEditor::TextHintProvider() , m_collection(collection) { } QString VariableProvider::textHint(KTextEditor::View* view, const KTextEditor::Cursor& cursor) { if (!hasStartedSession()) return QString(); if (ICore::self()->uiController()->activeArea()->objectName() != QLatin1String("debug")) return QString(); //TODO: These keyboardModifiers should also hide already opened tooltip, and show another one for code area. if (QApplication::keyboardModifiers() == Qt::ControlModifier || QApplication::keyboardModifiers() == Qt::AltModifier){ return QString(); } KTextEditor::Document* doc = view->document(); KTextEditor::Range expressionRange = currentSession()->variableController()->expressionRangeUnderCursor(doc, cursor); if (!expressionRange.isValid()) return QString(); QString expression = doc->text(expressionRange).trimmed(); // Don't do anything if there's already an open tooltip with matching range if (m_collection->m_activeTooltip && m_collection->m_activeTooltip->variable()->expression() == expression) return QString(); if (expression.isEmpty()) return QString(); QPoint local = view->cursorToCoordinate(cursor); QPoint global = view->mapToGlobal(local); QWidget* w = view->childAt(local); if (!w) w = view; m_collection->m_activeTooltip = new VariableToolTip(w, global+QPoint(30,30), expression); m_collection->m_activeTooltip->setHandleRect(KTextEditorHelpers::getItemBoundingRect(view, expressionRange)); return QString(); } } diff --git a/kdevplatform/language/duchain/navigation/abstractdeclarationnavigationcontext.cpp b/kdevplatform/language/duchain/navigation/abstractdeclarationnavigationcontext.cpp index ff7f540c1d..5ae249a9b8 100644 --- a/kdevplatform/language/duchain/navigation/abstractdeclarationnavigationcontext.cpp +++ b/kdevplatform/language/duchain/navigation/abstractdeclarationnavigationcontext.cpp @@ -1,790 +1,787 @@ /* 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 "abstractdeclarationnavigationcontext.h" #include #include #include "../functiondeclaration.h" #include "../functiondefinition.h" #include "../classfunctiondeclaration.h" #include "../namespacealiasdeclaration.h" #include "../forwarddeclaration.h" #include "../types/enumeratortype.h" #include "../types/enumerationtype.h" #include "../types/functiontype.h" #include "../duchainutils.h" #include "../types/pointertype.h" #include "../types/referencetype.h" #include "../types/typeutils.h" #include "../types/typesystem.h" #include "../persistentsymboltable.h" #include #include #include #include #include #include namespace KDevelop { class AbstractDeclarationNavigationContextPrivate { public: DeclarationPointer m_declaration; bool m_fullBackwardSearch = false; }; AbstractDeclarationNavigationContext::AbstractDeclarationNavigationContext(const DeclarationPointer& decl, const TopDUContextPointer& topContext, AbstractNavigationContext* previousContext) : AbstractNavigationContext((topContext ? topContext : TopDUContextPointer(decl ? decl->topContext() : nullptr)), previousContext) , d(new AbstractDeclarationNavigationContextPrivate) { d->m_declaration = decl; //Jump from definition to declaration if possible FunctionDefinition* definition = dynamic_cast(d->m_declaration.data()); if(definition && definition->declaration()) d->m_declaration = DeclarationPointer(definition->declaration()); } AbstractDeclarationNavigationContext::~AbstractDeclarationNavigationContext() { } QString AbstractDeclarationNavigationContext::name() const { if(d->m_declaration.data()) return prettyQualifiedIdentifier(d->m_declaration).toString(); else return declarationName(d->m_declaration); } QString AbstractDeclarationNavigationContext::html(bool shorten) { DUChainReadLocker lock(DUChain::lock(), 300); if ( !lock.locked() ) { return {}; } clear(); AbstractNavigationContext::html(shorten); modifyHtml() += QLatin1String("

") + fontSizePrefix(shorten); addExternalHtml(prefix()); if(!d->m_declaration.data()) { modifyHtml() += i18n("
lost declaration
"); return currentHtml(); } if(auto context = previousContext()) { const QString link = createLink(context->name(), context->name(), NavigationAction(context)); modifyHtml() += navigationHighlight(i18n("Back to %1
", link)); } QExplicitlySharedDataPointer doc; if( !shorten ) { doc = ICore::self()->documentationController()->documentationForDeclaration(d->m_declaration.data()); const AbstractFunctionDeclaration* function = dynamic_cast(d->m_declaration.data()); if( function ) { htmlFunction(); } else if( d->m_declaration->isTypeAlias() || d->m_declaration->type() || d->m_declaration->kind() == Declaration::Instance ) { if( d->m_declaration->isTypeAlias() ) modifyHtml() += importantHighlight(QStringLiteral("typedef ")); if(d->m_declaration->type()) modifyHtml() += i18n("enumerator "); AbstractType::Ptr useType = d->m_declaration->abstractType(); if(d->m_declaration->isTypeAlias()) { //Do not show the own name as type of typedefs if(useType.cast()) useType = useType.cast()->type(); } eventuallyMakeTypeLinks( useType ); modifyHtml() += QLatin1Char(' ') + identifierHighlight(declarationName(d->m_declaration).toHtmlEscaped(), d->m_declaration); if(auto integralType = d->m_declaration->type()) { const QString plainValue = integralType->valueAsString(); if (!plainValue.isEmpty()) { modifyHtml() += QStringLiteral(" = ") + plainValue; } } modifyHtml() += QStringLiteral("
"); }else{ if( d->m_declaration->kind() == Declaration::Type && d->m_declaration->abstractType().cast() ) { htmlClass(); } if ( d->m_declaration->kind() == Declaration::Namespace ) { modifyHtml() += i18n("namespace %1 ", identifierHighlight(d->m_declaration->qualifiedIdentifier().toString().toHtmlEscaped(), d->m_declaration)); } else if ( d->m_declaration->kind() == Declaration::NamespaceAlias ) { modifyHtml() += identifierHighlight(declarationName(d->m_declaration).toHtmlEscaped(), d->m_declaration); } if(d->m_declaration->type()) { EnumerationType::Ptr enumeration = d->m_declaration->type(); modifyHtml() += i18n("enumeration %1 ", identifierHighlight(d->m_declaration->identifier().toString().toHtmlEscaped(), d->m_declaration)); } if(d->m_declaration->isForwardDeclaration()) { ForwardDeclaration* forwardDec = static_cast(d->m_declaration.data()); Declaration* resolved = forwardDec->resolve(topContext().data()); if(resolved) { modifyHtml() += i18n("(resolved forward-declaration: "); makeLink(resolved->identifier().toString(), DeclarationPointer(resolved), NavigationAction::NavigateDeclaration ); modifyHtml() += i18n(") "); }else{ modifyHtml() += i18n("(unresolved forward-declaration) "); QualifiedIdentifier id = forwardDec->qualifiedIdentifier(); const auto& forwardDecFile = forwardDec->topContext()->parsingEnvironmentFile(); uint count; const IndexedDeclaration* decls; PersistentSymbolTable::self().declarations(id, count, decls); for(uint a = 0; a < count; ++a) { auto dec = decls[a].data(); if (!dec || dec->isForwardDeclaration()) { continue; } const auto& decFile = forwardDec->topContext()->parsingEnvironmentFile(); if ((static_cast(decFile) != static_cast(forwardDecFile)) || (decFile && forwardDecFile && decFile->language() != forwardDecFile->language())) { // the language of the declarations must match continue; } modifyHtml() += QStringLiteral("
"); makeLink(i18n("possible resolution from"), DeclarationPointer(dec), NavigationAction::NavigateDeclaration); modifyHtml() += QLatin1Char(' ') + dec->url().str(); } } } modifyHtml() += QStringLiteral("
"); } }else{ AbstractType::Ptr showType = d->m_declaration->abstractType(); if(showType && showType.cast()) { showType = showType.cast()->returnType(); if(showType) modifyHtml() += labelHighlight(i18n("Returns: ")); }else if(showType) { modifyHtml() += labelHighlight(i18n("Type: ")); } if(showType) { eventuallyMakeTypeLinks(showType); modifyHtml() += QStringLiteral(" "); } } QualifiedIdentifier identifier = d->m_declaration->qualifiedIdentifier(); if( identifier.count() > 1 ) { if( d->m_declaration->context() && d->m_declaration->context()->owner() ) { Declaration* decl = d->m_declaration->context()->owner(); FunctionDefinition* definition = dynamic_cast(decl); if(definition && definition->declaration()) decl = definition->declaration(); if(decl->abstractType().cast()) modifyHtml() += labelHighlight(i18n("Enum: ")); else modifyHtml() += labelHighlight(i18n("Container: ")); makeLink( declarationName(DeclarationPointer(decl)), DeclarationPointer(decl), NavigationAction::NavigateDeclaration ); modifyHtml() += QStringLiteral(" "); } else { QualifiedIdentifier parent = identifier; parent.pop(); modifyHtml() += labelHighlight(i18n("Scope: %1 ", typeHighlight(parent.toString().toHtmlEscaped()))); } } if( shorten && !d->m_declaration->comment().isEmpty() ) { QString comment = QString::fromUtf8(d->m_declaration->comment()); if( comment.length() > 60 ) { comment.truncate(60); comment += QLatin1String("..."); } comment.replace(QLatin1Char('\n'), QLatin1Char(' ')); comment.replace(QLatin1String("
"), QLatin1String(" ")); comment.replace(QLatin1String("
"), QLatin1String(" ")); modifyHtml() += commentHighlight(comment.toHtmlEscaped()) + QLatin1String(" "); } QString access = stringFromAccess(d->m_declaration); if( !access.isEmpty() ) modifyHtml() += labelHighlight(i18n("Access: %1 ", propertyHighlight(access.toHtmlEscaped()))); ///@todo Enumerations QString detailsHtml; QStringList details = declarationDetails(d->m_declaration); if( !details.isEmpty() ) { bool first = true; foreach( const QString &str, details ) { if( !first ) detailsHtml += QLatin1String(", "); first = false; detailsHtml += propertyHighlight(str); } } QString kind = declarationKind(d->m_declaration); if( !kind.isEmpty() ) { if( !detailsHtml.isEmpty() ) modifyHtml() += labelHighlight(i18n("Kind: %1 %2 ", importantHighlight(kind.toHtmlEscaped()), detailsHtml)); else modifyHtml() += labelHighlight(i18n("Kind: %1 ", importantHighlight(kind.toHtmlEscaped()))); } if (d->m_declaration->isDeprecated()) { modifyHtml() += labelHighlight(i18n("Status: %1 ", propertyHighlight(i18n("Deprecated")))); } modifyHtml() += QStringLiteral("
"); if(!shorten) htmlAdditionalNavigation(); if( !shorten ) { if(dynamic_cast(d->m_declaration.data())) modifyHtml() += labelHighlight(i18n( "Def.: " )); else modifyHtml() += labelHighlight(i18n( "Decl.: " )); makeLink( QStringLiteral("%1 :%2").arg( d->m_declaration->url().toUrl().fileName() ).arg( d->m_declaration->rangeInCurrentRevision().start().line()+1 ), d->m_declaration, NavigationAction::JumpToSource ); modifyHtml() += QStringLiteral(" "); //modifyHtml() += "
"; if(!dynamic_cast(d->m_declaration.data())) { if( FunctionDefinition* definition = FunctionDefinition::definition(d->m_declaration.data()) ) { modifyHtml() += labelHighlight(i18n( " Def.: " )); makeLink( QStringLiteral("%1 :%2").arg( definition->url().toUrl().fileName() ).arg( definition->rangeInCurrentRevision().start().line()+1 ), DeclarationPointer(definition), NavigationAction::JumpToSource ); } } if( FunctionDefinition* definition = dynamic_cast(d->m_declaration.data()) ) { if(definition->declaration()) { modifyHtml() += labelHighlight(i18n( " Decl.: " )); makeLink( QStringLiteral("%1 :%2").arg( definition->declaration()->url().toUrl().fileName() ).arg( definition->declaration()->rangeInCurrentRevision().start().line()+1 ), DeclarationPointer(definition->declaration()), NavigationAction::JumpToSource ); } } modifyHtml() += QStringLiteral(" "); //The action name _must_ stay "show_uses", since that is also used from outside makeLink(i18n("Show uses"), QStringLiteral("show_uses"), NavigationAction(d->m_declaration, NavigationAction::NavigateUses)); } QByteArray declarationComment = d->m_declaration->comment(); if( !shorten && (!declarationComment.isEmpty() || doc) ) { modifyHtml() += QStringLiteral("

"); if(doc) { QString comment = doc->description(); connect(doc.data(), &IDocumentation::descriptionChanged, this, &AbstractDeclarationNavigationContext::contentsChanged); if(!comment.isEmpty()) { modifyHtml() += QLatin1String("

") + commentHighlight(comment) + QLatin1String("

"); } } QString comment = QString::fromUtf8(declarationComment); if(!comment.isEmpty()) { // if the first paragraph does not contain a tag, we assume that this is a plain-text comment if (!Qt::mightBeRichText(comment)) { // still might contain extra html tags for line breaks (this is the case for doxygen-style comments sometimes) // let's protect them from being removed completely comment.replace(QRegExp(QStringLiteral("
")), QStringLiteral("\n")); comment = comment.toHtmlEscaped(); comment.replace(QLatin1Char('\n'), QLatin1String("
")); //Replicate newlines in html } modifyHtml() += commentHighlight(comment); modifyHtml() += QStringLiteral("

"); } } if(!shorten && doc) { modifyHtml() += QLatin1String("

") + i18n("Show documentation for "); makeLink(prettyQualifiedName(d->m_declaration), d->m_declaration, NavigationAction::ShowDocumentation); modifyHtml() += QStringLiteral("

"); } //modifyHtml() += "
"; addExternalHtml(suffix()); modifyHtml() += fontSizeSuffix(shorten) + QLatin1String("

"); return currentHtml(); } AbstractType::Ptr AbstractDeclarationNavigationContext::typeToShow(AbstractType::Ptr type) { return type; } void AbstractDeclarationNavigationContext::htmlFunction() { const AbstractFunctionDeclaration* function = dynamic_cast(d->m_declaration.data()); Q_ASSERT(function); const ClassFunctionDeclaration* classFunDecl = dynamic_cast(d->m_declaration.data()); const FunctionType::Ptr type = d->m_declaration->abstractType().cast(); if( !type ) { modifyHtml() += errorHighlight(QStringLiteral("Invalid type
")); return; } if( !classFunDecl || (!classFunDecl->isConstructor() && !classFunDecl->isDestructor()) ) { // only print return type for global functions and non-ctor/dtor methods eventuallyMakeTypeLinks( type->returnType() ); } modifyHtml() += QLatin1Char(' ') + identifierHighlight(prettyIdentifier(d->m_declaration).toString().toHtmlEscaped(), d->m_declaration); if( type->indexedArgumentsSize() == 0 ) { modifyHtml() += QStringLiteral("()"); } else { modifyHtml() += QStringLiteral("( "); bool first = true; int firstDefaultParam = type->indexedArgumentsSize() - function->defaultParametersSize(); int currentArgNum = 0; QVector decls; if (DUContext* argumentContext = DUChainUtils::getArgumentContext(d->m_declaration.data())) { decls = argumentContext->localDeclarations(topContext().data()); } foreach(const AbstractType::Ptr& argType, type->arguments()) { if( !first ) modifyHtml() += QStringLiteral(", "); first = false; eventuallyMakeTypeLinks( argType ); if (currentArgNum < decls.size()) { modifyHtml() += QLatin1Char(' ') + identifierHighlight(decls[currentArgNum]->identifier().toString().toHtmlEscaped(), d->m_declaration); } if (currentArgNum >= firstDefaultParam) { IndexedString defaultStr = function->defaultParameters()[currentArgNum - firstDefaultParam]; if (!defaultStr.isEmpty()) { modifyHtml() += QLatin1String(" = ") + defaultStr.str().toHtmlEscaped(); } } ++currentArgNum; } modifyHtml() += QStringLiteral(" )"); } modifyHtml() += QStringLiteral("
"); } Identifier AbstractDeclarationNavigationContext::prettyIdentifier(const DeclarationPointer& decl) const { Identifier ret; QualifiedIdentifier q = prettyQualifiedIdentifier(decl); if(!q.isEmpty()) ret = q.last(); return ret; } QualifiedIdentifier AbstractDeclarationNavigationContext::prettyQualifiedIdentifier(DeclarationPointer decl) const { if(decl) return decl->qualifiedIdentifier(); else return QualifiedIdentifier(); } QString AbstractDeclarationNavigationContext::prettyQualifiedName(const DeclarationPointer& decl) const { const auto qid = prettyQualifiedIdentifier(decl); if (qid.isEmpty()) { return i18nc("An anonymous declaration (class, function, etc.)", ""); } return qid.toString(); } void AbstractDeclarationNavigationContext::htmlAdditionalNavigation() { ///Check if the function overrides or hides another one const ClassFunctionDeclaration* classFunDecl = dynamic_cast(d->m_declaration.data()); if(classFunDecl) { Declaration* overridden = DUChainUtils::getOverridden(d->m_declaration.data()); if(overridden) { modifyHtml() += i18n("Overrides a "); makeLink(i18n("function"), QStringLiteral("jump_to_overridden"), NavigationAction(DeclarationPointer(overridden), NavigationAction::NavigateDeclaration)); modifyHtml() += i18n(" from "); makeLink(prettyQualifiedName(DeclarationPointer(overridden->context()->owner())), QStringLiteral("jump_to_overridden_container"), NavigationAction(DeclarationPointer(overridden->context()->owner()), NavigationAction::NavigateDeclaration)); modifyHtml() += QStringLiteral("
"); }else{ //Check if this declarations hides other declarations QList decls; foreach(const DUContext::Import &import, d->m_declaration->context()->importedParentContexts()) if(import.context(topContext().data())) decls += import.context(topContext().data())->findDeclarations(QualifiedIdentifier(d->m_declaration->identifier()), CursorInRevision::invalid(), AbstractType::Ptr(), topContext().data(), DUContext::DontSearchInParent); uint num = 0; foreach(Declaration* decl, decls) { modifyHtml() += i18n("Hides a "); makeLink(i18n("function"), QStringLiteral("jump_to_hide_%1").arg(num), NavigationAction(DeclarationPointer(decl), NavigationAction::NavigateDeclaration)); modifyHtml() += i18n(" from "); makeLink(prettyQualifiedName(DeclarationPointer(decl->context()->owner())), QStringLiteral("jump_to_hide_container_%1").arg(num), NavigationAction(DeclarationPointer(decl->context()->owner()), NavigationAction::NavigateDeclaration)); modifyHtml() += QStringLiteral("
"); ++num; } } ///Show all places where this function is overridden if(classFunDecl->isVirtual()) { Declaration* classDecl = d->m_declaration->context()->owner(); if(classDecl) { uint maxAllowedSteps = d->m_fullBackwardSearch ? (uint)-1 : 10; QList overriders = DUChainUtils::getOverriders(classDecl, classFunDecl, maxAllowedSteps); if(!overriders.isEmpty()) { modifyHtml() += i18n("Overridden in "); bool first = true; foreach(Declaration* overrider, overriders) { if(!first) modifyHtml() += QStringLiteral(", "); first = false; const auto owner = DeclarationPointer(overrider->context()->owner()); const QString name = prettyQualifiedName(owner); makeLink(name, name, NavigationAction(DeclarationPointer(overrider), NavigationAction::NavigateDeclaration)); } modifyHtml() += QStringLiteral("
"); } if(maxAllowedSteps == 0) createFullBackwardSearchLink(overriders.isEmpty() ? i18n("Overriders possible, show all") : i18n("More overriders possible, show all")); } } } ///Show all classes that inherit this one uint maxAllowedSteps = d->m_fullBackwardSearch ? (uint)-1 : 10; QList inheriters = DUChainUtils::getInheriters(d->m_declaration.data(), maxAllowedSteps); if(!inheriters.isEmpty()) { modifyHtml() += i18n("Inherited by "); bool first = true; foreach(Declaration* importer, inheriters) { if(!first) modifyHtml() += QStringLiteral(", "); first = false; const QString importerName = prettyQualifiedName(DeclarationPointer(importer)); makeLink(importerName, importerName, NavigationAction(DeclarationPointer(importer), NavigationAction::NavigateDeclaration)); } modifyHtml() += QStringLiteral("
"); } if(maxAllowedSteps == 0) createFullBackwardSearchLink(inheriters.isEmpty() ? i18n("Inheriters possible, show all") : i18n("More inheriters possible, show all")); } void AbstractDeclarationNavigationContext::createFullBackwardSearchLink(const QString& string) { makeLink(string, QStringLiteral("m_fullBackwardSearch=true"), NavigationAction(QStringLiteral("m_fullBackwardSearch=true"))); modifyHtml() += QStringLiteral("
"); } NavigationContextPointer AbstractDeclarationNavigationContext::executeKeyAction( QString key ) { if(key == QLatin1String("m_fullBackwardSearch=true")) { d->m_fullBackwardSearch = true; clear(); } return NavigationContextPointer(this); } void AbstractDeclarationNavigationContext::htmlClass() { StructureType::Ptr klass = d->m_declaration->abstractType().cast(); Q_ASSERT(klass); ClassDeclaration* classDecl = dynamic_cast(klass->declaration(topContext().data())); if(classDecl) { switch ( classDecl->classType() ) { case ClassDeclarationData::Class: modifyHtml() += QStringLiteral("class "); break; case ClassDeclarationData::Struct: modifyHtml() += QStringLiteral("struct "); break; case ClassDeclarationData::Union: modifyHtml() += QStringLiteral("union "); break; case ClassDeclarationData::Interface: modifyHtml() += QStringLiteral("interface "); break; case ClassDeclarationData::Trait: modifyHtml() += QStringLiteral("trait "); break; - default: - modifyHtml() += QStringLiteral(" "); - break; } eventuallyMakeTypeLinks( klass.cast() ); FOREACH_FUNCTION( const BaseClassInstance& base, classDecl->baseClasses ) { modifyHtml() += QLatin1String(", ") + stringFromAccess(base.access) + QLatin1Char(' ') + (base.virtualInheritance ? QStringLiteral("virtual") : QString()) + QLatin1Char(' '); eventuallyMakeTypeLinks(base.baseClass.abstractType()); } } else { /// @todo How can we get here? and should this really be a class? modifyHtml() += QStringLiteral("class "); eventuallyMakeTypeLinks( klass.cast() ); } modifyHtml() += QStringLiteral(" "); } void AbstractDeclarationNavigationContext::htmlIdentifiedType(AbstractType::Ptr type, const IdentifiedType* idType) { Q_ASSERT(type); Q_ASSERT(idType); if( Declaration* decl = idType->declaration(topContext().data()) ) { //Remove the last template-identifiers, because we create those directly QualifiedIdentifier id = prettyQualifiedIdentifier(DeclarationPointer(decl)); Identifier lastId = id.last(); id.pop(); lastId.clearTemplateIdentifiers(); id.push(lastId); if(decl->context() && decl->context()->owner()) { //Also create full type-links for the context around AbstractType::Ptr contextType = decl->context()->owner()->abstractType(); IdentifiedType* contextIdType = dynamic_cast(contextType.data()); if(contextIdType && !contextIdType->equals(idType)) { //Create full type information for the context if(!id.isEmpty()) id = id.mid(id.count()-1); htmlIdentifiedType(contextType, contextIdType); modifyHtml() += QStringLiteral("::").toHtmlEscaped(); } } //We leave out the * and & reference and pointer signs, those are added to the end makeLink(id.toString() , DeclarationPointer(idType->declaration(topContext().data())), NavigationAction::NavigateDeclaration ); } else { qCDebug(LANGUAGE) << "could not resolve declaration:" << idType->declarationId().isDirect() << idType->qualifiedIdentifier().toString() << "in top-context" << topContext()->url().str(); modifyHtml() += typeHighlight(type->toString().toHtmlEscaped()); } } void AbstractDeclarationNavigationContext::eventuallyMakeTypeLinks( AbstractType::Ptr type ) { type = typeToShow(type); if( !type ) { modifyHtml() += typeHighlight(QStringLiteral("").toHtmlEscaped()); return; } AbstractType::Ptr target = TypeUtils::targetTypeKeepAliases( type, topContext().data() ); const IdentifiedType* idType = dynamic_cast( target.data() ); qCDebug(LANGUAGE) << "making type-links for" << type->toString(); if( idType && idType->declaration(topContext().data()) ) { ///@todo This is C++ specific, move into subclass if(target->modifiers() & AbstractType::ConstModifier) modifyHtml() += typeHighlight(QStringLiteral("const ")); htmlIdentifiedType(target, idType); //We need to exchange the target type, else template-parameters may confuse this SimpleTypeExchanger exchangeTarget(target, AbstractType::Ptr()); AbstractType::Ptr exchanged = exchangeTarget.exchange(type); if(exchanged) { QString typeSuffixString = exchanged->toString(); QRegExp suffixExp(QStringLiteral("\\&|\\*")); int suffixPos = typeSuffixString.indexOf(suffixExp); if(suffixPos != -1) modifyHtml() += typeHighlight(typeSuffixString.mid(suffixPos)); } } else { if(idType) { qCDebug(LANGUAGE) << "identified type could not be resolved:" << idType->qualifiedIdentifier() << idType->declarationId().isValid() << idType->declarationId().isDirect(); } modifyHtml() += typeHighlight(type->toString().toHtmlEscaped()); } } DeclarationPointer AbstractDeclarationNavigationContext::declaration() const { return d->m_declaration; } QString AbstractDeclarationNavigationContext::identifierHighlight(const QString& identifier, const DeclarationPointer& decl) const { QString ret = nameHighlight(identifier); if (!decl) { return ret; } if (decl->isDeprecated()) { ret = QStringLiteral("") + ret + QStringLiteral(""); } return ret; } QString AbstractDeclarationNavigationContext::stringFromAccess(Declaration::AccessPolicy access) { switch(access) { case Declaration::Private: return QStringLiteral("private"); case Declaration::Protected: return QStringLiteral("protected"); case Declaration::Public: return QStringLiteral("public"); default: break; } return QString(); } QString AbstractDeclarationNavigationContext::stringFromAccess(const DeclarationPointer& decl) { const ClassMemberDeclaration* memberDecl = dynamic_cast(decl.data()); if( memberDecl ) { return stringFromAccess(memberDecl->accessPolicy()); } return QString(); } QString AbstractDeclarationNavigationContext::declarationName( const DeclarationPointer& decl ) const { if( NamespaceAliasDeclaration* alias = dynamic_cast(decl.data()) ) { if( alias->identifier().isEmpty() ) return QLatin1String("using namespace ") + alias->importIdentifier().toString(); else return QLatin1String("namespace ") + alias->identifier().toString() + QLatin1String(" = ") + alias->importIdentifier().toString(); } if( !decl ) return i18nc("A declaration that is unknown", "Unknown"); else return prettyIdentifier(decl).toString(); } QStringList AbstractDeclarationNavigationContext::declarationDetails(const DeclarationPointer& decl) { QStringList details; const AbstractFunctionDeclaration* function = dynamic_cast(decl.data()); const ClassMemberDeclaration* memberDecl = dynamic_cast(decl.data()); if( memberDecl ) { if( memberDecl->isMutable() ) details << QStringLiteral("mutable"); if( memberDecl->isRegister() ) details << QStringLiteral("register"); if( memberDecl->isStatic() ) details << QStringLiteral("static"); if( memberDecl->isAuto() ) details << QStringLiteral("auto"); if( memberDecl->isExtern() ) details << QStringLiteral("extern"); if( memberDecl->isFriend() ) details << QStringLiteral("friend"); } if( decl->isDefinition() ) details << i18nc("tells if a declaration is defining the variable's value", "definition"); if( decl->isExplicitlyDeleted() ) details << QStringLiteral("deleted"); if( memberDecl && memberDecl->isForwardDeclaration() ) details << i18nc("as in c++ forward declaration", "forward"); AbstractType::Ptr t(decl->abstractType()); if( t ) { if( t->modifiers() & AbstractType::ConstModifier ) details << i18nc("a variable that won't change, const", "constant"); if( t->modifiers() & AbstractType::VolatileModifier ) details << QStringLiteral("volatile"); } if( function ) { if( function->isInline() ) details << QStringLiteral("inline"); if( function->isExplicit() ) details << QStringLiteral("explicit"); if( function->isVirtual() ) details << QStringLiteral("virtual"); const ClassFunctionDeclaration* classFunDecl = dynamic_cast(decl.data()); if( classFunDecl ) { if( classFunDecl->isSignal() ) details << QStringLiteral("signal"); if( classFunDecl->isSlot() ) details << QStringLiteral("slot"); if( classFunDecl->isFinal() ) details << QStringLiteral("final"); if( classFunDecl->isConstructor() ) details << QStringLiteral("constructor"); if( classFunDecl->isDestructor() ) details << QStringLiteral("destructor"); if( classFunDecl->isConversionFunction() ) details << QStringLiteral("conversion-function"); if( classFunDecl->isAbstract() ) details << QStringLiteral("abstract"); } } return details; } } diff --git a/kdevplatform/outputview/outputexecutejob.cpp b/kdevplatform/outputview/outputexecutejob.cpp index 2c173e3777..33e0b0450b 100644 --- a/kdevplatform/outputview/outputexecutejob.cpp +++ b/kdevplatform/outputview/outputexecutejob.cpp @@ -1,550 +1,549 @@ /* This file is part of KDevelop 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 "outputexecutejob.h" #include "outputmodel.h" #include "outputdelegate.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include namespace KDevelop { class OutputExecuteJobPrivate { public: explicit OutputExecuteJobPrivate( KDevelop::OutputExecuteJob* owner ); void childProcessStdout(); void childProcessStderr(); void emitProgress(const IFilterStrategy::Progress& progress); QString joinCommandLine() const; QString getJobName(); template< typename T > static void mergeEnvironment( QProcessEnvironment& dest, const T& src ); QProcessEnvironment effectiveEnvironment(const QUrl& workingDirectory) const; QStringList effectiveCommandLine() const; OutputExecuteJob* m_owner; KProcess* m_process; ProcessLineMaker* m_lineMaker; OutputExecuteJob::JobStatus m_status; OutputExecuteJob::JobProperties m_properties; OutputModel::OutputFilterStrategy m_filteringStrategy; QScopedPointer m_filteringStrategyPtr; QStringList m_arguments; QStringList m_privilegedExecutionCommand; QUrl m_workingDirectory; QString m_environmentProfile; QHash m_environmentOverrides; QString m_jobName; bool m_outputStarted; bool m_executeOnHost = false; }; OutputExecuteJobPrivate::OutputExecuteJobPrivate( OutputExecuteJob* owner ) : m_owner( owner ), m_process( new KProcess( m_owner ) ), m_lineMaker( new ProcessLineMaker( m_owner ) ), // do not assign process to the line maker as we'll feed it data ourselves m_status( OutputExecuteJob::JobNotStarted ), m_properties( OutputExecuteJob::DisplayStdout ), m_filteringStrategy( OutputModel::NoFilter ), m_outputStarted( false ) { } OutputExecuteJob::OutputExecuteJob( QObject* parent, OutputJob::OutputJobVerbosity verbosity ): OutputJob( parent, verbosity ), d( new OutputExecuteJobPrivate( this ) ) { d->m_process->setOutputChannelMode( KProcess::SeparateChannels ); connect( d->m_process, static_cast(&KProcess::finished), this, &OutputExecuteJob::childProcessExited ); connect( d->m_process, static_cast(&KProcess::error), this, &OutputExecuteJob::childProcessError ); connect( d->m_process, &KProcess::readyReadStandardOutput, this, [=] { d->childProcessStdout(); } ); connect( d->m_process, &KProcess::readyReadStandardError, this, [=] { d->childProcessStderr(); } ); } OutputExecuteJob::~OutputExecuteJob() { // indicates if process is running and survives kill, then we cannot do anything bool killSuccessful = d->m_process->state() == QProcess::NotRunning; if( !killSuccessful ) { killSuccessful = doKill(); } Q_ASSERT( d->m_process->state() == QProcess::NotRunning || !killSuccessful ); } OutputExecuteJob::JobStatus OutputExecuteJob::status() const { return d->m_status; } OutputModel* OutputExecuteJob::model() const { return dynamic_cast ( OutputJob::model() ); } QStringList OutputExecuteJob::commandLine() const { return d->m_arguments; } OutputExecuteJob& OutputExecuteJob::operator<<( const QString& argument ) { d->m_arguments << argument; return *this; } OutputExecuteJob& OutputExecuteJob::operator<<( const QStringList& arguments ) { d->m_arguments << arguments; return *this; } QStringList OutputExecuteJob::privilegedExecutionCommand() const { return d->m_privilegedExecutionCommand; } void OutputExecuteJob::setPrivilegedExecutionCommand( const QStringList& command ) { d->m_privilegedExecutionCommand = command; } void OutputExecuteJob::setJobName( const QString& name ) { d->m_jobName = name; QString jobName = d->getJobName(); setObjectName( jobName ); setTitle( jobName ); } QUrl OutputExecuteJob::workingDirectory() const { return d->m_workingDirectory; } void OutputExecuteJob::setWorkingDirectory( const QUrl& url ) { d->m_workingDirectory = url; } void OutputExecuteJob::start() { Q_ASSERT( d->m_status == JobNotStarted ); d->m_status = JobRunning; const bool isBuilder = d->m_properties.testFlag( IsBuilderHint ); const QUrl effectiveWorkingDirectory = workingDirectory(); if( effectiveWorkingDirectory.isEmpty() ) { if( d->m_properties.testFlag( NeedWorkingDirectory ) ) { // A directory is not given, but we need it. setError( InvalidWorkingDirectoryError ); if( isBuilder ) { setErrorText( i18n( "No build directory specified for a builder job." ) ); } else { setErrorText( i18n( "No working directory specified for a process." ) ); } return emitResult(); } setModel( new OutputModel ); } else { // Basic sanity checks. if( !effectiveWorkingDirectory.isValid() ) { setError( InvalidWorkingDirectoryError ); if( isBuilder ) { setErrorText( i18n( "Invalid build directory '%1'", effectiveWorkingDirectory.toDisplayString(QUrl::PreferLocalFile) ) ); } else { setErrorText( i18n( "Invalid working directory '%1'", effectiveWorkingDirectory.toDisplayString(QUrl::PreferLocalFile) ) ); } return emitResult(); } else if( !effectiveWorkingDirectory.isLocalFile() ) { setError( InvalidWorkingDirectoryError ); if( isBuilder ) { setErrorText( i18n( "Build directory '%1' is not a local path", effectiveWorkingDirectory.toDisplayString(QUrl::PreferLocalFile) ) ); } else { setErrorText( i18n( "Working directory '%1' is not a local path", effectiveWorkingDirectory.toDisplayString(QUrl::PreferLocalFile) ) ); } return emitResult(); } QFileInfo workingDirInfo( effectiveWorkingDirectory.toLocalFile() ); if( !workingDirInfo.isDir() ) { // If a working directory does not actually exist, either bail out or create it empty, // depending on what we need by properties. // We use a dedicated bool variable since !isDir() may also mean that it exists, // but is not a directory, or a symlink to an inexistent object. bool successfullyCreated = false; if( !d->m_properties.testFlag( CheckWorkingDirectory ) ) { successfullyCreated = QDir().mkdir( effectiveWorkingDirectory.toLocalFile() ); } if( !successfullyCreated ) { setError( InvalidWorkingDirectoryError ); if( isBuilder ) { setErrorText( i18n( "Build directory '%1' does not exist or is not a directory", effectiveWorkingDirectory.toDisplayString(QUrl::PreferLocalFile) ) ); } else { setErrorText( i18n( "Working directory '%1' does not exist or is not a directory", effectiveWorkingDirectory.toDisplayString(QUrl::PreferLocalFile) ) ); } return emitResult(); } } setModel( new OutputModel( effectiveWorkingDirectory ) ); } Q_ASSERT( model() ); if (d->m_filteringStrategyPtr) { model()->setFilteringStrategy(d->m_filteringStrategyPtr.take()); } else { model()->setFilteringStrategy(d->m_filteringStrategy); } setDelegate( new OutputDelegate ); connect(model(), &OutputModel::progress, this, [&](const IFilterStrategy::Progress& progress) { d->emitProgress(progress); }); // Slots hasRawStdout() and hasRawStderr() are responsible // for feeding raw data to the line maker; so property-based channel filtering is implemented there. if( d->m_properties.testFlag( PostProcessOutput ) ) { connect( d->m_lineMaker, &ProcessLineMaker::receivedStdoutLines, this, &OutputExecuteJob::postProcessStdout ); connect( d->m_lineMaker, &ProcessLineMaker::receivedStderrLines, this, &OutputExecuteJob::postProcessStderr ); } else { connect( d->m_lineMaker, &ProcessLineMaker::receivedStdoutLines, model(), &OutputModel::appendLines ); connect( d->m_lineMaker, &ProcessLineMaker::receivedStderrLines, model(), &OutputModel::appendLines ); } if( !d->m_properties.testFlag( NoSilentOutput ) || verbosity() != Silent ) { d->m_outputStarted = true; startOutput(); } if( !effectiveWorkingDirectory.isEmpty() ) { d->m_process->setWorkingDirectory( effectiveWorkingDirectory.toLocalFile() ); } d->m_process->setProcessEnvironment( d->effectiveEnvironment(effectiveWorkingDirectory) ); if (!d->effectiveCommandLine().isEmpty()) { d->m_process->setProgram( d->effectiveCommandLine() ); // there is no way to input data in the output view so redirect stdin to the null device d->m_process->setStandardInputFile(QProcess::nullDevice()); qCDebug(OUTPUTVIEW) << "Starting:" << d->effectiveCommandLine() << d->m_process->program() << "in" << d->m_process->workingDirectory(); if (d->m_executeOnHost) { d->m_process->start(); } else { KDevelop::ICore::self()->runtimeController()->currentRuntime()->startProcess(d->m_process); } model()->appendLine(d->m_process->workingDirectory() + QStringLiteral("> ") + KShell::joinArgs(d->m_process->program())); } else { QString errorMessage = i18n("Failed to specify program to start: %1", d->joinCommandLine()); model()->appendLine( i18n( "*** %1 ***", errorMessage) ); setErrorText(errorMessage); setError( FailedShownError ); emitResult(); } } bool OutputExecuteJob::doKill() { const int terminateKillTimeout = 1000; // msecs if( d->m_status != JobRunning ) { return true; } d->m_status = JobCanceled; d->m_process->terminate(); bool terminated = d->m_process->waitForFinished( terminateKillTimeout ); if( !terminated ) { d->m_process->kill(); terminated = d->m_process->waitForFinished( terminateKillTimeout ); } d->m_lineMaker->flushBuffers(); if( terminated ) { model()->appendLine( i18n( "*** Killed process ***" ) ); } else { // It survived SIGKILL, leave it alone... qCWarning(OUTPUTVIEW) << "Could not kill the running process:" << d->m_process->error(); model()->appendLine( i18n( "*** Warning: could not kill the process ***" ) ); return false; } return true; } void OutputExecuteJob::childProcessError( QProcess::ProcessError processError ) { // This can be called twice: one time via an error() signal, and second - from childProcessExited(). // Avoid doing things in second time. if( d->m_status != OutputExecuteJob::JobRunning ) return; d->m_status = OutputExecuteJob::JobFailed; QString errorValue; switch( processError ) { case QProcess::FailedToStart: errorValue = i18n("%1 has failed to start", commandLine().at(0)); break; case QProcess::Crashed: errorValue = i18n("%1 has crashed", commandLine().at(0)); break; case QProcess::ReadError: errorValue = i18n("Read error"); break; case QProcess::WriteError: errorValue = i18n("Write error"); break; case QProcess::Timedout: errorValue = i18n("Waiting for the process has timed out"); break; - default: case QProcess::UnknownError: errorValue = i18n("Exit code %1", d->m_process->exitCode()); break; } // Show the tool view if it's hidden for the user to be able to diagnose errors. if( !d->m_outputStarted ) { d->m_outputStarted = true; startOutput(); } setError( FailedShownError ); setErrorText( errorValue ); d->m_lineMaker->flushBuffers(); model()->appendLine( i18n("*** Failure: %1 ***", errorValue) ); emitResult(); } void OutputExecuteJob::childProcessExited( int exitCode, QProcess::ExitStatus exitStatus ) { if( d->m_status != JobRunning ) return; if( exitStatus == QProcess::CrashExit ) { childProcessError( QProcess::Crashed ); } else if ( exitCode != 0 ) { childProcessError( QProcess::UnknownError ); } else { d->m_status = JobSucceeded; d->m_lineMaker->flushBuffers(); model()->appendLine( i18n("*** Finished ***") ); emitResult(); } } void OutputExecuteJobPrivate::childProcessStdout() { QByteArray out = m_process->readAllStandardOutput(); if( m_properties.testFlag( OutputExecuteJob::DisplayStdout ) ) { m_lineMaker->slotReceivedStdout( out ); } } void OutputExecuteJobPrivate::childProcessStderr() { QByteArray err = m_process->readAllStandardError(); if( m_properties.testFlag( OutputExecuteJob::DisplayStderr ) ) { m_lineMaker->slotReceivedStderr( err ); } } void OutputExecuteJobPrivate::emitProgress(const IFilterStrategy::Progress& progress) { if (progress.percent != -1) { m_owner->emitPercent(progress.percent, 100); } if (!progress.status.isEmpty()) { emit m_owner->infoMessage(m_owner, progress.status); } } void OutputExecuteJob::postProcessStdout( const QStringList& lines ) { model()->appendLines( lines ); } void OutputExecuteJob::postProcessStderr( const QStringList& lines ) { model()->appendLines( lines ); } void OutputExecuteJob::setFilteringStrategy( OutputModel::OutputFilterStrategy strategy ) { d->m_filteringStrategy = strategy; // clear the other d->m_filteringStrategyPtr.reset(nullptr); } void OutputExecuteJob::setFilteringStrategy(IFilterStrategy* filterStrategy) { d->m_filteringStrategyPtr.reset(filterStrategy); // clear the other d->m_filteringStrategy = OutputModel::NoFilter; } OutputExecuteJob::JobProperties OutputExecuteJob::properties() const { return d->m_properties; } void OutputExecuteJob::setProperties( OutputExecuteJob::JobProperties properties, bool override ) { if( override ) { d->m_properties = properties; } else { d->m_properties |= properties; } } void OutputExecuteJob::unsetProperties( OutputExecuteJob::JobProperties properties ) { d->m_properties &= ~properties; } QString OutputExecuteJob::environmentProfile() const { return d->m_environmentProfile; } void OutputExecuteJob::setEnvironmentProfile( const QString& profile ) { d->m_environmentProfile = profile; } void OutputExecuteJob::addEnvironmentOverride( const QString& name, const QString& value ) { d->m_environmentOverrides[name] = value; } void OutputExecuteJob::removeEnvironmentOverride( const QString& name ) { d->m_environmentOverrides.remove( name ); } void OutputExecuteJob::setExecuteOnHost(bool executeHost) { d->m_executeOnHost = executeHost; } bool OutputExecuteJob::executeOnHost() const { return d->m_executeOnHost; } template< typename T > void OutputExecuteJobPrivate::mergeEnvironment( QProcessEnvironment& dest, const T& src ) { for( typename T::const_iterator it = src.begin(); it != src.end(); ++it ) { dest.insert( it.key(), it.value() ); } } QProcessEnvironment OutputExecuteJobPrivate::effectiveEnvironment(const QUrl& workingDirectory) const { const EnvironmentProfileList environmentProfiles(KSharedConfig::openConfig()); QString environmentProfile = m_owner->environmentProfile(); if( environmentProfile.isEmpty() ) { environmentProfile = environmentProfiles.defaultProfileName(); } QProcessEnvironment environment = QProcessEnvironment::systemEnvironment(); auto userEnv = environmentProfiles.variables(environmentProfile); expandVariables(userEnv, environment); OutputExecuteJobPrivate::mergeEnvironment( environment, userEnv ); OutputExecuteJobPrivate::mergeEnvironment( environment, m_environmentOverrides ); if( m_properties.testFlag( OutputExecuteJob::PortableMessages ) ) { environment.remove( QStringLiteral( "LC_ALL" ) ); environment.insert( QStringLiteral( "LC_MESSAGES" ), QStringLiteral( "C" ) ); } if (!workingDirectory.isEmpty() && environment.contains(QStringLiteral("PWD"))) { // also update the environment variable for the cwd, otherwise scripts can break easily environment.insert(QStringLiteral("PWD"), workingDirectory.toLocalFile()); } return environment; } QString OutputExecuteJobPrivate::joinCommandLine() const { return KShell::joinArgs( effectiveCommandLine() ); } QStringList OutputExecuteJobPrivate::effectiveCommandLine() const { // If we need to use a su-like helper, invoke it as // "helper -- our command line". QStringList privilegedCommand = m_owner->privilegedExecutionCommand(); if( !privilegedCommand.isEmpty() ) { return QStringList() << m_owner->privilegedExecutionCommand() << QStringLiteral("--") << m_owner->commandLine(); } else { return m_owner->commandLine(); } } QString OutputExecuteJobPrivate::getJobName() { const QString joinedCommandLine = joinCommandLine(); if( m_properties.testFlag( OutputExecuteJob::AppendProcessString ) ) { if( !m_jobName.isEmpty() ) { return m_jobName + QLatin1String(": ") + joinedCommandLine; } else { return joinedCommandLine; } } else { return m_jobName; } } } // namespace KDevelop #include "moc_outputexecutejob.cpp" diff --git a/kdevplatform/outputview/outputmodel.cpp b/kdevplatform/outputview/outputmodel.cpp index 2575589c1d..9d0596a328 100644 --- a/kdevplatform/outputview/outputmodel.cpp +++ b/kdevplatform/outputview/outputmodel.cpp @@ -1,473 +1,471 @@ /*************************************************************************** * 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); foreach( 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; - default: - // assert(false); - filter = new NoFilterStrategy; - break; } - Q_ASSERT(filter); + 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/outputview/tests/test_filteringstrategy.cpp b/kdevplatform/outputview/tests/test_filteringstrategy.cpp index 5a157ad960..e80f7ef36c 100644 --- a/kdevplatform/outputview/tests/test_filteringstrategy.cpp +++ b/kdevplatform/outputview/tests/test_filteringstrategy.cpp @@ -1,555 +1,554 @@ /* This file is part of KDevelop Copyright 2012 Milian Wolff 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 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 "test_filteringstrategy.h" #include "testlinebuilderfunctions.h" #include #include #include using namespace KDevelop; QTEST_GUILESS_MAIN(TestFilteringStrategy) namespace QTest { template<> inline char* toString(const FilteredItem::FilteredOutputItemType& type) { switch (type) { case FilteredItem::ActionItem: return qstrdup("ActionItem"); case FilteredItem::CustomItem: return qstrdup("CustomItem"); case FilteredItem::ErrorItem: return qstrdup("ErrorItem"); case FilteredItem::InformationItem: return qstrdup("InformationItem"); case FilteredItem::InvalidItem: return qstrdup("InvalidItem"); case FilteredItem::StandardItem: return qstrdup("StandardItem"); case FilteredItem::WarningItem: return qstrdup("WarningItem"); } return qstrdup("unknown"); } inline QTestData& newRowForPathType(const char *dataTag, TestPathType pathType) { switch (pathType) { case UnixFilePathNoSpaces: return QTest::newRow(QString(QLatin1String(dataTag)+QLatin1String("-unix-ns")).toUtf8().constData()); case UnixFilePathWithSpaces: return QTest::newRow(QString(QLatin1String(dataTag)+QLatin1String("-unix-ws")).toUtf8().constData()); case WindowsFilePathNoSpaces: return QTest::newRow(QString(QLatin1String(dataTag)+QLatin1String("-windows-ns")).toUtf8().constData()); case WindowsFilePathWithSpaces: return QTest::newRow(QString(QLatin1String(dataTag)+QLatin1String("-windows-ws")).toUtf8().constData()); - default: - return QTest::newRow(dataTag); } + Q_UNREACHABLE(); } } void TestFilteringStrategy::testNoFilterStrategy_data() { QTest::addColumn("line"); QTest::addColumn("expected"); QTest::newRow("cppcheck-info-line") << buildCppCheckInformationLine() << FilteredItem::InvalidItem; for (TestPathType pathType : {UnixFilePathNoSpaces, UnixFilePathWithSpaces}) { QTest::newRowForPathType("cppcheck-error-line", pathType) << buildCppCheckErrorLine(pathType) << FilteredItem::InvalidItem; QTest::newRowForPathType("compiler-line", pathType) << buildCompilerLine(pathType) << FilteredItem::InvalidItem; QTest::newRowForPathType("compiler-error-line", pathType) << buildCompilerErrorLine(pathType) << FilteredItem::InvalidItem; } QTest::newRow("compiler-action-line") << buildCompilerActionLine() << FilteredItem::InvalidItem; for (TestPathType pathType : {UnixFilePathNoSpaces, UnixFilePathWithSpaces}) { QTest::newRowForPathType("compiler-information-line", pathType) << buildCompilerInformationLine(pathType) << FilteredItem::InvalidItem; QTest::newRowForPathType("python-error-line", pathType) << buildPythonErrorLine(pathType) << FilteredItem::InvalidItem; } } void TestFilteringStrategy::testNoFilterStrategy() { QFETCH(QString, line); QFETCH(FilteredItem::FilteredOutputItemType, expected); NoFilterStrategy testee; FilteredItem item1 = testee.errorInLine(line); QCOMPARE(item1.type, expected); item1 = testee.actionInLine(line); QCOMPARE(item1.type, expected); } void TestFilteringStrategy::testCompilerFilterStrategy_data() { QTest::addColumn("line"); QTest::addColumn("expectedError"); QTest::addColumn("expectedAction"); QTest::addColumn("pathType"); QTest::newRow("cppcheck-info-line") << buildCppCheckInformationLine() << FilteredItem::InvalidItem << FilteredItem::InvalidItem << UnixFilePathNoSpaces; for (TestPathType pathType : {UnixFilePathNoSpaces, UnixFilePathWithSpaces}) { QTest::newRowForPathType("cppcheck-error-line", pathType) << buildCppCheckErrorLine(pathType) << FilteredItem::InvalidItem << FilteredItem::InvalidItem << pathType; QTest::newRowForPathType("compiler-line", pathType) << buildCompilerLine(pathType) << FilteredItem::InvalidItem << FilteredItem::InvalidItem << pathType; QTest::newRowForPathType("compiler-error-line", pathType) << buildCompilerErrorLine(pathType) << FilteredItem::ErrorItem << FilteredItem::InvalidItem << pathType; QTest::newRowForPathType("compiler-information-line", pathType) << buildCompilerInformationLine(pathType) << FilteredItem::InformationItem << FilteredItem::InvalidItem << pathType; QTest::newRowForPathType("compiler-information-line2", pathType) << buildInfileIncludedFromFirstLine(pathType) << FilteredItem::InformationItem << FilteredItem::InvalidItem << pathType; QTest::newRowForPathType("compiler-information-line3", pathType) << buildInfileIncludedFromSecondLine(pathType) << FilteredItem::InformationItem << FilteredItem::InvalidItem << pathType; } QTest::newRow("cmake-error-line1") << "CMake Error at CMakeLists.txt:2 (cmake_minimum_required):" << FilteredItem::ErrorItem << FilteredItem::InvalidItem << UnixFilePathNoSpaces; QTest::newRow("cmake-error-multiline1") << "CMake Error: Error in cmake code at" << FilteredItem::InvalidItem << FilteredItem::InvalidItem << UnixFilePathNoSpaces; for (TestPathType pathType : {UnixFilePathNoSpaces, UnixFilePathWithSpaces}) { QTest::newRowForPathType("cmake-error-multiline2", pathType) << buildCmakeConfigureMultiLine(pathType) << FilteredItem::ErrorItem << FilteredItem::InvalidItem << pathType; } QTest::newRow("cmake-warning-line") << "CMake Warning (dev) in CMakeLists.txt:" << FilteredItem::WarningItem << FilteredItem::InvalidItem << UnixFilePathNoSpaces; QTest::newRow("cmake-automoc-error") << "AUTOMOC: error: /foo/bar.cpp The file includes the moc file \"moc_bar1.cpp\"" << FilteredItem::ErrorItem << FilteredItem::InvalidItem << UnixFilePathNoSpaces; QTest::newRow("cmake-automoc4-error") << "automoc4: The file \"/foo/bar.cpp\" includes the moc file \"bar1.moc\"" << FilteredItem::InformationItem << FilteredItem::InvalidItem << UnixFilePathNoSpaces; QTest::newRow("cmake-autogen-error") << "AUTOGEN: error: /foo/bar.cpp The file includes the moc file \"moc_bar1.cpp\"" << FilteredItem::ErrorItem << FilteredItem::InvalidItem << UnixFilePathNoSpaces; QTest::newRow("linker-action-line") << "linking testCustombuild (g++)" << FilteredItem::InvalidItem << FilteredItem::ActionItem << UnixFilePathNoSpaces; for (TestPathType pathType : {UnixFilePathNoSpaces, UnixFilePathWithSpaces}) { QTest::newRowForPathType("linker-error-line", pathType) << buildLinkerErrorLine(pathType) << FilteredItem::ErrorItem << FilteredItem::InvalidItem << pathType; QTest::newRowForPathType("python-error-line", pathType) << buildPythonErrorLine(pathType) << FilteredItem::InvalidItem << FilteredItem::InvalidItem << pathType; } } void TestFilteringStrategy::testCompilerFilterStrategy() { QFETCH(QString, line); QFETCH(FilteredItem::FilteredOutputItemType, expectedError); QFETCH(FilteredItem::FilteredOutputItemType, expectedAction); QFETCH(TestPathType, pathType); QUrl projecturl = QUrl::fromLocalFile( projectPath(pathType) ); CompilerFilterStrategy testee(projecturl); FilteredItem item1 = testee.errorInLine(line); QCOMPARE(item1.type, expectedError); item1 = testee.actionInLine(line); QCOMPARE(item1.type, expectedAction); } void TestFilteringStrategy::testCompilerFilterstrategyMultipleKeywords_data() { QTest::addColumn("line"); QTest::addColumn("expectedError"); QTest::addColumn("expectedAction"); QTest::newRow("warning-containing-error-word") << "RingBuffer.cpp:64:6: warning: unused parameter ‘errorItem’ [-Wunused-parameter]" << FilteredItem::WarningItem << FilteredItem::InvalidItem; QTest::newRow("error-containing-info-word") << "NodeSet.hpp:89:27: error: ‘Info’ was not declared in this scope" << FilteredItem::ErrorItem << FilteredItem::InvalidItem; QTest::newRow("warning-in-filename-containing-error-word") << "ErrorHandling.cpp:100:56: warning: unused parameter ‘item’ [-Wunused-parameter]" << FilteredItem::WarningItem << FilteredItem::InvalidItem; QTest::newRow("error-in-filename-containing-warning-word") << "WarningHandling.cpp:100:56: error: ‘Item’ was not declared in this scope" << FilteredItem::ErrorItem << FilteredItem::InvalidItem; } void TestFilteringStrategy::testCompilerFilterstrategyMultipleKeywords() { QFETCH(QString, line); QFETCH(FilteredItem::FilteredOutputItemType, expectedError); QFETCH(FilteredItem::FilteredOutputItemType, expectedAction); QUrl projecturl = QUrl::fromLocalFile( projectPath() ); CompilerFilterStrategy testee(projecturl); FilteredItem item1 = testee.errorInLine(line); QCOMPARE(item1.type, expectedError); item1 = testee.actionInLine(line); QCOMPARE(item1.type, expectedAction); } void TestFilteringStrategy::testScriptErrorFilterStrategy_data() { QTest::addColumn("line"); QTest::addColumn("expectedError"); QTest::addColumn("expectedAction"); QTest::newRow("cppcheck-info-line") << buildCppCheckInformationLine() << FilteredItem::InvalidItem << FilteredItem::InvalidItem; for (TestPathType pathType : {UnixFilePathNoSpaces, UnixFilePathWithSpaces}) { QTest::newRowForPathType("cppcheck-error-line", pathType) << buildCppCheckErrorLine(pathType) << FilteredItem::ErrorItem << FilteredItem::InvalidItem; QTest::newRowForPathType("compiler-line", pathType) << buildCompilerLine(pathType) << FilteredItem::InvalidItem << FilteredItem::InvalidItem; QTest::newRowForPathType("compiler-error-line", pathType) << buildCompilerErrorLine(pathType) << FilteredItem::ErrorItem << FilteredItem::InvalidItem; } QTest::newRow("compiler-action-line") << "linking testCustombuild (g++)" << FilteredItem::InvalidItem << FilteredItem::InvalidItem; for (TestPathType pathType : {UnixFilePathNoSpaces, UnixFilePathWithSpaces}) { QTest::newRowForPathType("python-error-line", pathType) << buildPythonErrorLine(pathType) << FilteredItem::InvalidItem << FilteredItem::InvalidItem; } } void TestFilteringStrategy::testScriptErrorFilterStrategy() { QFETCH(QString, line); QFETCH(FilteredItem::FilteredOutputItemType, expectedError); QFETCH(FilteredItem::FilteredOutputItemType, expectedAction); ScriptErrorFilterStrategy testee; FilteredItem item1 = testee.errorInLine(line); QCOMPARE(item1.type, expectedError); item1 = testee.actionInLine(line); QCOMPARE(item1.type, expectedAction); } void TestFilteringStrategy::testNativeAppErrorFilterStrategy_data() { QTest::addColumn("line"); QTest::addColumn("file"); QTest::addColumn("lineNo"); QTest::addColumn("column"); QTest::addColumn("itemtype"); // BEGIN: C++ QTest::newRow("cassert") << "a.out: /foo/bar/test.cpp:5: int main(): Assertion `false' failed." << "/foo/bar/test.cpp" << 4 << 0 << FilteredItem::ErrorItem; // END: C++ // BEGIN: Qt // TODO: qt-connect-* and friends shouldn't be error items but warnings items instead // this needs refactoring in outputfilteringstrategies, though... QTest::newRow("qt-connect-nosuch-slot") << "QObject::connect: No such slot Foo::bar() in /foo/bar.cpp:313" << "/foo/bar.cpp" << 312 << 0 << FilteredItem::ErrorItem; QTest::newRow("qt-connect-nosuch-signal") << "QObject::connect: No such signal Foo::bar() in /foo/bar.cpp:313" << "/foo/bar.cpp" << 312 << 0 << FilteredItem::ErrorItem; QTest::newRow("qt-connect-parentheses-slot") << "QObject::connect: Parentheses expected, slot Foo::bar() in /foo/bar.cpp:313" << "/foo/bar.cpp" << 312 << 0 << FilteredItem::ErrorItem; QTest::newRow("qt-connect-parentheses-signal") << "QObject::connect: Parentheses expected, signal Foo::bar() in /foo/bar.cpp:313" << "/foo/bar.cpp" << 312 << 0 << FilteredItem::ErrorItem; QTest::newRow("qt-assert") << "ASSERT: \"errors().isEmpty()\" in file /tmp/foo/bar.cpp, line 49" << "/tmp/foo/bar.cpp" << 48 << 0 << FilteredItem::ErrorItem; QTest::newRow("qttest-assert") << "QFATAL : FooTest::testBar() ASSERT: \"index.isValid()\" in file /foo/bar.cpp, line 32" << "/foo/bar.cpp" << 31 << 0 << FilteredItem::ErrorItem; QTest::newRow("qttest-loc") << " Loc: [/foo/bar.cpp(33)]" << "/foo/bar.cpp" << 32 << 0 << FilteredItem::ErrorItem; QTest::newRow("qttest-loc-nocatch") << " Loc: [Unknown file(0)]" << "" << -1 << -1 << FilteredItem::InvalidItem; QTest::newRow("qml-import-unix") << "file:///path/to/foo.qml:7:1: Bar is not a type" << "/path/to/foo.qml" << 6 << 0 << FilteredItem::ErrorItem; QTest::newRow("qml-import-unix1") << "file:///path/to/foo.qml:7:1: Bar is ambiguous. Found in A and in B" << "/path/to/foo.qml" << 6 << 0 << FilteredItem::ErrorItem; QTest::newRow("qml-import-unix2") << "file:///path/to/foo.qml:7:1: Bar is instantiated recursively" << "/path/to/foo.qml" << 6 << 0 << FilteredItem::ErrorItem; QTest::newRow("qml-typeerror") << "file:///path/to/foo.qml:7: TypeError: Cannot read property 'height' of null" << "/path/to/foo.qml" << 6 << 0 << FilteredItem::ErrorItem; QTest::newRow("qml-referenceerror") << "file:///path/to/foo.qml:7: ReferenceError: readOnly is not defined" << "/path/to/foo.qml" << 6 << 0 << FilteredItem::ErrorItem; QTest::newRow("qml-bindingloop") << "file:///path/to/foo.qml:7:5: QML Row: Binding loop detected for property \"height\"" << "/path/to/foo.qml" << 6 << 4 << FilteredItem::ErrorItem; // END: Qt } void TestFilteringStrategy::testNativeAppErrorFilterStrategy() { QFETCH(QString, line); QFETCH(QString, file); QFETCH(int, lineNo); QFETCH(int, column); QFETCH(FilteredItem::FilteredOutputItemType, itemtype); NativeAppErrorFilterStrategy testee; FilteredItem item = testee.errorInLine(line); QCOMPARE(item.url.path(), file); QCOMPARE(item.lineNo , lineNo); QCOMPARE(item.columnNo , column); QCOMPARE(item.type , itemtype); } void TestFilteringStrategy::testStaticAnalysisFilterStrategy_data() { QTest::addColumn("line"); QTest::addColumn("expectedError"); QTest::addColumn("expectedAction"); QTest::addColumn("pathType"); QTest::newRow("cppcheck-info-line") << buildCppCheckInformationLine() << FilteredItem::InvalidItem << FilteredItem::InvalidItem << UnixFilePathNoSpaces; for (TestPathType pathType : {UnixFilePathNoSpaces, UnixFilePathWithSpaces}) { QTest::newRowForPathType("cppcheck-error-line", pathType) << buildCppCheckErrorLine(pathType) << FilteredItem::ErrorItem << FilteredItem::InvalidItem << pathType;; QTest::newRowForPathType("krazy2-error-line", pathType) << buildKrazyErrorLine(pathType) << FilteredItem::ErrorItem << FilteredItem::InvalidItem << pathType; QTest::newRowForPathType("krazy2-error-line-two-colons", pathType) << buildKrazyErrorLine2(pathType) << FilteredItem::ErrorItem << FilteredItem::InvalidItem << pathType; QTest::newRowForPathType("krazy2-error-line-error-description", pathType) << buildKrazyErrorLine3(pathType) << FilteredItem::ErrorItem << FilteredItem::InvalidItem << pathType; QTest::newRowForPathType("krazy2-error-line-wo-line-info", pathType) << buildKrazyErrorLineNoLineInfo(pathType) << FilteredItem::ErrorItem << FilteredItem::InvalidItem << pathType; QTest::newRowForPathType("compiler-line", pathType) << buildCompilerLine(pathType) << FilteredItem::InvalidItem << FilteredItem::InvalidItem << pathType; QTest::newRowForPathType("compiler-error-line", pathType) << buildCompilerErrorLine(pathType) << FilteredItem::InvalidItem << FilteredItem::InvalidItem << pathType; } QTest::newRow("compiler-action-line") << "linking testCustombuild (g++)" << FilteredItem::InvalidItem << FilteredItem::InvalidItem << UnixFilePathNoSpaces; for (TestPathType pathType : {UnixFilePathNoSpaces, UnixFilePathWithSpaces}) { QTest::newRowForPathType("python-error-line", pathType) << buildPythonErrorLine(pathType) << FilteredItem::InvalidItem << FilteredItem::InvalidItem << pathType; } } void TestFilteringStrategy::testStaticAnalysisFilterStrategy() { QFETCH(QString, line); QFETCH(FilteredItem::FilteredOutputItemType, expectedError); QFETCH(FilteredItem::FilteredOutputItemType, expectedAction); QFETCH(TestPathType, pathType); // Test that url's are extracted correctly as well QString referencePath = projectPath(pathType) + "main.cpp"; StaticAnalysisFilterStrategy testee; FilteredItem item1 = testee.errorInLine(line); QString extractedPath = item1.url.toLocalFile(); QVERIFY((item1.type != FilteredItem::ErrorItem) || ( extractedPath == referencePath)); QCOMPARE(item1.type, expectedError); item1 = testee.actionInLine(line); QCOMPARE(item1.type, expectedAction); } void TestFilteringStrategy::testCompilerFilterstrategyUrlFromAction_data() { QTest::addColumn("line"); QTest::addColumn("expectedLastDir"); QTest::addColumn("pathType"); for (TestPathType pathType : #ifdef Q_OS_WIN {WindowsFilePathNoSpaces, WindowsFilePathWithSpaces} #else {UnixFilePathNoSpaces, UnixFilePathWithSpaces} #endif ) { const QString basepath = projectPath(pathType); QTest::newRowForPathType("cmake-line1", pathType) << "[ 25%] Building CXX object path/to/one/CMakeFiles/file.o" << QString( basepath + "/path/to/one" ) << pathType; QTest::newRowForPathType("cmake-line2", pathType) << "[ 26%] Building CXX object path/to/two/CMakeFiles/file.o" << QString( basepath + "/path/to/two") << pathType; QTest::newRowForPathType("cmake-line3", pathType) << "[ 26%] Building CXX object path/to/three/CMakeFiles/file.o" << QString( basepath + "/path/to/three") << pathType; QTest::newRowForPathType("cmake-line4", pathType) << "[ 26%] Building CXX object path/to/four/CMakeFiles/file.o" << QString( basepath + "/path/to/four") << pathType; QTest::newRowForPathType("cmake-line5", pathType) << "[ 26%] Building CXX object path/to/two/CMakeFiles/file.o" << QString( basepath + "/path/to/two") << pathType; QTest::newRowForPathType("cd-line6", pathType) << QString("make[4]: Entering directory '" + basepath + "/path/to/one/'") << QString( basepath + "/path/to/one") << pathType; QTest::newRowForPathType("waf-cd", pathType) << QString("Waf: Entering directory `" + basepath + "/path/to/two/'") << QString( basepath + "/path/to/two") << pathType; QTest::newRowForPathType("cmake-line7", pathType) << QStringLiteral("[ 50%] Building CXX object CMakeFiles/testdeque.dir/RingBuffer.cpp.o") << QString( basepath) << pathType; QTest::newRowForPathType("cmake-cd-line8", pathType) << QString("> /usr/bin/cmake -DCMAKE_INSTALL_PREFIX=/usr/local -DCMAKE_BUILD_TYPE=Debug " + basepath) << QString( basepath ) << pathType; } } void TestFilteringStrategy::testCompilerFilterstrategyUrlFromAction() { QFETCH(QString, line); QFETCH(QString, expectedLastDir); QFETCH(TestPathType, pathType); QUrl projecturl = QUrl::fromLocalFile( projectPath(pathType) ); CompilerFilterStrategy testee(projecturl); FilteredItem item1 = testee.actionInLine(line); int last = testee.getCurrentDirs().size() - 1; QCOMPARE(testee.getCurrentDirs().at(last), expectedLastDir); } void TestFilteringStrategy::benchMarkCompilerFilterAction() { QString projecturl = projectPath(); QStringList outputlines; const int numLines(10000); int j(0), k(0), l(0), m(0); do { ++j; ++k; ++l; QString tmp; if(m % 2 == 0) { tmp = QStringLiteral( "[ 26%] Building CXX object /this/is/the/path/to/the/files/%1/%2/%3/CMakeFiles/file.o").arg( j ).arg( k ).arg( l ); } else { tmp = QString( "make[4]: Entering directory '" + projecturl + "/this/is/the/path/to/the/files/%1/%2/%3/").arg( j ).arg( k ).arg( l ); } outputlines << tmp; if(j % 6 == 0) { j = 0; ++m; } if(k % 9 == 0) { k = 0; ++m; } if(l % 13 == 0) { l = 0; ++m; } } while(outputlines.size() < numLines ); // gives us numLines (-ish) QElapsedTimer totalTime; totalTime.start(); static CompilerFilterStrategy testee(QUrl::fromLocalFile(projecturl)); FilteredItem item1(QStringLiteral("dummyline"), FilteredItem::InvalidItem); QBENCHMARK { for(int i = 0; i < outputlines.size(); ++i) { item1 = testee.actionInLine(outputlines.at(i)); } } const qint64 elapsed = totalTime.elapsed(); qDebug() << "ms elapsed to add directories: " << elapsed; qDebug() << "total number of directories: " << outputlines.count(); const double avgDirectoryInsertion = double(elapsed) / outputlines.count(); qDebug() << "average ms spend pr. dir: " << avgDirectoryInsertion; QVERIFY(avgDirectoryInsertion < 2); } void TestFilteringStrategy::testExtractionOfLineAndColumn_data() { QTest::addColumn("line"); QTest::addColumn("file"); QTest::addColumn("lineNr"); QTest::addColumn("column"); QTest::addColumn("itemtype"); #ifdef Q_OS_WIN QTest::newRow("msvc-compiler-error-line") << "Z:\\kderoot\\download\\git\\kcoreaddons\\src\\lib\\jobs\\kjob.cpp(3): error C2065: 'dadsads': undeclared identifier" << "Z:/kderoot/download/git/kcoreaddons/src/lib/jobs/kjob.cpp" << 2 << 0 << FilteredItem::ErrorItem; QTest::newRow("msvc-compiler-warning-line") << "c:\\program files\\microsoft visual studio 10.0\\vc\\include\\crtdefs.h(527): warning C4229: anachronism used : modifiers on data are ignored" << "c:/program files/microsoft visual studio 10.0/vc/include/crtdefs.h" << 526 << 0 << FilteredItem::WarningItem; #else QTest::newRow("gcc-with-col") << "/path/to/file.cpp:123:45: fatal error: ..." << "/path/to/file.cpp" << 122 << 44 << FilteredItem::ErrorItem; QTest::newRow("gcc-no-col") << "/path/to/file.cpp:123: error ..." << "/path/to/file.cpp" << 122 << 0 << FilteredItem::ErrorItem; QTest::newRow("gcc-app-gives-invalid-column") << "/path/to/file.h:60:0:\ warning: \"SOME_MACRO\" redefined" << "/path/to/file.h" << 59 << 0 << FilteredItem::WarningItem; QTest::newRow("fortcom") << "fortcom: Error: Ogive8.f90, line 123: ..." << QString(projectPath() + "/Ogive8.f90") << 122 << 0 << FilteredItem::ErrorItem; QTest::newRow("fortcomError") << "fortcom: Error: ./Ogive8.f90, line 123: ..." << QString(projectPath() + "/Ogive8.f90") << 122 << 0 << FilteredItem::ErrorItem; QTest::newRow("fortcomWarning") << "fortcom: Warning: /path/Ogive8.f90, line 123: ..." << "/path/Ogive8.f90" << 122 << 0 << FilteredItem::WarningItem; QTest::newRow("fortcomInfo") << "fortcom: Info: Ogive8.f90, line 123: ..." << QString(projectPath() + "/Ogive8.f90") << 122 << 0 << FilteredItem::InformationItem; QTest::newRow("libtool") << "libtool: link: warning: ..." << "" << -1 << 0 << FilteredItem::WarningItem; QTest::newRow("gfortranError1") << "/path/to/file.f90:123.456:Error: ...." << "/path/to/file.f90" << 122 << 455 << FilteredItem::ErrorItem; QTest::newRow("gfortranError2") << "/path/flib.f90:3567.22:" << "/path/flib.f90" << 3566 << 21 << FilteredItem::ErrorItem; QTest::newRow("ant-javac-Warning") << " [javac] /path/class.java:383: warning: [deprecation] ..." << "/path/class.java" << 382 << 0 << FilteredItem::WarningItem; QTest::newRow("ant-javac-Error") << " [javac] /path/class.java:447: error: cannot find symbol" << "/path/class.java" << 446 << 0 << FilteredItem::ErrorItem; QTest::newRow("cmake-error") << "CMake Error at somesubdir/CMakeLists.txt:214:" << "/some/path/to/a/somesubdir/CMakeLists.txt" << 213 << 0 << FilteredItem::ErrorItem; #endif } void TestFilteringStrategy::testExtractionOfLineAndColumn() { QFETCH(QString, line); QFETCH(QString, file); QFETCH(int, lineNr); QFETCH(int, column); QFETCH(FilteredItem::FilteredOutputItemType, itemtype); QUrl projecturl = QUrl::fromLocalFile( projectPath() ); CompilerFilterStrategy testee(projecturl); FilteredItem item1 = testee.errorInLine(line); QCOMPARE(item1.type , itemtype); QCOMPARE(KDevelop::toUrlOrLocalFile(item1.url), file); QCOMPARE(item1.lineNo , lineNr); QCOMPARE(item1.columnNo , column); } diff --git a/kdevplatform/outputview/tests/testlinebuilderfunctions.h b/kdevplatform/outputview/tests/testlinebuilderfunctions.h index 6948b1bf8a..6897fcb9f8 100644 --- a/kdevplatform/outputview/tests/testlinebuilderfunctions.h +++ b/kdevplatform/outputview/tests/testlinebuilderfunctions.h @@ -1,182 +1,180 @@ /* This file is part of KDevelop 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 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 . */ #ifndef KDEVPLATFORM_TESTLINEBUILDERFUNCTIONS_H #define KDEVPLATFORM_TESTLINEBUILDERFUNCTIONS_H #include namespace KDevelop { enum TestPathType { UnixFilePathNoSpaces, UnixFilePathWithSpaces, WindowsFilePathNoSpaces, WindowsFilePathWithSpaces }; } Q_DECLARE_METATYPE( KDevelop::TestPathType) namespace KDevelop { // TODO: extend with other potential path patterns (network shares?) static QString projectPath(TestPathType pathType = UnixFilePathNoSpaces) { switch (pathType) { case WindowsFilePathNoSpaces: return QStringLiteral("C:/some/path/to/a/project"); case WindowsFilePathWithSpaces: return QStringLiteral("C:/some/path with spaces/to/a/project"); case UnixFilePathNoSpaces: return QStringLiteral("/some/path/to/a/project"); case UnixFilePathWithSpaces: return QStringLiteral("/some/path with spaces/to/a/project"); - default: - Q_ASSERT(0); - return QStringLiteral("/we/should/never/end/up/here"); } + Q_UNREACHABLE(); } QString buildCppCheckErrorLine(TestPathType pathType = UnixFilePathNoSpaces) { /// Test CPP check output QString outputline(QStringLiteral("[")); outputline.append(projectPath(pathType)); outputline.append("main.cpp:26]: (error) Memory leak: str"); return outputline; } QString buildKrazyErrorLine(TestPathType pathType = UnixFilePathNoSpaces) { /// Test krazy2 output QString outputline(QStringLiteral("\t")); outputline.append(projectPath(pathType)); outputline.append("main.cpp: line#22 (1)"); return outputline; } QString buildKrazyErrorLine2(TestPathType pathType = UnixFilePathNoSpaces) { /// Test krazy2 output QString outputline(QStringLiteral("\t")); outputline.append(projectPath(pathType)); outputline.append("main.cpp: missing tags: email address line#2 (1)"); return outputline; } QString buildKrazyErrorLine3(TestPathType pathType = UnixFilePathNoSpaces) { /// Test krazy2 output QString outputline(QStringLiteral("\t")); outputline.append(projectPath(pathType)); outputline.append("main.cpp: non-const ref iterator line#451 (1)"); return outputline; } QString buildKrazyErrorLineNoLineInfo(TestPathType pathType = UnixFilePathNoSpaces) { /// Test krazy2 output QString outputline(QStringLiteral("\t")); outputline.append(projectPath(pathType)); outputline.append("main.cpp: missing license"); return outputline; } QString buildCompilerLine(TestPathType pathType = UnixFilePathNoSpaces) { /// Test with compiler output QString outputline; outputline.append(projectPath(pathType)); outputline.append(">make"); return outputline; } QString buildCompilerErrorLine(TestPathType pathType = UnixFilePathNoSpaces) { QString outputline; outputline.append(projectPath(pathType)); outputline.append("main.cpp:5:5: error: ‘RingBuffer’ was not declared in this scope"); return outputline; } QString buildCompilerInformationLine(TestPathType pathType = UnixFilePathNoSpaces) { QString outputline; outputline.append(projectPath(pathType)); outputline.append("main.cpp:6:14: instantiated from here"); return outputline; } QString buildInfileIncludedFromFirstLine(TestPathType pathType = UnixFilePathNoSpaces) { QString outputline(QStringLiteral("In file included from ")); outputline.append(projectPath(pathType)); outputline.append("PriorityFactory.h:52:0,"); return outputline; } QString buildInfileIncludedFromSecondLine(TestPathType pathType = UnixFilePathNoSpaces) { QString outputline(QStringLiteral(" from ")); outputline.append(projectPath(pathType)); outputline.append("PatchBasedInpainting.hxx:29,"); return outputline; } QString buildCompilerActionLine() { return QStringLiteral("linking testCustombuild (g++)"); } QString buildCmakeConfigureMultiLine(TestPathType pathType = UnixFilePathNoSpaces) { QString outputline; outputline.append(projectPath(pathType)); outputline.append("CMakeLists.txt:10:"); return outputline; } QString buildLinkerErrorLine(TestPathType pathType = UnixFilePathNoSpaces) { return projectPath(pathType) + QLatin1String("Buffer.cpp:66: undefined reference to `Buffer::does_not_exist()'"); } QString buildPythonErrorLine(TestPathType pathType = UnixFilePathNoSpaces) { QString outputline(QStringLiteral("File \"")); outputline.append(projectPath(pathType)); outputline.append("pythonExample.py\", line 10"); return outputline; } QString buildCppCheckInformationLine() { return QStringLiteral("(information) Cppcheck cannot find all the include files. Cpppcheck can check the code without the include\ files found. But the results will probably be more accurate if all the include files are found. Please check your project's \ include directories and add all of them as include directories for Cppcheck. To see what files Cppcheck cannot find use --check-config."); } } #endif diff --git a/kdevplatform/project/builderjob.cpp b/kdevplatform/project/builderjob.cpp index 8b09dd10d2..3ce1673fab 100644 --- a/kdevplatform/project/builderjob.cpp +++ b/kdevplatform/project/builderjob.cpp @@ -1,268 +1,265 @@ /*************************************************************************** * 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 "builderjob.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; struct SubJobData { BuilderJob::BuildType type; KJob* job; ProjectBaseItem* item; }; Q_DECLARE_TYPEINFO(SubJobData, Q_MOVABLE_TYPE); namespace KDevelop { class BuilderJobPrivate { public: explicit BuilderJobPrivate( BuilderJob* job ) : q(job) , failOnFirstError(true) { } BuilderJob* q; void addJob( BuilderJob::BuildType, ProjectBaseItem* ); bool failOnFirstError; QString buildTypeToString( BuilderJob::BuildType type ) const; bool hasJobForProject( BuilderJob::BuildType type, IProject* project ) const { foreach(const SubJobData& data, m_metadata) { if (data.type == type && data.item->project() == project) { return true; } } return false; } /** * a structure to keep metadata of all registered jobs */ QVector m_metadata; /** * get the subjob list and clear this composite job */ QVector takeJobList(); }; } QString BuilderJobPrivate::buildTypeToString(BuilderJob::BuildType type) const { switch( type ) { case BuilderJob::Build: return i18nc( "@info:status", "build" ); case BuilderJob::Clean: return i18nc( "@info:status", "clean" ); case BuilderJob::Configure: return i18nc( "@info:status", "configure" ); case BuilderJob::Install: return i18nc( "@info:status", "install" ); case BuilderJob::Prune: return i18nc( "@info:status", "prune" ); - default: - return QString(); } + return QString(); } void BuilderJobPrivate::addJob( BuilderJob::BuildType t, ProjectBaseItem* item ) { Q_ASSERT(item); qCDebug(PROJECT) << "adding build job for item:" << item->text(); Q_ASSERT(item->project()); qCDebug(PROJECT) << "project for item:" << item->project()->name(); Q_ASSERT(item->project()->projectItem()); qCDebug(PROJECT) << "project item for the project:" << item->project()->projectItem()->text(); if( !item->project()->buildSystemManager() ) { qCWarning(PROJECT) << "no buildsystem manager for:" << item->text() << item->project()->name(); return; } qCDebug(PROJECT) << "got build system manager"; Q_ASSERT(item->project()->buildSystemManager()->builder()); KJob* j = nullptr; switch( t ) { case BuilderJob::Build: j = item->project()->buildSystemManager()->builder()->build( item ); break; case BuilderJob::Clean: j = item->project()->buildSystemManager()->builder()->clean( item ); break; case BuilderJob::Install: j = item->project()->buildSystemManager()->builder()->install( item ); break; case BuilderJob::Prune: if (!hasJobForProject(t, item->project())) { j = item->project()->buildSystemManager()->builder()->prune( item->project() ); } break; case BuilderJob::Configure: if (!hasJobForProject(t, item->project())) { j = item->project()->buildSystemManager()->builder()->configure( item->project() ); } break; - default: - break; } if( j ) { q->addCustomJob( t, j, item ); } } BuilderJob::BuilderJob() : d( new BuilderJobPrivate( this ) ) { } BuilderJob::~BuilderJob() = default; void BuilderJob::addItems( BuildType t, const QList& items ) { foreach( ProjectBaseItem* item, items ) { d->addJob( t, item ); } } void BuilderJob::addProjects( BuildType t, const QList& projects ) { foreach( IProject* project, projects ) { d->addJob( t, project->projectItem() ); } } void BuilderJob::addItem( BuildType t, ProjectBaseItem* item ) { d->addJob( t, item ); } void BuilderJob::addCustomJob( BuilderJob::BuildType type, KJob* job, ProjectBaseItem* item ) { if( BuilderJob* builderJob = dynamic_cast( job ) ) { // If a subjob is a builder job itself, re-own its job list to avoid having recursive composite jobs. QVector subjobs = builderJob->d->takeJobList(); builderJob->deleteLater(); foreach( const SubJobData& subjob, subjobs ) { subjob.job->setParent(this); addSubjob( subjob.job ); } d->m_metadata << subjobs; } else { job->setParent(this); addSubjob( job ); SubJobData data; data.type = type; data.job = job; data.item = item; d->m_metadata << data; } } QVector< SubJobData > BuilderJobPrivate::takeJobList() { QVector< SubJobData > ret = m_metadata; m_metadata.clear(); q->clearSubjobs(); q->setObjectName( QString() ); return ret; } void BuilderJob::updateJobName() { // Which items are mentioned in the set // Make it a list to preserve ordering; search overhead (n^2) isn't too big QList< ProjectBaseItem* > registeredItems; // Which build types are mentioned in the set // (Same rationale applies) QList< BuildType > buildTypes; // Whether there are jobs without any specific item bool hasNullItems = false; foreach( const SubJobData& subjob, d->m_metadata ) { if( subjob.item ) { if( !registeredItems.contains( subjob.item ) ) { registeredItems.append( subjob.item ); } if( !buildTypes.contains( subjob.type ) ) { buildTypes.append( subjob.type ); } } else { hasNullItems = true; } } QString itemNames; if( !hasNullItems ) { QStringList itemNamesList; foreach( ProjectBaseItem* item, registeredItems ) { itemNamesList << item->text(); } itemNames = itemNamesList.join(QStringLiteral(", ")); } else { itemNames = i18nc( "Unspecified set of build items (e. g. projects, targets)", "Various items" ); } QString methodNames; QStringList methodNamesList; foreach( BuildType type, buildTypes ) { methodNamesList << d->buildTypeToString( type ); } methodNames = methodNamesList.join( QStringLiteral( ", " ) ); QString jobName = QStringLiteral( "%1: %2" ).arg( itemNames, methodNames ); setObjectName( jobName ); } void BuilderJob::start() { // Automatically save all documents before starting to build // might need an option to turn off at some point // Also should be moved into the builder and there try to find target(s) for the given item and then just save the documents of that target -> list?? if( ICore::self()->activeSession()->config()->group("Project Manager").readEntry( "Save All Documents Before Building", true ) ) { ICore::self()->documentController()->saveAllDocuments( IDocument::Silent ); } ExecuteCompositeJob::start(); } diff --git a/plugins/debuggercommon/mi/micommand.cpp b/plugins/debuggercommon/mi/micommand.cpp index b7611e2fef..3a0174e99e 100644 --- a/plugins/debuggercommon/mi/micommand.cpp +++ b/plugins/debuggercommon/mi/micommand.cpp @@ -1,489 +1,402 @@ /*************************************************************************** begin : Sun Aug 8 1999 copyright : (C) 1999 by John Birch email : jbb@kdevelop.org ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "micommand.h" #include using namespace KDevMI::MI; FunctionCommandHandler::FunctionCommandHandler(const FunctionCommandHandler::Function& callback, CommandFlags flags) : _flags(flags) , _callback(callback) { } bool FunctionCommandHandler::handlesError() { return _flags & CmdHandlesError; } void FunctionCommandHandler::handle(const ResultRecord& r) { _callback(r); } MICommand::MICommand(CommandType type, const QString& command, CommandFlags flags) : type_(type) , flags_(flags) , command_(command) , commandHandler_(nullptr) , stateReloading_(false) , m_thread(-1) , m_frame(-1) { } MICommand::~MICommand() { if (commandHandler_ && commandHandler_->autoDelete()) { delete commandHandler_; } commandHandler_ = nullptr; } QString MICommand::cmdToSend() { return initialString() + '\n'; } QString MICommand::initialString() const { QString result = QString::number(token()); if (type() == NonMI) { result += command_; } else { result += miCommand(); if (m_thread != -1) result = result + QStringLiteral(" --thread %1").arg(m_thread); if (m_frame != -1) result = result + QStringLiteral(" --frame %1").arg(m_frame); if (!command_.isEmpty()) result += ' ' + command_; } return result; } bool MICommand::isUserCommand() const { return false; } void MICommand::setHandler(MICommandHandler* handler) { if (commandHandler_ && commandHandler_->autoDelete()) delete commandHandler_; commandHandler_ = handler; if (!commandHandler_) { flags_ = flags_ & ~CmdHandlesError; } } void MICommand::setHandler(const FunctionCommandHandler::Function& callback) { setHandler(new FunctionCommandHandler(callback, flags())); } bool MICommand::invokeHandler(const ResultRecord& r) { if (commandHandler_) { //ask before calling handler as it might deleted itself in handler bool autoDelete = commandHandler_->autoDelete(); commandHandler_->handle(r); if (autoDelete) { delete commandHandler_; } commandHandler_ = nullptr; return true; } else { return false; } } void MICommand::newOutput(const QString& line) { lines.push_back(line); } const QStringList& MICommand::allStreamOutput() const { return lines; } bool MICommand::handlesError() const { return commandHandler_ ? commandHandler_->handlesError() : false; } UserCommand::UserCommand(CommandType type, const QString& s) : MICommand(type, s, CmdMaybeStartsRunning) { } bool UserCommand::isUserCommand() const { return true; } QString MICommand::miCommand() const { - QString command; - switch (type()) { case NonMI: - command = QLatin1String(""); - break; + return QStringLiteral("-"); case BreakAfter: - command = QStringLiteral("break-after");//"ignore" - break; + return QStringLiteral("-break-after");//"ignore" case BreakCommands: - command = QStringLiteral("break-commands"); - break; + return QStringLiteral("-break-commands"); case BreakCondition: - command = QStringLiteral("break-condition");//"cond" - break; + return QStringLiteral("-break-condition");//"cond" case BreakDelete: - command = QStringLiteral("break-delete");//"delete breakpoint" - break; + return QStringLiteral("-break-delete");//"delete breakpoint" case BreakDisable: - command = QStringLiteral("break-disable");//"disable breakpoint" - break; + return QStringLiteral("-break-disable");//"disable breakpoint" case BreakEnable: - command = QStringLiteral("break-enable");//"enable breakpoint" - break; + return QStringLiteral("-break-enable");//"enable breakpoint" case BreakInfo: - command = QStringLiteral("break-info");//"info break" - break; + return QStringLiteral("-break-info");//"info break" case BreakInsert: - command = QStringLiteral("break-insert -f"); - break; + return QStringLiteral("-break-insert -f"); case BreakList: - command = QStringLiteral("break-list");//"info break" - break; + return QStringLiteral("-break-list");//"info break" case BreakWatch: - command = QStringLiteral("break-watch"); - break; + return QStringLiteral("-break-watch"); case DataDisassemble: - command = QStringLiteral("data-disassemble"); - break; + return QStringLiteral("-data-disassemble"); case DataEvaluateExpression: - command = QStringLiteral("data-evaluate-expression"); - break; + return QStringLiteral("-data-evaluate-expression"); case DataListChangedRegisters: - command = QStringLiteral("data-list-changed-registers"); - break; + return QStringLiteral("-data-list-changed-registers"); case DataListRegisterNames: - command = QStringLiteral("data-list-register-names"); - break; + return QStringLiteral("-data-list-register-names"); case DataListRegisterValues: - command = QStringLiteral("data-list-register-values"); - break; + return QStringLiteral("-data-list-register-values"); case DataReadMemory: - command = QStringLiteral("data-read-memory"); - break; + return QStringLiteral("-data-read-memory"); case DataWriteMemory: - command = QStringLiteral("data-write-memory"); - break; + return QStringLiteral("-data-write-memory"); case DataWriteRegisterVariables: - command = QStringLiteral("data-write-register-values"); - break; + return QStringLiteral("-data-write-register-values"); case EnablePrettyPrinting: - command = QStringLiteral("enable-pretty-printing"); - break; + return QStringLiteral("-enable-pretty-printing"); case EnableTimings: - command = QStringLiteral("enable-timings"); - break; + return QStringLiteral("-enable-timings"); case EnvironmentCd: - command = QStringLiteral("environment-cd"); - break; + return QStringLiteral("-environment-cd"); case EnvironmentDirectory: - command = QStringLiteral("environment-directory"); - break; + return QStringLiteral("-environment-directory"); case EnvironmentPath: - command = QStringLiteral("environment-path"); - break; + return QStringLiteral("-environment-path"); case EnvironmentPwd: - command = QStringLiteral("environment-pwd"); - break; + return QStringLiteral("-environment-pwd"); case ExecAbort: - command = QStringLiteral("exec-abort"); - break; + return QStringLiteral("-exec-abort"); case ExecArguments: - command = QStringLiteral("exec-arguments");//"set args" - break; + return QStringLiteral("-exec-arguments");//"set args" case ExecContinue: - command = QStringLiteral("exec-continue"); - break; + return QStringLiteral("-exec-continue"); case ExecFinish: - command = QStringLiteral("exec-finish"); - break; + return QStringLiteral("-exec-finish"); case ExecInterrupt: - command = QStringLiteral("exec-interrupt"); - break; + return QStringLiteral("-exec-interrupt"); case ExecNext: - command = QStringLiteral("exec-next"); - break; + return QStringLiteral("-exec-next"); case ExecNextInstruction: - command = QStringLiteral("exec-next-instruction"); - break; + return QStringLiteral("-exec-next-instruction"); case ExecRun: - command = QStringLiteral("exec-run"); - break; + return QStringLiteral("-exec-run"); case ExecStep: - command = QStringLiteral("exec-step"); - break; + return QStringLiteral("-exec-step"); case ExecStepInstruction: - command = QStringLiteral("exec-step-instruction"); - break; + return QStringLiteral("-exec-step-instruction"); case ExecUntil: - command = QStringLiteral("exec-until"); - break; + return QStringLiteral("-exec-until"); case FileExecAndSymbols: - command = QStringLiteral("file-exec-and-symbols");//"file" - break; + return QStringLiteral("-file-exec-and-symbols");//"file" case FileExecFile: - command = QStringLiteral("file-exec-file");//"exec-file" - break; + return QStringLiteral("-file-exec-file");//"exec-file" case FileListExecSourceFile: - command = QStringLiteral("file-list-exec-source-file"); - break; + return QStringLiteral("-file-list-exec-source-file"); case FileListExecSourceFiles: - command = QStringLiteral("file-list-exec-source-files"); - break; + return QStringLiteral("-file-list-exec-source-files"); case FileSymbolFile: - command = QStringLiteral("file-symbol-file");//"symbol-file" - break; + return QStringLiteral("-file-symbol-file");//"symbol-file" case GdbExit: - command = QStringLiteral("gdb-exit"); - break; + return QStringLiteral("-gdb-exit"); case GdbSet: - command = QStringLiteral("gdb-set");//"set" - break; + return QStringLiteral("-gdb-set");//"set" case GdbShow: - command = QStringLiteral("gdb-show");//"show" - break; + return QStringLiteral("-gdb-show");//"show" case GdbVersion: - command = QStringLiteral("gdb-version");//"show version" - break; + return QStringLiteral("-gdb-version");//"show version" case InferiorTtySet: - command = QStringLiteral("inferior-tty-set"); - break; + return QStringLiteral("-inferior-tty-set"); case InferiorTtyShow: - command = QStringLiteral("inferior-tty-show"); - break; + return QStringLiteral("-inferior-tty-show"); case InterpreterExec: - command = QStringLiteral("interpreter-exec"); - break; + return QStringLiteral("-interpreter-exec"); case ListFeatures: - command = QStringLiteral("list-features"); - break; + return QStringLiteral("-list-features"); case SignalHandle: return QStringLiteral("handle"); - //command = "signal-handle"; - break; + //return QStringLiteral("-signal-handle"); case StackInfoDepth: - command = QStringLiteral("stack-info-depth"); - break; + return QStringLiteral("-stack-info-depth"); case StackInfoFrame: - command = QStringLiteral("stack-info-frame"); - break; + return QStringLiteral("-stack-info-frame"); case StackListArguments: - command = QStringLiteral("stack-list-arguments"); - break; + return QStringLiteral("-stack-list-arguments"); case StackListFrames: - command = QStringLiteral("stack-list-frames"); - break; + return QStringLiteral("-stack-list-frames"); case StackListLocals: - command = QStringLiteral("stack-list-locals"); - break; + return QStringLiteral("-stack-list-locals"); case StackSelectFrame: - command = QStringLiteral("stack-select-frame"); - break; + return QStringLiteral("-stack-select-frame"); case SymbolListLines: - command = QStringLiteral("symbol-list-lines"); - break; + return QStringLiteral("-symbol-list-lines"); case TargetAttach: - command = QStringLiteral("target-attach"); - break; + return QStringLiteral("-target-attach"); case TargetDetach: - command = QStringLiteral("target-detach");//"detach" - break; + return QStringLiteral("-target-detach");//"detach" case TargetDisconnect: - command = QStringLiteral("target-disconnect");//"disconnect" - break; + return QStringLiteral("-target-disconnect");//"disconnect" case TargetDownload: - command = QStringLiteral("target-download"); - break; + return QStringLiteral("-target-download"); case TargetSelect: - command = QStringLiteral("target-select"); - break; + return QStringLiteral("-target-select"); case ThreadInfo: - command = QStringLiteral("thread-info"); - break; + return QStringLiteral("-thread-info"); case ThreadListIds: - command = QStringLiteral("thread-list-ids"); - break; + return QStringLiteral("-thread-list-ids"); case ThreadSelect: - command = QStringLiteral("thread-select"); - break; + return QStringLiteral("-thread-select"); case TraceFind: - command = QStringLiteral("trace-find"); - break; + return QStringLiteral("-trace-find"); case TraceStart: - command = QStringLiteral("trace-start"); - break; + return QStringLiteral("-trace-start"); case TraceStop: - command = QStringLiteral("trace-stop"); - break; + return QStringLiteral("-trace-stop"); case VarAssign: - command = QStringLiteral("var-assign"); - break; + return QStringLiteral("-var-assign"); case VarCreate: - command = QStringLiteral("var-create"); - break; + return QStringLiteral("-var-create"); case VarDelete: - command = QStringLiteral("var-delete"); - break; + return QStringLiteral("-var-delete"); case VarEvaluateExpression: - command = QStringLiteral("var-evaluate-expression"); - break; + return QStringLiteral("-var-evaluate-expression"); case VarInfoPathExpression: - command = QStringLiteral("var-info-path-expression"); - break; + return QStringLiteral("-var-info-path-expression"); case VarInfoNumChildren: - command = QStringLiteral("var-info-num-children"); - break; + return QStringLiteral("-var-info-num-children"); case VarInfoType: - command = QStringLiteral("var-info-type"); - break; + return QStringLiteral("-var-info-type"); case VarListChildren: - command = QStringLiteral("var-list-children"); - break; + return QStringLiteral("-var-list-children"); case VarSetFormat: - command = QStringLiteral("var-set-format"); - break; + return QStringLiteral("-var-set-format"); case VarSetFrozen: - command = QStringLiteral("var-set-frozen"); - break; + return QStringLiteral("-var-set-frozen"); case VarShowAttributes: - command = QStringLiteral("var-show-attributes"); - break; + return QStringLiteral("-var-show-attributes"); case VarShowFormat: - command = QStringLiteral("var-show-format"); - break; + return QStringLiteral("-var-show-format"); case VarUpdate: - command = QStringLiteral("var-update"); - break; - - default: - command = QStringLiteral("unknown"); - break; + return QStringLiteral("-var-update"); } - return '-' + command; + return QStringLiteral("-unknown"); } CommandType MICommand::type() const { return type_; } int MICommand::thread() const { return m_thread; } void MICommand::setThread(int thread) { m_thread = thread; } int MICommand::frame() const { return m_frame; } void MICommand::setFrame(int frame) { m_frame = frame; } QString MICommand::command() const { return command_; } void MICommand::setStateReloading(bool f) { stateReloading_ = f; } bool MICommand::stateReloading() const { return stateReloading_; } void MICommand::markAsEnqueued() { m_enqueueTimestamp = QDateTime::currentMSecsSinceEpoch(); } void MICommand::markAsSubmitted() { m_submitTimestamp = QDateTime::currentMSecsSinceEpoch(); } void MICommand::markAsCompleted() { m_completeTimestamp = QDateTime::currentMSecsSinceEpoch(); } qint64 MICommand::gdbProcessingTime() const { return m_completeTimestamp - m_submitTimestamp; } qint64 MICommand::queueTime() const { return m_submitTimestamp - m_enqueueTimestamp; } qint64 MICommand::totalProcessingTime() const { return m_completeTimestamp - m_enqueueTimestamp; } diff --git a/plugins/debuggercommon/midebugger.cpp b/plugins/debuggercommon/midebugger.cpp index 81ca2a71b6..d46f8f7c10 100644 --- a/plugins/debuggercommon/midebugger.cpp +++ b/plugins/debuggercommon/midebugger.cpp @@ -1,374 +1,371 @@ /* * Low level GDB interface. * * Copyright 1999 John Birch * Copyright 2007 Vladimir Prus * Copyright 2016 Aetf * * 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 "midebugger.h" #include "debuglog.h" #include "mi/micommand.h" #include #include #include #include #include #include #include #include #include #ifdef Q_OS_WIN #include #endif // #define DEBUG_NO_TRY //to get a backtrace to where the exception was thrown using namespace KDevMI; using namespace KDevMI::MI; MIDebugger::MIDebugger(QObject* parent) : QObject(parent) , m_process(nullptr) , m_currentCmd(nullptr) { m_process = new KProcess(this); m_process->setOutputChannelMode(KProcess::SeparateChannels); connect(m_process, &KProcess::readyReadStandardOutput, this, &MIDebugger::readyReadStandardOutput); connect(m_process, &KProcess::readyReadStandardError, this, &MIDebugger::readyReadStandardError); connect(m_process, static_cast(&KProcess::finished), this, &MIDebugger::processFinished); connect(m_process, static_cast(&KProcess::error), this, &MIDebugger::processErrored); } MIDebugger::~MIDebugger() { // prevent Qt warning: QProcess: Destroyed while process is still running. if (m_process && m_process->state() == QProcess::Running) { disconnect(m_process, static_cast(&KProcess::error), this, &MIDebugger::processErrored); m_process->kill(); m_process->waitForFinished(10); } } void MIDebugger::execute(MICommand* command) { m_currentCmd = command; QString commandText = m_currentCmd->cmdToSend(); qCDebug(DEBUGGERCOMMON) << "SEND:" << commandText.trimmed(); QByteArray commandUtf8 = commandText.toUtf8(); m_process->write(commandUtf8, commandUtf8.length()); command->markAsSubmitted(); QString prettyCmd = m_currentCmd->cmdToSend(); prettyCmd.remove( QRegExp("set prompt \032.\n") ); prettyCmd = "(gdb) " + prettyCmd; if (m_currentCmd->isUserCommand()) emit userCommandOutput(prettyCmd); else emit internalCommandOutput(prettyCmd); } bool MIDebugger::isReady() const { return m_currentCmd == nullptr; } void MIDebugger::interrupt() { #ifndef Q_OS_WIN int pid = m_process->pid(); if (pid != 0) { ::kill(pid, SIGINT); } #else SetConsoleCtrlHandler(nullptr, true); GenerateConsoleCtrlEvent(0, 0); SetConsoleCtrlHandler(nullptr, false); #endif } MICommand* MIDebugger::currentCommand() const { return m_currentCmd; } void MIDebugger::kill() { m_process->kill(); } void MIDebugger::readyReadStandardOutput() { m_process->setReadChannel(QProcess::StandardOutput); m_buffer += m_process->readAll(); for (;;) { /* In MI mode, all messages are exactly one line. See if we have any complete lines in the buffer. */ int i = m_buffer.indexOf('\n'); if (i == -1) break; QByteArray reply(m_buffer.left(i)); m_buffer = m_buffer.mid(i+1); processLine(reply); } } void MIDebugger::readyReadStandardError() { m_process->setReadChannel(QProcess::StandardError); emit debuggerInternalOutput(QString::fromUtf8(m_process->readAll())); } void MIDebugger::processLine(const QByteArray& line) { qCDebug(DEBUGGERCOMMON) << "Debugger (" << m_process->pid() <<") output: " << line; FileSymbol file; file.contents = line; std::unique_ptr r(m_parser.parse(&file)); if (!r) { // simply ignore the invalid MI message because both gdb and lldb // sometimes produces invalid messages that can be safely ignored. qCDebug(DEBUGGERCOMMON) << "Invalid MI message:" << line; // We don't consider the current command done. // So, if a command results in unparseable reply, // we'll just wait for the "right" reply, which might // never come. However, marking the command as // done in this case is even more risky. // It's probably possible to get here if we're debugging // natively without PTY, though this is uncommon case. return; } #ifndef DEBUG_NO_TRY try { #endif switch(r->kind) { case MI::Record::Result: { MI::ResultRecord& result = static_cast(*r); // it's still possible for the user to issue a MI command, // emit correct signal if (m_currentCmd && m_currentCmd->isUserCommand()) { emit userCommandOutput(QString::fromUtf8(line) + '\n'); } else { emit internalCommandOutput(QString::fromUtf8(line) + '\n'); } // protect against wild replies that sometimes returned from gdb without a pending command if (!m_currentCmd) { qCWarning(DEBUGGERCOMMON) << "Received a result without a pending command"; throw std::runtime_error("Received a result without a pending command"); } else if (m_currentCmd->token() != result.token) { std::stringstream ss; ss << "Received a result with token not matching pending command. " << "Pending: " << m_currentCmd->token() << "Received: " << result.token; qCWarning(DEBUGGERCOMMON) << ss.str().c_str(); throw std::runtime_error(ss.str()); } // GDB doc: "running" and "exit" are status codes equivalent to "done" if (result.reason == QLatin1String("done") || result.reason == QLatin1String("running") || result.reason == QLatin1String("exit")) { qCDebug(DEBUGGERCOMMON) << "Result token is" << result.token; m_currentCmd->markAsCompleted(); qCDebug(DEBUGGERCOMMON) << "Command successful, times " << m_currentCmd->totalProcessingTime() << m_currentCmd->queueTime() << m_currentCmd->gdbProcessingTime(); m_currentCmd->invokeHandler(result); } else if (result.reason == QLatin1String("error")) { qCDebug(DEBUGGERCOMMON) << "Handling error"; m_currentCmd->markAsCompleted(); qCDebug(DEBUGGERCOMMON) << "Command error, times" << m_currentCmd->totalProcessingTime() << m_currentCmd->queueTime() << m_currentCmd->gdbProcessingTime(); // Some commands want to handle errors themself. if (m_currentCmd->handlesError() && m_currentCmd->invokeHandler(result)) { qCDebug(DEBUGGERCOMMON) << "Invoked custom handler\n"; // Done, nothing more needed } else emit error(result); } else { qCDebug(DEBUGGERCOMMON) << "Unhandled result code: " << result.reason; } delete m_currentCmd; m_currentCmd = nullptr; emit ready(); break; } case MI::Record::Async: { MI::AsyncRecord& async = static_cast(*r); switch (async.subkind) { case MI::AsyncRecord::Exec: { // Prefix '*'; asynchronous state changes of the target if (async.reason == QLatin1String("stopped")) { emit programStopped(async); } else if (async.reason == QLatin1String("running")) { emit programRunning(); } else { qCDebug(DEBUGGERCOMMON) << "Unhandled exec notification: " << async.reason; } break; } case MI::AsyncRecord::Notify: { // Prefix '='; supplementary information that we should handle (new breakpoint etc.) emit notification(async); break; } case MI::AsyncRecord::Status: { // Prefix '+'; GDB documentation: // On-going status information about progress of a slow operation; may be ignored break; } - - default: - Q_ASSERT(false); } break; } case MI::Record::Stream: { MI::StreamRecord& s = static_cast(*r); if (s.subkind == MI::StreamRecord::Target) { emit applicationOutput(s.message); } else if (s.subkind == MI::StreamRecord::Console) { if (m_currentCmd && m_currentCmd->isUserCommand()) emit userCommandOutput(s.message); else emit internalCommandOutput(s.message); if (m_currentCmd) m_currentCmd->newOutput(s.message); } else { emit debuggerInternalOutput(s.message); } emit streamRecord(s); break; } case MI::Record::Prompt: break; } #ifndef DEBUG_NO_TRY } catch(const std::exception& e) { KMessageBox::detailedSorry( qApp->activeWindow(), i18nc("Internal debugger error", "

The debugger component encountered internal error while " "processing reply from gdb. Please submit a bug report. " "The debug session will now end to prevent potential crash"), i18n("The exception is: %1\n" "The MI response is: %2", e.what(), QString::fromLatin1(line)), i18n("Internal debugger error")); emit exited(true, e.what()); } #endif } void MIDebugger::processFinished(int exitCode, QProcess::ExitStatus exitStatus) { qCDebug(DEBUGGERCOMMON) << "Debugger FINISHED\n"; bool abnormal = exitCode != 0 || exitStatus != QProcess::NormalExit; emit userCommandOutput(QStringLiteral("Process exited\n")); emit exited(abnormal, i18n("Process exited")); } void MIDebugger::processErrored(QProcess::ProcessError error) { qCDebug(DEBUGGERCOMMON) << "Debugger ERRORED" << error; if(error == QProcess::FailedToStart) { KMessageBox::information( qApp->activeWindow(), i18n("Could not start debugger." "

Could not run '%1'. " "Make sure that the path name is specified correctly.", m_debuggerExecutable), i18n("Could not start debugger")); emit userCommandOutput(QStringLiteral("Process failed to start\n")); emit exited(true, i18n("Process failed to start")); } else if (error == QProcess::Crashed) { KMessageBox::error( qApp->activeWindow(), i18n("Debugger crashed." "

The debugger process '%1' crashed.
" "Because of that the debug session has to be ended.
" "Try to reproduce the crash without KDevelop and report a bug.
", m_debuggerExecutable), i18n("Debugger crashed")); emit userCommandOutput(QStringLiteral("Process crashed\n")); emit exited(true, i18n("Process crashed")); } } diff --git a/plugins/debuggercommon/widgets/disassemblewidget.cpp b/plugins/debuggercommon/widgets/disassemblewidget.cpp index 6552f8fa89..a6fc1919a0 100644 --- a/plugins/debuggercommon/widgets/disassemblewidget.cpp +++ b/plugins/debuggercommon/widgets/disassemblewidget.cpp @@ -1,539 +1,538 @@ /* * GDB Debugger Support * * Copyright 2000 John Birch * Copyright 2006 Vladimir Prus * Copyright 2007 Hamish Rodda * Copyright 2013 Vlas Puhov * * 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 "disassemblewidget.h" #include "midebuggerplugin.h" #include "debuglog.h" #include "midebugsession.h" #include "mi/micommand.h" #include "registers/registersmanager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevMI; using namespace KDevMI::MI; SelectAddressDialog::SelectAddressDialog(QWidget* parent) : QDialog(parent) { m_ui.setupUi(this); setWindowTitle(i18n("Address Selector")); connect(m_ui.comboBox, &KHistoryComboBox::editTextChanged, this, &SelectAddressDialog::validateInput); connect(m_ui.comboBox, static_cast(&KHistoryComboBox::returnPressed), this, &SelectAddressDialog::itemSelected); } QString SelectAddressDialog::address() const { return hasValidAddress() ? m_ui.comboBox->currentText() : QString(); } bool SelectAddressDialog::hasValidAddress() const { bool ok; m_ui.comboBox->currentText().toLongLong(&ok, 16); return ok; } void SelectAddressDialog::updateOkState() { m_ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(hasValidAddress()); } void SelectAddressDialog::validateInput() { updateOkState(); } void SelectAddressDialog::itemSelected() { QString text = m_ui.comboBox->currentText(); if( hasValidAddress() && m_ui.comboBox->findText(text) < 0 ) m_ui.comboBox->addItem(text); } DisassembleWindow::DisassembleWindow(QWidget *parent, DisassembleWidget* widget) : QTreeWidget(parent) { /*context menu commands */{ m_selectAddrAction = new QAction(i18n("Change &address"), this); m_selectAddrAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); connect(m_selectAddrAction, &QAction::triggered, widget, &DisassembleWidget::slotChangeAddress); m_jumpToLocation = new QAction(QIcon::fromTheme(QStringLiteral("debug-execute-to-cursor")), i18n("&Jump to Cursor"), this); m_jumpToLocation->setWhatsThis(i18n("Sets the execution pointer to the current cursor position.")); connect(m_jumpToLocation,&QAction::triggered, widget, &DisassembleWidget::jumpToCursor); m_runUntilCursor = new QAction(QIcon::fromTheme(QStringLiteral("debug-run-cursor")), i18n("&Run to Cursor"), this); m_runUntilCursor->setWhatsThis(i18n("Continues execution until the cursor position is reached.")); connect(m_runUntilCursor,&QAction::triggered, widget, &DisassembleWidget::runToCursor); m_disassemblyFlavorAtt = new QAction(i18n("&AT&&T"), this); m_disassemblyFlavorAtt->setToolTip(i18n("GDB will use the AT&T disassembly flavor (e.g. mov 0xc(%ebp),%eax).")); m_disassemblyFlavorAtt->setData(DisassemblyFlavorATT); m_disassemblyFlavorAtt->setCheckable(true); m_disassemblyFlavorIntel = new QAction(i18n("&Intel"), this); m_disassemblyFlavorIntel->setToolTip(i18n("GDB will use the Intel disassembly flavor (e.g. mov eax, DWORD PTR [ebp+0xc]).")); m_disassemblyFlavorIntel->setData(DisassemblyFlavorIntel); m_disassemblyFlavorIntel->setCheckable(true); m_disassemblyFlavorActionGroup = new QActionGroup(this); m_disassemblyFlavorActionGroup->setExclusive(true); m_disassemblyFlavorActionGroup->addAction(m_disassemblyFlavorAtt); m_disassemblyFlavorActionGroup->addAction(m_disassemblyFlavorIntel); connect(m_disassemblyFlavorActionGroup, &QActionGroup::triggered, widget, &DisassembleWidget::setDisassemblyFlavor); } } void DisassembleWindow::setDisassemblyFlavor(DisassemblyFlavor flavor) { switch(flavor) { - default: case DisassemblyFlavorUnknown: m_disassemblyFlavorAtt->setChecked(false); m_disassemblyFlavorIntel->setChecked(false); break; case DisassemblyFlavorATT: m_disassemblyFlavorAtt->setChecked(true); m_disassemblyFlavorIntel->setChecked(false); break; case DisassemblyFlavorIntel: m_disassemblyFlavorAtt->setChecked(false); m_disassemblyFlavorIntel->setChecked(true); break; } } void DisassembleWindow::contextMenuEvent(QContextMenuEvent *e) { QMenu popup(this); popup.addAction(m_selectAddrAction); popup.addAction(m_jumpToLocation); popup.addAction(m_runUntilCursor); QMenu* disassemblyFlavorMenu = popup.addMenu(i18n("Disassembly flavor")); disassemblyFlavorMenu->addAction(m_disassemblyFlavorAtt); disassemblyFlavorMenu->addAction(m_disassemblyFlavorIntel); popup.exec(e->globalPos()); } /***************************************************************************/ /***************************************************************************/ /***************************************************************************/ DisassembleWidget::DisassembleWidget(MIDebuggerPlugin* plugin, QWidget *parent) : QWidget(parent), active_(false), lower_(0), upper_(0), address_(0), m_splitter(new KDevelop::AutoOrientedSplitter(this)) { QVBoxLayout* topLayout = new QVBoxLayout(this); topLayout->setMargin(0); QHBoxLayout* controlsLayout = new QHBoxLayout; topLayout->addLayout(controlsLayout); { // initialize disasm/registers views topLayout->addWidget(m_splitter); //topLayout->setMargin(0); m_disassembleWindow = new DisassembleWindow(m_splitter, this); m_disassembleWindow->setWhatsThis(i18n("Machine code display

" "A machine code view into your running " "executable with the current instruction " "highlighted. You can step instruction by " "instruction using the debuggers toolbar " "buttons of \"step over\" instruction and " "\"step into\" instruction.")); m_disassembleWindow->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); m_disassembleWindow->setSelectionMode(QTreeWidget::SingleSelection); m_disassembleWindow->setColumnCount(ColumnCount); m_disassembleWindow->setUniformRowHeights(true); m_disassembleWindow->setRootIsDecorated(false); m_disassembleWindow->setHeaderLabels(QStringList() << QLatin1String("") << i18n("Address") << i18n("Function") << i18n("Instruction")); m_splitter->setStretchFactor(0, 1); m_splitter->setContentsMargins(0, 0, 0, 0); m_registersManager = new RegistersManager(m_splitter); m_config = KSharedConfig::openConfig()->group("Disassemble/Registers View"); QByteArray state = m_config.readEntry("splitterState", QByteArray()); if (!state.isEmpty()) { m_splitter->restoreState(state); } } setLayout(topLayout); setWindowIcon( QIcon::fromTheme(QStringLiteral("system-run"), windowIcon()) ); setWindowTitle(i18n("Disassemble/Registers View")); KDevelop::IDebugController* pDC=KDevelop::ICore::self()->debugController(); Q_ASSERT(pDC); connect(pDC, &KDevelop::IDebugController::currentSessionChanged, this, &DisassembleWidget::currentSessionChanged); connect(plugin, &MIDebuggerPlugin::reset, this, &DisassembleWidget::slotDeactivate); m_dlg = new SelectAddressDialog(this); // show the data if debug session is active KDevelop::IDebugSession* pS = pDC->currentSession(); currentSessionChanged(pS); if(pS && !pS->currentAddr().isEmpty()) slotShowStepInSource(pS->currentUrl(), pS->currentLine(), pS->currentAddr()); } void DisassembleWidget::jumpToCursor() { MIDebugSession *s = qobject_cast(KDevelop::ICore:: self()->debugController()->currentSession()); if (s && s->isRunning()) { QString address = m_disassembleWindow->selectedItems().at(0)->text(Address); s->jumpToMemoryAddress(address); } } void DisassembleWidget::runToCursor(){ MIDebugSession *s = qobject_cast(KDevelop::ICore:: self()->debugController()->currentSession()); if (s && s->isRunning()) { QString address = m_disassembleWindow->selectedItems().at(0)->text(Address); s->runUntil(address); } } void DisassembleWidget::currentSessionChanged(KDevelop::IDebugSession* s) { MIDebugSession *session = qobject_cast(s); enableControls( session != nullptr ); // disable if session closed m_registersManager->setSession(session); if (session) { connect(session, &MIDebugSession::showStepInSource, this, &DisassembleWidget::slotShowStepInSource); connect(session,&MIDebugSession::showStepInDisassemble,this, &DisassembleWidget::update); } } /***************************************************************************/ DisassembleWidget::~DisassembleWidget() { m_config.writeEntry("splitterState", m_splitter->saveState()); } /***************************************************************************/ bool DisassembleWidget::displayCurrent() { if(address_ < lower_ || address_ > upper_) return false; bool bFound=false; for (int line=0; line < m_disassembleWindow->topLevelItemCount(); line++) { QTreeWidgetItem* item = m_disassembleWindow->topLevelItem(line); unsigned long address = item->text(Address).toULong(&ok,16); if (address == address_) { // put cursor at start of line and highlight the line m_disassembleWindow->setCurrentItem(item); static const QIcon icon = QIcon::fromTheme(QStringLiteral("go-next")); item->setIcon(Icon, icon); bFound = true; // need to process all items to clear icons } else if(!item->icon(Icon).isNull()) item->setIcon(Icon, QIcon()); } return bFound; } /***************************************************************************/ void DisassembleWidget::slotActivate(bool activate) { qCDebug(DEBUGGERCOMMON) << "Disassemble widget active: " << activate; if (active_ != activate) { active_ = activate; if (active_) { updateDisassemblyFlavor(); m_registersManager->updateRegisters(); if (!displayCurrent()) disassembleMemoryRegion(); } } } /***************************************************************************/ void DisassembleWidget::slotShowStepInSource(const QUrl&, int, const QString& currentAddress) { update(currentAddress); } void DisassembleWidget::updateExecutionAddressHandler(const ResultRecord& r) { const Value& content = r[QStringLiteral("asm_insns")]; const Value& pc = content[0]; if( pc.hasField(QStringLiteral("address")) ){ QString addr = pc[QStringLiteral("address")].literal(); address_ = addr.toULong(&ok,16); disassembleMemoryRegion(addr); } } /***************************************************************************/ void DisassembleWidget::disassembleMemoryRegion(const QString& from, const QString& to) { MIDebugSession *s = qobject_cast(KDevelop::ICore:: self()->debugController()->currentSession()); if(!s || !s->isRunning()) return; //only get $pc if (from.isEmpty()){ s->addCommand(DataDisassemble, QStringLiteral("-s \"$pc\" -e \"$pc+1\" -- 0"), this, &DisassembleWidget::updateExecutionAddressHandler); }else{ QString cmd = (to.isEmpty())? QStringLiteral("-s %1 -e \"%1 + 256\" -- 0").arg(from ): QStringLiteral("-s %1 -e %2+1 -- 0").arg(from, to); // if both addr set s->addCommand(DataDisassemble, cmd, this, &DisassembleWidget::disassembleMemoryHandler); } } /***************************************************************************/ void DisassembleWidget::disassembleMemoryHandler(const ResultRecord& r) { const Value& content = r[QStringLiteral("asm_insns")]; QString currentFunction; m_disassembleWindow->clear(); for(int i = 0; i < content.size(); ++i) { const Value& line = content[i]; QString addr, fct, offs, inst; if( line.hasField(QStringLiteral("address")) ) addr = line[QStringLiteral("address")].literal(); if( line.hasField(QStringLiteral("func-name")) ) fct = line[QStringLiteral("func-name")].literal(); if( line.hasField(QStringLiteral("offset")) ) offs = line[QStringLiteral("offset")].literal(); if( line.hasField(QStringLiteral("inst")) ) inst = line[QStringLiteral("inst")].literal(); //We use offset at the same column where function is. if(currentFunction == fct){ if(!fct.isEmpty()){ fct = QStringLiteral("+") + offs; } }else { currentFunction = fct; } m_disassembleWindow->addTopLevelItem(new QTreeWidgetItem(m_disassembleWindow, QStringList() << QString() << addr << fct << inst)); if (i == 0) { lower_ = addr.toULong(&ok,16); } else if (i == content.size()-1) { upper_ = addr.toULong(&ok,16); } } displayCurrent(); m_disassembleWindow->resizeColumnToContents(Icon); // make Icon always visible m_disassembleWindow->resizeColumnToContents(Address); // make entire address always visible } void DisassembleWidget::showEvent(QShowEvent*) { slotActivate(true); //it doesn't work for large names of functions // for (int i = 0; i < m_disassembleWindow->model()->columnCount(); ++i) // m_disassembleWindow->resizeColumnToContents(i); } void DisassembleWidget::hideEvent(QHideEvent*) { slotActivate(false); } void DisassembleWidget::slotDeactivate() { slotActivate(false); } void DisassembleWidget::enableControls(bool enabled) { m_disassembleWindow->setEnabled(enabled); } void DisassembleWidget::slotChangeAddress() { if(!m_dlg) return; m_dlg->updateOkState(); if (!m_disassembleWindow->selectedItems().isEmpty()) { m_dlg->setAddress(m_disassembleWindow->selectedItems().first()->text(Address)); } if (m_dlg->exec() == QDialog::Rejected) return; unsigned long addr = m_dlg->address().toULong(&ok,16); if (addr < lower_ || addr > upper_ || !displayCurrent()) disassembleMemoryRegion(m_dlg->address()); } void SelectAddressDialog::setAddress ( const QString& address ) { m_ui.comboBox->setCurrentItem ( address, true ); } void DisassembleWidget::update(const QString &address) { if (!active_) { return; } address_ = address.toULong(&ok, 16); if (!displayCurrent()) { disassembleMemoryRegion(); } m_registersManager->updateRegisters(); } void DisassembleWidget::setDisassemblyFlavor(QAction* action) { MIDebugSession* s = qobject_cast(KDevelop::ICore:: self()->debugController()->currentSession()); if(!s || !s->isRunning()) { return; } DisassemblyFlavor disassemblyFlavor = static_cast(action->data().toInt()); QString cmd; switch(disassemblyFlavor) { default: // unknown flavor, do not build a GDB command break; case DisassemblyFlavorATT: cmd = QStringLiteral("disassembly-flavor att"); break; case DisassemblyFlavorIntel: cmd = QStringLiteral("disassembly-flavor intel"); break; } qCDebug(DEBUGGERCOMMON) << "Disassemble widget set " << cmd; if (!cmd.isEmpty()) { s->addCommand(GdbSet, cmd, this, &DisassembleWidget::setDisassemblyFlavorHandler); } } void DisassembleWidget::setDisassemblyFlavorHandler(const ResultRecord& r) { if (r.reason == QLatin1String("done") && active_) { disassembleMemoryRegion(); } } void DisassembleWidget::updateDisassemblyFlavor() { MIDebugSession* s = qobject_cast(KDevelop::ICore:: self()->debugController()->currentSession()); if(!s || !s->isRunning()) { return; } s->addCommand(GdbShow, QStringLiteral("disassembly-flavor"), this, &DisassembleWidget::showDisassemblyFlavorHandler); } void DisassembleWidget::showDisassemblyFlavorHandler(const ResultRecord& r) { const Value& value = r[QStringLiteral("value")]; qCDebug(DEBUGGERCOMMON) << "Disassemble widget disassembly flavor" << value.literal(); DisassemblyFlavor disassemblyFlavor = DisassemblyFlavorUnknown; if (value.literal() == QLatin1String("att")) { disassemblyFlavor = DisassemblyFlavorATT; } else if (value.literal() == QLatin1String("intel")) { disassemblyFlavor = DisassemblyFlavorIntel; } else if (value.literal() == QLatin1String("default")) { disassemblyFlavor = DisassemblyFlavorATT; } m_disassembleWindow->setDisassemblyFlavor(disassemblyFlavor); } diff --git a/plugins/documentview/kdevdocumentmodel.cpp b/plugins/documentview/kdevdocumentmodel.cpp index 06bfc75182..53074ac13d 100644 --- a/plugins/documentview/kdevdocumentmodel.cpp +++ b/plugins/documentview/kdevdocumentmodel.cpp @@ -1,164 +1,163 @@ /* This file is part of KDevelop Copyright 2005 Adam Treat Copyright 2013 Sebastian Kügler 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 "kdevdocumentmodel.h" #include using namespace KDevelop; KDevDocumentItem::KDevDocumentItem( const QString &name ) : QStandardItem(name) , m_documentState(IDocument::Clean) { setIcon( icon() ); } KDevDocumentItem::~KDevDocumentItem() {} QIcon KDevDocumentItem::icon() const { switch(m_documentState) { case IDocument::Clean: return QIcon::fromTheme(m_fileIcon); case IDocument::Modified: return QIcon::fromTheme(QStringLiteral("document-save")); case IDocument::Dirty: return QIcon::fromTheme(QStringLiteral("document-revert")); case IDocument::DirtyAndModified: return QIcon::fromTheme(QStringLiteral("edit-delete")); - default: - return QIcon(); } + Q_UNREACHABLE(); } IDocument::DocumentState KDevDocumentItem::documentState() const { return m_documentState; } void KDevDocumentItem::setDocumentState(IDocument::DocumentState state) { m_documentState = state; setIcon(icon()); } const QUrl KDevDocumentItem::url() const { return m_url; } void KDevDocumentItem::setUrl(const QUrl& url) { m_url = url; } QVariant KDevDocumentItem::data(int role) const { if (role == UrlRole) { return m_url; } return QStandardItem::data(role); } KDevCategoryItem::KDevCategoryItem( const QString &name ) : KDevDocumentItem( name ) { setFlags(Qt::ItemIsEnabled); setToolTip( name ); setIcon( QIcon::fromTheme( QStringLiteral( "folder") ) ); } KDevCategoryItem::~KDevCategoryItem() {} QList KDevCategoryItem::fileList() const { QList lst; for ( int i = 0; i < rowCount(); ++i ) { if (KDevFileItem* item = static_cast(child(i))->fileItem()) lst.append( item ); } return lst; } KDevFileItem* KDevCategoryItem::file( const QUrl &url ) const { foreach( KDevFileItem * item, fileList() ) { if ( item->url() == url ) return item; } return nullptr; } KDevFileItem::KDevFileItem( const QUrl &url ) : KDevDocumentItem( url.fileName() ) { setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); setUrl( url ); if (!url.isEmpty()) { m_fileIcon = KFileItem(url, QString(), 0).iconName(); } setIcon( QIcon::fromTheme( m_fileIcon ) ); } KDevFileItem::~KDevFileItem() {} KDevDocumentModel::KDevDocumentModel( QObject *parent ) : QStandardItemModel( parent ) { setRowCount(0); setColumnCount(1); } KDevDocumentModel::~KDevDocumentModel() {} QList KDevDocumentModel::categoryList() const { QList lst; for ( int i = 0; i < rowCount() ; ++i ) { if (KDevCategoryItem* categoryitem = static_cast(item(i))->categoryItem()) { lst.append( categoryitem ); } } return lst; } KDevCategoryItem* KDevDocumentModel::category( const QString& category ) const { foreach( KDevCategoryItem * item, categoryList() ) { if ( item->toolTip() == category ) return item; } return nullptr; } diff --git a/plugins/filetemplates/overridespage.cpp b/plugins/filetemplates/overridespage.cpp index 53f71008a1..d6dc828c00 100644 --- a/plugins/filetemplates/overridespage.cpp +++ b/plugins/filetemplates/overridespage.cpp @@ -1,274 +1,271 @@ /* Copyright 2008 Hamish Rodda 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 "overridespage.h" #include "ui_overridevirtuals.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; enum Column { ClassOrFunctionColumn, ///< Column represents either a base class item or a function item AccessColumn, ///< Column represents the access policy of a function PropertiesColumn ///< Column represents the properties of a function (e.g. if it's a ctor, dtor, signal, slot, ...) }; static QString accessPolicyToString(Declaration::AccessPolicy accessPolicy) { switch (accessPolicy) { case Declaration::DefaultAccess: case Declaration::Public: return i18n("Public"); case Declaration::Protected: return i18n("Protected"); case Declaration::Private: return i18n("Private"); - default: - qCritical("Unexpected value for Declaration::AccessPolicy: %d", accessPolicy); - Q_ASSERT(false); - return QString(); } + Q_UNREACHABLE(); } static QString functionPropertiesToString(ClassFunctionDeclaration* decl) { Q_ASSERT(decl); QStringList properties; if (decl->isConstructor()) { properties << i18n("Constructor"); } else if (decl->isDestructor()) { properties << i18n("Destructor"); } else if (decl->isSignal()) { properties << i18n("Signal"); } else if (decl->isSlot()) { properties << i18n("Slot"); } else if (decl->isAbstract()) { properties << i18n("Abstract function"); } return properties.join(QStringLiteral(", ")); } struct KDevelop::OverridesPagePrivate { OverridesPagePrivate() : overrides(nullptr) { } Ui::OverridesDialog* overrides; QMultiHash overriddenFunctions; QMap declarationMap; QList chosenOverrides; }; OverridesPage::OverridesPage(QWidget* parent) : QWidget(parent) , d(new OverridesPagePrivate) { d->overrides = new Ui::OverridesDialog; d->overrides->setupUi(this); connect(d->overrides->selectAllPushButton, &QPushButton::pressed, this, &OverridesPage::selectAll); connect(d->overrides->deselectAllPushButton, &QPushButton::pressed, this, &OverridesPage::deselectAll); } OverridesPage::~OverridesPage() { delete d->overrides; delete d; } QList< DeclarationPointer > OverridesPage::selectedOverrides() const { QList declarations; for (int i = 0; i < d->overrides->overridesTree->topLevelItemCount(); ++i) { QTreeWidgetItem* item = d->overrides->overridesTree->topLevelItem(i); for (int j = 0; j < item->childCount(); ++j) { QTreeWidgetItem* child = item->child(j); if (child->checkState(ClassOrFunctionColumn) == Qt::Checked) { qCDebug(PLUGIN_FILETEMPLATES) << "Adding declaration" << d->declarationMap[child]->toString(); declarations << d->declarationMap[child]; } } } qCDebug(PLUGIN_FILETEMPLATES) << declarations.size(); return declarations; } void OverridesPage::clear() { d->overriddenFunctions.clear(); overrideTree()->clear(); d->chosenOverrides.clear(); d->declarationMap.clear(); } void OverridesPage::addBaseClasses(const QList& directBases, const QList& allBases) { DUChainReadLocker lock; foreach(const DeclarationPointer& baseClass, allBases) { QTreeWidgetItem* classItem = new QTreeWidgetItem(overrideTree(), QStringList() << baseClass->qualifiedIdentifier().toString()); classItem->setIcon(ClassOrFunctionColumn, DUChainUtils::iconForDeclaration(baseClass.data())); DUContext* context = baseClass->internalContext(); if (!context) { continue; } //For this internal context get all the function declarations inside the class foreach (Declaration * childDeclaration, context->localDeclarations()) { if (AbstractFunctionDeclaration * func = dynamic_cast(childDeclaration)) { if (func->isVirtual()) { // Its a virtual function, add it to the list unless it's a destructor ClassFunctionDeclaration* cFunc = dynamic_cast(childDeclaration); if (cFunc && !cFunc->isDestructor()) { addPotentialOverride(classItem, DeclarationPointer(childDeclaration)); } } else if (directBases.contains(baseClass)) { // add ctors of direct parents ClassFunctionDeclaration* cFunc = dynamic_cast(childDeclaration); if (cFunc && cFunc->isConstructor()) { addPotentialOverride(classItem, DeclarationPointer(childDeclaration)); } } } } } overrideTree()->expandAll(); overrideTree()->header()->resizeSections(QHeaderView::ResizeToContents); } void OverridesPage::addPotentialOverride(QTreeWidgetItem* classItem, const DeclarationPointer& childDeclaration) { ClassFunctionDeclaration* function = dynamic_cast(childDeclaration.data()); if (!function) { qCDebug(PLUGIN_FILETEMPLATES) << "Declaration is not a function:" << childDeclaration->identifier().toString(); return; } if (function->accessPolicy() == Declaration::Private) { qCDebug(PLUGIN_FILETEMPLATES) << "Declaration is private, returning:" << function->identifier().toString(); return; } qCDebug(PLUGIN_FILETEMPLATES) << childDeclaration->toString(); if (d->overriddenFunctions.contains(childDeclaration->identifier())) { foreach (const DeclarationPointer& decl, d->overriddenFunctions.values(childDeclaration->identifier())) { if (decl->indexedType() == childDeclaration->indexedType()) { qCDebug(PLUGIN_FILETEMPLATES) << "Declaration is already shown"; return; } } } d->overriddenFunctions.insert(childDeclaration->identifier(), childDeclaration); QTreeWidgetItem* overrideItem = new QTreeWidgetItem(classItem, QStringList() << childDeclaration->toString()); overrideItem->setFlags( Qt::ItemFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable) ); overrideItem->setCheckState(ClassOrFunctionColumn, d->chosenOverrides.contains(childDeclaration) ? Qt::Checked : Qt::Unchecked); overrideItem->setIcon(ClassOrFunctionColumn, DUChainUtils::iconForDeclaration(childDeclaration.data())); overrideItem->setData(ClassOrFunctionColumn, Qt::UserRole, QVariant::fromValue(IndexedDeclaration(childDeclaration.data()))); overrideItem->setText(AccessColumn, accessPolicyToString(function->accessPolicy())); overrideItem->setText(PropertiesColumn, functionPropertiesToString(function)); if (function->isAbstract()) { overrideItem->setIcon(ClassOrFunctionColumn, QIcon::fromTheme(QStringLiteral("flag-red"))); overrideItem->setCheckState(ClassOrFunctionColumn, Qt::Checked); classItem->removeChild(overrideItem); classItem->insertChild(0, overrideItem); } d->declarationMap[overrideItem] = childDeclaration; } QTreeWidget* OverridesPage::overrideTree() const { return d->overrides->overridesTree; } QWidget* OverridesPage::extraFunctionsContainer() const { return d->overrides->extraFunctionWidget; } void OverridesPage::selectAll() { for (int i = 0; i < d->overrides->overridesTree->topLevelItemCount(); ++i) { QTreeWidgetItem* item = d->overrides->overridesTree->topLevelItem(i); for (int j = 0; j < item->childCount(); ++j) item->child(j)->setCheckState(ClassOrFunctionColumn, Qt::Checked); } } void OverridesPage::deselectAll() { for (int i = 0; i < d->overrides->overridesTree->topLevelItemCount(); ++i) { QTreeWidgetItem* item = d->overrides->overridesTree->topLevelItem(i); for (int j = 0; j < item->childCount(); ++j) item->child(j)->setCheckState(ClassOrFunctionColumn, Qt::Unchecked); } } void OverridesPage::addCustomDeclarations (const QString& category, const QList& declarations) { qCDebug(PLUGIN_FILETEMPLATES) << category << declarations.size(); DUChainReadLocker lock(DUChain::lock()); QTreeWidgetItem* item = new QTreeWidgetItem(overrideTree(), QStringList() << category); foreach (const DeclarationPointer& declaration, declarations) { addPotentialOverride(item, declaration); } overrideTree()->expandAll(); overrideTree()->header()->resizeSections(QHeaderView::ResizeToContents); } void OverridesPage::setFocusToFirstEditWidget() { d->overrides->overridesTree->setFocus(); } diff --git a/plugins/testview/testview.cpp b/plugins/testview/testview.cpp index a52b7cb304..e81f060bbf 100644 --- a/plugins/testview/testview.cpp +++ b/plugins/testview/testview.cpp @@ -1,411 +1,409 @@ /* This file is part of KDevelop Copyright 2012 Miha Čančula 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "testview.h" #include "testviewplugin.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; enum CustomRoles { ProjectRole = Qt::UserRole + 1, SuiteRole, CaseRole }; TestView::TestView(TestViewPlugin* plugin, QWidget* parent) : QWidget(parent) , m_plugin(plugin) , m_tree(new QTreeView(this)) , m_filter(new KRecursiveFilterProxyModel(this)) { setWindowIcon(QIcon::fromTheme(QStringLiteral("preflight-verifier"), windowIcon())); setWindowTitle(i18n("Unit Tests")); QVBoxLayout* layout = new QVBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); setLayout(layout); layout->addWidget(m_tree); m_tree->setSortingEnabled(true); m_tree->header()->hide(); m_tree->setIndentation(10); m_tree->setEditTriggers(QTreeView::NoEditTriggers); m_tree->setSelectionBehavior(QTreeView::SelectRows); m_tree->setSelectionMode(QTreeView::SingleSelection); m_tree->setExpandsOnDoubleClick(false); m_tree->sortByColumn(0, Qt::AscendingOrder); connect(m_tree, &QTreeView::activated, this, &TestView::doubleClicked); m_model = new QStandardItemModel(this); m_filter->setSourceModel(m_model); m_tree->setModel(m_filter); QAction* showSource = new QAction( QIcon::fromTheme(QStringLiteral("code-context")), i18n("Show Source"), this ); connect (showSource, &QAction::triggered, this, &TestView::showSource); m_contextMenuActions << showSource; addAction(plugin->actionCollection()->action(QStringLiteral("run_all_tests"))); addAction(plugin->actionCollection()->action(QStringLiteral("stop_running_tests"))); QAction* runSelected = new QAction( QIcon::fromTheme(QStringLiteral("system-run")), i18n("Run Selected Tests"), this ); connect (runSelected, &QAction::triggered, this, &TestView::runSelectedTests); addAction(runSelected); QLineEdit* edit = new QLineEdit(parent); edit->setPlaceholderText(i18n("Filter...")); edit->setClearButtonEnabled(true); QWidgetAction* widgetAction = new QWidgetAction(this); widgetAction->setDefaultWidget(edit); connect(edit, &QLineEdit::textChanged, this, &TestView::changeFilter); addAction(widgetAction); setFocusProxy(edit); IProjectController* pc = ICore::self()->projectController(); connect (pc, &IProjectController::projectClosed, this, &TestView::removeProject); ITestController* tc = ICore::self()->testController(); connect (tc, &ITestController::testSuiteAdded, this, &TestView::addTestSuite); connect (tc, &ITestController::testSuiteRemoved, this, &TestView::removeTestSuite); connect (tc, &ITestController::testRunFinished, this, &TestView::updateTestSuite); connect (tc, &ITestController::testRunStarted, this, &TestView::notifyTestCaseStarted); foreach (ITestSuite* suite, tc->testSuites()) { addTestSuite(suite); } } TestView::~TestView() { } void TestView::updateTestSuite(ITestSuite* suite, const TestResult& result) { QStandardItem* item = itemForSuite(suite); if (!item) { return; } qCDebug(PLUGIN_TESTVIEW) << "Updating test suite" << suite->name(); item->setIcon(iconForTestResult(result.suiteResult)); for (int i = 0; i < item->rowCount(); ++i) { qCDebug(PLUGIN_TESTVIEW) << "Found a test case" << item->child(i)->text(); QStandardItem* caseItem = item->child(i); if (result.testCaseResults.contains(caseItem->text())) { TestResult::TestCaseResult caseResult = result.testCaseResults.value(caseItem->text(), TestResult::NotRun); caseItem->setIcon(iconForTestResult(caseResult)); } } } void TestView::changeFilter(const QString &newFilter) { m_filter->setFilterWildcard(newFilter); if (newFilter.isEmpty()) { m_tree->collapseAll(); } else { m_tree->expandAll(); } } void TestView::notifyTestCaseStarted(ITestSuite* suite, const QStringList& test_cases) { QStandardItem* item = itemForSuite(suite); if (!item) { return; } qCDebug(PLUGIN_TESTVIEW) << "Notify a test of the suite " << suite->name() << " has started"; // Global test suite icon item->setIcon(QIcon::fromTheme(QStringLiteral("process-idle"))); for (int i = 0; i < item->rowCount(); ++i) { qCDebug(PLUGIN_TESTVIEW) << "Found a test case" << item->child(i)->text(); QStandardItem* caseItem = item->child(i); if (test_cases.contains(caseItem->text())) { // Each test case icon caseItem->setIcon(QIcon::fromTheme(QStringLiteral("process-idle"))); } } } QIcon TestView::iconForTestResult(TestResult::TestCaseResult result) { switch (result) { case TestResult::NotRun: return QIcon::fromTheme(QStringLiteral("code-function")); case TestResult::Skipped: return QIcon::fromTheme(QStringLiteral("task-delegate")); case TestResult::Passed: return QIcon::fromTheme(QStringLiteral("dialog-ok-apply")); case TestResult::UnexpectedPass: // This is a very rare occurrence, so the icon should stand out return QIcon::fromTheme(QStringLiteral("dialog-warning")); case TestResult::Failed: return QIcon::fromTheme(QStringLiteral("edit-delete")); case TestResult::ExpectedFail: return QIcon::fromTheme(QStringLiteral("dialog-ok")); case TestResult::Error: return QIcon::fromTheme(QStringLiteral("dialog-cancel")); - - default: - return QIcon::fromTheme(QLatin1String("")); } + Q_UNREACHABLE(); } QStandardItem* TestView::itemForSuite(ITestSuite* suite) { foreach (QStandardItem* item, m_model->findItems(suite->name(), Qt::MatchRecursive)) { if (item->parent() && item->parent()->text() == suite->project()->name() && !item->parent()->parent()) { return item; } } return nullptr; } QStandardItem* TestView::itemForProject(IProject* project) { QList itemsForProject = m_model->findItems(project->name()); if (!itemsForProject.isEmpty()) { return itemsForProject.first(); } return addProject(project); } void TestView::runSelectedTests() { QModelIndexList indexes = m_tree->selectionModel()->selectedIndexes(); if (indexes.isEmpty()) { //if there's no selection we'll run all of them (or only the filtered) //in case there's a filter. const int rc = m_filter->rowCount(); indexes.reserve(rc); for(int i=0; iindex(i, 0); } } QList jobs; ITestController* tc = ICore::self()->testController(); /* * NOTE: If a test suite or a single test case was selected, * the job is launched in Verbose mode with raised output window. * If a project is selected, it is launched silently. * * This is the somewhat-intuitive approach. Maybe a configuration should be offered. */ foreach (const QModelIndex& idx, indexes) { QModelIndex index = m_filter->mapToSource(idx); if (index.parent().isValid() && indexes.contains(index.parent())) { continue; } QStandardItem* item = m_model->itemFromIndex(index); if (item->parent() == nullptr) { // A project was selected IProject* project = ICore::self()->projectController()->findProjectByName(item->data(ProjectRole).toString()); foreach (ITestSuite* suite, tc->testSuitesForProject(project)) { jobs << suite->launchAllCases(ITestSuite::Silent); } } else if (item->parent()->parent() == nullptr) { // A suite was selected IProject* project = ICore::self()->projectController()->findProjectByName(item->parent()->data(ProjectRole).toString()); ITestSuite* suite = tc->findTestSuite(project, item->data(SuiteRole).toString()); jobs << suite->launchAllCases(ITestSuite::Verbose); } else { // This was a single test case IProject* project = ICore::self()->projectController()->findProjectByName(item->parent()->parent()->data(ProjectRole).toString()); ITestSuite* suite = tc->findTestSuite(project, item->parent()->data(SuiteRole).toString()); const QString testCase = item->data(CaseRole).toString(); jobs << suite->launchCase(testCase, ITestSuite::Verbose); } } if (!jobs.isEmpty()) { KDevelop::ExecuteCompositeJob* compositeJob = new KDevelop::ExecuteCompositeJob(this, jobs); compositeJob->setObjectName(i18np("Run 1 test", "Run %1 tests", jobs.size())); compositeJob->setProperty("test_job", true); ICore::self()->runController()->registerJob(compositeJob); } } void TestView::showSource() { QModelIndexList indexes = m_tree->selectionModel()->selectedIndexes(); if (indexes.isEmpty()) { return; } IndexedDeclaration declaration; ITestController* tc = ICore::self()->testController(); QModelIndex index = m_filter->mapToSource(indexes.first()); QStandardItem* item = m_model->itemFromIndex(index); if (item->parent() == nullptr) { // No sense in finding source code for projects. return; } else if (item->parent()->parent() == nullptr) { IProject* project = ICore::self()->projectController()->findProjectByName(item->parent()->data(ProjectRole).toString()); ITestSuite* suite = tc->findTestSuite(project, item->data(SuiteRole).toString()); declaration = suite->declaration(); } else { IProject* project = ICore::self()->projectController()->findProjectByName(item->parent()->parent()->data(ProjectRole).toString()); ITestSuite* suite = tc->findTestSuite(project, item->parent()->data(SuiteRole).toString()); declaration = suite->caseDeclaration(item->data(CaseRole).toString()); } DUChainReadLocker locker; Declaration* d = declaration.data(); if (!d) { return; } QUrl url = d->url().toUrl(); KTextEditor::Cursor cursor = d->rangeInCurrentRevision().start(); locker.unlock(); IDocumentController* dc = ICore::self()->documentController(); qCDebug(PLUGIN_TESTVIEW) << "Activating declaration in" << url; dc->openDocument(url, cursor); } void TestView::addTestSuite(ITestSuite* suite) { QStandardItem* projectItem = itemForProject(suite->project()); Q_ASSERT(projectItem); QStandardItem* suiteItem = new QStandardItem(QIcon::fromTheme(QStringLiteral("view-list-tree")), suite->name()); suiteItem->setData(suite->name(), SuiteRole); foreach (const QString& caseName, suite->cases()) { QStandardItem* caseItem = new QStandardItem(iconForTestResult(TestResult::NotRun), caseName); caseItem->setData(caseName, CaseRole); suiteItem->appendRow(caseItem); } projectItem->appendRow(suiteItem); } void TestView::removeTestSuite(ITestSuite* suite) { QStandardItem* item = itemForSuite(suite); item->parent()->removeRow(item->row()); } QStandardItem* TestView::addProject(IProject* project) { QStandardItem* projectItem = new QStandardItem(QIcon::fromTheme(QStringLiteral("project-development")), project->name()); projectItem->setData(project->name(), ProjectRole); m_model->appendRow(projectItem); return projectItem; } void TestView::removeProject(IProject* project) { QStandardItem* projectItem = itemForProject(project); m_model->removeRow(projectItem->row()); } void TestView::doubleClicked(const QModelIndex& index) { m_tree->selectionModel()->select(index, QItemSelectionModel::ClearAndSelect); runSelectedTests(); } QList< QAction* > TestView::contextMenuActions() { return m_contextMenuActions; }