diff --git a/CMakeLists.txt b/CMakeLists.txt index cdae31d7c..e61d09eec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,184 +1,185 @@ cmake_minimum_required(VERSION 2.8.12) # kdevplatform version set(KDEVPLATFORM_VERSION_MAJOR 5) set(KDEVPLATFORM_VERSION_MINOR 1) set(KDEVPLATFORM_VERSION_PATCH 40) # plugin versions listed in the .desktop files set(KDEV_PLUGIN_VERSION 27) # Increase this to reset incompatible item-repositories set(KDEV_ITEMREPOSITORY_VERSION 86) # library version / SO version set(KDEVPLATFORM_LIB_VERSION 10.0.0) set(KDEVPLATFORM_LIB_SOVERSION 10) project(KDevPlatform) # we need some parts of the ECM CMake helpers -find_package (ECM 0.0.9 REQUIRED NO_MODULE) +find_package (ECM "5.14.0" REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${KDevPlatform_SOURCE_DIR}/cmake/modules ${ECM_MODULE_PATH}) include(KDECompilerSettings NO_POLICY_SCOPE) include(ECMAddTests) include(ECMOptionalAddSubdirectory) include(ECMInstallIcons) include(ECMSetupVersion) include(ECMMarkAsTest) include(ECMMarkNonGuiExecutable) include(ECMGenerateHeaders) include(ECMPackageConfigHelpers) +include(ECMQtDeclareLoggingCategory) include(GenerateExportHeader) include(FeatureSummary) include(WriteBasicConfigVersionFile) include(CheckFunctionExists) include(KDEInstallDirs) include(KDECMakeSettings) include(KDevPlatformMacros) set(QT_MIN_VERSION "5.4.0") find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED Core DBus Widgets Concurrent Test) find_package(Qt5QuickWidgets ${QT_MIN_VERSION}) set_package_properties(Qt5QuickWidgets PROPERTIES PURPOSE "Qt5 QuickWidgets library (part of Qt >=5.3). Required for the Welcome Page plugin." ) set(KF5_DEP_VERSION "5.18.0") # need KAboutData::fromPluginMetaData in kcoreaddons find_package(KF5 ${KF5_DEP_VERSION} REQUIRED COMPONENTS Archive Config GuiAddons WidgetsAddons IconThemes I18n ItemModels ItemViews JobWidgets KCMUtils KIO NewStuff Notifications NotifyConfig Parts Service Sonnet TextEditor ThreadWeaver WindowSystem Declarative XmlGui ) find_package(Grantlee5) set_package_properties(Grantlee5 PROPERTIES PURPOSE "Grantlee templating library, needed for file templates" URL "http://www.grantlee.org/" TYPE RECOMMENDED) set(Boost_ADDITIONAL_VERSIONS 1.39.0 1.39) find_package(Boost 1.35.0) set_package_properties(Boost PROPERTIES PURPOSE "Boost libraries for enabling the classbrowser" URL "http://www.boost.org" TYPE REQUIRED) add_definitions( -DQT_DEPRECATED_WARNINGS -DQT_DISABLE_DEPRECATED_BEFORE=0x050400 -DQT_NO_SIGNALS_SLOTS_KEYWORDS -DQT_NO_URL_CAST_FROM_STRING -DQT_STRICT_ITERATORS -DQT_USE_FAST_CONCATENATION -DQT_USE_FAST_OPERATOR_PLUS ) function(add_compile_flag_if_supported _flag) unset(_have_flag CACHE) string(REGEX REPLACE "[-=]" "_" _varname ${_flag}) string(TOUPPER ${_varname} _varname) set(_varname "HAVE${_varname}") check_cxx_compiler_flag("${_flag}" "${_varname}") if (${${_varname}}) add_compile_options(${_flag}) endif() endfunction() # Turn off missing-field-initializers warning for GCC to avoid noise from false positives with empty {} # See discussion: http://mail.kde.org/pipermail/kdevelop-devel/2014-February/046910.html add_compile_flag_if_supported(-Wno-missing-field-initializers) add_compile_flag_if_supported(-Werror=undefined-bool-conversion) add_compile_flag_if_supported(-Werror=tautological-undefined-compare) if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") add_compile_flag_if_supported(-Wdocumentation) # This warning is triggered by every call to qCDebug() add_compile_flag_if_supported(-Wno-gnu-zero-variadic-macro-arguments) endif() if (CMAKE_COMPILER_CXX_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") add_compile_flag_if_supported(-pedantic) endif() configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/config-kdevplatform.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kdevplatform.h ) include_directories(${KDevPlatform_SOURCE_DIR} ${KDevPlatform_BINARY_DIR}) string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_TOLOWER) if(CMAKE_BUILD_TYPE_TOLOWER MATCHES "debug" OR CMAKE_BUILD_TYPE_TOLOWER STREQUAL "") set(COMPILER_OPTIMIZATIONS_DISABLED TRUE) else() set(COMPILER_OPTIMIZATIONS_DISABLED FALSE) endif() add_subdirectory(sublime) add_subdirectory(interfaces) add_subdirectory(project) add_subdirectory(language) add_subdirectory(shell) add_subdirectory(util) add_subdirectory(outputview) add_subdirectory(vcs) add_subdirectory(pics) add_subdirectory(debugger) add_subdirectory(documentation) add_subdirectory(serialization) add_subdirectory(template) add_subdirectory(tests) add_subdirectory(plugins) set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KDevPlatform") ecm_configure_package_config_file("${CMAKE_CURRENT_SOURCE_DIR}/KDevPlatformConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/KDevPlatformConfig.cmake" INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR} ) ecm_setup_version(${KDEVPLATFORM_VERSION_MAJOR}.${KDEVPLATFORM_VERSION_MINOR}.${KDEVPLATFORM_VERSION_PATCH} VARIABLE_PREFIX KDEVPLATFORM VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/kdevplatform_version.h" PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KDevPlatformConfigVersion.cmake" SOVERSION ${KDEVPLATFORM_LIB_SOVERSION}) install( FILES "${KDevPlatform_BINARY_DIR}/kdevplatform_version.h" "${KDevPlatform_BINARY_DIR}/config-kdevplatform.h" DESTINATION "${KDE_INSTALL_INCLUDEDIR}/kdevplatform" ) install( FILES "${KDevPlatform_BINARY_DIR}/KDevPlatformConfig.cmake" "${KDevPlatform_BINARY_DIR}/KDevPlatformConfigVersion.cmake" cmake/modules/KDevPlatformMacros.cmake DESTINATION "${CMAKECONFIG_INSTALL_DIR}" ) install( EXPORT KDevPlatformTargets DESTINATION "${CMAKECONFIG_INSTALL_DIR}" NAMESPACE KDev:: FILE KDevPlatformTargets.cmake ) include(CTest) # CTestCustom.cmake has to be in the CTEST_BINARY_DIR. # in the KDE build system, this is the same as CMAKE_BINARY_DIR. configure_file(${CMAKE_SOURCE_DIR}/CTestCustom.cmake ${CMAKE_BINARY_DIR}/CTestCustom.cmake) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/debugger/CMakeLists.txt b/debugger/CMakeLists.txt index ad91f8282..8c1f7b805 100644 --- a/debugger/CMakeLists.txt +++ b/debugger/CMakeLists.txt @@ -1,66 +1,70 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kdevplatform\") set(KDevPlatformDebugger_LIB_SRCS interfaces/idebugsession.cpp interfaces/iframestackmodel.cpp interfaces/ibreakpointcontroller.cpp interfaces/ivariablecontroller.cpp util/treeitem.cpp util/treemodel.cpp util/treeview.cpp util/pathmappings.cpp - util/debug.cpp breakpoint/breakpoint.cpp breakpoint/breakpointmodel.cpp breakpoint/breakpointwidget.cpp breakpoint/breakpointdetails.cpp variable/variablewidget.cpp variable/variablecollection.cpp variable/variabletooltip.cpp variable/variablesortmodel.cpp framestack/framestackmodel.cpp framestack/framestackwidget.cpp ) +ecm_qt_declare_logging_category(KDevPlatformDebugger_LIB_SRCS + HEADER debug.h + IDENTIFIER DEBUGGER + CATEGORY_NAME "kdevplatform.debugger" +) kdevplatform_add_library(KDevPlatformDebugger SOURCES ${KDevPlatformDebugger_LIB_SRCS}) target_link_libraries(KDevPlatformDebugger LINK_PUBLIC KDev::Interfaces KDev::Util LINK_PRIVATE Qt5::Core KF5::Notifications KF5::TextEditor KDev::Sublime ) install(FILES interfaces/idebugsession.h interfaces/ibreakpointcontroller.h interfaces/ivariablecontroller.h interfaces/iframestackmodel.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/kdevplatform/debugger/interfaces COMPONENT Devel ) install(FILES util/treemodel.h util/treeitem.h util/treeview.h util/pathmappings.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/kdevplatform/debugger/util COMPONENT Devel ) install(FILES breakpoint/breakpointwidget.h breakpoint/breakpointdetails.h breakpoint/breakpoint.h breakpoint/breakpointmodel.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/kdevplatform/debugger/breakpoint COMPONENT Devel ) install(FILES variable/variablecollection.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/kdevplatform/debugger/variable COMPONENT Devel ) install(FILES framestack/framestackmodel.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/kdevplatform/debugger/framestack COMPONENT Devel ) add_subdirectory(tests) diff --git a/debugger/breakpoint/breakpointmodel.cpp b/debugger/breakpoint/breakpointmodel.cpp index 6813056ee..d2ac79e83 100644 --- a/debugger/breakpoint/breakpointmodel.cpp +++ b/debugger/breakpoint/breakpointmodel.cpp @@ -1,635 +1,635 @@ /* This file is part of the KDE project Copyright (C) 2002 Matthias Hoelzer-Kluepfel Copyright (C) 2002 John Firebaugh Copyright (C) 2006, 2008 Vladimir Prus Copyright (C) 2007 Hamish Rodda Copyright (C) 2009 Niko Sams 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, see . */ #include "breakpointmodel.h" #include #include #include #include #include #include #include "../interfaces/icore.h" #include "../interfaces/idebugcontroller.h" #include "../interfaces/idocumentcontroller.h" #include "../interfaces/idocument.h" #include "../interfaces/ipartcontroller.h" #include #include #include -#include "util/debug.h" +#include #include "breakpoint.h" #include #include #include #include #define IF_DEBUG(x) using namespace KDevelop; using namespace KTextEditor; namespace { IBreakpointController* breakpointController() { KDevelop::ICore* core = KDevelop::ICore::self(); if (!core) { return nullptr; } IDebugController* controller = core->debugController(); if (!controller) { return nullptr; } IDebugSession* session = controller->currentSession(); return session ? session->breakpointController() : nullptr; } } // anonymous namespace BreakpointModel::BreakpointModel(QObject* parent) : QAbstractTableModel(parent), m_dontUpdateMarks(false) { connect(this, &BreakpointModel::dataChanged, this, &BreakpointModel::updateMarks); if (KDevelop::ICore::self()->partController()) { //TODO remove if foreach(KParts::Part* p, KDevelop::ICore::self()->partController()->parts()) slotPartAdded(p); connect(KDevelop::ICore::self()->partController(), &IPartController::partAdded, this, &BreakpointModel::slotPartAdded); } connect (KDevelop::ICore::self()->documentController(), &IDocumentController::textDocumentCreated, this, &BreakpointModel::textDocumentCreated); connect (KDevelop::ICore::self()->documentController(), &IDocumentController::documentSaved, this, &BreakpointModel::documentSaved); } BreakpointModel::~BreakpointModel() { qDeleteAll(m_breakpoints); } void BreakpointModel::slotPartAdded(KParts::Part* part) { if (auto doc = qobject_cast(part)) { MarkInterface *iface = dynamic_cast(doc); if( !iface ) return; iface->setMarkDescription((MarkInterface::MarkTypes)BreakpointMark, i18n("Breakpoint")); iface->setMarkPixmap((MarkInterface::MarkTypes)BreakpointMark, *breakpointPixmap()); iface->setMarkPixmap((MarkInterface::MarkTypes)PendingBreakpointMark, *pendingBreakpointPixmap()); iface->setMarkPixmap((MarkInterface::MarkTypes)ReachedBreakpointMark, *reachedBreakpointPixmap()); iface->setMarkPixmap((MarkInterface::MarkTypes)DisabledBreakpointMark, *disabledBreakpointPixmap()); iface->setEditableMarks( MarkInterface::Bookmark | BreakpointMark ); updateMarks(); } } void BreakpointModel::textDocumentCreated(KDevelop::IDocument* doc) { KTextEditor::MarkInterface *iface = qobject_cast(doc->textDocument()); if (iface) { // can't use new signal slot syntax here, MarkInterface is not a QObject connect(doc->textDocument(), SIGNAL(markChanged(KTextEditor::Document*,KTextEditor::Mark,KTextEditor::MarkInterface::MarkChangeAction)), this, SLOT(markChanged(KTextEditor::Document*,KTextEditor::Mark,KTextEditor::MarkInterface::MarkChangeAction))); connect(doc->textDocument(), SIGNAL(markContextMenuRequested(KTextEditor::Document*,KTextEditor::Mark,QPoint,bool&)), SLOT(markContextMenuRequested(KTextEditor::Document*,KTextEditor::Mark,QPoint,bool&))); } } void BreakpointModel::markContextMenuRequested(Document* document, Mark mark, const QPoint &pos, bool& handled) { int type = mark.type; qCDebug(DEBUGGER) << type; /* Is this a breakpoint mark, to begin with? */ if (!(type & AllBreakpointMarks)) return; Breakpoint *b = breakpoint(document->url(), mark.line); if (!b) { QMessageBox::critical(nullptr, i18n("Breakpoint not found"), i18n("Couldn't find breakpoint at %1:%2", document->url().toString(), mark.line)); return; } QMenu menu; QAction deleteAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18n("&Delete Breakpoint"), nullptr); QAction disableAction(QIcon::fromTheme(QStringLiteral("dialog-cancel")), i18n("&Disable Breakpoint"), nullptr); QAction enableAction(QIcon::fromTheme(QStringLiteral("dialog-ok-apply")), i18n("&Enable Breakpoint"), nullptr); menu.addAction(&deleteAction); if (b->enabled()) { menu.addAction(&disableAction); } else { menu.addAction(&enableAction); } QAction *a = menu.exec(pos); if (a == &deleteAction) { b->setDeleted(); } else if (a == &disableAction) { b->setData(Breakpoint::EnableColumn, Qt::Unchecked); } else if (a == &enableAction) { b->setData(Breakpoint::EnableColumn, Qt::Checked); } handled = true; } QVariant BreakpointModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Vertical) return QVariant(); if (role == Qt::DecorationRole ) { if (section == 0) return QIcon::fromTheme(QStringLiteral("dialog-ok-apply")); else if (section == 1) return QIcon::fromTheme(QStringLiteral("system-switch-user")); } if (role == Qt::DisplayRole) { if (section == 0 || section == 1) return ""; if (section == 2) return i18n("Type"); if (section == 3) return i18n("Location"); if (section == 4) return i18n("Condition"); } if (role == Qt::ToolTipRole) { if (section == 0) return i18n("Active status"); if (section == 1) return i18n("State"); return headerData(section, orientation, Qt::DisplayRole); } return QVariant(); } Qt::ItemFlags BreakpointModel::flags(const QModelIndex &index) const { /* FIXME: all this logic must be in item */ if (!index.isValid()) return nullptr; if (index.column() == 0) return static_cast( Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsUserCheckable); if (index.column() == Breakpoint::LocationColumn || index.column() == Breakpoint::ConditionColumn) return static_cast( Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable); return static_cast(Qt::ItemIsEnabled | Qt::ItemIsSelectable); } QModelIndex BreakpointModel::breakpointIndex(KDevelop::Breakpoint* b, int column) { int row = m_breakpoints.indexOf(b); if (row == -1) return QModelIndex(); return index(row, column); } bool KDevelop::BreakpointModel::removeRows(int row, int count, const QModelIndex& parent) { if (count < 1 || (row < 0) || (row + count) > rowCount(parent)) return false; IBreakpointController* controller = breakpointController(); beginRemoveRows(parent, row, row+count-1); for (int i=0; i < count; ++i) { Breakpoint* b = m_breakpoints.at(row); b->m_deleted = true; if (controller) controller->breakpointAboutToBeDeleted(row); m_breakpoints.removeAt(row); b->m_model = nullptr; // To be changed: the controller is currently still responsible for deleting the breakpoint // object } endRemoveRows(); updateMarks(); scheduleSave(); return true; } int KDevelop::BreakpointModel::rowCount(const QModelIndex& parent) const { if (!parent.isValid()) { return m_breakpoints.count(); } return 0; } int KDevelop::BreakpointModel::columnCount(const QModelIndex& parent) const { Q_UNUSED(parent); return 5; } QVariant BreakpointModel::data(const QModelIndex& index, int role) const { if (!index.parent().isValid() && index.row() < m_breakpoints.count()) { return m_breakpoints.at(index.row())->data(index.column(), role); } return QVariant(); } bool KDevelop::BreakpointModel::setData(const QModelIndex& index, const QVariant& value, int role) { if (!index.parent().isValid() && index.row() < m_breakpoints.count() && (role == Qt::EditRole || role == Qt::CheckStateRole)) { return m_breakpoints.at(index.row())->setData(index.column(), value); } return false; } void BreakpointModel::updateState(int row, Breakpoint::BreakpointState state) { Breakpoint* breakpoint = m_breakpoints.at(row); if (state != breakpoint->m_state) { breakpoint->m_state = state; reportChange(breakpoint, Breakpoint::StateColumn); } } void BreakpointModel::updateHitCount(int row, int hitCount) { Breakpoint* breakpoint = m_breakpoints.at(row); if (hitCount != breakpoint->m_hitCount) { breakpoint->m_hitCount = hitCount; reportChange(breakpoint, Breakpoint::HitCountColumn); } } void BreakpointModel::updateErrorText(int row, const QString& errorText) { Breakpoint* breakpoint = m_breakpoints.at(row); if (breakpoint->m_errorText != errorText) { breakpoint->m_errorText = errorText; reportChange(breakpoint, Breakpoint::StateColumn); } if (!errorText.isEmpty()) { emit error(row, errorText); } } void BreakpointModel::notifyHit(int row) { emit hit(row); } void BreakpointModel::markChanged( KTextEditor::Document *document, KTextEditor::Mark mark, KTextEditor::MarkInterface::MarkChangeAction action) { int type = mark.type; /* Is this a breakpoint mark, to begin with? */ if (!(type & AllBreakpointMarks)) return; if (action == KTextEditor::MarkInterface::MarkAdded) { Breakpoint *b = breakpoint(document->url(), mark.line); if (b) { //there was already a breakpoint, so delete instead of adding b->setDeleted(); return; } Breakpoint *breakpoint = addCodeBreakpoint(document->url(), mark.line); KTextEditor::MovingInterface *moving = qobject_cast(document); if (moving) { KTextEditor::MovingCursor* cursor = moving->newMovingCursor(KTextEditor::Cursor(mark.line, 0)); // can't use new signal/slot syntax here, MovingInterface is not a QObject connect(document, SIGNAL(aboutToDeleteMovingInterfaceContent(KTextEditor::Document*)), this, SLOT(aboutToDeleteMovingInterfaceContent(KTextEditor::Document*)), Qt::UniqueConnection); breakpoint->setMovingCursor(cursor); } } else { // Find this breakpoint and delete it Breakpoint *b = breakpoint(document->url(), mark.line); if (b) { b->setDeleted(); } } #if 0 if ( KDevelop::ICore::self()->documentController()->activeDocument() && KDevelop::ICore::self()->documentController()->activeDocument()->textDocument() == document ) { //bring focus back to the editor // TODO probably want a different command here KDevelop::ICore::self()->documentController()->activateDocument(KDevelop::ICore::self()->documentController()->activeDocument()); } #endif } const QPixmap* BreakpointModel::breakpointPixmap() { static QPixmap pixmap=QIcon::fromTheme(QStringLiteral("breakpoint")).pixmap(QSize(16,16), QIcon::Active, QIcon::Off); return &pixmap; } const QPixmap* BreakpointModel::pendingBreakpointPixmap() { static QPixmap pixmap=QIcon::fromTheme(QStringLiteral("breakpoint")).pixmap(QSize(16,16), QIcon::Normal, QIcon::Off); return &pixmap; } const QPixmap* BreakpointModel::reachedBreakpointPixmap() { static QPixmap pixmap=QIcon::fromTheme(QStringLiteral("breakpoint")).pixmap(QSize(16,16), QIcon::Selected, QIcon::Off); return &pixmap; } const QPixmap* BreakpointModel::disabledBreakpointPixmap() { static QPixmap pixmap=QIcon::fromTheme(QStringLiteral("breakpoint")).pixmap(QSize(16,16), QIcon::Disabled, QIcon::Off); return &pixmap; } void BreakpointModel::toggleBreakpoint(const QUrl& url, const KTextEditor::Cursor& cursor) { Breakpoint *b = breakpoint(url, cursor.line()); if (b) { b->setDeleted(); } else { addCodeBreakpoint(url, cursor.line()); } } void BreakpointModel::reportChange(Breakpoint* breakpoint, Breakpoint::Column column) { // note: just a portion of Breakpoint::Column is displayed in this model! if (column >= 0 && column < columnCount()) { QModelIndex idx = breakpointIndex(breakpoint, column); Q_ASSERT(idx.isValid()); // make sure we don't pass invalid indices to dataChanged() emit dataChanged(idx, idx); } if (IBreakpointController* controller = breakpointController()) { int row = m_breakpoints.indexOf(breakpoint); Q_ASSERT(row != -1); controller->breakpointModelChanged(row, ColumnFlags(1 << column)); } scheduleSave(); } uint BreakpointModel::breakpointType(Breakpoint *breakpoint) { uint type = BreakpointMark; if (!breakpoint->enabled()) { type = DisabledBreakpointMark; } else if (breakpoint->hitCount() > 0) { type = ReachedBreakpointMark; } else if (breakpoint->state() == Breakpoint::PendingState) { type = PendingBreakpointMark; } return type; } void KDevelop::BreakpointModel::updateMarks() { if (m_dontUpdateMarks) return; //add marks foreach (Breakpoint *breakpoint, m_breakpoints) { if (breakpoint->kind() != Breakpoint::CodeBreakpoint) continue; if (breakpoint->line() == -1) continue; IDocument *doc = ICore::self()->documentController()->documentForUrl(breakpoint->url()); if (!doc) continue; KTextEditor::MarkInterface *mark = qobject_cast(doc->textDocument()); if (!mark) continue; uint type = breakpointType(breakpoint); IF_DEBUG( qCDebug(DEBUGGER) << type << breakpoint->url() << mark->mark(breakpoint->line()); ) { QSignalBlocker blocker(doc->textDocument()); if (mark->mark(breakpoint->line()) & AllBreakpointMarks) { if (!(mark->mark(breakpoint->line()) & type)) { mark->removeMark(breakpoint->line(), AllBreakpointMarks); mark->addMark(breakpoint->line(), type); } } else { mark->addMark(breakpoint->line(), type); } } } //remove marks foreach (IDocument *doc, ICore::self()->documentController()->openDocuments()) { KTextEditor::MarkInterface *mark = qobject_cast(doc->textDocument()); if (!mark) continue; { QSignalBlocker blocker(doc->textDocument()); foreach (KTextEditor::Mark *m, mark->marks()) { if (!(m->type & AllBreakpointMarks)) continue; IF_DEBUG( qCDebug(DEBUGGER) << m->line << m->type; ) foreach (Breakpoint *breakpoint, m_breakpoints) { if (breakpoint->kind() != Breakpoint::CodeBreakpoint) continue; if (doc->url() == breakpoint->url() && m->line == breakpoint->line()) { goto continueNextMark; } } mark->removeMark(m->line, AllBreakpointMarks); continueNextMark:; } } } } void BreakpointModel::documentSaved(KDevelop::IDocument* doc) { IF_DEBUG( qCDebug(DEBUGGER); ) foreach (Breakpoint *breakpoint, m_breakpoints) { if (breakpoint->movingCursor()) { if (breakpoint->movingCursor()->document() != doc->textDocument()) continue; if (breakpoint->movingCursor()->line() == breakpoint->line()) continue; m_dontUpdateMarks = true; breakpoint->setLine(breakpoint->movingCursor()->line()); m_dontUpdateMarks = false; } } } void BreakpointModel::aboutToDeleteMovingInterfaceContent(KTextEditor::Document* document) { foreach (Breakpoint *breakpoint, m_breakpoints) { if (breakpoint->movingCursor() && breakpoint->movingCursor()->document() == document) { breakpoint->setMovingCursor(nullptr); } } } void BreakpointModel::load() { KConfigGroup breakpoints = ICore::self()->activeSession()->config()->group("Breakpoints"); int count = breakpoints.readEntry("number", 0); if (count == 0) return; beginInsertRows(QModelIndex(), 0, count - 1); for (int i = 0; i < count; ++i) { if (!breakpoints.group(QString::number(i)).readEntry("kind", "").isEmpty()) { new Breakpoint(this, breakpoints.group(QString::number(i))); } } endInsertRows(); } void BreakpointModel::save() { m_dirty = false; KConfigGroup breakpoints = ICore::self()->activeSession()->config()->group("Breakpoints"); breakpoints.writeEntry("number", m_breakpoints.count()); int i = 0; foreach (Breakpoint *b, m_breakpoints) { KConfigGroup g = breakpoints.group(QString::number(i)); b->save(g); ++i; } breakpoints.sync(); } void BreakpointModel::scheduleSave() { if (m_dirty) return; m_dirty = true; QTimer::singleShot(0, this, &BreakpointModel::save); } QList KDevelop::BreakpointModel::breakpoints() const { return m_breakpoints; } Breakpoint* BreakpointModel::breakpoint(int row) { if (row >= m_breakpoints.count()) return nullptr; return m_breakpoints.at(row); } Breakpoint* BreakpointModel::addCodeBreakpoint() { beginInsertRows(QModelIndex(), m_breakpoints.count(), m_breakpoints.count()); Breakpoint* n = new Breakpoint(this, Breakpoint::CodeBreakpoint); endInsertRows(); return n; } Breakpoint* BreakpointModel::addCodeBreakpoint(const QUrl& url, int line) { Breakpoint* n = addCodeBreakpoint(); n->setLocation(url, line); return n; } Breakpoint* BreakpointModel::addCodeBreakpoint(const QString& expression) { Breakpoint* n = addCodeBreakpoint(); n->setExpression(expression); return n; } Breakpoint* BreakpointModel::addWatchpoint() { beginInsertRows(QModelIndex(), m_breakpoints.count(), m_breakpoints.count()); Breakpoint* n = new Breakpoint(this, Breakpoint::WriteBreakpoint); endInsertRows(); return n; } Breakpoint* BreakpointModel::addWatchpoint(const QString& expression) { Breakpoint* n = addWatchpoint(); n->setExpression(expression); return n; } Breakpoint* BreakpointModel::addReadWatchpoint() { beginInsertRows(QModelIndex(), m_breakpoints.count(), m_breakpoints.count()); Breakpoint* n = new Breakpoint(this, Breakpoint::ReadBreakpoint); endInsertRows(); return n; } Breakpoint* BreakpointModel::addReadWatchpoint(const QString& expression) { Breakpoint* n = addReadWatchpoint(); n->setExpression(expression); return n; } Breakpoint* BreakpointModel::addAccessWatchpoint() { beginInsertRows(QModelIndex(), m_breakpoints.count(), m_breakpoints.count()); Breakpoint* n = new Breakpoint(this, Breakpoint::AccessBreakpoint); endInsertRows(); return n; } Breakpoint* BreakpointModel::addAccessWatchpoint(const QString& expression) { Breakpoint* n = addAccessWatchpoint(); n->setExpression(expression); return n; } void BreakpointModel::registerBreakpoint(Breakpoint* breakpoint) { Q_ASSERT(!m_breakpoints.contains(breakpoint)); int row = m_breakpoints.size(); m_breakpoints << breakpoint; if (IBreakpointController* controller = breakpointController()) { controller->breakpointAdded(row); } scheduleSave(); } Breakpoint* BreakpointModel::breakpoint(const QUrl& url, int line) { foreach (Breakpoint *b, m_breakpoints) { if (b->url() == url && b->line() == line) { return b; } } return nullptr; } diff --git a/debugger/breakpoint/breakpointwidget.cpp b/debugger/breakpoint/breakpointwidget.cpp index 978324992..b4ed40dce 100644 --- a/debugger/breakpoint/breakpointwidget.cpp +++ b/debugger/breakpoint/breakpointwidget.cpp @@ -1,314 +1,314 @@ /* * This file is part of KDevelop * * Copyright 2008 Vladimir Prus * 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 "breakpointwidget.h" #include #include #include #include #include #include #include #include #include #include "breakpointdetails.h" #include "../breakpoint/breakpoint.h" #include "../breakpoint/breakpointmodel.h" -#include "util/debug.h" +#include #include #include #define IF_DEBUG(x) #include #include #include using namespace KDevelop; BreakpointWidget::BreakpointWidget(IDebugController *controller, QWidget *parent) : AutoOrientedSplitter(parent), m_firstShow(true), m_debugController(controller), m_breakpointDisableAllAction(nullptr), m_breakpointEnableAllAction(nullptr), m_breakpointRemoveAll(nullptr) { setWindowTitle(i18nc("@title:window", "Debugger Breakpoints")); setWhatsThis(i18nc("@info:whatsthis", "Displays a list of breakpoints with " "their current status. Clicking on a " "breakpoint item allows you to change " "the breakpoint and will take you " "to the source in the editor window.")); setWindowIcon( QIcon::fromTheme( QStringLiteral( "media-playback-pause"), windowIcon() ) ); m_breakpointsView = new QTreeView(this); m_breakpointsView->setSelectionBehavior(QAbstractItemView::SelectRows); m_breakpointsView->setSelectionMode(QAbstractItemView::SingleSelection); m_breakpointsView->setRootIsDecorated(false); auto detailsContainer = new QGroupBox(i18n("Breakpoint Details"), this); auto detailsLayout = new QVBoxLayout(detailsContainer); m_details = new BreakpointDetails(detailsContainer); detailsLayout->addWidget(m_details); setStretchFactor(0, 2); PlaceholderItemProxyModel* proxyModel = new PlaceholderItemProxyModel(this); proxyModel->setSourceModel(m_debugController->breakpointModel()); proxyModel->setColumnHint(Breakpoint::LocationColumn, i18n("New code breakpoint ...")); proxyModel->setColumnHint(Breakpoint::ConditionColumn, i18n("Enter condition ...")); m_breakpointsView->setModel(proxyModel); connect(proxyModel, &PlaceholderItemProxyModel::dataInserted, this, &BreakpointWidget::slotDataInserted); m_proxyModel = proxyModel; connect(m_breakpointsView, &QTreeView::activated, this, &BreakpointWidget::slotOpenFile); connect(m_breakpointsView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &BreakpointWidget::slotUpdateBreakpointDetail); connect(m_debugController->breakpointModel(), &BreakpointModel::rowsInserted, this, &BreakpointWidget::slotUpdateBreakpointDetail); connect(m_debugController->breakpointModel(), &BreakpointModel::rowsRemoved, this, &BreakpointWidget::slotUpdateBreakpointDetail); connect(m_debugController->breakpointModel(), &BreakpointModel::modelReset, this, &BreakpointWidget::slotUpdateBreakpointDetail); connect(m_debugController->breakpointModel(), &BreakpointModel::dataChanged, this, &BreakpointWidget::slotUpdateBreakpointDetail); connect(m_debugController->breakpointModel(), &BreakpointModel::hit, this, &BreakpointWidget::breakpointHit); connect(m_debugController->breakpointModel(), &BreakpointModel::error, this, &BreakpointWidget::breakpointError); setupPopupMenu(); } void BreakpointWidget::setupPopupMenu() { m_popup = new QMenu(this); QMenu* newBreakpoint = m_popup->addMenu( i18nc("New breakpoint", "&New") ); newBreakpoint->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); QAction* action = newBreakpoint->addAction( i18nc("Code breakpoint", "&Code"), this, SLOT(slotAddBlankBreakpoint()) ); // Use this action also to provide a local shortcut action->setShortcut(QKeySequence(Qt::Key_B + Qt::CTRL, Qt::Key_C)); addAction(action); newBreakpoint->addAction( i18nc("Data breakpoint", "Data &Write"), this, SLOT(slotAddBlankWatchpoint())); newBreakpoint->addAction( i18nc("Data read breakpoint", "Data &Read"), this, SLOT(slotAddBlankReadWatchpoint())); newBreakpoint->addAction( i18nc("Data access breakpoint", "Data &Access"), this, SLOT(slotAddBlankAccessWatchpoint())); QAction* breakpointDelete = m_popup->addAction( QIcon::fromTheme(QStringLiteral("edit-delete")), i18n( "&Delete" ), this, SLOT(slotRemoveBreakpoint())); breakpointDelete->setShortcut(Qt::Key_Delete); breakpointDelete->setShortcutContext(Qt::WidgetWithChildrenShortcut); addAction(breakpointDelete); m_popup->addSeparator(); m_breakpointDisableAllAction = m_popup->addAction(i18n("Disable &All"), this, SLOT(slotDisableAllBreakpoints())); m_breakpointEnableAllAction = m_popup->addAction(i18n("&Enable All"), this, SLOT(slotEnableAllBreakpoints())); m_breakpointRemoveAll = m_popup->addAction(i18n("&Remove All"), this, SLOT(slotRemoveAllBreakpoints())); connect(m_popup,&QMenu::aboutToShow, this, &BreakpointWidget::slotPopupMenuAboutToShow); } void BreakpointWidget::contextMenuEvent(QContextMenuEvent* event) { m_popup->popup(event->globalPos()); } void BreakpointWidget::slotPopupMenuAboutToShow() { if (m_debugController->breakpointModel()->rowCount() < 1) { m_breakpointDisableAllAction->setDisabled(true); m_breakpointEnableAllAction->setDisabled(true); m_breakpointRemoveAll->setDisabled(true); } else { m_breakpointRemoveAll->setEnabled(true); bool allDisabled = true; bool allEnabled = true; for (int i = 0; i < m_debugController->breakpointModel()->rowCount(); i++) { Breakpoint *bp = m_debugController->breakpointModel()->breakpoint(i); if (bp->enabled()) allDisabled = false; else allEnabled = false; } m_breakpointDisableAllAction->setDisabled(allDisabled); m_breakpointEnableAllAction->setDisabled(allEnabled); } } void BreakpointWidget::showEvent(QShowEvent *) { if (m_firstShow && m_debugController->breakpointModel()->rowCount() > 0) { for (int i = 0; i < m_breakpointsView->model()->columnCount(); ++i) { if(i == Breakpoint::LocationColumn){ continue; } m_breakpointsView->resizeColumnToContents(i); } //for some reasons sometimes width can be very small about 200... But it doesn't matter as we use tooltip anyway. int width = m_breakpointsView->size().width(); QHeaderView* header = m_breakpointsView->header(); header->resizeSection(Breakpoint::LocationColumn, width > 400 ? width/2 : header->sectionSize(Breakpoint::LocationColumn)*2 ); m_firstShow = false; } } void BreakpointWidget::edit(KDevelop::Breakpoint *n) { QModelIndex index = m_proxyModel->mapFromSource(m_debugController->breakpointModel()->breakpointIndex(n, Breakpoint::LocationColumn)); m_breakpointsView->setCurrentIndex(index); m_breakpointsView->edit(index); } void BreakpointWidget::slotDataInserted(int column, const QVariant& value) { Breakpoint* breakpoint = m_debugController->breakpointModel()->addCodeBreakpoint(); breakpoint->setData(column, value); } void BreakpointWidget::slotAddBlankBreakpoint() { edit(m_debugController->breakpointModel()->addCodeBreakpoint()); } void BreakpointWidget::slotAddBlankWatchpoint() { edit(m_debugController->breakpointModel()->addWatchpoint()); } void BreakpointWidget::slotAddBlankReadWatchpoint() { edit(m_debugController->breakpointModel()->addReadWatchpoint()); } void KDevelop::BreakpointWidget::slotAddBlankAccessWatchpoint() { edit(m_debugController->breakpointModel()->addAccessWatchpoint()); } void BreakpointWidget::slotRemoveBreakpoint() { QItemSelectionModel* sel = m_breakpointsView->selectionModel(); QModelIndexList selected = sel->selectedIndexes(); IF_DEBUG( qCDebug(DEBUGGER) << selected; ) if (!selected.isEmpty()) { m_debugController->breakpointModel()->removeRow(selected.first().row()); } } void BreakpointWidget::slotRemoveAllBreakpoints() { m_debugController->breakpointModel()->removeRows(0, m_debugController->breakpointModel()->rowCount()); } void BreakpointWidget::slotUpdateBreakpointDetail() { showEvent(nullptr); QModelIndexList selected = m_breakpointsView->selectionModel()->selectedIndexes(); IF_DEBUG( qCDebug(DEBUGGER) << selected; ) if (selected.isEmpty()) { m_details->setItem(nullptr); } else { m_details->setItem(m_debugController->breakpointModel()->breakpoint(selected.first().row())); } } void BreakpointWidget::breakpointHit(int row) { const QModelIndex index = m_proxyModel->mapFromSource(m_debugController->breakpointModel()->index(row, 0)); m_breakpointsView->selectionModel()->select( index, QItemSelectionModel::Rows | QItemSelectionModel::ClearAndSelect); } void BreakpointWidget::breakpointError(int row, const QString& msg) { // FIXME: we probably should prevent this error notification during // initial setting of breakpoint, to avoid a cloud of popups. if (!m_breakpointsView->isVisible()) return; const QModelIndex index = m_proxyModel->mapFromSource( m_debugController->breakpointModel()->index(row, BreakpointModel::LocationColumn)); QPoint p = m_breakpointsView->visualRect(index).topLeft(); p = m_breakpointsView->mapToGlobal(p); KPassivePopup *pop = new KPassivePopup(m_breakpointsView); pop->setPopupStyle(KPassivePopup::Boxed); pop->setAutoDelete(true); // FIXME: the icon, too. pop->setView(QString(), msg); pop->setTimeout(-1); pop->show(p); } void BreakpointWidget::slotOpenFile(const QModelIndex& breakpointIdx) { if (breakpointIdx.column() != Breakpoint::LocationColumn){ return; } Breakpoint *bp = m_debugController->breakpointModel()->breakpoint(breakpointIdx.row()); if (!bp || bp->line() == -1 || bp->url().isEmpty() ){ return; } ICore::self()->documentController()->openDocument(bp->url(), KTextEditor::Cursor(bp->line(), 0), IDocumentController::DoNotFocus); } void BreakpointWidget::slotDisableAllBreakpoints() { for (int i = 0; i < m_debugController->breakpointModel()->rowCount(); i++) { Breakpoint *bp = m_debugController->breakpointModel()->breakpoint(i); bp->setData(Breakpoint::EnableColumn, Qt::Unchecked); } } void BreakpointWidget::slotEnableAllBreakpoints() { for (int i = 0; i < m_debugController->breakpointModel()->rowCount(); i++) { Breakpoint *bp = m_debugController->breakpointModel()->breakpoint(i); bp->setData(Breakpoint::EnableColumn, Qt::Checked); } } diff --git a/debugger/framestack/framestackmodel.cpp b/debugger/framestack/framestackmodel.cpp index 61453c319..f7afabcc7 100644 --- a/debugger/framestack/framestackmodel.cpp +++ b/debugger/framestack/framestackmodel.cpp @@ -1,409 +1,409 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2009 Niko Sams * * * * 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 "framestackmodel.h" #include #include #include #include #include #include #include "../../interfaces/icore.h" #include "../../interfaces/iprojectcontroller.h" #include "../interfaces/isession.h" -#include "util/debug.h" +#include namespace KDevelop { class FrameStackModelPrivate { public: explicit FrameStackModelPrivate(FrameStackModel* q) : q(q) {} void update(); QModelIndex indexForThreadNumber(int threadNumber); FrameStackModel* q; int m_currentThread = -1; int m_currentFrame = -1; int m_crashedThreadIndex = -1; // used to count how often a user has scrolled down and more frames needed to be fetched; // this way, the number of frames fetched in each chunk can be increased if the user wants // to scroll far int m_subsequentFrameFetchOperations = 0; bool m_updateCurrentFrameOnNextFetch = false; QList m_threads; QHash > m_frames; QHash m_hasMoreFrames; // Caches QHash m_fileExistsCache; }; FrameStackModel::FrameStackModel(IDebugSession *session) : IFrameStackModel(session) , d(new FrameStackModelPrivate(this)) { connect(session, &IDebugSession::stateChanged, this, &FrameStackModel::stateChanged); } FrameStackModel::~FrameStackModel() { } void FrameStackModel::setThreads(const QList &threads) { qCDebug(DEBUGGER) << threads.count(); if (!d->m_threads.isEmpty()) { beginRemoveRows(QModelIndex(), 0, d->m_threads.count()-1); d->m_threads.clear(); endRemoveRows(); } if (!threads.isEmpty()) { beginInsertRows(QModelIndex(), 0, threads.count()-1); d->m_threads = threads; endInsertRows(); } } QModelIndex FrameStackModelPrivate::indexForThreadNumber(int threadNumber) { int i=0; foreach (const auto &t, m_threads) { if (t.nr == threadNumber) { return q->index(i, 0); } i++; } return QModelIndex(); } void FrameStackModel::setFrames(int threadNumber, QList frames) { QModelIndex threadIndex = d->indexForThreadNumber(threadNumber); Q_ASSERT(threadIndex.isValid()); if (!d->m_frames[threadNumber].isEmpty()) { beginRemoveRows(threadIndex, 0, d->m_frames[threadNumber].count()-1); d->m_frames[threadNumber].clear(); endRemoveRows(); } if (!frames.isEmpty()) { beginInsertRows(threadIndex, 0, frames.count()-1); d->m_frames[threadNumber] = frames; endInsertRows(); } if (d->m_currentThread == threadNumber && d->m_updateCurrentFrameOnNextFetch) { d->m_currentFrame = 0; d->m_updateCurrentFrameOnNextFetch = false; } d->m_fileExistsCache.clear(); session()->raiseEvent(IDebugSession::thread_or_frame_changed); // FIXME: Ugly hack. Apparently, when rows are added, the selection // in the view is cleared. Emit this so that some frame is still // selected. emit currentFrameChanged(d->m_currentFrame); } void FrameStackModel::insertFrames(int threadNumber, const QList &frames) { QModelIndex threadIndex = d->indexForThreadNumber(threadNumber); Q_ASSERT(threadIndex.isValid()); beginInsertRows(threadIndex, d->m_frames[threadNumber].count()-1, d->m_frames[threadNumber].count()+frames.count()-1); d->m_frames[threadNumber] << frames; endInsertRows(); } void FrameStackModel::setHasMoreFrames(int threadNumber, bool hasMoreFrames) { d->m_hasMoreFrames[threadNumber] = hasMoreFrames; } FrameStackModel::FrameItem FrameStackModel::frame(const QModelIndex& index) { Q_ASSERT(index.internalId()); Q_ASSERT(static_cast(d->m_threads.count()) >= index.internalId()); const ThreadItem &thread = d->m_threads.at(index.internalId()-1); return d->m_frames[thread.nr].at(index.row()); } QList FrameStackModel::frames(int threadNumber) const { return d->m_frames.value(threadNumber); } QVariant FrameStackModel::data(const QModelIndex& index, int role) const { if (!index.internalId()) { //thread if (d->m_threads.count() <= index.row()) return QVariant(); const ThreadItem &thread = d->m_threads.at(index.row()); if (index.column() == 0) { if (role == Qt::DisplayRole) { if (thread.nr == d->m_crashedThreadIndex) { return i18nc("#thread-id at function-name or address", "#%1 at %2 (crashed)", thread.nr, thread.name); } else { return i18nc("#thread-id at function-name or address", "#%1 at %2", thread.nr, thread.name); } } else if (role == Qt::TextColorRole) { if (thread.nr == d->m_crashedThreadIndex) { KColorScheme scheme(QPalette::Active); return scheme.foreground(KColorScheme::NegativeText).color(); } } } } else { //frame if (static_cast(d->m_threads.count()) < index.internalId()) return QVariant(); const ThreadItem &thread = d->m_threads.at(index.internalId()-1); if (d->m_frames[thread.nr].count() <= index.row()) return QVariant(); const FrameItem &frame = d->m_frames[thread.nr].at(index.row()); if (index.column() == 0) { if (role == Qt::DisplayRole) { return QVariant(QString::number(frame.nr)); } } else if (index.column() == 1) { if (role == Qt::DisplayRole) { return QVariant(frame.name); } } else if (index.column() == 2) { if (role == Qt::DisplayRole) { QString ret = ICore::self()->projectController() ->prettyFileName(frame.file, IProjectController::FormatPlain); if (frame.line != -1) { ret += ':' + QString::number(frame.line + 1); } return ret; } else if (role == Qt::DecorationRole) { QMimeType mime = QMimeDatabase().mimeTypeForUrl(frame.file); return QIcon::fromTheme(mime.iconName()); } else if (role == Qt::TextColorRole) { const auto fileName = frame.file.toLocalFile(); auto cacheIt = d->m_fileExistsCache.find(fileName); if (cacheIt == d->m_fileExistsCache.end()) { cacheIt = d->m_fileExistsCache.insert(fileName, QFileInfo::exists(fileName)); } const bool fileExists = cacheIt.value(); if (!fileExists) { KColorScheme scheme(QPalette::Active); return scheme.foreground(KColorScheme::InactiveText).color(); } } } } return QVariant(); } int FrameStackModel::columnCount(const QModelIndex& parent) const { Q_UNUSED(parent); return 3; } int FrameStackModel::rowCount(const QModelIndex& parent) const { if (!parent.isValid()) { return d->m_threads.count(); } else if (!parent.internalId() && parent.column() == 0) { if (parent.row() < d->m_threads.count()) { return d->m_frames[d->m_threads.at(parent.row()).nr].count(); } } return 0; } QModelIndex FrameStackModel::parent(const QModelIndex& child) const { if (!child.internalId()) { return QModelIndex(); } else { return index(child.internalId()-1, 0); } } QModelIndex FrameStackModel::index(int row, int column, const QModelIndex& parent) const { if (parent.isValid()) { Q_ASSERT(!parent.internalId()); Q_ASSERT(parent.column() == 0); Q_ASSERT(parent.row() < d->m_threads.count()); return createIndex(row, column, parent.row()+1); } else { return createIndex(row, column); } } QVariant FrameStackModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { if (section == 0) { return i18n("Depth"); } else if (section == 1) { return i18n("Function"); } else if (section == 2) { return i18n("Source"); } } return QAbstractItemModel::headerData(section, orientation, role); } void FrameStackModel::setCurrentThread(int threadNumber) { qCDebug(DEBUGGER) << threadNumber; if (d->m_currentThread != threadNumber && threadNumber != -1) { // FIXME: this logic means that if we switch to thread 3 and // then to thread 2 and then to thread 3, we'll request frames // for thread 3 again, even if the program was not run in between // and therefore frames could not have changed. d->m_currentFrame = 0; //set before fetchFrames else --frame argument would be wrong d->m_updateCurrentFrameOnNextFetch = true; fetchFrames(threadNumber, 0, 20); } if (threadNumber != d->m_currentThread) { d->m_currentFrame = 0; d->m_currentThread = threadNumber; emit currentFrameChanged(d->m_currentFrame); } qCDebug(DEBUGGER) << "currentThread: " << d->m_currentThread << "currentFrame: " << d->m_currentFrame; emit currentThreadChanged(threadNumber); session()->raiseEvent(IDebugSession::thread_or_frame_changed); } void FrameStackModel::setCurrentThread(const QModelIndex& index) { Q_ASSERT(index.isValid()); Q_ASSERT(!index.internalId()); Q_ASSERT(index.column() == 0); setCurrentThread(d->m_threads[index.row()].nr); } void FrameStackModel::setCrashedThreadIndex(int index) { d->m_crashedThreadIndex = index; } int FrameStackModel::crashedThreadIndex() const { return d->m_crashedThreadIndex; } int FrameStackModel::currentThread() const { return d->m_currentThread; } QModelIndex FrameStackModel::currentThreadIndex() const { int i = 0; foreach (const ThreadItem &t, d->m_threads) { if (t.nr == currentThread()) { return index(i, 0); } ++i; } return QModelIndex(); } int FrameStackModel::currentFrame() const { return d->m_currentFrame; } QModelIndex FrameStackModel::currentFrameIndex() const { return index(d->m_currentFrame, 0, currentThreadIndex()); } void FrameStackModel::setCurrentFrame(int frame) { qCDebug(DEBUGGER) << frame; if (frame != d->m_currentFrame) { d->m_currentFrame = frame; session()->raiseEvent(IDebugSession::thread_or_frame_changed); emit currentFrameChanged(frame); } } void FrameStackModelPrivate::update() { m_subsequentFrameFetchOperations = 0; q->fetchThreads(); if (m_currentThread != -1) { q->fetchFrames(m_currentThread, 0, 20); } } void FrameStackModel::handleEvent(IDebugSession::event_t event) { switch (event) { case IDebugSession::program_state_changed: d->update(); break; default: break; } } void FrameStackModel::stateChanged(IDebugSession::DebuggerState state) { if (state == IDebugSession::PausedState) { setCurrentFrame(-1); d->m_updateCurrentFrameOnNextFetch = true; } else if (state == IDebugSession::EndedState || state == IDebugSession::NotStartedState) { setThreads(QList()); } } // FIXME: it should be possible to fetch more frames for // an arbitrary thread, without making it current. void FrameStackModel::fetchMoreFrames() { d->m_subsequentFrameFetchOperations += 1; const int fetch = 20 * d->m_subsequentFrameFetchOperations * d->m_subsequentFrameFetchOperations; if (d->m_currentThread != -1 && d->m_hasMoreFrames[d->m_currentThread]) { setHasMoreFrames(d->m_currentThread, false); fetchFrames(d->m_currentThread, d->m_frames[d->m_currentThread].count(), d->m_frames[d->m_currentThread].count()-1+fetch); } } } diff --git a/debugger/framestack/framestackwidget.cpp b/debugger/framestack/framestackwidget.cpp index be18b573a..4720558f1 100644 --- a/debugger/framestack/framestackwidget.cpp +++ b/debugger/framestack/framestackwidget.cpp @@ -1,261 +1,261 @@ /* * This file is part of KDevelop * * Copyright 1999 John Birch * Copyright 2007 Hamish Rodda * Copyright 2009 Niko Sams * Copyright 2009 Aleix Pol * * 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 "framestackwidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include -#include "util/debug.h" +#include #include "framestackmodel.h" namespace KDevelop { class FrameStackItemDelegate : public QItemDelegate { Q_OBJECT public: using QItemDelegate::QItemDelegate; void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override; }; void FrameStackItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { QStyleOptionViewItem newOption(option); newOption.textElideMode = index.column() == 2 ? Qt::ElideMiddle : Qt::ElideRight; QItemDelegate::paint(painter, newOption, index); } FramestackWidget::FramestackWidget(IDebugController* controller, QWidget* parent) : AutoOrientedSplitter(Qt::Horizontal, parent), m_session(nullptr) { connect(controller, &IDebugController::currentSessionChanged, this, &FramestackWidget::currentSessionChanged); //TODO: shouldn't this signal be in IDebugController? Otherwise we are effectively depending on it being a DebugController here connect(controller, SIGNAL(raiseFramestackViews()), SIGNAL(requestRaise())); setWhatsThis(i18n("Frame stack" "Often referred to as the \"call stack\", " "this is a list showing which function is " "currently active, and what called each " "function to get to this point in your " "program. By clicking on an item you " "can see the values in any of the " "previous calling functions.")); setWindowIcon(QIcon::fromTheme(QStringLiteral("view-list-text"), windowIcon())); m_threadsWidget = new QWidget(this); m_threadsListView = new QListView(m_threadsWidget); m_framesTreeView = new QTreeView(this); m_framesTreeView->setRootIsDecorated(false); m_framesTreeView->setItemDelegate(new FrameStackItemDelegate(this)); m_framesTreeView->setSelectionMode(QAbstractItemView::ContiguousSelection); m_framesTreeView->setSelectionBehavior(QAbstractItemView::SelectRows); m_framesTreeView->setAllColumnsShowFocus(true); m_framesTreeView->setContextMenuPolicy(Qt::CustomContextMenu); m_framesContextMenu = new QMenu(m_framesTreeView); QAction* selectAllAction = KStandardAction::selectAll(m_framesTreeView); selectAllAction->setShortcut(QKeySequence()); //FIXME: why does CTRL-A conflict with Katepart (while CTRL-Cbelow doesn't) ? selectAllAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); connect(selectAllAction, &QAction::triggered, this, &FramestackWidget::selectAll); m_framesContextMenu->addAction(selectAllAction); QAction* copyAction = KStandardAction::copy(m_framesTreeView); copyAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); connect(copyAction, &QAction::triggered, this, &FramestackWidget::copySelection); m_framesContextMenu->addAction(copyAction); addAction(copyAction); connect(m_framesTreeView, &QTreeView::customContextMenuRequested, this, &FramestackWidget::frameContextMenuRequested); m_threadsWidget->setLayout(new QVBoxLayout()); m_threadsWidget->layout()->addWidget(new QLabel(i18n("Threads:"))); m_threadsWidget->layout()->addWidget(m_threadsListView); addWidget(m_threadsWidget); addWidget(m_framesTreeView); setStretchFactor(1, 3); connect(m_framesTreeView->verticalScrollBar(), &QScrollBar::valueChanged, this, &FramestackWidget::checkFetchMoreFrames); // Show the selected frame when clicked, even if it has previously been selected connect(m_framesTreeView, &QTreeView::clicked, this, &FramestackWidget::frameSelectionChanged); currentSessionChanged(controller->currentSession()); } FramestackWidget::~FramestackWidget() { } void FramestackWidget::currentSessionChanged(KDevelop::IDebugSession* session) { m_session = session; m_threadsListView->setModel(session ? session->frameStackModel() : nullptr); m_framesTreeView->setModel(session ? session->frameStackModel() : nullptr); if (session) { connect(session->frameStackModel(), &IFrameStackModel::dataChanged, this, &FramestackWidget::checkFetchMoreFrames); connect(session->frameStackModel(), &IFrameStackModel::currentThreadChanged, this, &FramestackWidget::currentThreadChanged); currentThreadChanged(session->frameStackModel()->currentThread()); connect(session->frameStackModel(), &IFrameStackModel::currentFrameChanged, this, &FramestackWidget::currentFrameChanged); currentFrameChanged(session->frameStackModel()->currentFrame()); connect(session, &IDebugSession::stateChanged, this, &FramestackWidget::sessionStateChanged); connect(m_threadsListView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FramestackWidget::setThreadShown); // Show the selected frame, independent of the means by which it has been selected connect(m_framesTreeView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FramestackWidget::frameSelectionChanged); sessionStateChanged(session->state()); } } void FramestackWidget::setThreadShown(const QModelIndex& current) { if (!current.isValid()) return; m_session->frameStackModel()->setCurrentThread(current); } void FramestackWidget::checkFetchMoreFrames() { int val = m_framesTreeView->verticalScrollBar()->value(); int max = m_framesTreeView->verticalScrollBar()->maximum(); const int offset = 20; if (val + offset > max && m_session) { m_session->frameStackModel()->fetchMoreFrames(); } } void FramestackWidget::currentThreadChanged(int thread) { if (thread != -1) { IFrameStackModel* model = m_session->frameStackModel(); QModelIndex idx = model->currentThreadIndex(); m_threadsListView->selectionModel()->select(idx, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); m_threadsWidget->setVisible(model->rowCount() > 1); m_framesTreeView->setRootIndex(idx); m_framesTreeView->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); } else { m_threadsWidget->hide(); m_threadsListView->selectionModel()->clear(); m_framesTreeView->setRootIndex(QModelIndex()); } } void FramestackWidget::currentFrameChanged(int frame) { if (frame != -1) { IFrameStackModel* model = m_session->frameStackModel(); QModelIndex idx = model->currentFrameIndex(); m_framesTreeView->selectionModel()->select( idx, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); } else { m_framesTreeView->selectionModel()->clear(); } } void FramestackWidget::frameSelectionChanged(const QModelIndex& current /* previous */) { if (!current.isValid()) return; IFrameStackModel::FrameItem f = m_session->frameStackModel()->frame(current); /* If line is -1, then it's not a source file at all. */ if (f.line != -1) { QPair file = m_session->convertToLocalUrl(qMakePair(f.file, f.line)); ICore::self()->documentController()->openDocument(file.first, KTextEditor::Cursor(file.second, 0), IDocumentController::DoNotFocus); } m_session->frameStackModel()->setCurrentFrame(f.nr); } void FramestackWidget::frameContextMenuRequested(const QPoint& pos) { m_framesContextMenu->popup( m_framesTreeView->mapToGlobal(pos) + QPoint(0, m_framesTreeView->header()->height()) ); } void FramestackWidget::copySelection() { QClipboard *cb = QApplication::clipboard(); QModelIndexList indexes = m_framesTreeView->selectionModel()->selectedRows(); QString content; Q_FOREACH(const QModelIndex& index, indexes) { IFrameStackModel::FrameItem frame = m_session->frameStackModel()->frame(index); if (frame.line == -1) { content += i18nc("#frame function() at file", "#%1 %2() at %3\n", frame.nr, frame.name, frame.file.url(QUrl::PreferLocalFile | QUrl::StripTrailingSlash)); } else { content += i18nc("#frame function() at file:line", "#%1 %2() at %3:%4\n", frame.nr, frame.name, frame.file.url(QUrl::PreferLocalFile | QUrl::StripTrailingSlash), frame.line+1); } } cb->setText(content); } void FramestackWidget::selectAll() { m_framesTreeView->selectAll(); } void FramestackWidget::sessionStateChanged(KDevelop::IDebugSession::DebuggerState state) { bool enable = state == IDebugSession::PausedState || state == IDebugSession::StoppedState; setEnabled(enable); } } #include "framestackwidget.moc" diff --git a/debugger/interfaces/ibreakpointcontroller.cpp b/debugger/interfaces/ibreakpointcontroller.cpp index 2bb88f5c3..2d59a086a 100644 --- a/debugger/interfaces/ibreakpointcontroller.cpp +++ b/debugger/interfaces/ibreakpointcontroller.cpp @@ -1,227 +1,227 @@ /* This file is part of the KDE project Copyright (C) 2002 Matthias Hoelzer-Kluepfel Copyright (C) 2002 John Firebaugh Copyright (C) 2006, 2008 Vladimir Prus Copyright (C) 2007 Hamish Rodda Copyright (C) 2009 Niko Sams 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 "ibreakpointcontroller.h" #include #include #include #include "idebugsession.h" #include "../../interfaces/icore.h" #include "../breakpoint/breakpointmodel.h" #include "../../interfaces/idebugcontroller.h" #include "../breakpoint/breakpoint.h" #include "../../interfaces/iuicontroller.h" -#include "util/debug.h" +#include namespace KDevelop { IBreakpointController::IBreakpointController(KDevelop::IDebugSession* parent) : QObject(parent), m_dontSendChanges(0) { connect(parent, &IDebugSession::stateChanged, this, &IBreakpointController::debuggerStateChanged); } IDebugSession* IBreakpointController::debugSession() const { return static_cast(const_cast(QObject::parent())); } BreakpointModel* IBreakpointController::breakpointModel() const { if (!ICore::self()) return nullptr; return ICore::self()->debugController()->breakpointModel(); } void IBreakpointController::updateState(int row, Breakpoint::BreakpointState state) { breakpointModel()->updateState(row, state); } void IBreakpointController::updateHitCount(int row, int hitCount) { breakpointModel()->updateHitCount(row, hitCount); } void IBreakpointController::updateErrorText(int row, const QString& errorText) { breakpointModel()->updateErrorText(row, errorText); } void IBreakpointController::notifyHit(int row, const QString& msg) { BreakpointModel* model = breakpointModel(); model->notifyHit(row); // This is a slightly odd place to issue this notification, // but then again it's not clear which place would be more natural Breakpoint* breakpoint = model->breakpoint(row); KNotification* ev = nullptr; switch(breakpoint->kind()) { case Breakpoint::CodeBreakpoint: ev = new KNotification(QStringLiteral("BreakpointHit"), ICore::self()->uiController()->activeMainWindow()); ev->setText(i18n("Breakpoint hit: %1", breakpoint->location()) + msg); break; case Breakpoint::WriteBreakpoint: case Breakpoint::ReadBreakpoint: case Breakpoint::AccessBreakpoint: ev = new KNotification(QStringLiteral("WatchpointHit"), ICore::self()->uiController()->activeMainWindow()); ev->setText(i18n("Watchpoint hit: %1", breakpoint->location()) + msg); break; default: Q_ASSERT(0); break; } if (ev) { ev->setPixmap(QIcon::fromTheme(QStringLiteral("breakpoint")).pixmap(QSize(22,22))); // TODO: Port //ev->setComponentName(ICore::self()->aboutData().componentName()); ev->sendEvent(); } } // Temporary: empty default implementation void IBreakpointController::breakpointAdded(int row) { Q_UNUSED(row); } // Temporary: implement old-style behavior to ease transition through API changes void IBreakpointController::breakpointModelChanged(int row, BreakpointModel::ColumnFlags columns) { if (m_dontSendChanges) return; if ((columns & ~BreakpointModel::StateColumnFlag) != 0) { Breakpoint * breakpoint = breakpointModel()->breakpoint(row); for (int column = 0; column < BreakpointModel::NumColumns; ++column) { if (columns & (1 << column)) { m_dirty[breakpoint].insert(Breakpoint::Column(column)); if (m_errors.contains(breakpoint)) { m_errors[breakpoint].remove(Breakpoint::Column(column)); } } } breakpointStateChanged(breakpoint); if (debugSession()->isRunning()) { sendMaybe(breakpoint); } } } void IBreakpointController::debuggerStateChanged(IDebugSession::DebuggerState state) { BreakpointModel* model = breakpointModel(); if (!model) return; //breakpoint state changes when session started or stopped foreach (Breakpoint* breakpoint, model->breakpoints()) { if (state == IDebugSession::StartingState) { auto& dirty = m_dirty[breakpoint]; //when starting everything is dirty dirty.insert(Breakpoint::LocationColumn); if (!breakpoint->condition().isEmpty()) { dirty.insert(Breakpoint::ConditionColumn); } if (!breakpoint->enabled()) { dirty.insert(KDevelop::Breakpoint::EnableColumn); } } breakpointStateChanged(breakpoint); } } void IBreakpointController::sendMaybeAll() { BreakpointModel* model = breakpointModel(); if (!model) return; foreach (Breakpoint *breakpoint, model->breakpoints()) { sendMaybe(breakpoint); } } // Temporary implementation to ease the API transition void IBreakpointController::breakpointAboutToBeDeleted(int row) { Breakpoint* breakpoint = breakpointModel()->breakpoint(row); qCDebug(DEBUGGER) << "breakpointAboutToBeDeleted(" << row << "): " << breakpoint; sendMaybe(breakpoint); } void IBreakpointController::breakpointStateChanged(Breakpoint* breakpoint) { if (breakpoint->deleted()) return; Breakpoint::BreakpointState newState = Breakpoint::NotStartedState; if (debugSession()->state() != IDebugSession::EndedState && debugSession()->state() != IDebugSession::NotStartedState) { if (m_dirty.value(breakpoint).isEmpty()) { if (m_pending.contains(breakpoint)) { newState = Breakpoint::PendingState; } else { newState = Breakpoint::CleanState; } } else { newState = Breakpoint::DirtyState; } } m_dontSendChanges++; updateState(breakpointModel()->breakpointIndex(breakpoint, 0).row(), newState); m_dontSendChanges--; } void IBreakpointController::setHitCount(Breakpoint* breakpoint, int count) { m_dontSendChanges++; updateHitCount(breakpointModel()->breakpointIndex(breakpoint, 0).row(), count); m_dontSendChanges--; } void IBreakpointController::error(Breakpoint* breakpoint, const QString &msg, Breakpoint::Column column) { BreakpointModel* model = breakpointModel(); int row = model->breakpointIndex(breakpoint, 0).row(); m_dontSendChanges++; m_errors[breakpoint].insert(column); updateErrorText(row, msg); m_dontSendChanges--; } void IBreakpointController::hit(KDevelop::Breakpoint* breakpoint, const QString &msg) { int row = breakpointModel()->breakpointIndex(breakpoint, 0).row(); notifyHit(row, msg); } } diff --git a/debugger/interfaces/idebugsession.cpp b/debugger/interfaces/idebugsession.cpp index ee01c16eb..0de3fe947 100644 --- a/debugger/interfaces/idebugsession.cpp +++ b/debugger/interfaces/idebugsession.cpp @@ -1,134 +1,134 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2009 Niko Sams * * * * 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 "idebugsession.h" #include "iframestackmodel.h" #include "ivariablecontroller.h" -#include "util/debug.h" +#include #include namespace KDevelop { class IDebugSessionPrivate { public: explicit IDebugSessionPrivate(IDebugSession* q) : q(q) {} void slotStateChanged(IDebugSession::DebuggerState state); IDebugSession* q; /// Current position in debugged program, gets set when the state changes QUrl m_url; int m_line; QString m_addr; }; IDebugSession::IDebugSession() : d(new IDebugSessionPrivate(this)) { connect(this, &IDebugSession::stateChanged, this, [&](IDebugSession::DebuggerState state) { d->slotStateChanged(state); }); } IDebugSession::~IDebugSession() { } bool IDebugSession::isRunning() const { DebuggerState s = state(); return (s == ActiveState || s == PausedState); } void IDebugSession::raiseEvent(event_t e) { if (IFrameStackModel* model = frameStackModel()) { model->handleEvent(e); } if (IVariableController* variables = variableController()) { variables->handleEvent(e); } // FIXME: consider if we actually need signals emit event(e); } QPair IDebugSession::convertToLocalUrl(const QPair &remoteUrl) const { return remoteUrl; } QPair IDebugSession::convertToRemoteUrl(const QPair& localUrl) const { return localUrl; } void IDebugSession::clearCurrentPosition() { qCDebug(DEBUGGER); d->m_url.clear(); d->m_addr.clear(); d->m_line = -1; emit clearExecutionPoint(); } void IDebugSession::setCurrentPosition(const QUrl& url, int line, const QString& addr) { qCDebug(DEBUGGER) << url << line << addr; if (url.isEmpty() || !QFileInfo::exists(convertToLocalUrl(qMakePair(url,line)).first.path())) { clearCurrentPosition(); d->m_addr = addr; emit showStepInDisassemble(addr); } else { d->m_url = url; d->m_line = line; d->m_addr = addr; emit showStepInSource(url, line, addr); } } QUrl IDebugSession::currentUrl() const { return d->m_url; } int IDebugSession::currentLine() const { return d->m_line; } QString IDebugSession::currentAddr() const { return d->m_addr; } void IDebugSessionPrivate::slotStateChanged(IDebugSession::DebuggerState state) { if (state != IDebugSession::PausedState) { q->clearCurrentPosition(); } } } #include "moc_idebugsession.cpp" diff --git a/debugger/interfaces/ivariablecontroller.cpp b/debugger/interfaces/ivariablecontroller.cpp index 9d36d01e4..9c1e39d67 100644 --- a/debugger/interfaces/ivariablecontroller.cpp +++ b/debugger/interfaces/ivariablecontroller.cpp @@ -1,132 +1,132 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2009 Niko Sams * * * * 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 "ivariablecontroller.h" #include "idebugsession.h" #include "../../interfaces/icore.h" #include "../../interfaces/idebugcontroller.h" #include "../variable/variablecollection.h" -#include "util/debug.h" +#include #include "iframestackmodel.h" namespace KDevelop { IVariableController::IVariableController(IDebugSession* parent) : QObject(parent), m_activeThread(-1), m_activeFrame(-1) { connect(parent, &IDebugSession::stateChanged, this, &IVariableController::stateChanged); } VariableCollection* IVariableController::variableCollection() { if (!ICore::self()) return nullptr; return ICore::self()->debugController()->variableCollection(); } IDebugSession* IVariableController::session() const { return static_cast(parent()); } void IVariableController::stateChanged(IDebugSession::DebuggerState state) { if (!ICore::self() || ICore::self()->shuttingDown()) { return; } if (state == IDebugSession::ActiveState) { //variables are now outdated, update them m_activeThread = -1; m_activeFrame = -1; } else if (state == IDebugSession::EndedState || state == IDebugSession::NotStartedState) { // Remove all locals. foreach (Locals *l, variableCollection()->allLocals()) { l->deleteChildren(); l->setHasMore(false); } for (int i=0; i < variableCollection()->watches()->childCount(); ++i) { Variable *var = qobject_cast(variableCollection()->watches()->child(i)); if (var) { var->setInScope(false); } } } } void IVariableController::updateIfFrameOrThreadChanged() { IFrameStackModel *sm = session()->frameStackModel(); if (sm->currentThread() != m_activeThread || sm->currentFrame() != m_activeFrame) { variableCollection()->root()->resetChanged(); update(); } } void IVariableController::handleEvent(IDebugSession::event_t event) { if (!variableCollection()) return; switch (event) { case IDebugSession::thread_or_frame_changed: qCDebug(DEBUGGER) << m_autoUpdate; if (!(m_autoUpdate & UpdateLocals)) { foreach (Locals *l, variableCollection()->allLocals()) { if (!l->isExpanded() && !l->childCount()) { l->setHasMore(true); } } } if (m_autoUpdate != UpdateNone) { updateIfFrameOrThreadChanged(); } // update our cache of active thread/frame regardless of m_autoUpdate // to keep them synced when user currently hides the variable list m_activeThread = session()->frameStackModel()->currentThread(); m_activeFrame = session()->frameStackModel()->currentFrame(); break; default: break; } } void IVariableController::setAutoUpdate(QFlags autoUpdate) { IDebugSession::DebuggerState state = session()->state(); m_autoUpdate = autoUpdate; qCDebug(DEBUGGER) << m_autoUpdate; if (m_autoUpdate != UpdateNone && state == IDebugSession::PausedState) { update(); } } QFlags IVariableController::autoUpdate() { return m_autoUpdate; } } diff --git a/debugger/util/debug.cpp b/debugger/util/debug.cpp deleted file mode 100644 index 037b4ae33..000000000 --- a/debugger/util/debug.cpp +++ /dev/null @@ -1,22 +0,0 @@ -/* - * This file is part of KDevelop - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#include "debug.h" - -Q_LOGGING_CATEGORY(DEBUGGER, "kdevplatform.debugger") diff --git a/debugger/util/debug.h b/debugger/util/debug.h deleted file mode 100644 index 67850835f..000000000 --- a/debugger/util/debug.h +++ /dev/null @@ -1,26 +0,0 @@ -/* - * This file is part of KDevelop - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#ifndef KDEVPLATFORM_DEBUGGER_DEBUG_H -#define KDEVPLATFORM_DEBUGGER_DEBUG_H - -#include -Q_DECLARE_LOGGING_CATEGORY(DEBUGGER) - -#endif diff --git a/debugger/util/pathmappings.cpp b/debugger/util/pathmappings.cpp index 0baf59493..38b4b4113 100644 --- a/debugger/util/pathmappings.cpp +++ b/debugger/util/pathmappings.cpp @@ -1,257 +1,258 @@ /* * This file is part of KDevelop * * 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 "pathmappings.h" -#include "debug.h" + +#include #include #include #include #include #include #include #include #include #include namespace { static QUrl rebaseMatchingUrl(const QUrl& toRebase, const KConfigGroup& config, const QString& baseEntry, const QString& rebaseEntry) { const QUrl::UrlFormattingOption matchOpts = QUrl::NormalizePathSegments; foreach (const QString &group, config.groupList()) { KConfigGroup pathCfg = config.group(group); const QString baseStr = pathCfg.readEntry(baseEntry, QUrl()).url(matchOpts); const QString searchStr = toRebase.url(matchOpts); if (searchStr.contains(baseStr)) { const QUrl rebase = pathCfg.readEntry(rebaseEntry, QUrl()); return rebase.resolved(QUrl(searchStr.mid(baseStr.length()))); } } //No mapping found return toRebase; } } namespace KDevelop { const QString PathMappings::pathMappingsEntry(QStringLiteral("Path Mappings")); const QString PathMappings::pathMappingRemoteEntry(QStringLiteral("Remote")); const QString PathMappings::pathMappingLocalEntry(QStringLiteral("Local")); QUrl PathMappings::convertToLocalUrl(const KConfigGroup& config, const QUrl& remoteUrl) { if (remoteUrl.isLocalFile() && QFile::exists(remoteUrl.toLocalFile())) { return remoteUrl; } KConfigGroup cfg = config.group(pathMappingsEntry); return rebaseMatchingUrl(remoteUrl, cfg, pathMappingRemoteEntry, pathMappingLocalEntry); } QUrl PathMappings::convertToRemoteUrl(const KConfigGroup& config, const QUrl& localUrl) { KConfigGroup cfg = config.group(pathMappingsEntry); return rebaseMatchingUrl(localUrl, cfg, pathMappingLocalEntry, pathMappingRemoteEntry); } class PathMappingModel : public QAbstractTableModel { Q_OBJECT public: int columnCount(const QModelIndex& parent = QModelIndex()) const override { if (parent.isValid()) return 0; return 2; } int rowCount(const QModelIndex& parent = QModelIndex()) const override { if (parent.isValid()) return 0; return m_paths.count() + 1; } QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override { if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { if (section == 0) { return i18n("Remote Path"); } else if (section == 1) { return i18n("Local Path"); } } return QAbstractTableModel::headerData(section, orientation, role); } QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override { if (!index.isValid()) return QVariant(); if (index.parent().isValid()) return QVariant(); if (index.column() > 1) return QVariant(); if (index.row() > m_paths.count()) return QVariant(); if (role == Qt::DisplayRole || role == Qt::EditRole) { if (index.row() == m_paths.count()) return QString(); if (index.column() == 0) { return m_paths[index.row()].remote.toDisplayString(QUrl::PreferLocalFile); } else if (index.column() == 1) { return m_paths[index.row()].local.toDisplayString(QUrl::PreferLocalFile); } } return QVariant(); } Qt::ItemFlags flags(const QModelIndex& index) const override { if (index.parent().isValid()) return Qt::NoItemFlags; if (!index.isValid()) return Qt::NoItemFlags; return ( Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled ); } bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override { if (!index.isValid()) return false; if (index.parent().isValid()) return false; if (index.column() > 1) return false; if (index.row() > m_paths.count()) return false; if (role == Qt::EditRole) { if (index.row() == m_paths.count()) { beginInsertRows(QModelIndex(), index.row()+1, index.row()+1); m_paths.append(Path()); endInsertRows(); } if (index.column() == 0) { m_paths[index.row()].remote = QUrl::fromUserInput(value.toString()); } else if (index.column() == 1) { m_paths[index.row()].local = QUrl::fromLocalFile(value.toString()); } emit dataChanged(index, index); return true; } return false; } bool removeRows(int row, int count, const QModelIndex& parent = QModelIndex()) override { if (parent.isValid()) return false; if (row+count > m_paths.count()) return false; beginRemoveRows(parent, row, row+count-1); for (int i=0; i m_paths; }; PathMappingsWidget::PathMappingsWidget(QWidget* parent): QWidget(parent) { QVBoxLayout *verticalLayout = new QVBoxLayout(this); m_pathMappingTable = new QTableView(this); m_pathMappingTable->setSelectionBehavior(QAbstractItemView::SelectRows); m_pathMappingTable->horizontalHeader()->setDefaultSectionSize(150); m_pathMappingTable->horizontalHeader()->setStretchLastSection(true); verticalLayout->addWidget(m_pathMappingTable); m_pathMappingTable->setModel(new PathMappingModel()); connect(m_pathMappingTable->model(), &QAbstractItemModel::dataChanged, this, &PathMappingsWidget::changed); connect(m_pathMappingTable->model(), &QAbstractItemModel::rowsRemoved, this, &PathMappingsWidget::changed); connect(m_pathMappingTable->model(), &QAbstractItemModel::rowsInserted, this, &PathMappingsWidget::changed); QAction* deletePath = new QAction( QIcon::fromTheme(QStringLiteral("edit-delete")), i18n( "Delete" ), this ); connect(deletePath, &QAction::triggered, this, &PathMappingsWidget::deletePath); deletePath->setShortcut(Qt::Key_Delete); deletePath->setShortcutContext(Qt::WidgetWithChildrenShortcut); m_pathMappingTable->addAction(deletePath); } void PathMappingsWidget::deletePath() { foreach (const QModelIndex &i, m_pathMappingTable->selectionModel()->selectedRows()) { m_pathMappingTable->model()->removeRow(i.row(), i.parent()); } } void PathMappingsWidget::loadFromConfiguration(const KConfigGroup& cfg) { static_cast(m_pathMappingTable->model())->loadFromConfiguration(cfg); } void PathMappingsWidget::saveToConfiguration(KConfigGroup cfg) const { static_cast(m_pathMappingTable->model())->saveToConfiguration(cfg); } } #include "pathmappings.moc" #include "moc_pathmappings.cpp" diff --git a/debugger/util/treeitem.cpp b/debugger/util/treeitem.cpp index 7daaeafb8..838db7039 100644 --- a/debugger/util/treeitem.cpp +++ b/debugger/util/treeitem.cpp @@ -1,261 +1,261 @@ /* * This file is part of KDevelop * * Copyright 2008 Vladimir Prus * * 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 "treeitem.h" #include -#include "debug.h" +#include #include "treemodel.h" using namespace KDevelop; TreeItem::TreeItem(TreeModel* model, TreeItem *parent) : model_(model), more_(false), ellipsis_(nullptr), expanded_(false) { parentItem = parent; } TreeItem::~TreeItem() { foreach (TreeItem *it, childItems) delete it; delete ellipsis_; } void TreeItem::setData(const QVector &data) { itemData=data; } void TreeItem::appendChild(TreeItem *item, bool initial) { QModelIndex index = model_->indexForItem(this, 0); // Note that we need emit beginRemoveRows, even if we're replacing // ellipsis item with the real one. The number of rows does not change // with the result, but the last item is different. The address of the // item is stored inside QModelIndex, so just replacing the item, and // deleting the old one, will lead to crash. if (more_) { if (!initial) model_->beginRemoveRows(index, childItems.size(), childItems.size()); more_ = false; delete ellipsis_; ellipsis_ = nullptr; if (!initial) model_->endRemoveRows(); } if (!initial) model_->beginInsertRows(index, childItems.size(), childItems.size()); childItems.append(item); if (!initial) model_->endInsertRows(); } void TreeItem::insertChild(int position, TreeItem *child, bool initial) { QModelIndex index = model_->indexForItem(this, 0); if (!initial) model_->beginInsertRows(index, position, position); childItems.insert(position, child); if (!initial) model_->endInsertRows(); } void TreeItem::reportChange() { QModelIndex index = model_->indexForItem(this, 0); QModelIndex index2 = model_->indexForItem(this, itemData.size()-1); emit model_->dataChanged(index, index2); } void KDevelop::TreeItem::reportChange(int column) { QModelIndex index = model_->indexForItem(this, column); emit model_->dataChanged(index, index); } void TreeItem::removeChild(int index) { QModelIndex modelIndex = model_->indexForItem(this, 0); model_->beginRemoveRows(modelIndex, index, index); childItems.erase(childItems.begin() + index); model_->endRemoveRows(); } void TreeItem::removeSelf() { QModelIndex modelIndex = model_->indexForItem(this, 0); parentItem->removeChild(modelIndex.row()); } void TreeItem::deleteChildren() { QVector copy = childItems; clear(); // Only delete the children after removing them // from model. Otherwise, the model will touch // deleted things, with undefined results. qDeleteAll(copy); } void TreeItem::clear() { if (!childItems.isEmpty() || more_) { QModelIndex index = model_->indexForItem(this, 0); model_->beginRemoveRows(index, 0, childItems.size()-1+more_); childItems.clear(); more_ = false; delete ellipsis_; ellipsis_ = nullptr; model_->endRemoveRows(); } } TreeItem *TreeItem::child(int row) { if (row < childItems.size()) return childItems.value(row); else if (row == childItems.size() && more_) return ellipsis_; else return nullptr; } int TreeItem::childCount() const { return childItems.count() + more_; } int TreeItem::columnCount() const { return itemData.count(); } QVariant TreeItem::data(int column, int role) const { if (role == Qt::DecorationRole) return icon(column); else if (role==Qt::DisplayRole || role == Qt::EditRole) return itemData.value(column); return QVariant(); } TreeItem *TreeItem::parent() { return parentItem; } int TreeItem::row() const { if (parentItem) return parentItem->childItems.indexOf(const_cast(this)); return 0; } class EllipsisItem : public TreeItem { Q_OBJECT public: EllipsisItem(TreeModel *model, TreeItem *parent) : TreeItem(model, parent) { QVector data; data.push_back("..."); for (int i = 1; i < model->columnCount(QModelIndex()); ++i) data.push_back(""); setData(data); } void clicked() override { qCDebug(DEBUGGER) << "Ellipsis item clicked"; /* FIXME: restore Q_ASSERT (parentItem->hasMore()); */ parentItem->fetchMoreChildren(); } void fetchMoreChildren() override {} }; void TreeItem::setHasMore(bool more) { /* FIXME: this will crash if used in ctor of root item, where the model is not associated with item or something. */ QModelIndex index = model_->indexForItem(this, 0); if (more && !more_) { model_->beginInsertRows(index, childItems.size(), childItems.size()); ellipsis_ = new EllipsisItem (model(), this); more_ = more; model_->endInsertRows(); } else if (!more && more_) { model_->beginRemoveRows(index, childItems.size(), childItems.size()); delete ellipsis_; ellipsis_ = nullptr; more_ = more; model_->endRemoveRows(); } } void TreeItem::emitAllChildrenFetched() { emit allChildrenFetched(); } void TreeItem::setHasMoreInitial(bool more) { more_ = more; if (more) { ellipsis_ = new EllipsisItem (model(), this); } } QVariant KDevelop::TreeItem::icon(int column) const { Q_UNUSED(column); return QVariant(); } void KDevelop::TreeItem::setExpanded(bool b) { if (expanded_ != b) { expanded_ = b; if (expanded_) emit expanded(); else emit collapsed(); } } #include "treeitem.moc" diff --git a/debugger/variable/variablecollection.cpp b/debugger/variable/variablecollection.cpp index 9bb8b8b6f..cc1f88bde 100644 --- a/debugger/variable/variablecollection.cpp +++ b/debugger/variable/variablecollection.cpp @@ -1,547 +1,547 @@ /* * 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 "util/debug.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(); } } 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); } 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/debugger/variable/variablewidget.cpp b/debugger/variable/variablewidget.cpp index 2a54bbe7f..13bb16922 100644 --- a/debugger/variable/variablewidget.cpp +++ b/debugger/variable/variablewidget.cpp @@ -1,513 +1,513 @@ // ************************************************************************** // begin : Sun Aug 8 1999 // copyright : (C) 1999 by John Birch // email : jbb@kdevelop.org // ************************************************************************** // * Copyright 2006 Vladimir Prus // ************************************************************************** // * * // * 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 "variablewidget.h" #include #include #include #include #include #include #include #include #include #include #include "../util/treemodel.h" #include "../../interfaces/icore.h" #include #include "../interfaces/ivariablecontroller.h" #include "variablecollection.h" #include "variablesortmodel.h" -#include "util/debug.h" +#include /** The variables widget is passive, and is invoked by the rest of the code via two main Q_SLOTS: - slotDbgStatus - slotCurrentFrame The first is received the program status changes and the second is received after current frame in the debugger can possibly changes. The widget has a list item for each frame/thread combination, with variables as children. However, at each moment only one item is shown. When handling the slotCurrentFrame, we check if variables for the current frame are available. If yes, we simply show the corresponding item. Otherwise, we fetch the new data from debugger. Fetching the data is done by emitting the produceVariablesInfo signal. In response, we get slotParametersReady and slotLocalsReady signal, in that order. The data is parsed and changed variables are highlighted. After that, we 'trim' variable items that were not reported by gdb -- that is, gone out of scope. */ // ************************************************************************** // ************************************************************************** // ************************************************************************** namespace KDevelop { VariableCollection *variableCollection() { return ICore::self()->debugController()->variableCollection(); } VariableWidget::VariableWidget(IDebugController* controller, QWidget *parent) : QWidget(parent), m_variablesRoot(controller->variableCollection()->root()) { //setWindowIcon(QIcon::fromTheme("math_brace")); setWindowIcon(QIcon::fromTheme(QStringLiteral("debugger"), windowIcon())); setWindowTitle(i18n("Debugger Variables")); m_proxy = new VariableSortProxyModel(this); m_varTree = new VariableTree(controller, this, m_proxy); setFocusProxy(m_varTree); m_watchVarEditor = new KHistoryComboBox( this ); QVBoxLayout *topLayout = new QVBoxLayout(this); topLayout->addWidget(m_varTree, 10); topLayout->addWidget(m_watchVarEditor); topLayout->setMargin(0); connect(m_watchVarEditor, static_cast(&KHistoryComboBox::returnPressed), this, &VariableWidget::slotAddWatch); //TODO //connect(plugin, SIGNAL(raiseVariableViews()), this, SIGNAL(requestRaise())); // Setup help items. setWhatsThis( i18n( "Variable tree" "The variable tree allows you to see the values of local " "variables and arbitrary expressions.
" "Local variables are displayed automatically and are updated " "as you step through your program. " "For each expression you enter, you can either evaluate it once, " "or \"watch\" it (make it auto-updated). Expressions that are not " "auto-updated can be updated manually from the context menu. " "Expressions can be renamed to more descriptive names by clicking " "on the name column.
" "To change the value of a variable or an expression, " "click on the value.
")); m_watchVarEditor->setWhatsThis( i18n("Expression entry" "Type in expression to watch.")); } void VariableWidget::slotAddWatch(const QString &expression) { if (!expression.isEmpty()) { m_watchVarEditor->addToHistory(expression); qCDebug(DEBUGGER) << "Trying to add watch"; Variable* v = m_variablesRoot->watches()->add(expression); if (v) { /* For watches on structures, we really do want them to be shown expanded. Except maybe for structure with custom pretty printing, but will handle that later. FIXME: it does not actually works now. */ //QModelIndex index = variableCollection()->indexForItem(v, 0); //varTree_->setExpanded(index, true); } m_watchVarEditor->clearEditText(); } } void VariableWidget::hideEvent(QHideEvent* e) { QWidget::hideEvent(e); variableCollection()->variableWidgetHidden(); } void VariableWidget::showEvent(QShowEvent* e) { QWidget::showEvent(e); variableCollection()->variableWidgetShown(); } // ************************************************************************** // ************************************************************************** // ************************************************************************** VariableTree::VariableTree(IDebugController *controller, VariableWidget *parent, QSortFilterProxyModel *proxy) : AsyncTreeView(controller->variableCollection(), proxy, parent) , m_proxy(proxy) #if 0 , activePopup_(0), toggleWatch_(0) #endif { setRootIsDecorated(true); setAllColumnsShowFocus(true); // setting proxy model m_model = static_cast(controller->variableCollection()); m_proxy->setSourceModel(m_model); setModel(m_proxy); setSortingEnabled(true); sortByColumn(VariableCollection::NameColumn, Qt::AscendingOrder); QModelIndex index = controller->variableCollection()->indexForItem( controller->variableCollection()->watches(), 0); setExpanded(index, true); m_signalMapper = new QSignalMapper(this); setupActions(); } VariableCollection* VariableTree::collection() const { Q_ASSERT(qobject_cast(static_cast(model())->sourceModel())); return static_cast(model()); } VariableTree::~VariableTree() { } void VariableTree::setupActions() { // TODO decorate this properly to make nice menu title m_contextMenuTitle = new QAction(this); m_contextMenuTitle->setEnabled(false); // make Format menu action group m_formatMenu = new QMenu(i18n("&Format"), this); QActionGroup *ag= new QActionGroup(m_formatMenu); QAction* act; act = new QAction(i18n("&Natural"), ag); act->setData(Variable::Natural); act->setShortcut(Qt::Key_N); m_formatMenu->addAction(act); act = new QAction(i18n("&Binary"), ag); act->setData(Variable::Binary); act->setShortcut(Qt::Key_B); m_formatMenu->addAction(act); act = new QAction(i18n("&Octal"), ag); act->setData(Variable::Octal); act->setShortcut(Qt::Key_O); m_formatMenu->addAction(act); act = new QAction(i18n("&Decimal"), ag); act->setData(Variable::Decimal); act->setShortcut(Qt::Key_D); m_formatMenu->addAction(act); act = new QAction(i18n("&Hexadecimal"), ag); act->setData(Variable::Hexadecimal); act->setShortcut(Qt::Key_H); m_formatMenu->addAction(act); foreach(QAction* act, m_formatMenu->actions()) { act->setCheckable(true); act->setShortcutContext(Qt::WidgetWithChildrenShortcut); m_signalMapper->setMapping(act, act->data().toInt()); connect(act, &QAction::triggered, m_signalMapper, static_cast(&QSignalMapper::map)); addAction(act); } connect(m_signalMapper, static_cast(&QSignalMapper::mapped), this, &VariableTree::changeVariableFormat); m_watchDelete = new QAction( QIcon::fromTheme(QStringLiteral("edit-delete")), i18n( "Remove Watch Variable" ), this); m_watchDelete->setShortcut(Qt::Key_Delete); m_watchDelete->setShortcutContext(Qt::WidgetWithChildrenShortcut); addAction(m_watchDelete); connect(m_watchDelete, &QAction::triggered, this, &VariableTree::watchDelete); m_copyVariableValue = new QAction(i18n("&Copy Value"), this); m_copyVariableValue->setShortcutContext(Qt::WidgetWithChildrenShortcut); m_copyVariableValue->setShortcut(QKeySequence::Copy); connect(m_copyVariableValue, &QAction::triggered, this, &VariableTree::copyVariableValue); m_stopOnChange = new QAction(i18n("&Stop on Change"), this); connect(m_stopOnChange, &QAction::triggered, this, &VariableTree::stopOnChange); } Variable* VariableTree::selectedVariable() const { if (selectionModel()->selectedRows().isEmpty()) return nullptr; auto item = selectionModel()->currentIndex().data(TreeModel::ItemRole).value(); if (!item) return nullptr; return qobject_cast(item); } void VariableTree::contextMenuEvent(QContextMenuEvent* event) { if (!selectedVariable()) return; // set up menu QMenu contextMenu(this->parentWidget()); m_contextMenuTitle->setText(selectedVariable()->expression()); contextMenu.addAction(m_contextMenuTitle); if(selectedVariable()->canSetFormat()) contextMenu.addMenu(m_formatMenu); foreach(QAction* act, m_formatMenu->actions()) { if(act->data().toInt()==selectedVariable()->format()) act->setChecked(true); } if (qobject_cast(selectedVariable()->parent())) { contextMenu.addAction(m_watchDelete); } contextMenu.addSeparator(); contextMenu.addAction(m_copyVariableValue); contextMenu.addAction(m_stopOnChange); contextMenu.exec(event->globalPos()); } void VariableTree::changeVariableFormat(int format) { if (!selectedVariable()) return; selectedVariable()->setFormat(static_cast(format)); } void VariableTree::watchDelete() { if (!selectedVariable()) return; if (!qobject_cast(selectedVariable()->parent())) return; selectedVariable()->die(); } void VariableTree::copyVariableValue() { if (!selectedVariable()) return; QApplication::clipboard()->setText(selectedVariable()->value()); } void VariableTree::stopOnChange() { if (!selectedVariable()) return; IDebugSession *session = ICore::self()->debugController()->currentSession(); if (session && session->state() != IDebugSession::NotStartedState && session->state() != IDebugSession::EndedState) { session->variableController()->addWatchpoint(selectedVariable()); } } #if 0 void VariableTree::contextMenuEvent(QContextMenuEvent* event) { QModelIndex index = indexAt(event->pos()); if (!index.isValid()) return; AbstractVariableItem* item = collection()->itemForIndex(index); if (RecentItem* recent = qobject_cast(item)) { QMenu popup(this); popup.addTitle(i18n("Recent Expressions")); QAction* remove = popup.addAction(QIcon::fromTheme("editdelete"), i18n("Remove All")); QAction* reevaluate = popup.addAction(QIcon::fromTheme("reload"), i18n("Re-evaluate All")); if (controller()->stateIsOn(s_dbgNotStarted)) reevaluate->setEnabled(false); QAction* res = popup.exec(QCursor::pos()); if (res == remove) { collection()->deleteItem(recent); } else if (res == reevaluate) { foreach (AbstractVariableItem* item, recent->children()) { if (VariableItem* variable = qobject_cast(item)) variable->updateValue(); } } } else { activePopup_ = new QMenu(this); QMenu format(this); QAction* remember = 0; QAction* remove = 0; QAction* reevaluate = 0; QAction* watch = 0; QAction* natural = 0; QAction* hex = 0; QAction* decimal = 0; QAction* character = 0; QAction* binary = 0; #define MAYBE_DISABLE(action) if (!var->isAlive()) action->setEnabled(false) VariableItem* var = qobject_cast(item); AbstractVariableItem* root = item->abstractRoot(); RecentItem* recentRoot = qobject_cast(root); if (!recentRoot) { remember = activePopup_->addAction(QIcon::fromTheme("draw-freehand"), i18n("Remember Value")); MAYBE_DISABLE(remember); } if (!recentRoot) { watch = activePopup_->addAction(i18n("Watch Variable")); MAYBE_DISABLE(watch); } if (recentRoot) { reevaluate = activePopup_->addAction(QIcon::fromTheme("reload"), i18n("Reevaluate Expression")); MAYBE_DISABLE(reevaluate); remove = activePopup_->addAction(QIcon::fromTheme("editdelete"), i18n("Remove Expression")); remove->setShortcut(Qt::Key_Delete); } if (var) { toggleWatch_ = activePopup_->addAction( i18n("Data write breakpoint") ); toggleWatch_->setCheckable(true); toggleWatch_->setEnabled(false); } /* This code can be executed when debugger is stopped, and we invoke popup menu on a var under "recent expressions" just to delete it. */ if (var && var->isAlive() && !controller()->stateIsOn(s_dbgNotStarted)) { GDBCommand* cmd = new GDBCommand(DataEvaluateExpression, QStringLiteral("&%1") .arg(var->gdbExpression())); cmd->setHandler(this, &VariableTree::handleAddressComputed, true /*handles error*/); cmd->setThread(var->thread()); cmd->setFrame(var->frame()); controller_->addCommand(cmd); } QAction* res = activePopup_->exec(event->globalPos()); delete activePopup_; activePopup_ = 0; if (res == remember) { if (var) { ((VariableWidget*)parent())-> slotEvaluateExpression(var->gdbExpression()); } } else if (res == watch) { if (var) { ((VariableWidget*)parent())-> slotAddWatchVariable(var->gdbExpression()); } } else if (res == remove) { delete item; } else if (res == toggleWatch_) { if (var) emit toggleWatchpoint(var->gdbExpression()); } else if (res == reevaluate) { if (var) { var->updateValue(); } } event->accept(); } } void VariableTree::updateCurrentFrame() { } // ************************************************************************** void VariableTree::handleAddressComputed(const GDBMI::ResultRecord& r) { if (r.reason == "error") { // Not lvalue, leave item disabled. return; } if (activePopup_) { toggleWatch_->setEnabled(true); //quint64 address = r["value"].literal().toULongLong(0, 16); /*if (breakpointWidget_->hasWatchpointForAddress(address)) { toggleWatch_->setChecked(true); }*/ } } VariableCollection * VariableTree::collection() const { return controller_->variables(); } GDBController * VariableTree::controller() const { return controller_; } void VariableTree::showEvent(QShowEvent * event) { Q_UNUSED(event) for (int i = 0; i < model()->columnCount(); ++i) resizeColumnToContents(i); } #endif // ************************************************************************** // ************************************************************************** // ************************************************************************** } diff --git a/documentation/CMakeLists.txt b/documentation/CMakeLists.txt index e605ac14c..e5301c91f 100644 --- a/documentation/CMakeLists.txt +++ b/documentation/CMakeLists.txt @@ -1,40 +1,46 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kdevplatform\") find_package(Qt5WebEngineWidgets) if(TARGET Qt5::WebEngineWidgets) set_package_properties(Qt5WebEngineWidgets PROPERTIES PURPOSE "QtWebEngine, for integrated documentation" URL "http://qt-project.org/" TYPE REQUIRED) else() find_package(Qt5WebKitWidgets) set_package_properties(Qt5WebKitWidgets PROPERTIES PURPOSE "QtWebKit, for integrated documentation" URL "http://qt-project.org/" TYPE REQUIRED) set(USE_QTWEBKIT 1) endif() set(KDevPlatformDocumentation_LIB_SRCS standarddocumentationview.cpp documentationfindwidget.cpp documentationview.cpp ) +ecm_qt_declare_logging_category(KDevPlatformDocumentation_LIB_SRCS + HEADER debug.h + IDENTIFIER DOCUMENTATION + CATEGORY_NAME "kdevplatform.documentation" +) + ki18n_wrap_ui(KDevPlatformDocumentation_LIB_SRCS documentationfindwidget.ui) kdevplatform_add_library(KDevPlatformDocumentation SOURCES ${KDevPlatformDocumentation_LIB_SRCS}) target_link_libraries(KDevPlatformDocumentation PUBLIC KDev::Interfaces PRIVATE KDev::Util) if(USE_QTWEBKIT) target_link_libraries(KDevPlatformDocumentation PRIVATE Qt5::WebKitWidgets) target_compile_definitions(KDevPlatformDocumentation PRIVATE -DUSE_QTWEBKIT) else() target_link_libraries(KDevPlatformDocumentation PRIVATE Qt5::WebEngineWidgets) endif() install(FILES documentationfindwidget.h standarddocumentationview.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/kdevplatform/documentation COMPONENT Devel ) diff --git a/documentation/debug.h b/documentation/debug.h deleted file mode 100644 index f4ae8abea..000000000 --- a/documentation/debug.h +++ /dev/null @@ -1,26 +0,0 @@ -/* - * This file is part of KDevelop - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#ifndef KDEVPLATFORM_DOCUMENTATION_DEBUG_H -#define KDEVPLATFORM_DOCUMENTATION_DEBUG_H - -#include -Q_DECLARE_LOGGING_CATEGORY(DOCUMENTATION) - -#endif diff --git a/documentation/documentationview.cpp b/documentation/documentationview.cpp index 253dacfcf..3d0740f7c 100644 --- a/documentation/documentationview.cpp +++ b/documentation/documentationview.cpp @@ -1,378 +1,375 @@ /* Copyright 2009 Aleix Pol Gonzalez Copyright 2010 Benjamin Port 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 "documentationview.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "documentationfindwidget.h" #include "debug.h" -Q_LOGGING_CATEGORY(DOCUMENTATION, "kdevplatform.documentation") - - using namespace KDevelop; DocumentationView::DocumentationView(QWidget* parent, ProvidersModel* model) : QWidget(parent), mProvidersModel(model) { setWindowIcon(QIcon::fromTheme(QStringLiteral("documentation"), windowIcon())); setWindowTitle(i18n("Documentation")); setLayout(new QVBoxLayout(this)); layout()->setMargin(0); layout()->setSpacing(0); //TODO: clean this up, simply use addAction as that will create a toolbar automatically // use custom QAction's with createWidget for mProviders and mIdentifiers mActions = new KToolBar(this); // set window title so the QAction from QToolBar::toggleViewAction gets a proper name set mActions->setWindowTitle(i18n("Documentation Tool Bar")); mActions->setToolButtonStyle(Qt::ToolButtonIconOnly); int iconSize = style()->pixelMetric(QStyle::PM_SmallIconSize); mActions->setIconSize(QSize(iconSize, iconSize)); mFindDoc = new DocumentationFindWidget; mFindDoc->hide(); mBack = mActions->addAction(QIcon::fromTheme(QStringLiteral("go-previous")), i18n("Back")); mForward = mActions->addAction(QIcon::fromTheme(QStringLiteral("go-next")), i18n("Forward")); mFind = mActions->addAction(QIcon::fromTheme(QStringLiteral("edit-find")), i18n("Find"), mFindDoc, SLOT(startSearch())); mActions->addSeparator(); mActions->addAction(QIcon::fromTheme(QStringLiteral("go-home")), i18n("Home"), this, SLOT(showHome())); mProviders = new QComboBox(mActions); mIdentifiers = new QLineEdit(mActions); mIdentifiers->setClearButtonEnabled(true); mIdentifiers->setPlaceholderText(i18n("Search...")); mIdentifiers->setCompleter(new QCompleter(mIdentifiers)); // mIdentifiers->completer()->setCompletionMode(QCompleter::UnfilteredPopupCompletion); mIdentifiers->completer()->setCaseSensitivity(Qt::CaseInsensitive); /* vertical size policy should be left to the style. */ mIdentifiers->setSizePolicy(QSizePolicy::Expanding, mIdentifiers->sizePolicy().verticalPolicy()); connect(mIdentifiers->completer(), static_cast(&QCompleter::activated), this, &DocumentationView::changedSelection); connect(mIdentifiers, &QLineEdit::returnPressed, this, &DocumentationView::returnPressed); QWidget::setTabOrder(mProviders, mIdentifiers); mActions->addWidget(mProviders); mActions->addWidget(mIdentifiers); mBack->setEnabled(false); mForward->setEnabled(false); connect(mBack, &QAction::triggered, this, &DocumentationView::browseBack); connect(mForward, &QAction::triggered, this, &DocumentationView::browseForward); mCurrent = mHistory.end(); layout()->addWidget(mActions); layout()->addWidget(new QWidget(this)); layout()->addWidget(mFindDoc); setFocusProxy(mIdentifiers); QMetaObject::invokeMethod(this, "initialize", Qt::QueuedConnection); } void DocumentationView::initialize() { mProviders->setModel(mProvidersModel); connect(mProviders, static_cast(&QComboBox::activated), this, &DocumentationView::changedProvider); foreach (IDocumentationProvider* p, mProvidersModel->providers()) { // can't use new signal/slot syntax here, IDocumentation is not a QObject connect(dynamic_cast(p), SIGNAL(addHistory(KDevelop::IDocumentation::Ptr)), this, SLOT(addHistory(KDevelop::IDocumentation::Ptr))); } connect(mProvidersModel, &ProvidersModel::providersChanged, this, &DocumentationView::emptyHistory); if (mProvidersModel->rowCount() > 0) { changedProvider(0); } } void DocumentationView::browseBack() { --mCurrent; mBack->setEnabled(mCurrent != mHistory.begin()); mForward->setEnabled(true); updateView(); } void DocumentationView::browseForward() { ++mCurrent; mForward->setEnabled(mCurrent+1 != mHistory.end()); mBack->setEnabled(true); updateView(); } void DocumentationView::showHome() { auto prov = mProvidersModel->provider(mProviders->currentIndex()); showDocumentation(prov->homePage()); } void DocumentationView::returnPressed() { // Exit if search text is empty. It's necessary because of empty // line edit text not leads to "empty" completer indexes. if (mIdentifiers->text().isEmpty()) return; // Exit if completer popup has selected item - in this case 'Return' // key press emits QCompleter::activated signal which is already connected. if (mIdentifiers->completer()->popup()->currentIndex().isValid()) return; // If user doesn't select any item in popup we will try to use the first row. if (mIdentifiers->completer()->setCurrentRow(0)) changedSelection(mIdentifiers->completer()->currentIndex()); } void DocumentationView::changedSelection(const QModelIndex& idx) { if (idx.isValid()) { // Skip view update if user try to show already opened documentation mIdentifiers->setText(idx.data(Qt::DisplayRole).toString()); if (mIdentifiers->text() == (*mCurrent)->name()) { return; } IDocumentationProvider* prov = mProvidersModel->provider(mProviders->currentIndex()); auto doc = prov->documentationForIndex(idx); if (doc) { showDocumentation(doc); } } } void DocumentationView::showDocumentation(const IDocumentation::Ptr& doc) { qCDebug(DOCUMENTATION) << "showing" << doc->name(); addHistory(doc); updateView(); } void DocumentationView::addHistory(const IDocumentation::Ptr& doc) { mBack->setEnabled(!mHistory.isEmpty()); mForward->setEnabled(false); // clear all history following the current item, unless we're already // at the end (otherwise this code crashes when history is empty, which // happens when addHistory is first called on startup to add the // homepage) if (mCurrent+1 < mHistory.end()) { mHistory.erase(mCurrent+1, mHistory.end()); } mHistory.append(doc); mCurrent = mHistory.end()-1; // NOTE: we assume an existing widget was used to navigate somewhere // but this history entry actually contains the new info for the // title... this is ugly and should be refactored somehow if (mIdentifiers->completer()->model() == (*mCurrent)->provider()->indexModel()) { mIdentifiers->setText((*mCurrent)->name()); } } void DocumentationView::emptyHistory() { mHistory.clear(); mCurrent = mHistory.end(); mBack->setEnabled(false); mForward->setEnabled(false); if (mProviders->count() > 0) { mProviders->setCurrentIndex(0); changedProvider(0); } } void DocumentationView::updateView() { mProviders->setCurrentIndex(mProvidersModel->rowForProvider((*mCurrent)->provider())); mIdentifiers->completer()->setModel((*mCurrent)->provider()->indexModel()); mIdentifiers->setText((*mCurrent)->name()); mIdentifiers->completer()->setCompletionPrefix((*mCurrent)->name()); QLayoutItem* lastview = layout()->takeAt(1); Q_ASSERT(lastview); if (lastview->widget()->parent() == this) { lastview->widget()->deleteLater(); } delete lastview; mFindDoc->setEnabled(false); QWidget* w = (*mCurrent)->documentationWidget(mFindDoc, this); Q_ASSERT(w); QWidget::setTabOrder(mIdentifiers, w); mFind->setEnabled(mFindDoc->isEnabled()); if (!mFindDoc->isEnabled()) { mFindDoc->hide(); } QLayoutItem* findWidget = layout()->takeAt(1); layout()->addWidget(w); layout()->addItem(findWidget); } void DocumentationView::changedProvider(int row) { mIdentifiers->completer()->setModel(mProvidersModel->provider(row)->indexModel()); mIdentifiers->clear(); showHome(); } ////////////// ProvidersModel ////////////////// ProvidersModel::ProvidersModel(QObject* parent) : QAbstractListModel(parent) , mProviders(ICore::self()->documentationController()->documentationProviders()) { connect(ICore::self()->pluginController(), &IPluginController::unloadingPlugin, this, &ProvidersModel::unloaded); connect(ICore::self()->pluginController(), &IPluginController::pluginLoaded, this, &ProvidersModel::loaded); connect(ICore::self()->documentationController(), &IDocumentationController::providersChanged, this, &ProvidersModel::reloadProviders); } void ProvidersModel::reloadProviders() { beginResetModel(); mProviders = ICore::self()->documentationController()->documentationProviders(); std::sort(mProviders.begin(), mProviders.end(), [](const KDevelop::IDocumentationProvider* a, const KDevelop::IDocumentationProvider* b) { return a->name() < b->name(); }); endResetModel(); emit providersChanged(); } QVariant ProvidersModel::data(const QModelIndex& index, int role) const { if (index.row() >= mProviders.count() || index.row() < 0) return QVariant(); QVariant ret; switch (role) { case Qt::DisplayRole: ret = provider(index.row())->name(); break; case Qt::DecorationRole: ret = provider(index.row())->icon(); break; } return ret; } void ProvidersModel::addProvider(IDocumentationProvider* provider) { if (!provider || mProviders.contains(provider)) return; int pos = 0; while (pos < mProviders.size() && mProviders[pos]->name() < provider->name()) ++pos; beginInsertRows(QModelIndex(), pos, pos); mProviders.insert(pos, provider); endInsertRows(); emit providersChanged(); } void ProvidersModel::removeProvider(IDocumentationProvider* provider) { int pos; if (!provider || (pos = mProviders.indexOf(provider)) < 0) return; beginRemoveRows(QModelIndex(), pos, pos); mProviders.removeAt(pos); endRemoveRows(); emit providersChanged(); } void ProvidersModel::unloaded(IPlugin* plugin) { removeProvider(plugin->extension()); IDocumentationProviderProvider* providerProvider = plugin->extension(); if (providerProvider) { foreach(IDocumentationProvider* provider, providerProvider->providers()) removeProvider(provider); } } void ProvidersModel::loaded(IPlugin* plugin) { addProvider(plugin->extension()); IDocumentationProviderProvider* providerProvider = plugin->extension(); if (providerProvider) { foreach(IDocumentationProvider* provider, providerProvider->providers()) addProvider(provider); } } int ProvidersModel::rowCount(const QModelIndex& parent) const { return parent.isValid() ? 0 : mProviders.count(); } int ProvidersModel::rowForProvider(IDocumentationProvider* provider) { return mProviders.indexOf(provider); } IDocumentationProvider* ProvidersModel::provider(int pos) const { return mProviders[pos]; } QList ProvidersModel::providers() { return mProviders; } diff --git a/language/CMakeLists.txt b/language/CMakeLists.txt index ce026596d..326e6e93c 100644 --- a/language/CMakeLists.txt +++ b/language/CMakeLists.txt @@ -1,401 +1,406 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kdevplatform\") # Check whether malloc_trim(3) is supported. include(CheckIncludeFile) include(CheckSymbolExists) check_include_file("malloc.h" HAVE_MALLOC_H) check_symbol_exists(malloc_trim "malloc.h" HAVE_MALLOC_TRIM) add_subdirectory(highlighting/tests) add_subdirectory(duchain/tests) add_subdirectory(backgroundparser/tests) add_subdirectory(codegen/tests) add_subdirectory(util/tests) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/language-features.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/language-features.h ) set(KDevPlatformLanguage_LIB_SRCS assistant/staticassistantsmanager.cpp assistant/renameaction.cpp assistant/renameassistant.cpp assistant/renamefileaction.cpp assistant/staticassistant.cpp editor/persistentmovingrangeprivate.cpp editor/persistentmovingrange.cpp editor/modificationrevisionset.cpp editor/modificationrevision.cpp backgroundparser/backgroundparser.cpp backgroundparser/parsejob.cpp backgroundparser/documentchangetracker.cpp backgroundparser/parseprojectjob.cpp backgroundparser/urlparselock.cpp duchain/specializationstore.cpp duchain/codemodel.cpp duchain/duchain.cpp duchain/waitforupdate.cpp duchain/duchainpointer.cpp duchain/ducontext.cpp duchain/indexedducontext.cpp duchain/indexedtopducontext.cpp duchain/localindexedducontext.cpp duchain/indexeddeclaration.cpp duchain/localindexeddeclaration.cpp duchain/topducontext.cpp duchain/topducontextdynamicdata.cpp duchain/topducontextutils.cpp duchain/functiondefinition.cpp duchain/declaration.cpp duchain/classmemberdeclaration.cpp duchain/classfunctiondeclaration.cpp duchain/classdeclaration.cpp duchain/use.cpp duchain/forwarddeclaration.cpp duchain/duchainbase.cpp duchain/duchainlock.cpp duchain/identifier.cpp duchain/parsingenvironment.cpp duchain/abstractfunctiondeclaration.cpp duchain/functiondeclaration.cpp duchain/stringhelpers.cpp duchain/namespacealiasdeclaration.cpp duchain/aliasdeclaration.cpp duchain/dumpdotgraph.cpp duchain/duchainutils.cpp duchain/declarationid.cpp duchain/definitions.cpp duchain/uses.cpp duchain/importers.cpp duchain/duchaindumper.cpp duchain/duchainregister.cpp duchain/persistentsymboltable.cpp duchain/instantiationinformation.cpp duchain/problem.cpp duchain/types/typesystem.cpp duchain/types/typeregister.cpp duchain/types/typerepository.cpp duchain/types/identifiedtype.cpp duchain/types/abstracttype.cpp duchain/types/integraltype.cpp duchain/types/functiontype.cpp duchain/types/structuretype.cpp duchain/types/pointertype.cpp duchain/types/referencetype.cpp duchain/types/delayedtype.cpp duchain/types/arraytype.cpp duchain/types/indexedtype.cpp duchain/types/enumerationtype.cpp duchain/types/constantintegraltype.cpp duchain/types/enumeratortype.cpp duchain/types/typeutils.cpp duchain/types/typealiastype.cpp duchain/types/unsuretype.cpp duchain/types/containertypes.cpp duchain/builders/dynamiclanguageexpressionvisitor.cpp duchain/navigation/problemnavigationcontext.cpp duchain/navigation/abstractnavigationwidget.cpp duchain/navigation/abstractnavigationcontext.cpp duchain/navigation/usesnavigationcontext.cpp duchain/navigation/abstractdeclarationnavigationcontext.cpp duchain/navigation/abstractincludenavigationcontext.cpp duchain/navigation/useswidget.cpp duchain/navigation/usescollector.cpp interfaces/abbreviations.cpp interfaces/iastcontainer.cpp interfaces/ilanguagesupport.cpp interfaces/quickopendataprovider.cpp interfaces/iquickopen.cpp interfaces/editorcontext.cpp interfaces/codecontext.cpp interfaces/icreateclasshelper.cpp interfaces/icontextbrowser.cpp codecompletion/codecompletion.cpp codecompletion/codecompletionworker.cpp codecompletion/codecompletionmodel.cpp codecompletion/codecompletionitem.cpp codecompletion/codecompletioncontext.cpp codecompletion/codecompletionitemgrouper.cpp codecompletion/codecompletionhelper.cpp codecompletion/normaldeclarationcompletionitem.cpp codegen/applychangeswidget.cpp codegen/coderepresentation.cpp codegen/documentchangeset.cpp codegen/duchainchangeset.cpp codegen/utilities.cpp codegen/codedescription.cpp codegen/basicrefactoring.cpp codegen/progressdialogs/refactoringdialog.cpp util/setrepository.cpp util/includeitem.cpp util/navigationtooltip.cpp - util/debug.cpp highlighting/colorcache.cpp highlighting/configurablecolors.cpp highlighting/codehighlighting.cpp checks/dataaccessrepository.cpp checks/dataaccess.cpp checks/controlflowgraph.cpp checks/controlflownode.cpp classmodel/classmodel.cpp classmodel/classmodelnode.cpp classmodel/classmodelnodescontroller.cpp classmodel/allclassesfolder.cpp classmodel/documentclassesfolder.cpp classmodel/projectfolder.cpp ) set(grantlee_LIB_SRCS codegen/templatesmodel.cpp codegen/templatepreviewicon.cpp codegen/templateclassgenerator.cpp codegen/sourcefiletemplate.cpp codegen/templaterenderer.cpp codegen/templateengine.cpp codegen/archivetemplateloader.cpp ) if (Grantlee5_FOUND) list(APPEND KDevPlatformLanguage_LIB_SRCS ${grantlee_LIB_SRCS}) endif() +ecm_qt_declare_logging_category(KDevPlatformLanguage_LIB_SRCS + HEADER debug.h + IDENTIFIER LANGUAGE + CATEGORY_NAME "kdevplatform.language" +) + ki18n_wrap_ui(KDevPlatformLanguage_LIB_SRCS codegen/basicrefactoring.ui codegen/progressdialogs/refactoringdialog.ui) kdevplatform_add_library(KDevPlatformLanguage SOURCES ${KDevPlatformLanguage_LIB_SRCS}) target_include_directories(KDevPlatformLanguage PRIVATE ${Boost_INCLUDE_DIRS}) target_link_libraries(KDevPlatformLanguage LINK_PUBLIC KF5::ThreadWeaver KDev::Interfaces KDev::Serialization LINK_PRIVATE KF5::GuiAddons KF5::TextEditor KF5::Parts KF5::Archive KF5::IconThemes KDev::Util KDev::Project ) if (Grantlee5_FOUND) target_link_libraries(KDevPlatformLanguage LINK_PRIVATE Grantlee5::Templates) endif() install(FILES assistant/renameaction.h assistant/renameassistant.h assistant/staticassistant.h assistant/staticassistantsmanager.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/kdevplatform/language/assistant COMPONENT Devel ) install(FILES interfaces/ilanguagesupport.h interfaces/icodehighlighting.h interfaces/quickopendataprovider.h interfaces/quickopenfilter.h interfaces/iquickopen.h interfaces/codecontext.h interfaces/editorcontext.h interfaces/iastcontainer.h interfaces/icreateclasshelper.h interfaces/icontextbrowser.h interfaces/abbreviations.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/kdevplatform/language/interfaces COMPONENT Devel ) install(FILES editor/persistentmovingrange.h editor/documentrange.h editor/documentcursor.h editor/cursorinrevision.h editor/rangeinrevision.h editor/modificationrevision.h editor/modificationrevisionset.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/kdevplatform/language/editor COMPONENT Devel ) install(FILES backgroundparser/backgroundparser.h backgroundparser/parsejob.h backgroundparser/parseprojectjob.h backgroundparser/urlparselock.h backgroundparser/documentchangetracker.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/kdevplatform/language/backgroundparser COMPONENT Devel ) install(FILES util/navigationtooltip.h util/setrepository.h util/basicsetrepository.h util/includeitem.h util/debuglanguageparserhelper.h util/kdevhash.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/kdevplatform/language/util COMPONENT Devel ) install(FILES duchain/parsingenvironment.h duchain/duchain.h duchain/codemodel.h duchain/ducontext.h duchain/ducontextdata.h duchain/topducontext.h duchain/topducontextutils.h duchain/topducontextdata.h duchain/declaration.h duchain/declarationdata.h duchain/classmemberdeclaration.h duchain/classmemberdeclarationdata.h duchain/classfunctiondeclaration.h duchain/classdeclaration.h duchain/functiondefinition.h duchain/use.h duchain/forwarddeclaration.h duchain/duchainbase.h duchain/duchainpointer.h duchain/duchainlock.h duchain/identifier.h duchain/abstractfunctiondeclaration.h duchain/functiondeclaration.h duchain/stringhelpers.h duchain/safetycounter.h duchain/namespacealiasdeclaration.h duchain/aliasdeclaration.h duchain/dumpdotgraph.h duchain/duchainutils.h duchain/duchaindumper.h duchain/declarationid.h duchain/appendedlist.h duchain/duchainregister.h duchain/persistentsymboltable.h duchain/instantiationinformation.h duchain/specializationstore.h duchain/persistentsetmap.h duchain/indexedducontext.h duchain/indexedtopducontext.h duchain/localindexedducontext.h duchain/indexeddeclaration.h duchain/localindexeddeclaration.h duchain/definitions.h duchain/problem.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/kdevplatform/language/duchain COMPONENT Devel ) install(FILES duchain/types/unsuretype.h duchain/types/identifiedtype.h duchain/types/typesystem.h duchain/types/typeregister.h duchain/types/typerepository.h duchain/types/typepointer.h duchain/types/typesystemdata.h duchain/types/abstracttype.h duchain/types/integraltype.h duchain/types/functiontype.h duchain/types/structuretype.h duchain/types/pointertype.h duchain/types/referencetype.h duchain/types/delayedtype.h duchain/types/arraytype.h duchain/types/indexedtype.h duchain/types/enumerationtype.h duchain/types/constantintegraltype.h duchain/types/enumeratortype.h duchain/types/alltypes.h duchain/types/typeutils.h duchain/types/typealiastype.h duchain/types/containertypes.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/kdevplatform/language/duchain/types COMPONENT Devel ) install(FILES duchain/builders/abstractcontextbuilder.h duchain/builders/abstractdeclarationbuilder.h duchain/builders/abstracttypebuilder.h duchain/builders/abstractusebuilder.h duchain/builders/dynamiclanguageexpressionvisitor.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/kdevplatform/language/duchain/builders COMPONENT Devel ) install(FILES codecompletion/codecompletion.h codecompletion/codecompletionworker.h codecompletion/codecompletionmodel.h codecompletion/codecompletionitem.h codecompletion/codecompletioncontext.h codecompletion/codecompletionitemgrouper.h codecompletion/codecompletionhelper.h codecompletion/normaldeclarationcompletionitem.h codecompletion/abstractincludefilecompletionitem.h codecompletion/codecompletiontesthelper.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/kdevplatform/language/codecompletion COMPONENT Devel ) install(FILES codegen/applychangeswidget.h codegen/astchangeset.h codegen/duchainchangeset.h codegen/documentchangeset.h codegen/coderepresentation.h codegen/utilities.h codegen/templatesmodel.h codegen/templatepreviewicon.h codegen/templaterenderer.h codegen/templateengine.h codegen/sourcefiletemplate.h codegen/templateclassgenerator.h codegen/codedescription.h codegen/basicrefactoring.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/kdevplatform/language/codegen COMPONENT Devel ) install(FILES duchain/navigation/usesnavigationcontext.h duchain/navigation/abstractnavigationcontext.h duchain/navigation/abstractdeclarationnavigationcontext.h duchain/navigation/abstractincludenavigationcontext.h duchain/navigation/abstractnavigationwidget.h duchain/navigation/navigationaction.h duchain/navigation/useswidget.h duchain/navigation/usescollector.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/kdevplatform/language/duchain/navigation COMPONENT Devel ) install(FILES highlighting/codehighlighting.h highlighting/colorcache.h highlighting/configurablecolors.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/kdevplatform/language/highlighting COMPONENT Devel ) install(FILES checks/dataaccess.h checks/dataaccessrepository.h checks/controlflowgraph.h checks/controlflownode.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/kdevplatform/language/checks COMPONENT Devel ) install(FILES classmodel/classmodel.h classmodel/classmodelnode.h classmodel/classmodelnodescontroller.h classmodel/allclassesfolder.h classmodel/documentclassesfolder.h classmodel/projectfolder.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/kdevplatform/language/classmodel COMPONENT Devel ) diff --git a/language/assistant/renameassistant.cpp b/language/assistant/renameassistant.cpp index 48675ffd8..fd0c62483 100644 --- a/language/assistant/renameassistant.cpp +++ b/language/assistant/renameassistant.cpp @@ -1,238 +1,238 @@ /* Copyright 2010 Olivier de Gaalon Copyright 2014 Kevin Funk 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 "renameassistant.h" #include "renameaction.h" #include "renamefileaction.h" -#include "util/debug.h" +#include #include "../codegen/basicrefactoring.h" #include "../duchain/duchain.h" #include "../duchain/duchainlock.h" #include "../duchain/duchainutils.h" #include "../duchain/declaration.h" #include "../duchain/functiondefinition.h" #include "../duchain/classfunctiondeclaration.h" #include #include #include #include #include #include using namespace KDevelop; namespace { bool rangesConnect(const KTextEditor::Range& firstRange, const KTextEditor::Range& secondRange) { return !firstRange.intersect(secondRange + KTextEditor::Range(0, -1, 0, +1)).isEmpty(); } Declaration* getDeclarationForChangedRange(KTextEditor::Document* doc, const KTextEditor::Range& changed) { const KTextEditor::Cursor cursor(changed.start()); Declaration* declaration = DUChainUtils::itemUnderCursor(doc->url(), cursor).declaration; //If it's null we could be appending, but there's a case where appending gives a wrong decl //and not a null declaration ... "type var(init)", so check for that too if (!declaration || !rangesConnect(declaration->rangeInCurrentRevision(), changed)) { declaration = DUChainUtils::itemUnderCursor(doc->url(), KTextEditor::Cursor(cursor.line(), cursor.column()-1)).declaration; } //In this case, we may either not have a decl at the cursor, or we got a decl, but are editing its use. //In either of those cases, give up and return 0 if (!declaration || !rangesConnect(declaration->rangeInCurrentRevision(), changed)) { return nullptr; } return declaration; } } struct RenameAssistant::Private { explicit Private(RenameAssistant* qq) : q(qq) , m_isUseful(false) , m_renameFile(false) { } void reset() { q->doHide(); q->clearActions(); m_oldDeclarationName = Identifier(); m_newDeclarationRange.reset(); m_oldDeclarationUses.clear(); m_isUseful = false; m_renameFile = false; } RenameAssistant* q; KDevelop::Identifier m_oldDeclarationName; QString m_newDeclarationName; KDevelop::PersistentMovingRange::Ptr m_newDeclarationRange; QVector m_oldDeclarationUses; bool m_isUseful; bool m_renameFile; KTextEditor::Cursor m_lastChangedLocation; QPointer m_lastChangedDocument = nullptr; }; RenameAssistant::RenameAssistant(ILanguageSupport* supportedLanguage) : StaticAssistant(supportedLanguage) , d(new Private(this)) { } RenameAssistant::~RenameAssistant() { } QString RenameAssistant::title() const { return i18n("Rename"); } bool RenameAssistant::isUseful() const { return d->m_isUseful; } void RenameAssistant::textChanged(KTextEditor::Document* doc, const KTextEditor::Range& invocationRange, const QString& removedText) { clearActions(); d->m_lastChangedLocation = invocationRange.end(); d->m_lastChangedDocument = doc; if (!supportedLanguage()->refactoring()) { qCWarning(LANGUAGE) << "Refactoring not supported. Aborting."; return; } if (!doc) return; //If the inserted text isn't valid for a variable name, consider the editing ended QRegExp validDeclName("^[0-9a-zA-Z_]*$"); if (removedText.isEmpty() && !validDeclName.exactMatch(doc->text(invocationRange))) { d->reset(); return; } const QUrl url = doc->url(); const IndexedString indexedUrl(url); DUChainReadLocker lock; //If we've stopped editing m_newDeclarationRange or switched the view, // reset and see if there's another declaration being edited if (!d->m_newDeclarationRange.data() || !rangesConnect(d->m_newDeclarationRange->range(), invocationRange) || d->m_newDeclarationRange->document() != indexedUrl) { d->reset(); Declaration* declAtCursor = getDeclarationForChangedRange(doc, invocationRange); if (!declAtCursor) { // not editing a declaration return; } if (supportedLanguage()->refactoring()->shouldRenameUses(declAtCursor)) { QMap< IndexedString, QList > declUses = declAtCursor->uses(); if (declUses.isEmpty()) { // new declaration has no uses return; } for(QMap< IndexedString, QList< RangeInRevision > >::const_iterator it = declUses.constBegin(); it != declUses.constEnd(); ++it) { foreach(const RangeInRevision range, it.value()) { KTextEditor::Range currentRange = declAtCursor->transformFromLocalRevision(range); if(currentRange.isEmpty() || doc->text(currentRange) != declAtCursor->identifier().identifier().str()) { return; // One of the uses is invalid. Maybe the replacement has already been performed. } } } d->m_oldDeclarationUses = RevisionedFileRanges::convert(declUses); } else if (supportedLanguage()->refactoring()->shouldRenameFile(declAtCursor)) { d->m_renameFile = true; } else { // not a valid declaration return; } d->m_oldDeclarationName = declAtCursor->identifier(); KTextEditor::Range newRange = declAtCursor->rangeInCurrentRevision(); if (removedText.isEmpty() && newRange.intersect(invocationRange).isEmpty()) { newRange = newRange.encompass(invocationRange); //if text was added to the ends, encompass it } d->m_newDeclarationRange = new PersistentMovingRange(newRange, indexedUrl, true); } //Unfortunately this happens when you make a selection including one end of the decl's range and replace it if (removedText.isEmpty() && d->m_newDeclarationRange->range().intersect(invocationRange).isEmpty()) { d->m_newDeclarationRange = new PersistentMovingRange( d->m_newDeclarationRange->range().encompass(invocationRange), indexedUrl, true); } d->m_newDeclarationName = doc->text(d->m_newDeclarationRange->range()).trimmed(); if (d->m_newDeclarationName == d->m_oldDeclarationName.toString()) { d->reset(); return; } if (d->m_renameFile && supportedLanguage()->refactoring()->newFileName(url, d->m_newDeclarationName) == url.fileName()) { // no change, don't do anything return; } d->m_isUseful = true; IAssistantAction::Ptr action; if (d->m_renameFile) { action = new RenameFileAction(supportedLanguage()->refactoring(), url, d->m_newDeclarationName); } else { action = new RenameAction(d->m_oldDeclarationName, d->m_newDeclarationName, d->m_oldDeclarationUses); } connect(action.data(), &IAssistantAction::executed, this, [&] { d->reset(); }); addAction(action); emit actionsChanged(); } KTextEditor::Range KDevelop::RenameAssistant::displayRange() const { if ( !d->m_lastChangedDocument ) { return {}; } auto range = d->m_lastChangedDocument->wordRangeAt(d->m_lastChangedLocation); qDebug() << "range:" << range; return range; } #include "moc_renameassistant.cpp" diff --git a/language/assistant/renamefileaction.cpp b/language/assistant/renamefileaction.cpp index bc070453c..1ea828f4e 100644 --- a/language/assistant/renamefileaction.cpp +++ b/language/assistant/renamefileaction.cpp @@ -1,83 +1,83 @@ /* Copyright 2012 Milian Wolff Copyright 2014 Kevin Funk 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 "renamefileaction.h" -#include "util/debug.h" +#include #include #include #include #include #include #include using namespace KDevelop; struct RenameFileAction::Private { KDevelop::BasicRefactoring* m_refactoring; QUrl m_file; QString m_newName; }; RenameFileAction::RenameFileAction(BasicRefactoring* refactoring, const QUrl& file, const QString& newName) : d(new Private) { d->m_refactoring = refactoring; d->m_file = file; d->m_newName = newName; } RenameFileAction::~RenameFileAction() { } QString RenameFileAction::description() const { return i18n("Rename file from \"%1\" to \"%2\".", d->m_file.fileName(), d->m_refactoring->newFileName(d->m_file, d->m_newName)); } void RenameFileAction::execute() { // save document to prevent unwanted dialogs IDocument* doc = ICore::self()->documentController()->documentForUrl(d->m_file); if (!doc) { qCWarning(LANGUAGE) << "could find no document for url:" << d->m_file; return; } if (!ICore::self()->documentController()->saveSomeDocuments(QList() << doc, IDocument::Silent)) { return; } // rename document DocumentChangeSet changes; DocumentChangeSet::ChangeResult result = d->m_refactoring->addRenameFileChanges(d->m_file, d->m_newName, &changes); if (result) { result = changes.applyAllChanges(); } if(!result) { KMessageBox::error(nullptr, i18n("Failed to apply changes: %1", result.m_failureReason)); } emit executed(this); } diff --git a/language/assistant/staticassistantsmanager.cpp b/language/assistant/staticassistantsmanager.cpp index 7b11392e2..1b21ac30f 100644 --- a/language/assistant/staticassistantsmanager.cpp +++ b/language/assistant/staticassistantsmanager.cpp @@ -1,188 +1,188 @@ /* Copyright 2009 David Nolden Copyright 2014 Kevin Funk 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 "staticassistantsmanager.h" -#include "util/debug.h" +#include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; using namespace KTextEditor; struct StaticAssistantsManager::Private { explicit Private(StaticAssistantsManager* qq) : q(qq) { } void updateReady(const IndexedString& document, const KDevelop::ReferencedTopDUContext& topContext); void documentLoaded(KDevelop::IDocument*); void textInserted(KTextEditor::Document* document, const Cursor& cursor, const QString& text); void textRemoved(KTextEditor::Document* document, const Range& cursor, const QString& removedText); StaticAssistantsManager* q; QVector m_registeredAssistants; }; StaticAssistantsManager::StaticAssistantsManager(QObject* parent) : QObject(parent) , d(new Private(this)) { connect(KDevelop::ICore::self()->documentController(), &IDocumentController::documentLoaded, this, [&] (IDocument* document) { d->documentLoaded(document); }); foreach (IDocument* document, ICore::self()->documentController()->openDocuments()) { d->documentLoaded(document); } connect(DUChain::self(), &DUChain::updateReady, this, &StaticAssistantsManager::notifyAssistants); } StaticAssistantsManager::~StaticAssistantsManager() { } void StaticAssistantsManager::registerAssistant(const StaticAssistant::Ptr assistant) { if (d->m_registeredAssistants.contains(assistant)) return; d->m_registeredAssistants << assistant; } void StaticAssistantsManager::unregisterAssistant(const StaticAssistant::Ptr assistant) { d->m_registeredAssistants.removeOne(assistant); } QVector StaticAssistantsManager::registeredAssistants() const { return d->m_registeredAssistants; } void StaticAssistantsManager::Private::documentLoaded(IDocument* document) { if (document->textDocument()) { auto doc = document->textDocument(); connect(doc, &KTextEditor::Document::textInserted, q, [&] (KTextEditor::Document* doc, const Cursor& cursor, const QString& text) { textInserted(doc, cursor, text); }); connect(doc, &KTextEditor::Document::textRemoved, q, [&] (KTextEditor::Document* doc, const Range& range, const QString& removedText) { textRemoved(doc, range, removedText); }); } } void StaticAssistantsManager::Private::textInserted(Document* doc, const Cursor& cursor, const QString& text) { auto changed = false; Q_FOREACH ( auto assistant, m_registeredAssistants ) { auto range = Range(cursor, cursor+Cursor(0, text.size())); auto wasUseful = assistant->isUseful(); assistant->textChanged(doc, range, {}); if ( wasUseful != assistant->isUseful() ) { changed = true; } } if ( changed ) { Q_EMIT q->problemsChanged(IndexedString(doc->url())); } } void StaticAssistantsManager::Private::textRemoved(Document* doc, const Range& range, const QString& removedText) { auto changed = false; Q_FOREACH ( auto assistant, m_registeredAssistants ) { auto wasUseful = assistant->isUseful(); assistant->textChanged(doc, range, removedText); if ( wasUseful != assistant->isUseful() ) { changed = true; } } if ( changed ) { Q_EMIT q->problemsChanged(IndexedString(doc->url())); } } void StaticAssistantsManager::notifyAssistants(const IndexedString& url, const KDevelop::ReferencedTopDUContext& context) { Q_FOREACH ( auto assistant, d->m_registeredAssistants ) { assistant->updateReady(url, context); } } QVector KDevelop::StaticAssistantsManager::problemsForContext(const KDevelop::ReferencedTopDUContext& top) { View* view = ICore::self()->documentController()->activeTextDocumentView(); if (!view || !top || IndexedString(view->document()->url()) != top->url()) { return {}; } auto doc = top->url(); auto language = ICore::self()->languageController()->languagesForUrl(doc.toUrl()).value(0); if (!language) { return {}; } auto ret = QVector(); qCDebug(LANGUAGE) << "Trying to find assistants for language" << language->name(); foreach (const auto& assistant, d->m_registeredAssistants) { if (assistant->supportedLanguage() != language) continue; if (assistant->isUseful()) { qDebug() << "assistant is now useful:" << assistant.data(); auto p = new KDevelop::StaticAssistantProblem(); auto range = assistant->displayRange(); qDebug() << "range:" << range; p->setFinalLocation(DocumentRange(doc, range)); p->setSource(KDevelop::IProblem::SemanticAnalysis); p->setSeverity(KDevelop::IProblem::Warning); p->setDescription(assistant->title()); p->setSolutionAssistant(IAssistant::Ptr(assistant.data())); ret.append(KDevelop::Problem::Ptr(p)); } } return ret; } #include "moc_staticassistantsmanager.cpp" diff --git a/language/backgroundparser/backgroundparser.cpp b/language/backgroundparser/backgroundparser.cpp index be5c26771..c79f2aa5e 100644 --- a/language/backgroundparser/backgroundparser.cpp +++ b/language/backgroundparser/backgroundparser.cpp @@ -1,917 +1,917 @@ /* * This file is part of KDevelop * * Copyright 2006 Adam Treat * Copyright 2007 Kris Wong * Copyright 2007-2008 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "backgroundparser.h" #include "qtcompat_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include -#include "util/debug.h" +#include #include "parsejob.h" using namespace KDevelop; namespace { const bool separateThreadForHighPriority = true; /** * Elides string in @p path, e.g. "VEEERY/LONG/PATH" -> ".../LONG/PATH" * - probably much faster than QFontMetrics::elidedText() * - we do not need a widget context * - takes path separators into account * * @p width Maximum number of characters * * TODO: Move to kdevutil? */ QString elidedPathLeft(const QString& path, int width) { static const QChar separator = QDir::separator(); static const QString placeholder = QStringLiteral("..."); if (path.size() <= width) { return path; } int start = (path.size() - width) + placeholder.size(); int pos = path.indexOf(separator, start); if (pos == -1) { pos = start; // no separator => just cut off the path at the beginning } Q_ASSERT(path.size() - pos >= 0 && path.size() - pos <= width); QStringRef elidedText = path.rightRef(path.size() - pos); QString result = placeholder; result.append(elidedText); return result; } /** * @return true if @p url is non-empty, valid and has a clean path, false otherwise. */ inline bool isValidURL(const IndexedString& url) { if (url.isEmpty()) { return false; } QUrl original = url.toUrl(); if (!original.isValid() || original.isRelative() || (original.fileName().isEmpty() && original.isLocalFile())) { qCWarning(LANGUAGE) << "INVALID URL ENCOUNTERED:" << url << original; return false; } QUrl cleaned = original.adjusted(QUrl::NormalizePathSegments); return original == cleaned; } } struct DocumentParseTarget { QPointer notifyWhenReady; int priority; TopDUContext::Features features; ParseJob::SequentialProcessingFlags sequentialProcessingFlags; bool operator==(const DocumentParseTarget& rhs) const { return notifyWhenReady == rhs.notifyWhenReady && priority == rhs.priority && features == rhs.features; } }; inline uint qHash(const DocumentParseTarget& target) { return target.features * 7 + target.priority * 13 + target.sequentialProcessingFlags * 17 + reinterpret_cast(target.notifyWhenReady.data()); }; struct DocumentParsePlan { QSet targets; ParseJob::SequentialProcessingFlags sequentialProcessingFlags() const { //Pick the strictest possible flags ParseJob::SequentialProcessingFlags ret = ParseJob::IgnoresSequentialProcessing; foreach(const DocumentParseTarget &target, targets) { ret |= target.sequentialProcessingFlags; } return ret; } int priority() const { //Pick the best priority int ret = BackgroundParser::WorstPriority; foreach(const DocumentParseTarget &target, targets) { if(target.priority < ret) { ret = target.priority; } } return ret; } TopDUContext::Features features() const { //Pick the best features TopDUContext::Features ret = (TopDUContext::Features)0; foreach(const DocumentParseTarget &target, targets) { ret = (TopDUContext::Features) (ret | target.features); } return ret; } QList > notifyWhenReady() const { QList > ret; foreach(const DocumentParseTarget &target, targets) { if(target.notifyWhenReady) ret << target.notifyWhenReady; } return ret; } }; Q_DECLARE_TYPEINFO(DocumentParseTarget, Q_MOVABLE_TYPE); Q_DECLARE_TYPEINFO(DocumentParsePlan, Q_MOVABLE_TYPE); class KDevelop::BackgroundParserPrivate { public: BackgroundParserPrivate(BackgroundParser *parser, ILanguageController *languageController) :m_parser(parser), m_languageController(languageController), m_shuttingDown(false), m_mutex(QMutex::Recursive) { parser->d = this; //Set this so we can safely call back BackgroundParser from within loadSettings() m_timer.setSingleShot(true); m_progressTimer.setSingleShot(true); m_progressTimer.setInterval(500); ThreadWeaver::setDebugLevel(true, 1); QObject::connect(&m_timer, &QTimer::timeout, m_parser, &BackgroundParser::parseDocuments); QObject::connect(&m_progressTimer, &QTimer::timeout, m_parser, &BackgroundParser::updateProgressBar); } void startTimerThreadSafe(int delay) { QMetaObject::invokeMethod(m_parser, "startTimer", Qt::QueuedConnection, Q_ARG(int, delay)); } ~BackgroundParserPrivate() { m_weaver.resume(); m_weaver.finish(); } // Non-mutex guarded functions, only call with m_mutex acquired. int currentBestRunningPriority() const { int bestRunningPriority = BackgroundParser::WorstPriority; for (const auto* decorator : m_parseJobs) { const ParseJob* parseJob = dynamic_cast(decorator->job()); Q_ASSERT(parseJob); if (parseJob->respectsSequentialProcessing() && parseJob->parsePriority() < bestRunningPriority) { bestRunningPriority = parseJob->parsePriority(); } } return bestRunningPriority; } IndexedString nextDocumentToParse() const { // Before starting a new job, first wait for all higher-priority ones to finish. // That way, parse job priorities can be used for dependency handling. const int bestRunningPriority = currentBestRunningPriority(); for (auto it1 = m_documentsForPriority.begin(); it1 != m_documentsForPriority.end(); ++it1 ) { const auto priority = it1.key(); if(priority > m_neededPriority) break; //The priority is not good enough to be processed right now if (m_parseJobs.count() >= m_threads && priority > BackgroundParser::NormalPriority && !specialParseJob) { break; //The additional parsing thread is reserved for higher priority parsing } for (const auto& url : it1.value()) { // When a document is scheduled for parsing while it is being parsed, it will be parsed // again once the job finished, but not now. if (m_parseJobs.contains(url)) { continue; } Q_ASSERT(m_documents.contains(url)); const auto& parsePlan = m_documents[url]; // If the current job requires sequential processing, but not all jobs with a better priority have been // completed yet, it will not be created now. if ( parsePlan.sequentialProcessingFlags() & ParseJob::RequiresSequentialProcessing && parsePlan.priority() > bestRunningPriority ) { continue; } return url; } } return {}; } /** * Create a single delayed parse job * * E.g. jobs for documents which have been changed by the user, but also to * handle initial startup where we parse all project files. */ void parseDocumentsInternal() { if(m_shuttingDown) return; //Only create parse-jobs for up to thread-count * 2 documents, so we don't fill the memory unnecessarily if (m_parseJobs.count() >= m_threads+1 || (m_parseJobs.count() >= m_threads && !separateThreadForHighPriority)) { return; } const auto& url = nextDocumentToParse(); if (!url.isEmpty()) { qCDebug(LANGUAGE) << "creating parse-job" << url << "new count of active parse-jobs:" << m_parseJobs.count() + 1; const QString elidedPathString = elidedPathLeft(url.str(), 70); emit m_parser->showMessage(m_parser, i18n("Parsing: %1", elidedPathString)); ThreadWeaver::QObjectDecorator* decorator = nullptr; { // copy shared data before unlocking the mutex const auto parsePlanConstIt = m_documents.constFind(url); const DocumentParsePlan parsePlan = *parsePlanConstIt; // we must not lock the mutex while creating a parse job // this could in turn lock e.g. the DUChain and then // we have a classic lock order inversion (since, usually, // we lock first the duchain and then our background parser // mutex) // see also: https://bugs.kde.org/show_bug.cgi?id=355100 m_mutex.unlock(); decorator = createParseJob(url, parsePlan); m_mutex.lock(); } // iterator might get invalid during the time we didn't have the lock // search again const auto parsePlanIt = m_documents.find(url); if (parsePlanIt != m_documents.end()) { // Remove all mentions of this document. for (const auto& target : qAsConst(parsePlanIt->targets)) { m_documentsForPriority[target.priority].remove(url); } m_documents.erase(parsePlanIt); } else { qWarning(LANGUAGE) << "Document got removed during parse job creation:" << url; } if (decorator) { if(m_parseJobs.count() == m_threads+1 && !specialParseJob) specialParseJob = decorator; //This parse-job is allocated into the reserved thread m_parseJobs.insert(url, decorator); m_weaver.enqueue(ThreadWeaver::JobPointer(decorator)); } else { --m_maxParseJobs; } if (!m_documents.isEmpty()) { // Only try creating one parse-job at a time, else we might iterate through thousands of files // without finding a language-support, and block the UI for a long time. QMetaObject::invokeMethod(m_parser, "parseDocuments", Qt::QueuedConnection); } else { // make sure we cleaned up properly // TODO: also empty m_documentsForPriority when m_documents is empty? or do we want to keep capacity? Q_ASSERT(std::none_of(m_documentsForPriority.constBegin(), m_documentsForPriority.constEnd(), [] (const QSet& docs) { return !docs.isEmpty(); })); } } m_parser->updateProgressData(); } // NOTE: you must not access any of the data structures that are protected by any of the // background parser internal mutexes in this method // see also: https://bugs.kde.org/show_bug.cgi?id=355100 ThreadWeaver::QObjectDecorator* createParseJob(const IndexedString& url, const DocumentParsePlan& parsePlan) { ///FIXME: use IndexedString in the other APIs as well! Esp. for createParseJob! QUrl qUrl = url.toUrl(); const auto languages = m_languageController->languagesForUrl(qUrl); const auto& notifyWhenReady = parsePlan.notifyWhenReady(); for (const auto language : languages) { if (!language) { qCWarning(LANGUAGE) << "got zero language for" << qUrl; continue; } ParseJob* job = language->createParseJob(url); if (!job) { continue; // Language part did not produce a valid ParseJob. } job->setParsePriority(parsePlan.priority()); job->setMinimumFeatures(parsePlan.features()); job->setNotifyWhenReady(notifyWhenReady); job->setSequentialProcessingFlags(parsePlan.sequentialProcessingFlags()); ThreadWeaver::QObjectDecorator* decorator = new ThreadWeaver::QObjectDecorator(job); QObject::connect(decorator, &ThreadWeaver::QObjectDecorator::done, m_parser, &BackgroundParser::parseComplete); QObject::connect(decorator, &ThreadWeaver::QObjectDecorator::failed, m_parser, &BackgroundParser::parseComplete); QObject::connect(job, &ParseJob::progress, m_parser, &BackgroundParser::parseProgress, Qt::QueuedConnection); // TODO more thinking required here to support multiple parse jobs per url (where multiple language plugins want to parse) return decorator; } if (languages.isEmpty()) qCDebug(LANGUAGE) << "found no languages for url" << qUrl; else qCDebug(LANGUAGE) << "could not create parse-job for url" << qUrl; //Notify that we failed for (const auto& n : notifyWhenReady) { if (!n) { continue; } QMetaObject::invokeMethod(n.data(), "updateReady", Qt::QueuedConnection, Q_ARG(KDevelop::IndexedString, url), Q_ARG(KDevelop::ReferencedTopDUContext, ReferencedTopDUContext())); } return nullptr; } void loadSettings() { ///@todo re-load settings when they have been changed! Q_ASSERT(ICore::self()->activeSession()); KConfigGroup config(ICore::self()->activeSession()->config(), "Background Parser"); // stay backwards compatible KConfigGroup oldConfig(KSharedConfig::openConfig(), "Background Parser"); #define BACKWARDS_COMPATIBLE_ENTRY(entry, default) \ config.readEntry(entry, oldConfig.readEntry(entry, default)) m_delay = BACKWARDS_COMPATIBLE_ENTRY("Delay", 500); m_timer.setInterval(m_delay); m_threads = 0; if (qEnvironmentVariableIsSet("KDEV_BACKGROUNDPARSER_MAXTHREADS")) { m_parser->setThreadCount(qgetenv("KDEV_BACKGROUNDPARSER_MAXTHREADS").toInt()); } else { m_parser->setThreadCount(BACKWARDS_COMPATIBLE_ENTRY("Number of Threads", QThread::idealThreadCount())); } resume(); if (BACKWARDS_COMPATIBLE_ENTRY("Enabled", true)) { m_parser->enableProcessing(); } else { m_parser->disableProcessing(); } } void suspend() { qCDebug(LANGUAGE) << "Suspending background parser"; bool s = m_weaver.state()->stateId() == ThreadWeaver::Suspended || m_weaver.state()->stateId() == ThreadWeaver::Suspending; if (s) { // Already suspending qCWarning(LANGUAGE) << "Already suspended or suspending"; return; } m_timer.stop(); m_weaver.suspend(); } void resume() { bool s = m_weaver.state()->stateId() == ThreadWeaver::Suspended || m_weaver.state()->stateId() == ThreadWeaver::Suspending; if (m_timer.isActive() && !s) { // Not suspending return; } m_timer.start(m_delay); m_weaver.resume(); } BackgroundParser *m_parser; ILanguageController* m_languageController; //Current parse-job that is executed in the additional thread QPointer specialParseJob; QTimer m_timer; int m_delay = 500; int m_threads = 1; bool m_shuttingDown; // A list of documents that are planned to be parsed, and their priority QHash m_documents; // The documents ordered by priority QMap > m_documentsForPriority; // Currently running parse jobs QHash m_parseJobs; // The url for each managed document. Those may temporarily differ from the real url. QHash m_managedTextDocumentUrls; // Projects currently in progress of loading QSet m_loadingProjects; ThreadWeaver::Queue m_weaver; // generic high-level mutex QMutex m_mutex; // local mutex only protecting m_managed QMutex m_managedMutex; // A change tracker for each managed document QHash m_managed; int m_maxParseJobs = 0; int m_doneParseJobs = 0; QHash m_jobProgress; /// The minimum priority needed for processed jobs int m_neededPriority = BackgroundParser::WorstPriority; int m_progressMax = 0; int m_progressDone = 0; QTimer m_progressTimer; }; BackgroundParser::BackgroundParser(ILanguageController *languageController) : QObject(languageController), d(new BackgroundParserPrivate(this, languageController)) { Q_ASSERT(ICore::self()->documentController()); connect(ICore::self()->documentController(), &IDocumentController::documentLoaded, this, &BackgroundParser::documentLoaded); connect(ICore::self()->documentController(), &IDocumentController::documentUrlChanged, this, &BackgroundParser::documentUrlChanged); connect(ICore::self()->documentController(), &IDocumentController::documentClosed, this, &BackgroundParser::documentClosed); connect(ICore::self(), &ICore::aboutToShutdown, this, &BackgroundParser::aboutToQuit); bool connected = QObject::connect(ICore::self()->projectController(), &IProjectController::projectAboutToBeOpened, this, &BackgroundParser::projectAboutToBeOpened); Q_ASSERT(connected); connected = QObject::connect(ICore::self()->projectController(), &IProjectController::projectOpened, this, &BackgroundParser::projectOpened); Q_ASSERT(connected); connected = QObject::connect(ICore::self()->projectController(), &IProjectController::projectOpeningAborted, this, &BackgroundParser::projectOpeningAborted); Q_ASSERT(connected); Q_UNUSED(connected); } void BackgroundParser::aboutToQuit() { d->m_shuttingDown = true; } BackgroundParser::~BackgroundParser() { delete d; } QString BackgroundParser::statusName() const { return i18n("Background Parser"); } void BackgroundParser::loadSettings() { d->loadSettings(); } void BackgroundParser::parseProgress(KDevelop::ParseJob* job, float value, QString text) { Q_UNUSED(text) d->m_jobProgress[job] = value; updateProgressData(); } void BackgroundParser::revertAllRequests(QObject* notifyWhenReady) { QMutexLocker lock(&d->m_mutex); for (auto it = d->m_documents.begin(); it != d->m_documents.end(); ) { d->m_documentsForPriority[it.value().priority()].remove(it.key()); foreach ( const DocumentParseTarget& target, (*it).targets ) { if ( notifyWhenReady && target.notifyWhenReady.data() == notifyWhenReady ) { (*it).targets.remove(target); } } if((*it).targets.isEmpty()) { it = d->m_documents.erase(it); --d->m_maxParseJobs; continue; } d->m_documentsForPriority[it.value().priority()].insert(it.key()); ++it; } } void BackgroundParser::addDocument(const IndexedString& url, TopDUContext::Features features, int priority, QObject* notifyWhenReady, ParseJob::SequentialProcessingFlags flags, int delay) { // qCDebug(LANGUAGE) << "BackgroundParser::addDocument" << url.toUrl(); Q_ASSERT(isValidURL(url)); QMutexLocker lock(&d->m_mutex); { DocumentParseTarget target; target.priority = priority; target.features = features; target.sequentialProcessingFlags = flags; target.notifyWhenReady = QPointer(notifyWhenReady); auto it = d->m_documents.find(url); if (it != d->m_documents.end()) { //Update the stored plan d->m_documentsForPriority[it.value().priority()].remove(url); it.value().targets << target; d->m_documentsForPriority[it.value().priority()].insert(url); }else{ // qCDebug(LANGUAGE) << "BackgroundParser::addDocument: queuing" << cleanedUrl; d->m_documents[url].targets << target; d->m_documentsForPriority[d->m_documents[url].priority()].insert(url); ++d->m_maxParseJobs; //So the progress-bar waits for this document } if ( delay == ILanguageSupport::DefaultDelay ) { delay = d->m_delay; } d->startTimerThreadSafe(delay); } } void BackgroundParser::removeDocument(const IndexedString& url, QObject* notifyWhenReady) { Q_ASSERT(isValidURL(url)); QMutexLocker lock(&d->m_mutex); if(d->m_documents.contains(url)) { d->m_documentsForPriority[d->m_documents[url].priority()].remove(url); foreach(const DocumentParseTarget& target, d->m_documents[url].targets) { if(target.notifyWhenReady.data() == notifyWhenReady) { d->m_documents[url].targets.remove(target); } } if(d->m_documents[url].targets.isEmpty()) { d->m_documents.remove(url); --d->m_maxParseJobs; }else{ //Insert with an eventually different priority d->m_documentsForPriority[d->m_documents[url].priority()].insert(url); } } } void BackgroundParser::parseDocuments() { if (!d->m_loadingProjects.empty()) { startTimer(d->m_delay); return; } QMutexLocker lock(&d->m_mutex); d->parseDocumentsInternal(); } void BackgroundParser::parseComplete(const ThreadWeaver::JobPointer& job) { auto decorator = dynamic_cast(job.data()); Q_ASSERT(decorator); ParseJob* parseJob = dynamic_cast(decorator->job()); Q_ASSERT(parseJob); emit parseJobFinished(parseJob); { QMutexLocker lock(&d->m_mutex); d->m_parseJobs.remove(parseJob->document()); d->m_jobProgress.remove(parseJob); ++d->m_doneParseJobs; updateProgressData(); } //Continue creating more parse-jobs QMetaObject::invokeMethod(this, "parseDocuments", Qt::QueuedConnection); } void BackgroundParser::disableProcessing() { setNeededPriority(BestPriority); } void BackgroundParser::enableProcessing() { setNeededPriority(WorstPriority); } int BackgroundParser::priorityForDocument(const IndexedString& url) const { Q_ASSERT(isValidURL(url)); QMutexLocker lock(&d->m_mutex); return d->m_documents[url].priority(); } bool BackgroundParser::isQueued(const IndexedString& url) const { Q_ASSERT(isValidURL(url)); QMutexLocker lock(&d->m_mutex); return d->m_documents.contains(url); } int BackgroundParser::queuedCount() const { QMutexLocker lock(&d->m_mutex); return d->m_documents.count(); } bool BackgroundParser::isIdle() const { QMutexLocker lock(&d->m_mutex); return d->m_documents.isEmpty() && d->m_weaver.isIdle(); } void BackgroundParser::setNeededPriority(int priority) { QMutexLocker lock(&d->m_mutex); d->m_neededPriority = priority; d->startTimerThreadSafe(d->m_delay); } void BackgroundParser::abortAllJobs() { qCDebug(LANGUAGE) << "Aborting all parse jobs"; d->m_weaver.requestAbort(); } void BackgroundParser::suspend() { d->suspend(); emit hideProgress(this); } void BackgroundParser::resume() { d->resume(); updateProgressData(); } void BackgroundParser::updateProgressData() { if (d->m_doneParseJobs >= d->m_maxParseJobs) { if(d->m_doneParseJobs > d->m_maxParseJobs) { qCDebug(LANGUAGE) << "m_doneParseJobs larger than m_maxParseJobs:" << d->m_doneParseJobs << d->m_maxParseJobs; } d->m_doneParseJobs = 0; d->m_maxParseJobs = 0; } else { float additionalProgress = 0; for (auto it = d->m_jobProgress.constBegin(); it != d->m_jobProgress.constEnd(); ++it) { additionalProgress += *it; } d->m_progressMax = d->m_maxParseJobs*1000; d->m_progressDone = (additionalProgress + d->m_doneParseJobs)*1000; if (!d->m_progressTimer.isActive()) { d->m_progressTimer.start(); } } // Cancel progress updating and hide progress-bar when parsing is done. if(d->m_doneParseJobs == d->m_maxParseJobs || (d->m_neededPriority == BackgroundParser::BestPriority && d->m_weaver.queueLength() == 0)) { if (d->m_progressTimer.isActive()) { d->m_progressTimer.stop(); } emit d->m_parser->hideProgress(d->m_parser); } } ParseJob* BackgroundParser::parseJobForDocument(const IndexedString& document) const { Q_ASSERT(isValidURL(document)); QMutexLocker lock(&d->m_mutex); auto decorator = d->m_parseJobs.value(document); return decorator ? dynamic_cast(decorator->job()) : nullptr; } void BackgroundParser::setThreadCount(int threadCount) { if (d->m_threads != threadCount) { d->m_threads = threadCount; d->m_weaver.setMaximumNumberOfThreads(d->m_threads+1); //1 Additional thread for high-priority parsing } } int BackgroundParser::threadCount() const { return d->m_threads; } void BackgroundParser::setDelay(int milliseconds) { if (d->m_delay != milliseconds) { d->m_delay = milliseconds; d->m_timer.setInterval(d->m_delay); } } QList< IndexedString > BackgroundParser::managedDocuments() { QMutexLocker l(&d->m_managedMutex); return d->m_managed.keys(); } DocumentChangeTracker* BackgroundParser::trackerForUrl(const KDevelop::IndexedString& url) const { if (url.isEmpty()) { // this happens e.g. when setting the final location of a problem that is not // yet associated with a top ctx. return nullptr; } if ( !isValidURL(url) ) { qCWarning(LANGUAGE) << "Tracker requested for invalild URL:" << url.toUrl(); } Q_ASSERT(isValidURL(url)); QMutexLocker l(&d->m_managedMutex); return d->m_managed.value(url, nullptr); } void BackgroundParser::documentClosed(IDocument* document) { QMutexLocker l(&d->m_mutex); if(document->textDocument()) { KTextEditor::Document* textDocument = document->textDocument(); if(!d->m_managedTextDocumentUrls.contains(textDocument)) return; // Probably the document had an invalid url, and thus it wasn't added to the background parser Q_ASSERT(d->m_managedTextDocumentUrls.contains(textDocument)); IndexedString url(d->m_managedTextDocumentUrls[textDocument]); QMutexLocker l2(&d->m_managedMutex); Q_ASSERT(d->m_managed.contains(url)); qCDebug(LANGUAGE) << "removing" << url.str() << "from background parser"; delete d->m_managed[url]; d->m_managedTextDocumentUrls.remove(textDocument); d->m_managed.remove(url); } } void BackgroundParser::documentLoaded( IDocument* document ) { QMutexLocker l(&d->m_mutex); if(document->textDocument() && document->textDocument()->url().isValid()) { KTextEditor::Document* textDocument = document->textDocument(); IndexedString url(document->url()); // Some debugging because we had issues with this QMutexLocker l2(&d->m_managedMutex); if(d->m_managed.contains(url) && d->m_managed[url]->document() == textDocument) { qCDebug(LANGUAGE) << "Got redundant documentLoaded from" << document->url() << textDocument; return; } qCDebug(LANGUAGE) << "Creating change tracker for " << document->url(); Q_ASSERT(!d->m_managed.contains(url)); Q_ASSERT(!d->m_managedTextDocumentUrls.contains(textDocument)); d->m_managedTextDocumentUrls[textDocument] = url; d->m_managed.insert(url, new DocumentChangeTracker(textDocument)); }else{ qCDebug(LANGUAGE) << "NOT creating change tracker for" << document->url(); } } void BackgroundParser::documentUrlChanged(IDocument* document) { documentClosed(document); // Only call documentLoaded if the file wasn't renamed to a filename that is already tracked. if(document->textDocument() && !trackerForUrl(IndexedString(document->textDocument()->url()))) documentLoaded(document); } void BackgroundParser::startTimer(int delay) { d->m_timer.start(delay); } void BackgroundParser::projectAboutToBeOpened(IProject* project) { d->m_loadingProjects.insert(project); } void BackgroundParser::projectOpened(IProject* project) { d->m_loadingProjects.remove(project); } void BackgroundParser::projectOpeningAborted(IProject* project) { d->m_loadingProjects.remove(project); } void BackgroundParser::updateProgressBar() { emit showProgress(this, 0, d->m_progressMax, d->m_progressDone); } diff --git a/language/backgroundparser/documentchangetracker.cpp b/language/backgroundparser/documentchangetracker.cpp index 1098d2856..dc87a4b08 100644 --- a/language/backgroundparser/documentchangetracker.cpp +++ b/language/backgroundparser/documentchangetracker.cpp @@ -1,468 +1,468 @@ /* * This file is part of KDevelop * * Copyright 2010 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "documentchangetracker.h" #include #include #include #include #include #include #include #include "backgroundparser.h" -#include "util/debug.h" +#include #include // Can be used to disable the 'clever' updating logic that ignores whitespace-only changes and such. // #define ALWAYS_UPDATE using namespace KTextEditor; /** * @todo Track the exact changes to the document, and then: * Do not reparse if: * - Comment added/changed * - Newlines added/changed (ready) * Complete the document for validation: * - Incomplete for-loops * - ... * Only reparse after a statement was completed (either with statement-completion or manually), or after the cursor was switched away * Incremental parsing: * - All changes within a local function (or function parameter context): Update only the context (and all its importers) * * @todo: Prevent recursive updates after insignificant changes * (whitespace changes, or changes that don't affect publically visible stuff, eg. local incremental changes) * -> Maybe alter the file-modification caches directly * */ namespace KDevelop { DocumentChangeTracker::DocumentChangeTracker( KTextEditor::Document* document ) : m_needUpdate(false) , m_document(document) , m_moving(nullptr) , m_url(IndexedString(document->url())) { Q_ASSERT(document); Q_ASSERT(document->url().isValid()); connect(document, &Document::textInserted, this, &DocumentChangeTracker::textInserted); connect(document, &Document::lineWrapped, this, &DocumentChangeTracker::lineWrapped); connect(document, &Document::lineUnwrapped, this, &DocumentChangeTracker::lineUnwrapped); connect(document, &Document::textRemoved, this, &DocumentChangeTracker::textRemoved); connect(document, &Document::destroyed, this, &DocumentChangeTracker::documentDestroyed); connect(document, &Document::documentSavedOrUploaded, this, &DocumentChangeTracker::documentSavedOrUploaded); m_moving = dynamic_cast(document); Q_ASSERT(m_moving); // can't use new connect syntax here, MovingInterface is not a QObject connect(m_document, SIGNAL(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document*)), this, SLOT(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document*))); ModificationRevision::setEditorRevisionForFile(m_url, m_moving->revision()); reset(); } QList< QPair< KTextEditor::Range, QString > > DocumentChangeTracker::completions() const { VERIFY_FOREGROUND_LOCKED QList< QPair< KTextEditor::Range , QString > > ret; return ret; } void DocumentChangeTracker::reset() { VERIFY_FOREGROUND_LOCKED // We don't reset the insertion here, as it may continue m_needUpdate = false; m_revisionAtLastReset = acquireRevision(m_moving->revision()); Q_ASSERT(m_revisionAtLastReset); } RevisionReference DocumentChangeTracker::currentRevision() { VERIFY_FOREGROUND_LOCKED return acquireRevision(m_moving->revision()); } RevisionReference DocumentChangeTracker::revisionAtLastReset() const { VERIFY_FOREGROUND_LOCKED return m_revisionAtLastReset; } bool DocumentChangeTracker::needUpdate() const { VERIFY_FOREGROUND_LOCKED return m_needUpdate; } void DocumentChangeTracker::updateChangedRange(int delay) { // Q_ASSERT(m_moving->revision() != m_revisionAtLastReset->revision()); // May happen after reload // When reloading, textRemoved is called with an invalid m_document->url(). For that reason, we use m_url instead. ModificationRevision::setEditorRevisionForFile(m_url, m_moving->revision()); if(needUpdate()) { ICore::self()->languageController()->backgroundParser()->addDocument(m_url, TopDUContext::AllDeclarationsContextsAndUses, 0, nullptr, ParseJob::IgnoresSequentialProcessing, delay); } } static Cursor cursorAdd(Cursor c, const QString& text) { c.setLine(c.line() + text.count('\n')); c.setColumn(c.column() + (text.length() - qMin(0, text.lastIndexOf('\n')))); return c; } int DocumentChangeTracker::recommendedDelay(KTextEditor::Document* doc, const KTextEditor::Range& range, const QString& text, bool removal) { auto languages = ICore::self()->languageController()->languagesForUrl(doc->url()); int delay = ILanguageSupport::NoUpdateRequired; Q_FOREACH (const auto& lang, languages) { // take the largest value, because NoUpdateRequired is -2 and we want to make sure // that if one language requires an update it actually happens delay = qMax(lang->suggestedReparseDelayForChange(doc, range, text, removal), delay); } return delay; } void DocumentChangeTracker::lineWrapped(KTextEditor::Document* document, const KTextEditor::Cursor& position) { textInserted(document, position, QStringLiteral("\n")); } void DocumentChangeTracker::lineUnwrapped(KTextEditor::Document* document, int line) { textRemoved(document, {{document->lineLength(line), line}, {0, line+1}}, QStringLiteral("\n")); } void DocumentChangeTracker::textInserted(Document* document, const Cursor& cursor, const QString& text) { /// TODO: get this data from KTextEditor directly, make its signal public KTextEditor::Range range(cursor, cursorAdd(cursor, text)); if(!m_lastInsertionPosition.isValid() || m_lastInsertionPosition == cursor) { m_currentCleanedInsertion.append(text); m_lastInsertionPosition = range.end(); } auto delay = recommendedDelay(document, range, text, false); m_needUpdate = delay != ILanguageSupport::NoUpdateRequired; updateChangedRange(delay); } void DocumentChangeTracker::textRemoved( Document* document, const KTextEditor::Range& oldRange, const QString& oldText ) { m_currentCleanedInsertion.clear(); m_lastInsertionPosition = KTextEditor::Cursor::invalid(); auto delay = recommendedDelay(document, oldRange, oldText, true); m_needUpdate = delay != ILanguageSupport::NoUpdateRequired; updateChangedRange(delay); } void DocumentChangeTracker::documentSavedOrUploaded(KTextEditor::Document* doc,bool) { ModificationRevision::clearModificationCache(IndexedString(doc->url())); } void DocumentChangeTracker::documentDestroyed( QObject* ) { m_document = nullptr; m_moving = nullptr; } DocumentChangeTracker::~DocumentChangeTracker() { Q_ASSERT(m_document); ModificationRevision::clearEditorRevisionForFile(KDevelop::IndexedString(m_document->url())); } Document* DocumentChangeTracker::document() const { return m_document; } MovingInterface* DocumentChangeTracker::documentMovingInterface() const { return m_moving; } void DocumentChangeTracker::aboutToInvalidateMovingInterfaceContent ( Document* ) { // Release all revisions! They must not be used any more. qCDebug(LANGUAGE) << "clearing all revisions"; m_revisionLocks.clear(); m_revisionAtLastReset = RevisionReference(); ModificationRevision::setEditorRevisionForFile(m_url, 0); } KDevelop::RangeInRevision DocumentChangeTracker::transformBetweenRevisions(KDevelop::RangeInRevision range, qint64 fromRevision, qint64 toRevision) const { VERIFY_FOREGROUND_LOCKED if((fromRevision == -1 || holdingRevision(fromRevision)) && (toRevision == -1 || holdingRevision(toRevision))) { m_moving->transformCursor(range.start.line, range.start.column, KTextEditor::MovingCursor::MoveOnInsert, fromRevision, toRevision); m_moving->transformCursor(range.end.line, range.end.column, KTextEditor::MovingCursor::StayOnInsert, fromRevision, toRevision); } return range; } KDevelop::CursorInRevision DocumentChangeTracker::transformBetweenRevisions(KDevelop::CursorInRevision cursor, qint64 fromRevision, qint64 toRevision, KTextEditor::MovingCursor::InsertBehavior behavior) const { VERIFY_FOREGROUND_LOCKED if((fromRevision == -1 || holdingRevision(fromRevision)) && (toRevision == -1 || holdingRevision(toRevision))) { m_moving->transformCursor(cursor.line, cursor.column, behavior, fromRevision, toRevision); } return cursor; } RangeInRevision DocumentChangeTracker::transformToRevision(KTextEditor::Range range, qint64 toRevision) const { return transformBetweenRevisions(RangeInRevision::castFromSimpleRange(range), -1, toRevision); } CursorInRevision DocumentChangeTracker::transformToRevision(KTextEditor::Cursor cursor, qint64 toRevision, MovingCursor::InsertBehavior behavior) const { return transformBetweenRevisions(CursorInRevision::castFromSimpleCursor(cursor), -1, toRevision, behavior); } KTextEditor::Range DocumentChangeTracker::transformToCurrentRevision(RangeInRevision range, qint64 fromRevision) const { return transformBetweenRevisions(range, fromRevision, -1).castToSimpleRange(); } KTextEditor::Cursor DocumentChangeTracker::transformToCurrentRevision(CursorInRevision cursor, qint64 fromRevision, MovingCursor::InsertBehavior behavior) const { return transformBetweenRevisions(cursor, fromRevision, -1, behavior).castToSimpleCursor(); } RevisionLockerAndClearerPrivate::RevisionLockerAndClearerPrivate(DocumentChangeTracker* tracker, qint64 revision) : m_tracker(tracker), m_revision(revision) { VERIFY_FOREGROUND_LOCKED moveToThread(QApplication::instance()->thread()); // Lock the revision m_tracker->lockRevision(revision); } RevisionLockerAndClearerPrivate::~RevisionLockerAndClearerPrivate() { if (m_tracker) m_tracker->unlockRevision(m_revision); } RevisionLockerAndClearer::~RevisionLockerAndClearer() { m_p->deleteLater(); // Will be deleted in the foreground thread, as the object was re-owned to the foreground } RevisionReference DocumentChangeTracker::acquireRevision(qint64 revision) { VERIFY_FOREGROUND_LOCKED if(!holdingRevision(revision) && revision != m_moving->revision()) return RevisionReference(); RevisionReference ret(new RevisionLockerAndClearer); ret->m_p = new RevisionLockerAndClearerPrivate(this, revision); return ret; } bool DocumentChangeTracker::holdingRevision(qint64 revision) const { VERIFY_FOREGROUND_LOCKED return m_revisionLocks.contains(revision); } void DocumentChangeTracker::lockRevision(qint64 revision) { VERIFY_FOREGROUND_LOCKED QMap< qint64, int >::iterator it = m_revisionLocks.find(revision); if(it != m_revisionLocks.end()) ++(*it); else { m_revisionLocks.insert(revision, 1); m_moving->lockRevision(revision); } } void DocumentChangeTracker::unlockRevision(qint64 revision) { VERIFY_FOREGROUND_LOCKED QMap< qint64, int >::iterator it = m_revisionLocks.find(revision); if(it == m_revisionLocks.end()) { qCDebug(LANGUAGE) << "cannot unlock revision" << revision << ", probably the revisions have been cleared"; return; } --(*it); if(*it == 0) { m_moving->unlockRevision(revision); m_revisionLocks.erase(it); } } qint64 RevisionLockerAndClearer::revision() const { return m_p->revision(); } RangeInRevision RevisionLockerAndClearer::transformToRevision(const KDevelop::RangeInRevision& range, const KDevelop::RevisionLockerAndClearer::Ptr& to) const { VERIFY_FOREGROUND_LOCKED if(!m_p->m_tracker || !valid() || (to && !to->valid())) return range; qint64 fromRevision = revision(); qint64 toRevision = -1; if(to) toRevision = to->revision(); return m_p->m_tracker->transformBetweenRevisions(range, fromRevision, toRevision); } CursorInRevision RevisionLockerAndClearer::transformToRevision(const KDevelop::CursorInRevision& cursor, const KDevelop::RevisionLockerAndClearer::Ptr& to, MovingCursor::InsertBehavior behavior) const { VERIFY_FOREGROUND_LOCKED if(!m_p->m_tracker || !valid() || (to && !to->valid())) return cursor; qint64 fromRevision = revision(); qint64 toRevision = -1; if(to) toRevision = to->revision(); return m_p->m_tracker->transformBetweenRevisions(cursor, fromRevision, toRevision, behavior); } RangeInRevision RevisionLockerAndClearer::transformFromRevision(const KDevelop::RangeInRevision& range, const KDevelop::RevisionLockerAndClearer::Ptr& from) const { VERIFY_FOREGROUND_LOCKED if(!m_p->m_tracker || !valid()) return range; qint64 toRevision = revision(); qint64 fromRevision = -1; if(from) fromRevision = from->revision(); return m_p->m_tracker->transformBetweenRevisions(range, fromRevision, toRevision); } CursorInRevision RevisionLockerAndClearer::transformFromRevision(const KDevelop::CursorInRevision& cursor, const KDevelop::RevisionLockerAndClearer::Ptr& from, MovingCursor::InsertBehavior behavior) const { VERIFY_FOREGROUND_LOCKED if(!m_p->m_tracker) return cursor; qint64 toRevision = revision(); qint64 fromRevision = -1; if(from) fromRevision = from->revision(); return m_p->m_tracker->transformBetweenRevisions(cursor, fromRevision, toRevision, behavior); } KTextEditor::Range RevisionLockerAndClearer::transformToCurrentRevision(const KDevelop::RangeInRevision& range) const { return transformToRevision(range, KDevelop::RevisionLockerAndClearer::Ptr()).castToSimpleRange(); } KTextEditor::Cursor RevisionLockerAndClearer::transformToCurrentRevision(const KDevelop::CursorInRevision& cursor, MovingCursor::InsertBehavior behavior) const { return transformToRevision(cursor, KDevelop::RevisionLockerAndClearer::Ptr(), behavior).castToSimpleCursor(); } RangeInRevision RevisionLockerAndClearer::transformFromCurrentRevision(const KTextEditor::Range& range) const { return transformFromRevision(RangeInRevision::castFromSimpleRange(range), RevisionReference()); } CursorInRevision RevisionLockerAndClearer::transformFromCurrentRevision(const KTextEditor::Cursor& cursor, MovingCursor::InsertBehavior behavior) const { return transformFromRevision(CursorInRevision::castFromSimpleCursor(cursor), RevisionReference(), behavior); } bool RevisionLockerAndClearer::valid() const { VERIFY_FOREGROUND_LOCKED if(!m_p->m_tracker) return false; if(revision() == -1) return true; // The 'current' revision is always valid return m_p->m_tracker->holdingRevision(revision()); } RevisionReference DocumentChangeTracker::diskRevision() const { ///@todo Track which revision was last saved to disk return RevisionReference(); } } diff --git a/language/backgroundparser/parsejob.cpp b/language/backgroundparser/parsejob.cpp index 897c2918d..2ed2d67f1 100644 --- a/language/backgroundparser/parsejob.cpp +++ b/language/backgroundparser/parsejob.cpp @@ -1,520 +1,519 @@ /* * This file is part of KDevelop * * Copyright 2006 Adam Treat * Copyright 2006-2008 Hamish Rodda * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "parsejob.h" #include #include #include -#include #include #include #include #include "backgroundparser.h" -#include "util/debug.h" +#include #include "duchain/topducontext.h" #include "duchain/duchainlock.h" #include "duchain/duchain.h" #include "duchain/parsingenvironment.h" #include "editor/documentrange.h" #include #include #include #include #include #include #include #include using namespace KTextEditor; static QMutex minimumFeaturesMutex; static QHash > staticMinimumFeatures; namespace KDevelop { class ParseJobPrivate { public: ParseJobPrivate(const IndexedString& url_, ILanguageSupport* languageSupport_) : url( url_ ) , languageSupport( languageSupport_ ) , abortRequested( 0 ) , hasReadContents( false ) , aborted( false ) , features( TopDUContext::VisibleDeclarationsAndContexts ) , parsePriority( 0 ) , sequentialProcessingFlags( ParseJob::IgnoresSequentialProcessing ) { } ~ParseJobPrivate() { } ReferencedTopDUContext duContext; IndexedString url; ILanguageSupport* languageSupport; ParseJob::Contents contents; QAtomicInt abortRequested; bool hasReadContents : 1; bool aborted : 1; TopDUContext::Features features; QList > notify; QPointer tracker; RevisionReference revision; RevisionReference previousRevision; int parsePriority; ParseJob::SequentialProcessingFlags sequentialProcessingFlags; }; ParseJob::ParseJob( const IndexedString& url, KDevelop::ILanguageSupport* languageSupport ) : ThreadWeaver::Sequence(), d(new ParseJobPrivate(url, languageSupport)) { } ParseJob::~ParseJob() { typedef QPointer QObjectPointer; foreach(const QObjectPointer &p, d->notify) { if(p) { QMetaObject::invokeMethod(p.data(), "updateReady", Qt::QueuedConnection, Q_ARG(KDevelop::IndexedString, d->url), Q_ARG(KDevelop::ReferencedTopDUContext, d->duContext)); } } delete d; } ILanguageSupport* ParseJob::languageSupport() const { return d->languageSupport; } void ParseJob::setParsePriority(int priority) { d->parsePriority = priority; } int ParseJob::parsePriority() const { return d->parsePriority; } bool ParseJob::requiresSequentialProcessing() const { return d->sequentialProcessingFlags & RequiresSequentialProcessing; } bool ParseJob::respectsSequentialProcessing() const { return d->sequentialProcessingFlags & RespectsSequentialProcessing; } void ParseJob::setSequentialProcessingFlags(SequentialProcessingFlags flags) { d->sequentialProcessingFlags = flags; } IndexedString ParseJob::document() const { return d->url; } bool ParseJob::success() const { return !d->aborted; } void ParseJob::setMinimumFeatures(TopDUContext::Features features) { d->features = features; } bool ParseJob::hasStaticMinimumFeatures() { QMutexLocker lock(&minimumFeaturesMutex); return !::staticMinimumFeatures.isEmpty(); } TopDUContext::Features ParseJob::staticMinimumFeatures(const IndexedString& url) { QMutexLocker lock(&minimumFeaturesMutex); TopDUContext::Features features = (TopDUContext::Features)0; if(::staticMinimumFeatures.contains(url)) foreach(const TopDUContext::Features f, ::staticMinimumFeatures[url]) features = (TopDUContext::Features)(features | f); return features; } TopDUContext::Features ParseJob::minimumFeatures() const { return (TopDUContext::Features)(d->features | staticMinimumFeatures(d->url)); } void ParseJob::setDuChain(ReferencedTopDUContext duChain) { d->duContext = duChain; } ReferencedTopDUContext ParseJob::duChain() const { return d->duContext; } bool ParseJob::abortRequested() const { return d->abortRequested.load(); } void ParseJob::requestAbort() { d->abortRequested = 1; } void ParseJob::abortJob() { d->aborted = true; setStatus(Status_Aborted); } void ParseJob::setNotifyWhenReady(const QList >& notify ) { d->notify = notify; } void ParseJob::setStaticMinimumFeatures(const IndexedString& url, TopDUContext::Features features) { QMutexLocker lock(&minimumFeaturesMutex); ::staticMinimumFeatures[url].append(features); } void ParseJob::unsetStaticMinimumFeatures(const IndexedString& url, TopDUContext::Features features) { QMutexLocker lock(&minimumFeaturesMutex); ::staticMinimumFeatures[url].removeOne(features); if(::staticMinimumFeatures[url].isEmpty()) ::staticMinimumFeatures.remove(url); } KDevelop::ProblemPointer ParseJob::readContents() { Q_ASSERT(!d->hasReadContents); d->hasReadContents = true; QString localFile(document().toUrl().toLocalFile()); QFileInfo fileInfo( localFile ); QDateTime lastModified = fileInfo.lastModified(); d->tracker = ICore::self()->languageController()->backgroundParser()->trackerForUrl(document()); //Try using an artificial code-representation, which overrides everything else if(artificialCodeRepresentationExists(document())) { CodeRepresentation::Ptr repr = createCodeRepresentation(document()); d->contents.contents = repr->text().toUtf8(); qCDebug(LANGUAGE) << "took contents for " << document().str() << " from artificial code-representation"; return KDevelop::ProblemPointer(); } bool hadTracker = false; if(d->tracker) { ForegroundLock lock; if(DocumentChangeTracker* t = d->tracker.data()) { // The file is open in an editor d->previousRevision = t->revisionAtLastReset(); t->reset(); // Reset the tracker to the current revision Q_ASSERT(t->revisionAtLastReset()); d->contents.contents = t->document()->text().toUtf8(); d->contents.modification = KDevelop::ModificationRevision( lastModified, t->revisionAtLastReset()->revision() ); d->revision = t->acquireRevision(d->contents.modification.revision); hadTracker = true; } } if (!hadTracker) { // We have to load the file from disk static const int maximumFileSize = 5 * 1024 * 1024; // 5 MB if (fileInfo.size() > maximumFileSize) { KFormat f; KDevelop::ProblemPointer p(new Problem()); p->setSource(IProblem::Disk); p->setDescription(i18nc("%1: filename", "Skipped file that is too large: '%1'", localFile )); p->setExplanation(i18nc("%1: file size, %2: limit file size", "The file is %1 and exceeds the limit of %2.", f.formatByteSize(fileInfo.size()), f.formatByteSize(maximumFileSize))); p->setFinalLocation(DocumentRange(document(), KTextEditor::Range::invalid())); qCWarning(LANGUAGE) << p->description() << p->explanation(); return p; } QFile file( localFile ); if ( !file.open( QIODevice::ReadOnly ) ) { KDevelop::ProblemPointer p(new Problem()); p->setSource(IProblem::Disk); p->setDescription(i18n( "Could not open file '%1'", localFile )); switch (file.error()) { case QFile::ReadError: p->setExplanation(i18n("File could not be read from disk.")); break; case QFile::OpenError: p->setExplanation(i18n("File could not be opened.")); break; case QFile::PermissionsError: p->setExplanation(i18n("File could not be read from disk due to permissions.")); break; default: break; } p->setFinalLocation(DocumentRange(document(), KTextEditor::Range::invalid())); qCWarning(LANGUAGE) << "Could not open file" << document().str() << "(path" << localFile << ")" ; return p; } d->contents.contents = file.readAll(); ///@todo Convert from local encoding to utf-8 if they don't match d->contents.modification = KDevelop::ModificationRevision(lastModified); file.close(); } return KDevelop::ProblemPointer(); } const KDevelop::ParseJob::Contents& ParseJob::contents() const { Q_ASSERT(d->hasReadContents); return d->contents; } struct MovingRangeTranslator : public DUChainVisitor { MovingRangeTranslator(qint64 _source, qint64 _target, MovingInterface* _moving) : source(_source), target(_target), moving(_moving) { } void visit(DUContext* context) override { translateRange(context); ///@todo Also map import-positions // Translate uses uint usesCount = context->usesCount(); for(uint u = 0; u < usesCount; ++u) { RangeInRevision r = context->uses()[u].m_range; translateRange(r); context->changeUseRange(u, r); } } void visit(Declaration* declaration) override { translateRange(declaration); } void translateRange(DUChainBase* object) { RangeInRevision r = object->range(); translateRange(r); object->setRange(r); } void translateRange(RangeInRevision& r) { // PHP and python use top contexts that start at (0, 0) end at INT_MAX, so make sure that doesn't overflow // or translate the start of the top context away from (0, 0) if ( r.start.line != 0 || r.start.column != 0 ) { moving->transformCursor(r.start.line, r.start.column, MovingCursor::MoveOnInsert, source, target); } if ( r.end.line != std::numeric_limits::max() || r.end.column != std::numeric_limits::max() ) { moving->transformCursor(r.end.line, r.end.column, MovingCursor::StayOnInsert, source, target); } } KTextEditor::Range range; qint64 source; qint64 target; MovingInterface* moving; }; void ParseJob::translateDUChainToRevision(TopDUContext* context) { qint64 targetRevision = d->contents.modification.revision; if(targetRevision == -1) { qCDebug(LANGUAGE) << "invalid target revision" << targetRevision; return; } qint64 sourceRevision; { DUChainReadLocker duChainLock; Q_ASSERT(context->parsingEnvironmentFile()); // Cannot map if there is no source revision sourceRevision = context->parsingEnvironmentFile()->modificationRevision().revision; if(sourceRevision == -1) { qCDebug(LANGUAGE) << "invalid source revision" << sourceRevision; return; } } if(sourceRevision > targetRevision) { qCDebug(LANGUAGE) << "for document" << document().str() << ": source revision is higher than target revision:" << sourceRevision << " > " << targetRevision; return; } ForegroundLock lock; if(DocumentChangeTracker* t = d->tracker.data()) { if(!d->previousRevision) { qCDebug(LANGUAGE) << "not translating because there is no valid predecessor-revision"; return; } if(sourceRevision != d->previousRevision->revision() || !d->previousRevision->valid()) { qCDebug(LANGUAGE) << "not translating because the document revision does not match the tracker start revision (maybe the document was cleared)"; return; } if(!t->holdingRevision(sourceRevision) || !t->holdingRevision(targetRevision)) { qCDebug(LANGUAGE) << "lost one of the translation revisions, not doing the map"; return; } // Perform translation MovingInterface* moving = t->documentMovingInterface(); DUChainWriteLocker wLock; MovingRangeTranslator translator(sourceRevision, targetRevision, moving); context->visit(translator); QList< ProblemPointer > problems = context->problems(); for(QList< ProblemPointer >::iterator problem = problems.begin(); problem != problems.end(); ++problem) { RangeInRevision r = (*problem)->range(); translator.translateRange(r); (*problem)->setRange(r); } // Update the modification revision in the meta-data ModificationRevision modRev = context->parsingEnvironmentFile()->modificationRevision(); modRev.revision = targetRevision; context->parsingEnvironmentFile()->setModificationRevision(modRev); } } bool ParseJob::isUpdateRequired(const IndexedString& languageString) { if (abortRequested()) { return false; } if (minimumFeatures() & TopDUContext::ForceUpdate) { return true; } DUChainReadLocker lock; if (abortRequested()) { return false; } foreach(const ParsingEnvironmentFilePointer &file, DUChain::self()->allEnvironmentFiles(document())) { if (file->language() != languageString) { continue; } if (!file->needsUpdate(environment()) && file->featuresSatisfied(minimumFeatures())) { qCDebug(LANGUAGE) << "Already up to date" << document().str(); setDuChain(file->topContext()); lock.unlock(); highlightDUChain(); return false; } break; } return !abortRequested(); } const ParsingEnvironment* ParseJob::environment() const { return nullptr; } void ParseJob::highlightDUChain() { ENSURE_CHAIN_NOT_LOCKED if (!d->languageSupport->codeHighlighting() || !duChain() || abortRequested()) { // language doesn't support highlighting return; } if (!d->hasReadContents && !d->tracker) { d->tracker = ICore::self()->languageController()->backgroundParser()->trackerForUrl(document()); } if (d->tracker) { d->languageSupport->codeHighlighting()->highlightDUChain(duChain()); } } ControlFlowGraph* ParseJob::controlFlowGraph() { return nullptr; } DataAccessRepository* ParseJob::dataAccessInformation() { return nullptr; } bool ParseJob::hasTracker() const { return d->tracker; } } diff --git a/language/backgroundparser/parseprojectjob.cpp b/language/backgroundparser/parseprojectjob.cpp index 398486864..708470ac9 100644 --- a/language/backgroundparser/parseprojectjob.cpp +++ b/language/backgroundparser/parseprojectjob.cpp @@ -1,157 +1,157 @@ /* Copyright 2009 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "parseprojectjob.h" -#include "util/debug.h" +#include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; bool ParseProjectJob::doKill() { qCDebug(LANGUAGE) << "stopping project parse job"; deleteLater(); return true; } ParseProjectJob::~ParseProjectJob() { ICore::self()->languageController()->backgroundParser()->revertAllRequests(this); if(ICore::self()->runController()->currentJobs().contains(this)) ICore::self()->runController()->unregisterJob(this); } ParseProjectJob::ParseProjectJob(IProject* project, bool forceUpdate) : m_updated(0) , m_forceUpdate(forceUpdate) , m_project(project) { connect(project, &IProject::destroyed, this, &ParseProjectJob::deleteNow); if (!ICore::self()->projectController()->parseAllProjectSources()) { // In case we don't want to parse the whole project, still add all currently open files that belong to the project to the background-parser foreach (auto document, ICore::self()->documentController()->openDocuments()) { const auto path = IndexedString(document->url()); if (project->fileSet().contains(path)) { m_filesToParse.insert(path); } } } else { m_filesToParse = project->fileSet(); } setCapabilities(Killable); setObjectName(i18np("Process 1 file in %2","Process %1 files in %2", m_filesToParse.size(), m_project->name())); } void ParseProjectJob::deleteNow() { delete this; } void ParseProjectJob::updateProgress() { } void ParseProjectJob::updateReady(const IndexedString& url, ReferencedTopDUContext topContext) { Q_UNUSED(url); Q_UNUSED(topContext); ++m_updated; if(m_updated % ((m_filesToParse.size() / 100)+1) == 0) updateProgress(); if(m_updated >= m_filesToParse.size()) deleteLater(); } void ParseProjectJob::start() { if (ICore::self()->shuttingDown()) { return; } if (m_filesToParse.isEmpty()) { deleteLater(); return; } qCDebug(LANGUAGE) << "starting project parse job"; TopDUContext::Features processingLevel = m_filesToParse.size() < ICore::self()->languageController()->completionSettings()->minFilesForSimplifiedParsing() ? TopDUContext::VisibleDeclarationsAndContexts : TopDUContext::SimplifiedVisibleDeclarationsAndContexts; if (m_forceUpdate) { if (processingLevel & TopDUContext::VisibleDeclarationsAndContexts) { processingLevel = TopDUContext::AllDeclarationsContextsAndUses; } processingLevel = (TopDUContext::Features)(TopDUContext::ForceUpdate | processingLevel); } if (auto currentDocument = ICore::self()->documentController()->activeDocument()) { const auto path = IndexedString(currentDocument->url()); if (m_filesToParse.contains(path)) { ICore::self()->languageController()->backgroundParser()->addDocument(path, TopDUContext::AllDeclarationsContextsAndUses, BackgroundParser::BestPriority, this); m_filesToParse.remove(path); } } // Add all currently open files that belong to the project to the background-parser, so that they'll be parsed first of all foreach (auto document, ICore::self()->documentController()->openDocuments()) { const auto path = IndexedString(document->url()); if (m_filesToParse.contains(path)) { ICore::self()->languageController()->backgroundParser()->addDocument(path, TopDUContext::AllDeclarationsContextsAndUses, 10, this ); m_filesToParse.remove(path); } } if (!ICore::self()->projectController()->parseAllProjectSources()) { return; } // prevent UI-lockup by processing events after some files // esp. noticeable when dealing with huge projects const int processAfter = 1000; int processed = 0; // guard against reentrancy issues, see also bug 345480 auto crashGuard = QPointer{this}; foreach(const IndexedString& url, m_filesToParse) { ICore::self()->languageController()->backgroundParser()->addDocument( url, processingLevel, BackgroundParser::InitialParsePriority, this ); ++processed; if (processed == processAfter) { QApplication::processEvents(); if (!crashGuard) { return; } processed = 0; } } } diff --git a/language/classmodel/classmodelnode.cpp b/language/classmodel/classmodelnode.cpp index aff09c1ca..7ba7b1d85 100644 --- a/language/classmodel/classmodelnode.cpp +++ b/language/classmodel/classmodelnode.cpp @@ -1,610 +1,610 @@ /* * KDevelop Class Browser * * Copyright 2007-2009 Hamish Rodda * Copyright 2009 Lior Mualem * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "classmodelnode.h" #include "debug.h" #include #include #include "../duchain/duchainlock.h" #include "../duchain/duchain.h" #include "../duchain/persistentsymboltable.h" #include "../duchain/duchainutils.h" #include "../duchain/classdeclaration.h" #include "../duchain/classfunctiondeclaration.h" #include "../duchain/types/functiontype.h" #include "../duchain/types/enumerationtype.h" -#include "util/debug.h" +#include using namespace KDevelop; using namespace ClassModelNodes; IdentifierNode::IdentifierNode(KDevelop::Declaration* a_decl, NodesModelInterface* a_model, const QString& a_displayName) : DynamicNode(a_displayName.isEmpty() ? a_decl->identifier().toString() : a_displayName, a_model) , m_identifier(a_decl->qualifiedIdentifier()) , m_indexedDeclaration(a_decl) , m_cachedDeclaration(a_decl) { } Declaration* IdentifierNode::getDeclaration() { if ( !m_cachedDeclaration ) m_cachedDeclaration = m_indexedDeclaration.declaration(); return m_cachedDeclaration.data(); } bool IdentifierNode::getIcon(QIcon& a_resultIcon) { DUChainReadLocker readLock(DUChain::lock()); Declaration* decl = getDeclaration(); if ( decl ) a_resultIcon = DUChainUtils::iconForDeclaration(decl); return !a_resultIcon.isNull(); } ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// EnumNode::EnumNode(KDevelop::Declaration* a_decl, NodesModelInterface* a_model) : IdentifierNode(a_decl, a_model) { // Set display name for anonymous enums if ( m_displayName.isEmpty() ) m_displayName = QStringLiteral("*Anonymous*"); } bool EnumNode::getIcon(QIcon& a_resultIcon) { DUChainReadLocker readLock(DUChain::lock()); ClassMemberDeclaration* decl = dynamic_cast(getDeclaration()); if ( decl == nullptr ) { static QIcon Icon = QIcon::fromTheme(QStringLiteral("enum")); a_resultIcon = Icon; } else { if ( decl->accessPolicy() == Declaration::Protected ) { static QIcon Icon = QIcon::fromTheme(QStringLiteral("protected_enum")); a_resultIcon = Icon; } else if ( decl->accessPolicy() == Declaration::Private ) { static QIcon Icon = QIcon::fromTheme(QStringLiteral("private_enum")); a_resultIcon = Icon; } else { static QIcon Icon = QIcon::fromTheme(QStringLiteral("enum")); a_resultIcon = Icon; } } return true; } void EnumNode::populateNode() { DUChainReadLocker readLock(DUChain::lock()); Declaration* decl = getDeclaration(); if ( decl->internalContext() ) foreach( Declaration* enumDecl, decl->internalContext()->localDeclarations() ) addNode( new EnumNode(enumDecl, m_model) ); } ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// ClassNode::ClassNode(Declaration* a_decl, NodesModelInterface* a_model) : IdentifierNode(a_decl, a_model) { } ClassNode::~ClassNode() { if ( !m_cachedUrl.isEmpty() ) { ClassModelNodesController::self().unregisterForChanges(m_cachedUrl, this); m_cachedUrl = IndexedString(); } } void ClassNode::populateNode() { DUChainReadLocker readLock(DUChain::lock()); if ( m_model->features().testFlag(NodesModelInterface::ClassInternals) ) { if ( updateClassDeclarations() ) { m_cachedUrl = getDeclaration()->url(); ClassModelNodesController::self().registerForChanges(m_cachedUrl, this); } } // Add special folders if (m_model->features().testFlag(NodesModelInterface::BaseAndDerivedClasses)) addBaseAndDerived(); } template <> inline bool qMapLessThanKey(const IndexedIdentifier &key1, const IndexedIdentifier &key2) { return key1.getIndex() < key2.getIndex(); } bool ClassNode::updateClassDeclarations() { bool hadChanges = false; SubIdentifiersMap existingIdentifiers = m_subIdentifiers; ClassDeclaration* klass = dynamic_cast(getDeclaration()); if ( klass ) { foreach(Declaration* decl, klass->internalContext()->localDeclarations()) { // Ignore forward declarations. if ( decl->isForwardDeclaration() ) continue; // Don't add existing declarations. if ( existingIdentifiers.contains( decl->ownIndex() ) ) { existingIdentifiers.remove(decl->ownIndex()); continue; } Node* newNode = nullptr; if ( EnumerationType::Ptr enumType = decl->type() ) newNode = new EnumNode( decl, m_model ); else if ( decl->isFunctionDeclaration() ) newNode = new FunctionNode( decl, m_model ); else if ( ClassDeclaration* classDecl = dynamic_cast(decl) ) newNode = new ClassNode(classDecl, m_model); else if ( ClassMemberDeclaration* memDecl = dynamic_cast(decl) ) newNode = new ClassMemberNode( memDecl, m_model ); else { // Debug - for reference. qCDebug(LANGUAGE) << "class: " << klass->toString() << "name: " << decl->toString() << " - unknown declaration type: " << typeid(*decl).name(); } if ( newNode ) { addNode(newNode); // Also remember the identifier. m_subIdentifiers.insert(decl->ownIndex(), newNode); hadChanges = true; } } } // Remove old existing identifiers for ( SubIdentifiersMap::iterator iter = existingIdentifiers.begin(); iter != existingIdentifiers.end(); ++iter ) { iter.value()->removeSelf(); m_subIdentifiers.remove(iter.key()); hadChanges = true; } return hadChanges; } bool ClassNode::addBaseAndDerived() { bool added = false; BaseClassesFolderNode *baseClassesNode = new BaseClassesFolderNode( m_model ); addNode( baseClassesNode ); if ( !baseClassesNode->hasChildren() ) removeNode( baseClassesNode ); else added = true; DerivedClassesFolderNode *derivedClassesNode = new DerivedClassesFolderNode( m_model ); addNode( derivedClassesNode ); if ( !derivedClassesNode->hasChildren() ) removeNode( derivedClassesNode ); else added = true; return added; } void ClassNode::nodeCleared() { if ( !m_cachedUrl.isEmpty() ) { ClassModelNodesController::self().unregisterForChanges(m_cachedUrl, this); m_cachedUrl = IndexedString(); } m_subIdentifiers.clear(); } void ClassModelNodes::ClassNode::documentChanged(const KDevelop::IndexedString&) { DUChainReadLocker readLock(DUChain::lock()); if ( updateClassDeclarations() ) recursiveSort(); } ClassNode* ClassNode::findSubClass(const KDevelop::IndexedQualifiedIdentifier& a_id) { // Make sure we have sub nodes. performPopulateNode(); /// @todo This is slow - we go over all the sub identifiers but the assumption is that /// this function call is rare and the list is not that long. foreach(Node* item, m_subIdentifiers) { ClassNode* classNode = dynamic_cast(item); if ( classNode == nullptr ) continue; if ( classNode->getIdentifier() == a_id ) return classNode; } return nullptr; } ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// FunctionNode::FunctionNode(Declaration* a_decl, NodesModelInterface* a_model) : IdentifierNode(a_decl, a_model) { // Append the argument signature to the identifier's name (which is what the displayName is. if (FunctionType::Ptr type = a_decl->type()) m_displayName += type->partToString(FunctionType::SignatureArguments); // Add special values for ctor / dtor to sort first ClassFunctionDeclaration* classmember = dynamic_cast(a_decl); if ( classmember ) { if ( classmember->isConstructor() || classmember->isDestructor() ) m_sortableString = '0' + m_displayName; else m_sortableString = '1' + m_displayName; } else { m_sortableString = m_displayName; } } ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// ClassMemberNode::ClassMemberNode(KDevelop::ClassMemberDeclaration* a_decl, NodesModelInterface* a_model) : IdentifierNode(a_decl, a_model) { } bool ClassMemberNode::getIcon(QIcon& a_resultIcon) { DUChainReadLocker readLock(DUChain::lock()); ClassMemberDeclaration* decl = dynamic_cast(getDeclaration()); if ( decl == nullptr ) return false; if ( decl->isTypeAlias() ) { static QIcon Icon = QIcon::fromTheme(QStringLiteral("typedef")); a_resultIcon = Icon; } else if ( decl->accessPolicy() == Declaration::Protected ) { static QIcon Icon = QIcon::fromTheme(QStringLiteral("protected_field")); a_resultIcon = Icon; } else if ( decl->accessPolicy() == Declaration::Private ) { static QIcon Icon = QIcon::fromTheme(QStringLiteral("private_field")); a_resultIcon = Icon; } else { static QIcon Icon = QIcon::fromTheme(QStringLiteral("field")); a_resultIcon = Icon; } return true; } ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// DynamicFolderNode::DynamicFolderNode(const QString& a_displayName, NodesModelInterface* a_model) : DynamicNode(a_displayName, a_model) { } bool DynamicFolderNode::getIcon(QIcon& a_resultIcon) { static QIcon folderIcon = QIcon::fromTheme(QStringLiteral("folder")); a_resultIcon = folderIcon; return true; } ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// FolderNode::FolderNode(const QString& a_displayName, NodesModelInterface* a_model) : Node(a_displayName, a_model) { } bool FolderNode::getIcon(QIcon& a_resultIcon) { static QIcon folderIcon = QIcon::fromTheme(QStringLiteral("folder")); a_resultIcon = folderIcon; return true; } ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// BaseClassesFolderNode::BaseClassesFolderNode(NodesModelInterface* a_model) : DynamicFolderNode(i18n("Base classes"), a_model) { } void BaseClassesFolderNode::populateNode() { DUChainReadLocker readLock(DUChain::lock()); ClassDeclaration* klass = dynamic_cast( static_cast(getParent())->getDeclaration() ); if ( klass ) { // I use the imports instead of the baseClasses in the ClassDeclaration because I need // to get to the base class identifier which is not directly accessible through the // baseClasses function. foreach( const DUContext::Import& import, klass->internalContext()->importedParentContexts() ) { DUContext* baseContext = import.context( klass->topContext() ); if ( baseContext && baseContext->type() == DUContext::Class ) { Declaration* baseClassDeclaration = baseContext->owner(); if ( baseClassDeclaration ) { // Add the base class. addNode( new ClassNode(baseClassDeclaration, m_model) ); } } } } } ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// DerivedClassesFolderNode::DerivedClassesFolderNode(NodesModelInterface* a_model) : DynamicFolderNode(i18n("Derived classes"), a_model) { } void DerivedClassesFolderNode::populateNode() { DUChainReadLocker readLock(DUChain::lock()); ClassDeclaration* klass = dynamic_cast( static_cast(getParent())->getDeclaration() ); if ( klass ) { uint steps = 10000; QList< Declaration* > inheriters = DUChainUtils::getInheriters(klass, steps, true); foreach( Declaration* decl, inheriters ) { addNode( new ClassNode(decl, m_model) ); } } } ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// Node::Node(const QString& a_displayName, NodesModelInterface* a_model) : m_parentNode(nullptr) , m_displayName(a_displayName) , m_model(a_model) { } Node::~Node() { // Notify the model about the removal of this nodes' children. if ( !m_children.empty() && m_model ) m_model->nodesRemoved(this, 0, m_children.size()-1); clear(); } void Node::clear() { qDeleteAll(m_children); m_children.clear(); } void Node::addNode(Node* a_child) { /// @note This is disabled for performance reasons - we add them to the bottom and a /// sort usually follows which causes a layout change to be fired. // m_model->nodesAboutToBeAdded(this, m_children.size(), 1); a_child->m_parentNode = this; m_children.push_back(a_child); // m_model->nodesAdded(this); } void Node::removeNode(Node* a_child) { int row = a_child->row(); m_children.removeAt(row); m_model->nodesRemoved(this, row, row ); delete a_child; } // Sort algorithm for the nodes. struct SortNodesFunctor { bool operator() (Node* a_lhs, Node* a_rhs) { if ( a_lhs->getScore() == a_rhs->getScore() ) { return a_lhs->getSortableString() < a_rhs->getSortableString(); } else return a_lhs->getScore() < a_rhs->getScore(); } }; void Node::recursiveSortInternal() { // Sort my nodes. std::sort(m_children.begin(), m_children.end(), SortNodesFunctor()); // Tell each node to sort it self. foreach (Node* node, m_children) node->recursiveSortInternal(); } void Node::recursiveSort() { m_model->nodesLayoutAboutToBeChanged(this); recursiveSortInternal(); m_model->nodesLayoutChanged(this); } int Node::row() { if ( m_parentNode == nullptr ) return -1; return m_parentNode->m_children.indexOf(this); } QIcon ClassModelNodes::Node::getCachedIcon() { // Load the cached icon if it's null. if ( m_cachedIcon.isNull() ) { if ( !getIcon(m_cachedIcon) ) m_cachedIcon = QIcon(); } return m_cachedIcon; } ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// DynamicNode::DynamicNode(const QString& a_displayName, NodesModelInterface* a_model) : Node(a_displayName, a_model) , m_populated(false) { } void DynamicNode::collapse() { performNodeCleanup(); } void DynamicNode::expand() { performPopulateNode(); } void DynamicNode::performNodeCleanup() { if ( !m_populated ) return; if ( !m_children.empty() ) { // Notify model for this node. m_model->nodesRemoved(this, 0, m_children.size()-1); } // Clear sub-nodes. clear(); // This shouldn't be called from clear since clear is called also from the d-tor // and the function is virtual. nodeCleared(); // Mark the fact that we've been collapsed m_populated = false; } void DynamicNode::performPopulateNode(bool a_forceRepopulate) { if ( m_populated ) { if ( a_forceRepopulate ) performNodeCleanup(); else return; } populateNode(); // We're populated. m_populated = true; // Sort the list. recursiveSort(); } bool DynamicNode::hasChildren() const { // To get a true status, we'll need to populate the node. const_cast(this)->performPopulateNode(); return !m_children.empty(); } // kate: space-indent on; indent-width 2; tab-width 4; replace-tabs on; auto-insert-doxygen on diff --git a/language/codecompletion/abstractincludefilecompletionitem.h b/language/codecompletion/abstractincludefilecompletionitem.h index e748fe24f..7cc17dfdf 100644 --- a/language/codecompletion/abstractincludefilecompletionitem.h +++ b/language/codecompletion/abstractincludefilecompletionitem.h @@ -1,98 +1,96 @@ /* * KDevelop Code Completion Support for file includes * * Copyright 2007-2008 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_ABSTRACTINCLUDEFILECOMPLETIONITEM_H #define KDEVPLATFORM_ABSTRACTINCLUDEFILECOMPLETIONITEM_H #include "codecompletionitem.h" #include #include "../util/includeitem.h" #include "../duchain/duchain.h" #include "../duchain/duchainlock.h" #include "codecompletionmodel.h" -#include - namespace KDevelop { //A completion item used for completing include-files template class AbstractIncludeFileCompletionItem : public CompletionTreeItem { public: AbstractIncludeFileCompletionItem(const IncludeItem& include) : includeItem(include) { } virtual QVariant data(const QModelIndex& index, int role, const KDevelop::CodeCompletionModel* model) const { DUChainReadLocker lock(DUChain::lock(), 500); if(!lock.locked()) { qDebug() << "Failed to lock the du-chain in time"; return QVariant(); } const IncludeItem& item( includeItem ); switch (role) { case CodeCompletionModel::IsExpandable: return QVariant(true); case CodeCompletionModel::ExpandingWidget: { NavigationWidget* nav = new NavigationWidget(item, model->currentTopContext()); model->addNavigationWidget(this, nav); QVariant v; v.setValue((QWidget*)nav); return v; } case Qt::DisplayRole: switch (index.column()) { case CodeCompletionModel::Prefix: if(item.isDirectory) return QStringLiteral("directory"); else return QStringLiteral("file"); case CodeCompletionModel::Name: { return item.isDirectory ? (item.name + QLatin1Char('/')) : item.name; } } break; case CodeCompletionModel::ItemSelected: { return QVariant( NavigationWidget::shortDescription(item) ); } } return QVariant(); } virtual void execute(KTextEditor::View* view, const KTextEditor::Range& word) = 0; virtual int inheritanceDepth() const { return includeItem.pathNumber; } virtual int argumentHintDepth() const { return 0; } IncludeItem includeItem; }; } #endif // KDEVPLATFORM_ABSTRACTINCLUDEFILECOMPLETIONITEM_H diff --git a/language/codecompletion/codecompletion.cpp b/language/codecompletion/codecompletion.cpp index 4c65976b3..232baf934 100644 --- a/language/codecompletion/codecompletion.cpp +++ b/language/codecompletion/codecompletion.cpp @@ -1,134 +1,134 @@ /* * KDevelop Generic Code Completion Support * * Copyright 2006 Hamish Rodda * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "codecompletion.h" #include #include #include #include #include #include #include #include "../duchain/duchain.h" #include "../duchain/topducontext.h" -#include "util/debug.h" +#include #include "codecompletionmodel.h" #include using namespace KTextEditor; using namespace KDevelop; CodeCompletion::CodeCompletion(QObject *parent, KTextEditor::CodeCompletionModel* aModel, const QString& language) : QObject(parent), m_model(aModel), m_language(language) { KDevelop::CodeCompletionModel* kdevModel = dynamic_cast(aModel); if(kdevModel) kdevModel->initialize(); connect(KDevelop::ICore::self()->documentController(), &IDocumentController::textDocumentCreated, this, &CodeCompletion::textDocumentCreated); connect( ICore::self()->documentController(), &IDocumentController::documentUrlChanged, this, &CodeCompletion::documentUrlChanged ); aModel->setParent(this); // prevent deadlock QMetaObject::invokeMethod(this, "checkDocuments", Qt::QueuedConnection); } CodeCompletion::~CodeCompletion() { } void CodeCompletion::checkDocuments() { foreach( KDevelop::IDocument* doc, KDevelop::ICore::self()->documentController()->openDocuments() ) { if (doc->textDocument()) { checkDocument(doc->textDocument()); } } } void CodeCompletion::viewCreated(KTextEditor::Document * document, KTextEditor::View * view) { Q_UNUSED(document); if (CodeCompletionInterface* cc = dynamic_cast(view)) { cc->registerCompletionModel(m_model); qCDebug(LANGUAGE) << "Registered completion model"; emit registeredToView(view); } } void CodeCompletion::documentUrlChanged(KDevelop::IDocument* document) { // The URL has changed (might have a different language now), so we re-register the document Document* textDocument = document->textDocument(); if(textDocument) { checkDocument(textDocument); } } void CodeCompletion::textDocumentCreated(KDevelop::IDocument* document) { Q_ASSERT(document->textDocument()); checkDocument(document->textDocument()); } void CodeCompletion::unregisterDocument(Document* textDocument) { foreach (KTextEditor::View* view, textDocument->views()) { if (CodeCompletionInterface* cc = dynamic_cast(view)) { cc->unregisterCompletionModel(m_model); emit unregisteredFromView(view); } } disconnect(textDocument, &Document::viewCreated, this, &CodeCompletion::viewCreated); } void CodeCompletion::checkDocument(Document* textDocument) { unregisterDocument(textDocument); auto langs = ICore::self()->languageController()->languagesForUrl( textDocument->url() ); bool found = false; foreach(const auto lang, langs) { if(m_language==lang->name()) { found=true; break; } } if(!found && !m_language.isEmpty()) return; foreach (KTextEditor::View* view, textDocument->views()) viewCreated(textDocument, view); connect(textDocument, &Document::viewCreated, this, &CodeCompletion::viewCreated); } diff --git a/language/codecompletion/codecompletioncontext.cpp b/language/codecompletion/codecompletioncontext.cpp index 1d8b7198b..12967951d 100644 --- a/language/codecompletion/codecompletioncontext.cpp +++ b/language/codecompletion/codecompletioncontext.cpp @@ -1,93 +1,91 @@ /* 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 "codecompletioncontext.h" -#include "util/debug.h" +#include #include #include -#include - using namespace KDevelop; typedef PushValue IntPusher; ///Extracts the last line from the given string QString CodeCompletionContext::extractLastLine(const QString& str) { int prevLineEnd = str.lastIndexOf('\n'); if(prevLineEnd != -1) return str.mid(prevLineEnd+1); else return str; } int completionRecursionDepth = 0; CodeCompletionContext::CodeCompletionContext(DUContextPointer context, const QString& text, const KDevelop::CursorInRevision& position, int depth) : m_text(text), m_depth(depth), m_valid(true), m_position(position), m_duContext(context), m_parentContext(nullptr) { IntPusher( completionRecursionDepth, completionRecursionDepth+1 ); if( depth > 10 ) { qCWarning(LANGUAGE) << "too much recursion"; m_valid = false; return; } if( completionRecursionDepth > 10 ) { qCWarning(LANGUAGE) << "too much recursion"; m_valid = false; return; } } CodeCompletionContext::~CodeCompletionContext() { } int CodeCompletionContext::depth() const { return m_depth; } bool CodeCompletionContext::isValid() const { return m_valid; } void KDevelop::CodeCompletionContext::setParentContext(QExplicitlySharedDataPointer< KDevelop::CodeCompletionContext > newParent) { m_parentContext = newParent; int newDepth = m_depth+1; while(newParent) { newParent->m_depth = newDepth; ++newDepth; newParent = newParent->m_parentContext; } } CodeCompletionContext* CodeCompletionContext::parentContext() { return m_parentContext.data(); } QList< QExplicitlySharedDataPointer< KDevelop::CompletionTreeElement > > KDevelop::CodeCompletionContext::ungroupedElements() { return QList< QExplicitlySharedDataPointer< KDevelop::CompletionTreeElement > >(); } KDevelop::DUContext* KDevelop::CodeCompletionContext::duContext() const { return m_duContext.data(); } diff --git a/language/codecompletion/codecompletionitem.cpp b/language/codecompletion/codecompletionitem.cpp index dc5e024ba..3a17c1176 100644 --- a/language/codecompletion/codecompletionitem.cpp +++ b/language/codecompletion/codecompletionitem.cpp @@ -1,164 +1,164 @@ /* * KDevelop Generic Code Completion Support * * Copyright 2007-2008 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "codecompletionitem.h" #include #include #include #include #include #include -#include "util/debug.h" +#include #include "../duchain/declaration.h" #include "../duchain/duchainutils.h" using namespace KTextEditor; namespace KDevelop { ///Intermediate nodes struct CompletionTreeNode; ///Leaf items class CompletionTreeItem; CompletionTreeElement::CompletionTreeElement() : m_parent(nullptr), m_rowInParent(0) { } CompletionTreeElement::~CompletionTreeElement() { } CompletionTreeElement* CompletionTreeElement::parent() const { return m_parent; } void CompletionTreeElement::setParent(CompletionTreeElement* parent) { Q_ASSERT(m_parent == nullptr); m_parent = parent; auto node = parent ? parent->asNode() : nullptr; if (node) { m_rowInParent = node->children.count(); } } void CompletionTreeNode::appendChildren(QList< QExplicitlySharedDataPointer< KDevelop::CompletionTreeElement > > children) { foreach (const auto& child, children) { appendChild(child); } } void CompletionTreeNode::appendChildren(QList< QExplicitlySharedDataPointer< KDevelop::CompletionTreeItem > > children) { foreach (const auto& child, children) { appendChild(CompletionTreeElementPointer(child.data())); } } void CompletionTreeNode::appendChild(QExplicitlySharedDataPointer< KDevelop::CompletionTreeElement > child) { child->setParent(this); children << child; } int CompletionTreeElement::columnInParent() const { return 0; } CompletionTreeNode::CompletionTreeNode() : CompletionTreeElement(), role((KTextEditor::CodeCompletionModel::ExtraItemDataRoles)Qt::DisplayRole) {} CompletionTreeNode::~CompletionTreeNode() { } CompletionTreeNode* CompletionTreeElement::asNode() { return dynamic_cast(this); } CompletionTreeItem* CompletionTreeElement::asItem() { return dynamic_cast(this); } const CompletionTreeNode* CompletionTreeElement::asNode() const { return dynamic_cast(this); } const CompletionTreeItem* CompletionTreeElement::asItem() const { return dynamic_cast(this); } int CompletionTreeElement::rowInParent() const { return m_rowInParent; } void CompletionTreeItem::execute(KTextEditor::View* view, const KTextEditor::Range& word) { Q_UNUSED(view) Q_UNUSED(word) qCWarning(LANGUAGE) << "doing nothing"; } QVariant CompletionTreeItem::data(const QModelIndex& index, int role, const CodeCompletionModel* model) const { Q_UNUSED(index) Q_UNUSED(model) if(role == Qt::DisplayRole) return i18n("not implemented"); return QVariant(); } int CompletionTreeItem::inheritanceDepth() const { return 0; } int CompletionTreeItem::argumentHintDepth() const { return 0; } KTextEditor::CodeCompletionModel::CompletionProperties CompletionTreeItem::completionProperties() const { Declaration* dec = declaration().data(); if(!dec) { return {}; } return DUChainUtils::completionProperties(dec); } DeclarationPointer CompletionTreeItem::declaration() const { return DeclarationPointer(); } QList CompletionTreeItem::typeForArgumentMatching() const { return QList(); } CompletionCustomGroupNode::CompletionCustomGroupNode(QString groupName, int _inheritanceDepth) { role = (KTextEditor::CodeCompletionModel::ExtraItemDataRoles)Qt::DisplayRole; roleValue = groupName; inheritanceDepth = _inheritanceDepth; } bool CompletionTreeItem::dataChangedWithInput() const { return false; } } diff --git a/language/codecompletion/codecompletionmodel.cpp b/language/codecompletion/codecompletionmodel.cpp index f90d7ddb7..860071381 100644 --- a/language/codecompletion/codecompletionmodel.cpp +++ b/language/codecompletion/codecompletionmodel.cpp @@ -1,441 +1,440 @@ /* * KDevelop Generic Code Completion Support * * Copyright 2006-2008 Hamish Rodda * Copyright 2007-2008 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "codecompletionmodel.h" #include -#include #include #include #include "../duchain/declaration.h" #include "../duchain/classfunctiondeclaration.h" #include "../duchain/ducontext.h" #include "../duchain/duchain.h" #include "../duchain/namespacealiasdeclaration.h" #include "../duchain/parsingenvironment.h" #include "../duchain/duchainlock.h" #include "../duchain/duchainbase.h" #include "../duchain/topducontext.h" #include "../duchain/duchainutils.h" #include "../interfaces/quickopendataprovider.h" #include "../interfaces/icore.h" #include "../interfaces/ilanguagecontroller.h" #include "../interfaces/icompletionsettings.h" -#include "util/debug.h" +#include #include "codecompletionworker.h" #include "codecompletioncontext.h" #include using namespace KTextEditor; //Multi-threaded completion creates some multi-threading related crashes, and sometimes shows the completions in the wrong position if the cursor was moved // #define SINGLE_THREADED_COMPLETION namespace KDevelop { class CompletionWorkerThread : public QThread { Q_OBJECT public: explicit CompletionWorkerThread(CodeCompletionModel* model) : QThread(model), m_model(model), m_worker(m_model->createCompletionWorker()) { Q_ASSERT(m_worker->parent() == nullptr); // Must be null, else we cannot change the thread affinity! m_worker->moveToThread(this); Q_ASSERT(m_worker->thread() == this); } ~CompletionWorkerThread() override { delete m_worker; } void run () override { //We connect directly, so we can do the pre-grouping within the background thread connect(m_worker, &CodeCompletionWorker::foundDeclarationsReal, m_model, &CodeCompletionModel::foundDeclarations, Qt::QueuedConnection); connect(m_model, &CodeCompletionModel::completionsNeeded, m_worker, static_cast,const Cursor&,View*)>(&CodeCompletionWorker::computeCompletions), Qt::QueuedConnection); connect(m_model, &CodeCompletionModel::doSpecialProcessingInBackground, m_worker, &CodeCompletionWorker::doSpecialProcessing); exec(); } CodeCompletionModel* m_model; CodeCompletionWorker* m_worker; }; bool CodeCompletionModel::forceWaitForModel() { return m_forceWaitForModel; } void CodeCompletionModel::setForceWaitForModel(bool wait) { m_forceWaitForModel = wait; } CodeCompletionModel::CodeCompletionModel( QObject * parent ) : KTextEditor::CodeCompletionModel(parent) , m_forceWaitForModel(false) , m_fullCompletion(true) , m_mutex(new QMutex) , m_thread(nullptr) { qRegisterMetaType(); } void CodeCompletionModel::initialize() { if(!m_thread) { m_thread = new CompletionWorkerThread(this); #ifdef SINGLE_THREADED_COMPLETION m_thread->m_worker = createCompletionWorker(); #endif m_thread->start(); } } CodeCompletionModel::~CodeCompletionModel() { if(m_thread->m_worker) m_thread->m_worker->abortCurrentCompletion(); m_thread->quit(); m_thread->wait(); delete m_thread; delete m_mutex; } void CodeCompletionModel::addNavigationWidget(const CompletionTreeElement* element, QWidget* widget) const { Q_ASSERT(dynamic_cast(widget)); m_navigationWidgets[element] = widget; } bool CodeCompletionModel::fullCompletion() const { return m_fullCompletion; } KDevelop::CodeCompletionWorker* CodeCompletionModel::worker() const { return m_thread->m_worker; } void CodeCompletionModel::clear() { beginResetModel(); m_completionItems.clear(); m_navigationWidgets.clear(); m_completionContext.reset(); endResetModel(); } void CodeCompletionModel::completionInvokedInternal(KTextEditor::View* view, const KTextEditor::Range& range, InvocationType invocationType, const QUrl& url) { Q_ASSERT(m_thread == worker()->thread()); Q_UNUSED(invocationType) DUChainReadLocker lock(DUChain::lock(), 400); if( !lock.locked() ) { qCDebug(LANGUAGE) << "could not lock du-chain in time"; return; } TopDUContext* top = DUChainUtils::standardContextForUrl( url ); if(!top) { qCDebug(LANGUAGE) << "================== NO CONTEXT FOUND ======================="; beginResetModel(); m_completionItems.clear(); m_navigationWidgets.clear(); endResetModel(); qCDebug(LANGUAGE) << "Completion invoked for unknown context. Document:" << url << ", Known documents:" << DUChain::self()->documents(); return; } setCurrentTopContext(TopDUContextPointer(top)); RangeInRevision rangeInRevision = top->transformToLocalRevision(KTextEditor::Range(range)); qCDebug(LANGUAGE) << "completion invoked for context" << (DUContext*)top; if( top->parsingEnvironmentFile() && top->parsingEnvironmentFile()->modificationRevision() != ModificationRevision::revisionForFile(IndexedString(url.toString())) ) { qCDebug(LANGUAGE) << "Found context is not current."; } DUContextPointer thisContext; { qCDebug(LANGUAGE) << "apply specialization:" << range.start(); thisContext = SpecializationStore::self().applySpecialization(top->findContextAt(rangeInRevision.start), top); if ( thisContext ) { qCDebug(LANGUAGE) << "after specialization:" << thisContext->localScopeIdentifier().toString() << thisContext->rangeInCurrentRevision(); } else { thisContext = top; } qCDebug(LANGUAGE) << "context is set to" << thisContext.data(); } lock.unlock(); if(m_forceWaitForModel) emit waitForReset(); emit completionsNeeded(thisContext, range.start(), view); } void CodeCompletionModel::completionInvoked(KTextEditor::View* view, const KTextEditor::Range& range, InvocationType invocationType) { //If this triggers, initialize() has not been called after creation. Q_ASSERT(m_thread); KDevelop::ICompletionSettings::CompletionLevel level = KDevelop::ICore::self()->languageController()->completionSettings()->completionLevel(); if(level == KDevelop::ICompletionSettings::AlwaysFull || (invocationType != AutomaticInvocation && level == KDevelop::ICompletionSettings::MinimalWhenAutomatic)) m_fullCompletion = true; else m_fullCompletion = false; //Only use grouping in full completion mode setHasGroups(m_fullCompletion); Q_UNUSED(invocationType) if (!worker()) { qCWarning(LANGUAGE) << "Completion invoked on a completion model which has no code completion worker assigned!"; } beginResetModel(); m_navigationWidgets.clear(); m_completionItems.clear(); endResetModel(); worker()->abortCurrentCompletion(); worker()->setFullCompletion(m_fullCompletion); QUrl url = view->document()->url(); completionInvokedInternal(view, range, invocationType, url); } void CodeCompletionModel::foundDeclarations(const QList>& items, const QExplicitlySharedDataPointer& completionContext) { m_completionContext = completionContext; if(m_completionItems.isEmpty() && items.isEmpty()) { if(m_forceWaitForModel) { // TODO KF5: Check if this actually works beginResetModel(); endResetModel(); //If we need to reset the model, reset it } return; //We don't need to reset, which is bad for target model } beginResetModel(); m_completionItems = items; endResetModel(); if(m_completionContext) { qCDebug(LANGUAGE) << "got completion-context with " << m_completionContext->ungroupedElements().size() << "ungrouped elements"; } } KTextEditor::CodeCompletionModelControllerInterface::MatchReaction CodeCompletionModel::matchingItem(const QModelIndex& /*matched*/) { return None; } void CodeCompletionModel::setCompletionContext(QExplicitlySharedDataPointer completionContext) { QMutexLocker lock(m_mutex); m_completionContext = completionContext; if(m_completionContext) { qCDebug(LANGUAGE) << "got completion-context with " << m_completionContext->ungroupedElements().size() << "ungrouped elements"; } } QExplicitlySharedDataPointer CodeCompletionModel::completionContext() const { QMutexLocker lock(m_mutex); return m_completionContext; } void CodeCompletionModel::executeCompletionItem(View* view, const KTextEditor::Range& word, const QModelIndex& index) const { //We must not lock the duchain at this place, because the items might rely on that CompletionTreeElement* element = static_cast(index.internalPointer()); if( !element || !element->asItem() ) return; element->asItem()->execute(view, word); } QExplicitlySharedDataPointer< KDevelop::CompletionTreeElement > CodeCompletionModel::itemForIndex(QModelIndex index) const { CompletionTreeElement* element = static_cast(index.internalPointer()); return QExplicitlySharedDataPointer< KDevelop::CompletionTreeElement >(element); } QVariant CodeCompletionModel::data(const QModelIndex& index, int role) const { if ( role == Qt::TextAlignmentRole && index.column() == 0 ) { return Qt::AlignRight; } auto element = static_cast(index.internalPointer()); if( !element ) return QVariant(); if( role == CodeCompletionModel::GroupRole ) { if( element->asNode() ) { return QVariant(element->asNode()->role); }else { qCDebug(LANGUAGE) << "Requested group-role from leaf tree element"; return QVariant(); } }else{ if( element->asNode() ) { if( role == CodeCompletionModel::InheritanceDepth ) { auto customGroupNode = dynamic_cast(element); if(customGroupNode) return QVariant(customGroupNode->inheritanceDepth); } if( role == element->asNode()->role ) { return element->asNode()->roleValue; } else { return QVariant(); } } } if(!element->asItem()) { qCWarning(LANGUAGE) << "Error in completion model"; return QVariant(); } //Navigation widget interaction is done here, the other stuff is done within the tree-elements switch (role) { case CodeCompletionModel::InheritanceDepth: return element->asItem()->inheritanceDepth(); case CodeCompletionModel::ArgumentHintDepth: return element->asItem()->argumentHintDepth(); case CodeCompletionModel::ItemSelected: { DeclarationPointer decl = element->asItem()->declaration(); if(decl) { DUChain::self()->emitDeclarationSelected(decl); } break; } } //In minimal completion mode, hide all columns except the "name" one if(!m_fullCompletion && role == Qt::DisplayRole && index.column() != Name && (element->asItem()->argumentHintDepth() == 0 || index.column() == Prefix)) return QVariant(); //In reduced completion mode, don't show information text with the selected items if(role == ItemSelected && (!m_fullCompletion || !ICore::self()->languageController()->completionSettings()->showMultiLineSelectionInformation())) return QVariant(); return element->asItem()->data(index, role, this); } KDevelop::TopDUContextPointer CodeCompletionModel::currentTopContext() const { return m_currentTopContext; } void CodeCompletionModel::setCurrentTopContext(KDevelop::TopDUContextPointer topContext) { m_currentTopContext = topContext; } QModelIndex CodeCompletionModel::index(int row, int column, const QModelIndex& parent) const { if( parent.isValid() ) { CompletionTreeElement* element = static_cast(parent.internalPointer()); CompletionTreeNode* node = element->asNode(); if( !node ) { qCDebug(LANGUAGE) << "Requested sub-index of leaf node"; return QModelIndex(); } if (row < 0 || row >= node->children.count() || column < 0 || column >= ColumnCount) return QModelIndex(); return createIndex(row, column, node->children[row].data()); } else { if (row < 0 || row >= m_completionItems.count() || column < 0 || column >= ColumnCount) return QModelIndex(); return createIndex(row, column, const_cast(m_completionItems[row].data())); } } QModelIndex CodeCompletionModel::parent ( const QModelIndex & index ) const { if(rowCount() == 0) return QModelIndex(); if( index.isValid() ) { CompletionTreeElement* element = static_cast(index.internalPointer()); if( element->parent() ) return createIndex( element->rowInParent(), element->columnInParent(), element->parent() ); } return QModelIndex(); } int CodeCompletionModel::rowCount ( const QModelIndex & parent ) const { if( parent.isValid() ) { CompletionTreeElement* element = static_cast(parent.internalPointer()); CompletionTreeNode* node = element->asNode(); if( !node ) return 0; return node->children.count(); }else{ return m_completionItems.count(); } } QString CodeCompletionModel::filterString(KTextEditor::View* view, const KTextEditor::Range& range, const KTextEditor::Cursor& position) { m_filterString = KTextEditor::CodeCompletionModelControllerInterface::filterString(view, range, position); return m_filterString; } } #include "moc_codecompletionmodel.cpp" #include "codecompletionmodel.moc" diff --git a/language/codecompletion/codecompletionworker.cpp b/language/codecompletion/codecompletionworker.cpp index bb2ead1fd..316c0198d 100644 --- a/language/codecompletion/codecompletionworker.cpp +++ b/language/codecompletion/codecompletionworker.cpp @@ -1,224 +1,224 @@ /* * KDevelop Generic Code Completion Support * * Copyright 2006-2008 Hamish Rodda * Copyright 2007-2008 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "codecompletionworker.h" #include #include #include #include "../duchain/ducontext.h" #include "../duchain/duchainlock.h" #include "../duchain/duchain.h" -#include "util/debug.h" +#include #include "codecompletion.h" #include "codecompletionitem.h" #include "codecompletionmodel.h" #include "codecompletionitemgrouper.h" #include using namespace KTextEditor; using namespace KDevelop; CodeCompletionWorker::CodeCompletionWorker(KDevelop::CodeCompletionModel* model) : m_hasFoundDeclarations(false) , m_mutex(new QMutex()) , m_abort(false) , m_fullCompletion(true) , m_model(model) { } CodeCompletionWorker::~CodeCompletionWorker() { delete m_mutex; } void CodeCompletionWorker::setFullCompletion(bool full) { m_fullCompletion = full; } bool CodeCompletionWorker::fullCompletion() const { return m_fullCompletion; } void CodeCompletionWorker::failed() { foundDeclarations({}, {}); } void CodeCompletionWorker::foundDeclarations(const QList& items, const CodeCompletionContext::Ptr& completionContext) { m_hasFoundDeclarations = true; emit foundDeclarationsReal(items, completionContext); } void CodeCompletionWorker::computeCompletions(KDevelop::DUContextPointer context, const KTextEditor::Cursor& position, KTextEditor::View* view) { { QMutexLocker lock(m_mutex); m_abort = false; } ///@todo It's not entirely safe to pass KTextEditor::View* through a queued connection // We access the view/document which is not thread-safe, so we need the foreground lock ForegroundLock foreground; //Compute the text we should complete on KTextEditor::Document* doc = view->document(); if( !doc ) { qCDebug(LANGUAGE) << "No document for completion"; failed(); return; } KTextEditor::Range range; QString text; { QMutexLocker lock(m_mutex); DUChainReadLocker lockDUChain; if(context) { qCDebug(LANGUAGE) << context->localScopeIdentifier().toString(); range = KTextEditor::Range(context->rangeInCurrentRevision().start(), position); } else range = KTextEditor::Range(KTextEditor::Cursor(position.line(), 0), position); updateContextRange(range, view, context); text = doc->text(range); } if( position.column() == 0 ) //Seems like when the cursor is a the beginning of a line, kate does not give the \n text += '\n'; if (aborting()) { failed(); return; } m_hasFoundDeclarations = false; KTextEditor::Cursor cursorPosition = view->cursorPosition(); QString followingText; //followingText may contain additional text that stands for the current item. For example in the case "QString|", QString is in addedText. if(position < cursorPosition) followingText = view->document()->text( KTextEditor::Range( position, cursorPosition ) ); foreground.unlock(); computeCompletions(context, position, followingText, range, text); if(!m_hasFoundDeclarations) failed(); } void KDevelop::CodeCompletionWorker::doSpecialProcessing(uint) { } CodeCompletionContext* CodeCompletionWorker::createCompletionContext(KDevelop::DUContextPointer context, const QString& contextText, const QString& followingText, const KDevelop::CursorInRevision& position) const { Q_UNUSED(context); Q_UNUSED(contextText); Q_UNUSED(followingText); Q_UNUSED(position); return nullptr; } void CodeCompletionWorker::computeCompletions(KDevelop::DUContextPointer context, const KTextEditor::Cursor& position, QString followingText, const KTextEditor::Range& contextRange, const QString& contextText) { Q_UNUSED(contextRange); qCDebug(LANGUAGE) << "added text:" << followingText; CodeCompletionContext::Ptr completionContext( createCompletionContext( context, contextText, followingText, CursorInRevision::castFromSimpleCursor(KTextEditor::Cursor(position)) ) ); if (KDevelop::CodeCompletionModel* m = model()) m->setCompletionContext(completionContext); if( completionContext && completionContext->isValid() ) { { DUChainReadLocker lock(DUChain::lock()); if (!context) { failed(); qCDebug(LANGUAGE) << "Completion context disappeared before completions could be calculated"; return; } } QList items = completionContext->completionItems(aborting(), fullCompletion()); if (aborting()) { failed(); return; } QList > tree = computeGroups( items, completionContext ); if(aborting()) { failed(); return; } tree += completionContext->ungroupedElements(); foundDeclarations( tree, completionContext ); } else { qCDebug(LANGUAGE) << "setContext: Invalid code-completion context"; } } QList > CodeCompletionWorker::computeGroups(QList items, QExplicitlySharedDataPointer completionContext) { Q_UNUSED(completionContext); QList > tree; /** * 1. Group by argument-hint depth * 2. Group by inheritance depth * 3. Group by simplified attributes * */ CodeCompletionItemGrouper > > argumentHintDepthGrouper(tree, nullptr, items); return tree; } void CodeCompletionWorker::abortCurrentCompletion() { QMutexLocker lock(m_mutex); m_abort = true; } bool& CodeCompletionWorker::aborting() { return m_abort; } KDevelop::CodeCompletionModel* CodeCompletionWorker::model() const { return m_model; } void CodeCompletionWorker::updateContextRange(Range& contextRange, View* view, DUContextPointer context) const { Q_UNUSED(contextRange); Q_UNUSED(view); Q_UNUSED(context); } diff --git a/language/codecompletion/normaldeclarationcompletionitem.cpp b/language/codecompletion/normaldeclarationcompletionitem.cpp index 9e30fea1e..82de30cc3 100644 --- a/language/codecompletion/normaldeclarationcompletionitem.cpp +++ b/language/codecompletion/normaldeclarationcompletionitem.cpp @@ -1,230 +1,230 @@ /* * KDevelop Generic Code Completion Support * * Copyright 2007-2008 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "normaldeclarationcompletionitem.h" #include "codecompletionmodel.h" #include "../duchain/duchainlock.h" #include "../duchain/duchain.h" #include "../duchain/classfunctiondeclaration.h" #include "../duchain/types/functiontype.h" #include "../duchain/types/enumeratortype.h" #include "../duchain/duchainutils.h" #include "../duchain/navigation/abstractdeclarationnavigationcontext.h" -#include "util/debug.h" +#include #include #include namespace KDevelop { const int NormalDeclarationCompletionItem::normalBestMatchesCount = 5; //If this is true, the return-values of argument-hints will be just written as "..." if they are too long const bool NormalDeclarationCompletionItem::shortenArgumentHintReturnValues = true; const int NormalDeclarationCompletionItem::maximumArgumentHintReturnValueLength = 30; const int NormalDeclarationCompletionItem::desiredTypeLength = 20; NormalDeclarationCompletionItem::NormalDeclarationCompletionItem(KDevelop::DeclarationPointer decl, QExplicitlySharedDataPointer context, int inheritanceDepth) : m_completionContext(context), m_declaration(decl), m_inheritanceDepth(inheritanceDepth) { } KDevelop::DeclarationPointer NormalDeclarationCompletionItem::declaration() const { return m_declaration; } QExplicitlySharedDataPointer< KDevelop::CodeCompletionContext > NormalDeclarationCompletionItem::completionContext() const { return m_completionContext; } int NormalDeclarationCompletionItem::inheritanceDepth() const { return m_inheritanceDepth; } int NormalDeclarationCompletionItem::argumentHintDepth() const { if( m_completionContext ) return m_completionContext->depth(); else return 0; } QString NormalDeclarationCompletionItem::declarationName() const { if (!m_declaration) { return QStringLiteral(""); } QString ret = m_declaration->identifier().toString(); if (ret.isEmpty()) return QStringLiteral(""); else return ret; } void NormalDeclarationCompletionItem::execute(KTextEditor::View* view, const KTextEditor::Range& word) { if( m_completionContext && m_completionContext->depth() != 0 ) return; //Do not replace any text when it is an argument-hint KTextEditor::Document* document = view->document(); QString newText; { KDevelop::DUChainReadLocker lock(KDevelop::DUChain::lock()); if(m_declaration) { newText = declarationName(); } else { qCDebug(LANGUAGE) << "Declaration disappeared"; return; } } document->replaceText(word, newText); KTextEditor::Range newRange = word; newRange.setEnd(KTextEditor::Cursor(newRange.end().line(), newRange.start().column() + newText.length())); executed(view, newRange); } QWidget* NormalDeclarationCompletionItem::createExpandingWidget(const KDevelop::CodeCompletionModel* model) const { Q_UNUSED(model); return nullptr; } bool NormalDeclarationCompletionItem::createsExpandingWidget() const { return false; } QString NormalDeclarationCompletionItem::shortenedTypeString(KDevelop::DeclarationPointer decl, int desiredTypeLength) const { Q_UNUSED(desiredTypeLength); return decl->abstractType()->toString(); } void NormalDeclarationCompletionItem::executed(KTextEditor::View* view, const KTextEditor::Range& word) { Q_UNUSED(view); Q_UNUSED(word); } QVariant NormalDeclarationCompletionItem::data(const QModelIndex& index, int role, const KDevelop::CodeCompletionModel* model) const { DUChainReadLocker lock(DUChain::lock(), 500); if(!lock.locked()) { qCDebug(LANGUAGE) << "Failed to lock the du-chain in time"; return QVariant(); } if(!m_declaration) return QVariant(); switch (role) { case Qt::DisplayRole: if (index.column() == CodeCompletionModel::Name) { return declarationName(); } else if(index.column() == CodeCompletionModel::Postfix) { if (FunctionType::Ptr functionType = m_declaration->type()) { // Retrieve const/volatile string return functionType->AbstractType::toString(); } } else if(index.column() == CodeCompletionModel::Prefix) { if(m_declaration->kind() == Declaration::Namespace) return QStringLiteral("namespace"); if (m_declaration->abstractType()) { if(EnumeratorType::Ptr enumerator = m_declaration->type()) { if(m_declaration->context()->owner() && m_declaration->context()->owner()->abstractType()) { if(!m_declaration->context()->owner()->identifier().isEmpty()) return shortenedTypeString(DeclarationPointer(m_declaration->context()->owner()), desiredTypeLength); else return "enum"; } } if (FunctionType::Ptr functionType = m_declaration->type()) { ClassFunctionDeclaration* funDecl = dynamic_cast(m_declaration.data()); if (functionType->returnType()) { QString ret = shortenedTypeString(m_declaration, desiredTypeLength); if(shortenArgumentHintReturnValues && argumentHintDepth() && ret.length() > maximumArgumentHintReturnValueLength) return QStringLiteral("..."); else return ret; }else if(argumentHintDepth()) { return QString();//Don't show useless prefixes in the argument-hints }else if(funDecl && funDecl->isConstructor() ) return ""; else if(funDecl && funDecl->isDestructor() ) return ""; else return ""; } else { return shortenedTypeString(m_declaration, desiredTypeLength); } } else { return ""; } } else if (index.column() == CodeCompletionModel::Arguments) { if (m_declaration->isFunctionDeclaration()) { auto functionType = declaration()->type(); return functionType ? functionType->partToString(FunctionType::SignatureArguments) : QVariant(); } } break; case CodeCompletionModel::BestMatchesCount: return QVariant(normalBestMatchesCount); break; case CodeCompletionModel::IsExpandable: return QVariant(createsExpandingWidget()); case CodeCompletionModel::ExpandingWidget: { QWidget* nav = createExpandingWidget(model); Q_ASSERT(nav); model->addNavigationWidget(this, nav); QVariant v; v.setValue(nav); return v; } case CodeCompletionModel::ScopeIndex: return static_cast(reinterpret_cast(m_declaration->context())); case CodeCompletionModel::CompletionRole: return (int)completionProperties(); case CodeCompletionModel::ItemSelected: { NavigationContextPointer ctx(new AbstractDeclarationNavigationContext(DeclarationPointer(m_declaration), TopDUContextPointer())); return ctx->html(true); } case Qt::DecorationRole: { if( index.column() == CodeCompletionModel::Icon ) { CodeCompletionModel::CompletionProperties p = completionProperties(); lock.unlock(); return DUChainUtils::iconForProperties(p); } break; } } return QVariant(); } } diff --git a/language/codegen/basicrefactoring.cpp b/language/codegen/basicrefactoring.cpp index 2ca3f5d61..f666b2444 100644 --- a/language/codegen/basicrefactoring.cpp +++ b/language/codegen/basicrefactoring.cpp @@ -1,364 +1,364 @@ /* This file is part of KDevelop * * Copyright 2014 Miquel Sabaté * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // Qt #include // KDE / KDevelop #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "progressdialogs/refactoringdialog.h" -#include "util/debug.h" +#include #include "ui_basicrefactoring.h" namespace { QPair splitFileAtExtension(const QString& fileName) { int idx = fileName.indexOf('.'); if (idx == -1) { return qMakePair(fileName, QString()); } return qMakePair(fileName.left(idx), fileName.mid(idx)); } } using namespace KDevelop; //BEGIN: BasicRefactoringCollector BasicRefactoringCollector::BasicRefactoringCollector(const IndexedDeclaration &decl) : UsesWidgetCollector(decl) { setCollectConstructors(true); setCollectDefinitions(true); setCollectOverloads(true); } QVector BasicRefactoringCollector::allUsingContexts() const { return m_allUsingContexts; } void BasicRefactoringCollector::processUses(KDevelop::ReferencedTopDUContext topContext) { m_allUsingContexts << IndexedTopDUContext(topContext.data()); UsesWidgetCollector::processUses(topContext); } //END: BasicRefactoringCollector //BEGIN: BasicRefactoring BasicRefactoring::BasicRefactoring(QObject *parent) : QObject(parent) { /* There's nothing to do here. */ } void BasicRefactoring::fillContextMenu(ContextMenuExtension &extension, Context *context) { DeclarationContext *declContext = dynamic_cast(context); if (!declContext) return; DUChainReadLocker lock; Declaration *declaration = declContext->declaration().data(); if (declaration && acceptForContextMenu(declaration)) { QFileInfo finfo(declaration->topContext()->url().str()); if (finfo.isWritable()) { QAction *action = new QAction(i18n("Rename \"%1\"...", declaration->qualifiedIdentifier().toString()), nullptr); action->setData(QVariant::fromValue(IndexedDeclaration(declaration))); action->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename"))); connect(action, &QAction::triggered, this, &BasicRefactoring::executeRenameAction); extension.addAction(ContextMenuExtension::RefactorGroup, action); } } } bool BasicRefactoring::shouldRenameUses(KDevelop::Declaration* declaration) const { // Now we know we're editing a declaration, but some declarations we don't offer a rename for // basically that's any declaration that wouldn't be fully renamed just by renaming its uses(). if (declaration->internalContext() || declaration->isForwardDeclaration()) { //make an exception for non-class functions if (!declaration->isFunctionDeclaration() || dynamic_cast(declaration)) return false; } return true; } QString BasicRefactoring::newFileName(const QUrl& current, const QString& newName) { QPair nameExtensionPair = splitFileAtExtension(current.fileName()); // if current file is lowercased, keep that if (nameExtensionPair.first == nameExtensionPair.first.toLower()) { return newName.toLower() + nameExtensionPair.second; } else { return newName + nameExtensionPair.second; } } DocumentChangeSet::ChangeResult BasicRefactoring::addRenameFileChanges(const QUrl& current, const QString& newName, DocumentChangeSet* changes) { return changes->addDocumentRenameChange( IndexedString(current), IndexedString(newFileName(current, newName))); } bool BasicRefactoring::shouldRenameFile(Declaration* declaration) { // only try to rename files when we renamed a class/struct if (!dynamic_cast(declaration)) { return false; } const QUrl currUrl = declaration->topContext()->url().toUrl(); const QString fileName = currUrl.fileName(); const QPair nameExtensionPair = splitFileAtExtension(fileName); // check whether we renamed something that is called like the document it lives in return nameExtensionPair.first.compare(declaration->identifier().toString(), Qt::CaseInsensitive) == 0; } DocumentChangeSet::ChangeResult BasicRefactoring::applyChanges(const QString &oldName, const QString &newName, DocumentChangeSet &changes, DUContext *context, int usedDeclarationIndex) { if (usedDeclarationIndex == std::numeric_limits::max()) return DocumentChangeSet::ChangeResult::successfulResult(); for (int a = 0; a < context->usesCount(); ++a) { const Use &use(context->uses()[a]); if (use.m_declarationIndex != usedDeclarationIndex) continue; if (use.m_range.isEmpty()) { qCDebug(LANGUAGE) << "found empty use"; continue; } DocumentChangeSet::ChangeResult result = changes.addChange(DocumentChange(context->url(), context->transformFromLocalRevision(use.m_range), oldName, newName)); if (!result) return result; } foreach (DUContext *child, context->childContexts()) { DocumentChangeSet::ChangeResult result = applyChanges(oldName, newName, changes, child, usedDeclarationIndex); if (!result) return result; } return DocumentChangeSet::ChangeResult::successfulResult(); } DocumentChangeSet::ChangeResult BasicRefactoring::applyChangesToDeclarations(const QString &oldName, const QString &newName, DocumentChangeSet &changes, const QList &declarations) { foreach (const IndexedDeclaration decl, declarations) { Declaration *declaration = decl.data(); if (!declaration) continue; if (declaration->range().isEmpty()) qCDebug(LANGUAGE) << "found empty declaration"; TopDUContext *top = declaration->topContext(); DocumentChangeSet::ChangeResult result = changes.addChange(DocumentChange(top->url(), declaration->rangeInCurrentRevision(), oldName, newName)); if (!result) return result; } return DocumentChangeSet::ChangeResult::successfulResult(); } KDevelop::IndexedDeclaration BasicRefactoring::declarationUnderCursor(bool allowUse) { KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView(); if (!view) return KDevelop::IndexedDeclaration(); KTextEditor::Document* doc = view->document(); DUChainReadLocker lock; if (allowUse) return DUChainUtils::itemUnderCursor(doc->url(), KTextEditor::Cursor(view->cursorPosition())).declaration; else return DUChainUtils::declarationInLine(KTextEditor::Cursor(view->cursorPosition()), DUChainUtils::standardContextForUrl(doc->url())); } void BasicRefactoring::startInteractiveRename(const KDevelop::IndexedDeclaration &decl) { DUChainReadLocker lock(DUChain::lock()); Declaration *declaration = decl.data(); if (!declaration) { KMessageBox::error(ICore::self()->uiController()->activeMainWindow(), i18n("No declaration under cursor")); return; } QFileInfo info(declaration->topContext()->url().str()); if (!info.isWritable()) { KMessageBox::error(ICore::self()->uiController()->activeMainWindow(), i18n("Declaration is located in non-writeable file %1.", declaration->topContext()->url().str())); return; } QString originalName = declaration->identifier().identifier().str(); lock.unlock(); NameAndCollector nc = newNameForDeclaration(DeclarationPointer(declaration)); if (nc.newName == originalName || nc.newName.isEmpty()) return; renameCollectedDeclarations(nc.collector.data(), nc.newName, originalName); } bool BasicRefactoring::acceptForContextMenu(const Declaration *decl) { // Default implementation. Some language plugins might override it to // handle some cases. Q_UNUSED(decl); return true; } void BasicRefactoring::executeRenameAction() { QAction *action = qobject_cast(sender()); if (action) { IndexedDeclaration decl = action->data().value(); if(!decl.isValid()) decl = declarationUnderCursor(); if(!decl.isValid()) return; startInteractiveRename(decl); } } BasicRefactoring::NameAndCollector BasicRefactoring::newNameForDeclaration(const KDevelop::DeclarationPointer& declaration) { DUChainReadLocker lock; if (!declaration) { return {}; } QSharedPointer collector(new BasicRefactoringCollector(declaration.data())); Ui::RenameDialog renameDialog; QDialog dialog; renameDialog.setupUi(&dialog); UsesWidget uses(declaration.data(), collector); //So the context-links work QWidget *navigationWidget = declaration->context()->createNavigationWidget(declaration.data()); AbstractNavigationWidget* abstractNavigationWidget = dynamic_cast(navigationWidget); if (abstractNavigationWidget) connect(&uses, &UsesWidget::navigateDeclaration, abstractNavigationWidget, &AbstractNavigationWidget::navigateDeclaration); QString declarationName = declaration->toString(); dialog.setWindowTitle(i18nc("Renaming some declaration", "Rename \"%1\"", declarationName)); renameDialog.edit->setText(declaration->identifier().identifier().str()); renameDialog.edit->selectAll(); renameDialog.tabWidget->addTab(&uses, i18n("Uses")); if (navigationWidget) renameDialog.tabWidget->addTab(navigationWidget, i18n("Declaration Info")); lock.unlock(); if (dialog.exec() != QDialog::Accepted) return {}; const auto text = renameDialog.edit->text().trimmed(); RefactoringProgressDialog refactoringProgress(i18n("Renaming \"%1\" to \"%2\"", declarationName, text), collector.data()); if (!collector->isReady()) { refactoringProgress.exec(); if (refactoringProgress.result() != QDialog::Accepted) { return {}; } } //TODO: input validation return {text,collector}; } DocumentChangeSet BasicRefactoring::renameCollectedDeclarations(KDevelop::BasicRefactoringCollector* collector, const QString& replacementName, const QString& originalName, bool apply) { DocumentChangeSet changes; DUChainReadLocker lock; foreach (const KDevelop::IndexedTopDUContext collected, collector->allUsingContexts()) { QSet hadIndices; foreach (const IndexedDeclaration decl, collector->declarations()) { uint usedDeclarationIndex = collected.data()->indexForUsedDeclaration(decl.data(), false); if (hadIndices.contains(usedDeclarationIndex)) continue; hadIndices.insert(usedDeclarationIndex); DocumentChangeSet::ChangeResult result = applyChanges(originalName, replacementName, changes, collected.data(), usedDeclarationIndex); if (!result) { KMessageBox::error(nullptr, i18n("Applying changes failed: %1", result.m_failureReason)); return {}; } } } DocumentChangeSet::ChangeResult result = applyChangesToDeclarations(originalName, replacementName, changes, collector->declarations()); if (!result) { KMessageBox::error(nullptr, i18n("Applying changes failed: %1", result.m_failureReason)); return {}; } ///We have to ignore failed changes for now, since uses of a constructor or of operator() may be created on "(" parens changes.setReplacementPolicy(DocumentChangeSet::IgnoreFailedChange); if (!apply) { return changes; } result = changes.applyAllChanges(); if (!result) { KMessageBox::error(nullptr, i18n("Applying changes failed: %1", result.m_failureReason)); } return {}; } //END: BasicRefactoring diff --git a/language/codegen/codedescription.cpp b/language/codegen/codedescription.cpp index 6b442d23c..3e9f8c086 100644 --- a/language/codegen/codedescription.cpp +++ b/language/codegen/codedescription.cpp @@ -1,182 +1,182 @@ /* This file is part of KDevelop Copyright 2012 Miha Čančula 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 "codedescription.h" -#include "util/debug.h" +#include #include #include #include #include #include #include #include #include using namespace KDevelop; /** * The access policy as a string, or an empty string * if the policy is set to default * * The DUChain must be locked when calling this function **/ QString accessPolicyName(const DeclarationPointer& declaration) { DUChainPointer member = declaration.dynamicCast(); if (member) { switch (member->accessPolicy()) { case Declaration::Private: return QStringLiteral("private"); case Declaration::Protected: return QStringLiteral("protected"); case Declaration::Public: return QStringLiteral("public"); default: break; } } return QString(); } VariableDescription::VariableDescription() { } VariableDescription::VariableDescription(const QString& type, const QString& name) : name(name) , type(type) { } VariableDescription::VariableDescription(const DeclarationPointer& declaration) { DUChainReadLocker lock; if (declaration) { name = declaration->identifier().toString(); if (auto abstractType = declaration->abstractType()) { type = abstractType->toString(); } } access = accessPolicyName(declaration); } FunctionDescription::FunctionDescription() : FunctionDescription::FunctionDescription({}, {}, {}) { } FunctionDescription::FunctionDescription(const QString& name, const VariableDescriptionList& arguments, const VariableDescriptionList& returnArguments) : name(name) , arguments(arguments) , returnArguments(returnArguments) , isConstructor(false) , isDestructor(false) , isVirtual(false) , isStatic(false) , isSlot(false) , isSignal(false) , isConst(false) { } FunctionDescription::FunctionDescription(const DeclarationPointer& declaration) : FunctionDescription::FunctionDescription({}, {}, {}) { DUChainReadLocker lock; if (declaration) { name = declaration->identifier().toString(); DUContext* context = declaration->internalContext(); DUChainPointer function = declaration.dynamicCast(); if (function) { context = DUChainUtils::getArgumentContext(declaration.data()); } DUChainPointer method = declaration.dynamicCast(); if (method) { isConstructor = method->isConstructor(); isDestructor = method->isDestructor(); isVirtual = method->isVirtual(); isStatic = method->isStatic(); isSlot = method->isSlot(); isSignal = method->isSignal(); } int i = 0; foreach (Declaration* arg, context->localDeclarations()) { VariableDescription var = VariableDescription(DeclarationPointer(arg)); if (function) { var.value = function->defaultParameterForArgument(i).str(); qCDebug(LANGUAGE) << var.name << var.value; } arguments << var; ++i; } FunctionType::Ptr functionType = declaration->abstractType().cast(); if (functionType) { isConst = (functionType->modifiers() & AbstractType::ConstModifier); } if (functionType && functionType->returnType()) { returnArguments << VariableDescription(functionType->returnType()->toString(), QString()); } access = accessPolicyName(declaration); } } QString FunctionDescription::returnType() const { if (returnArguments.isEmpty()) { return QString(); } return returnArguments.first().type; } ClassDescription::ClassDescription() { } ClassDescription::ClassDescription(const QString& name) : name(name) { } diff --git a/language/codegen/codegenerator.cpp b/language/codegen/codegenerator.cpp index 325bfb239..d021d58cb 100644 --- a/language/codegen/codegenerator.cpp +++ b/language/codegen/codegenerator.cpp @@ -1,241 +1,241 @@ /* Copyright 2008 Hamish Rodda Copyright 2009 Ramon Zarazua 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 "codegenerator.h" #include "documentchangeset.h" #include "duchainchangeset.h" #include #include #include #include #include "applychangeswidget.h" -#include "util/debug.h" +#include namespace KDevelop { class CodeGeneratorPrivate { public: CodeGeneratorPrivate() : autoGen(false), context(0) {} QMap duchainChanges; DocumentChangeSet documentChanges; bool autoGen; DUContext * context; DocumentRange range; QString error; }; CodeGeneratorBase::CodeGeneratorBase() : d(new CodeGeneratorPrivate) { } CodeGeneratorBase::~CodeGeneratorBase() { clearChangeSets(); delete d; } void CodeGeneratorBase::autoGenerate ( DUContext* context, const KDevelop::DocumentRange* range ) { d->autoGen = true; d->context = context; d->range = *range; } void CodeGeneratorBase::addChangeSet(DUChainChangeSet * duchainChange) { IndexedString file = duchainChange->topDuContext().data()->url() ; QMap::iterator it = d->duchainChanges.find(file); //if we already have an entry for this file, merge it if(it !=d->duchainChanges.end()) { **it << *duchainChange; delete duchainChange; } else d->duchainChanges.insert(file, duchainChange); } void CodeGeneratorBase::addChangeSet(DocumentChangeSet & docChangeSet) { d->documentChanges << docChangeSet; } DocumentChangeSet & CodeGeneratorBase::documentChangeSet() { return d->documentChanges; } const QString & CodeGeneratorBase::errorText() const { return d->error; } bool CodeGeneratorBase::autoGeneration() const { return d->autoGen; } void CodeGeneratorBase::setErrorText(const QString & errorText) { d->error = errorText; } void CodeGeneratorBase::clearChangeSets() { qCDebug(LANGUAGE) << "Cleaning up all the changesets registered by the generator"; foreach(DUChainChangeSet * changeSet, d->duchainChanges) delete changeSet; d->duchainChanges.clear(); d->documentChanges = DocumentChangeSet(); } bool CodeGeneratorBase::execute() { qCDebug(LANGUAGE) << "Checking Preconditions for the codegenerator"; //Shouldn't there be a method in iDocument to get a DocumentRange as well? QUrl document; if(!d->autoGen) { if( !ICore::self()->documentController()->activeDocument() ) { setErrorText( i18n("Could not find an open document" ) ); return false; } document = ICore::self()->documentController()->activeDocument()->url(); if(d->range.isEmpty()) { DUChainReadLocker lock(DUChain::lock()); d->range = DocumentRange(document.url(), ICore::self()->documentController()->activeDocument()->textSelection()); } } if(!d->context) { DUChainReadLocker lock(DUChain::lock()); TopDUContext * documentChain = DUChain::self()->chainForDocument(document); if(!documentChain) { setErrorText(i18n("Could not find the chain for the selected document: %1", document.url())); return false; } d->context = documentChain->findContextIncluding(d->range); if(!d->context) { //Attempt to get the context again QList contexts = DUChain::self()->chainsForDocument(document); foreach(TopDUContext * top, contexts) { qCDebug(LANGUAGE) << "Checking top context with range: " << top->range() << " for a context"; if((d->context = top->findContextIncluding(d->range))) break; } } } if(!d->context) { setErrorText(i18n("Error finding context for selection range")); return false; } if(!checkPreconditions(d->context,d->range)) { setErrorText(i18n("Error checking conditions to generate code: %1",errorText())); return false; } if(!d->autoGen) { qCDebug(LANGUAGE) << "Gathering user information for the codegenerator"; if(!gatherInformation()) { setErrorText(i18n("Error Gathering user information: %1",errorText())); return false; } } qCDebug(LANGUAGE) << "Generating code"; if(!process()) { setErrorText(i18n("Error generating code: %1",errorText())); return false; } if(!d->autoGen) { qCDebug(LANGUAGE) << "Submitting to the user for review"; return displayChanges(); } //If it is autogenerated, it shouldn't need to apply changes, instead return them to client that my be another generator DocumentChangeSet::ChangeResult result(true); if(!d->autoGen && !(result = d->documentChanges.applyAllChanges())) { setErrorText(result.m_failureReason); return false; } return true; } bool CodeGeneratorBase::displayChanges() { DocumentChangeSet::ChangeResult result = d->documentChanges.applyAllToTemp(); if(!result) { setErrorText(result.m_failureReason); return false; } ApplyChangesWidget widget; //TODO: Find some good information to put widget.setInformation("Info?"); QMap temps = d->documentChanges.tempNamesForAll(); for(QMap::iterator it = temps.begin(); it != temps.end(); ++it) widget.addDocuments(it.key() , it.value()); if(widget.exec()) return widget.applyAllChanges(); else return false; } } diff --git a/language/codegen/codegenerator.h b/language/codegen/codegenerator.h index 8796f11ac..6602addb9 100644 --- a/language/codegen/codegenerator.h +++ b/language/codegen/codegenerator.h @@ -1,252 +1,252 @@ /* This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_CODEGENERATOR_H #define KDEVPLATFORM_CODEGENERATOR_H #include #include "../duchain/topducontext.h" #include "../duchain/duchain.h" #include "language/interfaces/iastcontainer.h" -#include "util/debug.h" +#include namespace KDevelop { template class AstChangeSet; class DUContext; class DUChainChangeSet; class DocumentChangeSet; class DocumentRange; /** * \short Base class for generic code generators * * CodeGeneratorBase provides an api for a step-by-step process to * create and/or refactor code. * * This class should be used as a superclass only when du-chain level * changes are made, since this level knows nothing about the * language-specific AST. For more complex changes that require knowledge * about the language AST, use CodeGenerator * * \see Refactoring * \see CodeGenerator * \author Hamish Rodda */ class KDEVPLATFORMLANGUAGE_EXPORT CodeGeneratorBase { public: CodeGeneratorBase(); virtual ~CodeGeneratorBase(); enum State { Precondition, UserInput, Processing, Review, Finished }; /** * Check whether the preconditions of this generation are met at the given \a context and * \a position. * \returns true if conditions are met and the generator can progress, otherwise false if * the conditions are not met. Use setErrorText to specify the nature of the Error. */ virtual bool checkPreconditions(DUContext* context, const DocumentRange& position) = 0; /** * Gather information required from the user for this generator. * * \returns true if all of the information is retrieved, otherwise false, Use setErrorText * to specify the nature of the Error. */ virtual bool gatherInformation() = 0; /** * Do final condition checking and perform the code generation. * * \returns true if code generation was successful, false otherwise. Use setErrorText to * specify the nature of the Error. */ virtual bool process() = 0; const QString & errorText() const; // Implementation from kJob bool execute(); /** * @brief Indicates that this generation should not expect interaction with the user/ * Most probable scenarios are: Testing, and a generator that is being used by another one * @param context If not NULL, the custom context to use, instead of user selection * @param range If not NULL, the cursom range to use instead of user selection * @note If this function is called, then gather information will not be called. * Derived classes should provide an alternative way of setting up the generator. */ void autoGenerate(DUContext * context, const DocumentRange * range); /** * \return The Document Change set to add a single Change, it is more addicient than creating a local DocumentChangeSet and merging it */ DocumentChangeSet & documentChangeSet(); protected: /** * Generate text edits from duchain change set. * This generator now owns the changeset, and will delete it. * * You may call this method multiple times to edit different files. */ void addChangeSet(DUChainChangeSet* duChainChange); void addChangeSet(DocumentChangeSet & docChangeSet); /** * Accessor for KJob's KJob::setErrorText. */ void setErrorText(const QString & error); /** * Inform the derived class if this generation is being performed without user interaction */ bool autoGeneration() const; /** * Clean up all the change sets that this generator is in charge of */ void clearChangeSets(); private: class CodeGeneratorPrivate * const d; bool displayChanges(); }; /** * \brief Base class for Ast aware code generators * * This class provides convenience for adding AstChangeSet, storing * the IAstContainer from the TopDUContext, and in general managing * Code generators that manipulate the AST * * \see CodeGeneratorBase * \author Ramón Zarazúa */ template class CodeGenerator : public CodeGeneratorBase { public: ~CodeGenerator() { clearChangeSets(); } protected: /// Convenience definition of the TopAstNode that is contained by this AstContainer typedef typename AstContainer::TopAstNode TopAstNode; typedef AstChangeSet LanguageChangeSet; /** * Query an AST of a particular file */ TopAstNode * ast(const IndexedString & file) { return astContainer(file)->topAstNode(); } TopAstNode * ast(const TopDUContext & context) { return astContainer(context)->topAstNode(); } typename AstContainer::Ptr astContainer(const IndexedString & file) { if(!m_AstContainers.contains(file)) { qCDebug(LANGUAGE) << "Ast requested for: " << file.str(); TopDUContext * context = DUChain::self()->waitForUpdate(file, KDevelop::TopDUContext::AST).data(); Q_ASSERT(context); m_AstContainers[file] = AstContainer::Ptr::template staticCast( context->ast() ); } return m_AstContainers[file]; } typename AstContainer::Ptr astContainer(const TopDUContext & context) { return astContainer(context.url()); } /** * Generate text edits from duchain / ast change set. * * You may call this method multiple times to edit different files. */ void addChangeSet(DUChainChangeSet * duChainChange) { CodeGeneratorBase::addChangeSet(duChainChange); } void addChangeSet(DocumentChangeSet & doc) { CodeGeneratorBase::addChangeSet(doc); } /** * Generate text edits from duchain / ast change set. * * You may call this method multiple times to edit different files. */ void addChangeSet(LanguageChangeSet * astChange); void clearChangeSets() { CodeGeneratorBase::clearChangeSets(); } /** * Inform the derived class if this generation is being performed without user interaction */ bool autoGeneration() const { return CodeGeneratorBase::autoGeneration(); } /** * Accessor for KJob's KJob::setErrorText. */ void setErrorText(const QString & error) { CodeGeneratorBase::setErrorText(error); } private: typedef QMap AstContainerMap; AstContainerMap m_AstContainers; }; } #endif // KDEVPLATFORM_CODEGENERATOR_H diff --git a/language/codegen/documentchangeset.cpp b/language/codegen/documentchangeset.cpp index af05f2e72..36beb5548 100644 --- a/language/codegen/documentchangeset.cpp +++ b/language/codegen/documentchangeset.cpp @@ -1,593 +1,593 @@ /* Copyright 2008 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "documentchangeset.h" #include "coderepresentation.h" -#include "util/debug.h" +#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace KDevelop { typedef QList ChangesList; typedef QHash ChangesHash; struct DocumentChangeSetPrivate { DocumentChangeSet::ReplacementPolicy replacePolicy; DocumentChangeSet::FormatPolicy formatPolicy; DocumentChangeSet::DUChainUpdateHandling updatePolicy; DocumentChangeSet::ActivationPolicy activationPolicy; ChangesHash changes; QHash documentsRename; DocumentChangeSet::ChangeResult addChange(const DocumentChangePointer& change); DocumentChangeSet::ChangeResult replaceOldText(CodeRepresentation* repr, const QString& newText, const ChangesList& sortedChangesList); DocumentChangeSet::ChangeResult generateNewText(const IndexedString& file, ChangesList& sortedChanges, const CodeRepresentation* repr, QString& output); DocumentChangeSet::ChangeResult removeDuplicates(const IndexedString& file, ChangesList& filteredChanges); void formatChanges(); void updateFiles(); }; // Simple helpers to clear up code clutter namespace { inline bool changeIsValid(const DocumentChange& change, const QStringList& textLines) { return change.m_range.start() <= change.m_range.end() && change.m_range.end().line() < textLines.size() && change.m_range.start().line() >= 0 && change.m_range.start().column() >= 0 && change.m_range.start().column() <= textLines[change.m_range.start().line()].length() && change.m_range.end().column() >= 0 && change.m_range.end().column() <= textLines[change.m_range.end().line()].length(); } inline bool duplicateChanges(const DocumentChangePointer& previous, const DocumentChangePointer& current) { // Given the option of considering a duplicate two changes in the same range // but with different old texts to be ignored return previous->m_range == current->m_range && previous->m_newText == current->m_newText && (previous->m_oldText == current->m_oldText || (previous->m_ignoreOldText && current->m_ignoreOldText)); } inline QString rangeText(const KTextEditor::Range& range, const QStringList& textLines) { QStringList ret; ret.reserve(range.end().line() - range.start().line() + 1); for(int line = range.start().line(); line <= range.end().line(); ++line) { const QString lineText = textLines.at(line); int startColumn = 0; int endColumn = lineText.length(); if (line == range.start().line()) { startColumn = range.start().column(); } if (line == range.end().line()) { endColumn = range.end().column(); } ret << lineText.mid(startColumn, endColumn - startColumn); } return ret.join(QStringLiteral("\n")); } // need to have it as otherwise the arguments can exceed the maximum of 10 static QString printRange(const KTextEditor::Range& r) { return i18nc("text range line:column->line:column", "%1:%2->%3:%4", r.start().line(), r.start().column(), r.end().line(), r.end().column()); } } DocumentChangeSet::DocumentChangeSet() : d(new DocumentChangeSetPrivate) { d->replacePolicy = StopOnFailedChange; d->formatPolicy = AutoFormatChanges; d->updatePolicy = SimpleUpdate; d->activationPolicy = DoNotActivate; } DocumentChangeSet::DocumentChangeSet(const DocumentChangeSet& rhs) : d(new DocumentChangeSetPrivate(*rhs.d)) { } DocumentChangeSet& DocumentChangeSet::operator=(const DocumentChangeSet& rhs) { *d = *rhs.d; return *this; } DocumentChangeSet::~DocumentChangeSet() { delete d; } DocumentChangeSet::ChangeResult DocumentChangeSet::addChange(const DocumentChange& change) { return d->addChange(DocumentChangePointer(new DocumentChange(change))); } DocumentChangeSet::ChangeResult DocumentChangeSet::addChange(const DocumentChangePointer& change) { return d->addChange(change); } DocumentChangeSet::ChangeResult DocumentChangeSet::addDocumentRenameChange(const IndexedString& oldFile, const IndexedString& newname) { d->documentsRename.insert(oldFile, newname); return DocumentChangeSet::ChangeResult::successfulResult(); } DocumentChangeSet::ChangeResult DocumentChangeSetPrivate::addChange(const DocumentChangePointer& change) { changes[change->m_document].append(change); return DocumentChangeSet::ChangeResult::successfulResult(); } void DocumentChangeSet::setReplacementPolicy(DocumentChangeSet::ReplacementPolicy policy) { d->replacePolicy = policy; } void DocumentChangeSet::setFormatPolicy(DocumentChangeSet::FormatPolicy policy) { d->formatPolicy = policy; } void DocumentChangeSet::setUpdateHandling(DocumentChangeSet::DUChainUpdateHandling policy) { d->updatePolicy = policy; } void DocumentChangeSet::setActivationPolicy(DocumentChangeSet::ActivationPolicy policy) { d->activationPolicy = policy; } DocumentChangeSet::ChangeResult DocumentChangeSet::applyAllChanges() { QUrl oldActiveDoc; if (IDocument* activeDoc = ICore::self()->documentController()->activeDocument()) { oldActiveDoc = activeDoc->url(); } QList allFiles; foreach (IndexedString file, d->documentsRename.keys().toSet() + d->changes.keys().toSet()) { allFiles << file.toUrl(); } if (!KDevelop::ensureWritable(allFiles)) { return ChangeResult(QStringLiteral("some affected files are not writable")); } // rename files QHash::const_iterator it = d->documentsRename.constBegin(); for(; it != d->documentsRename.constEnd(); ++it) { QUrl url = it.key().toUrl(); IProject* p = ICore::self()->projectController()->findProjectForUrl(url); if(p) { QList files = p->filesForPath(it.key()); if(!files.isEmpty()) { ProjectBaseItem::RenameStatus renamed = files.first()->rename(it.value().str()); if(renamed == ProjectBaseItem::RenameOk) { const QUrl newUrl = Path(Path(url).parent(), it.value().str()).toUrl(); if (url == oldActiveDoc) { oldActiveDoc = newUrl; } IndexedString idxNewDoc(newUrl); // ensure changes operate on new file name ChangesHash::iterator iter = d->changes.find(it.key()); if (iter != d->changes.end()) { // copy changes ChangesList value = iter.value(); // remove old entry d->changes.erase(iter); // adapt to new url ChangesList::iterator itChange = value.begin(); ChangesList::iterator itEnd = value.end(); for(; itChange != itEnd; ++itChange) { (*itChange)->m_document = idxNewDoc; } d->changes[idxNewDoc] = value; } } else { ///FIXME: share code with project manager for the error code string representation return ChangeResult(i18n("Could not rename '%1' to '%2'", url.toDisplayString(QUrl::PreferLocalFile), it.value().str())); } } else { //TODO: do it outside the project management? qCWarning(LANGUAGE) << "tried to rename file not tracked by project - not implemented"; } } else { qCWarning(LANGUAGE) << "tried to rename a file outside of a project - not implemented"; } } QMap codeRepresentations; QMap newTexts; ChangesHash filteredSortedChanges; ChangeResult result = ChangeResult::successfulResult(); QList files(d->changes.keys()); foreach(const IndexedString &file, files) { CodeRepresentation::Ptr repr = createCodeRepresentation(file); if(!repr) { return ChangeResult(QStringLiteral("Could not create a Representation for %1").arg(file.str())); } codeRepresentations[file] = repr; QList& sortedChangesList(filteredSortedChanges[file]); { result = d->removeDuplicates(file, sortedChangesList); if(!result) return result; } { result = d->generateNewText(file, sortedChangesList, repr.data(), newTexts[file]); if(!result) return result; } } QMap oldTexts; //Apply the changes to the files foreach(const IndexedString &file, files) { oldTexts[file] = codeRepresentations[file]->text(); result = d->replaceOldText(codeRepresentations[file].data(), newTexts[file], filteredSortedChanges[file]); if(!result && d->replacePolicy == StopOnFailedChange) { //Revert all files foreach(const IndexedString &revertFile, oldTexts.keys()) { codeRepresentations[revertFile]->setText(oldTexts[revertFile]); } return result; } } d->updateFiles(); if(d->activationPolicy == Activate) { foreach(const IndexedString& file, files) { ICore::self()->documentController()->openDocument(file.toUrl()); } } // ensure the old document is still activated if (oldActiveDoc.isValid()) { ICore::self()->documentController()->openDocument(oldActiveDoc); } return result; } DocumentChangeSet::ChangeResult DocumentChangeSetPrivate::replaceOldText(CodeRepresentation* repr, const QString& newText, const ChangesList& sortedChangesList) { DynamicCodeRepresentation* dynamic = dynamic_cast(repr); if(dynamic) { auto transaction = dynamic->makeEditTransaction(); //Replay the changes one by one for(int pos = sortedChangesList.size()-1; pos >= 0; --pos) { const DocumentChange& change(*sortedChangesList[pos]); if(!dynamic->replace(change.m_range, change.m_oldText, change.m_newText, change.m_ignoreOldText)) { QString warningString = i18nc("Inconsistent change in between , found (encountered ) -> ", "Inconsistent change in %1 between %2, found %3 (encountered \"%4\") -> \"%5\"" , change.m_document.str() , printRange(change.m_range) , change.m_oldText , dynamic->rangeText(change.m_range) , change.m_newText); if(replacePolicy == DocumentChangeSet::WarnOnFailedChange) { qCWarning(LANGUAGE) << warningString; } else if(replacePolicy == DocumentChangeSet::StopOnFailedChange) { return DocumentChangeSet::ChangeResult(warningString); } //If set to ignore failed changes just continue with the others } } return DocumentChangeSet::ChangeResult::successfulResult(); } //For files on disk if (!repr->setText(newText)) { QString warningString = i18n("Could not replace text in the document: %1", sortedChangesList.begin()->data()->m_document.str()); if(replacePolicy == DocumentChangeSet::WarnOnFailedChange) { qCWarning(LANGUAGE) << warningString; } return DocumentChangeSet::ChangeResult(warningString); } return DocumentChangeSet::ChangeResult::successfulResult(); } DocumentChangeSet::ChangeResult DocumentChangeSetPrivate::generateNewText(const IndexedString & file, ChangesList& sortedChanges, const CodeRepresentation * repr, QString & output) { ISourceFormatter* formatter = nullptr; if(ICore::self()) { formatter = ICore::self()->sourceFormatterController()->formatterForUrl(file.toUrl()); } //Create the actual new modified file QStringList textLines = repr->text().split('\n'); QUrl url = file.toUrl(); QMimeType mime = QMimeDatabase().mimeTypeForUrl(url); QVector removedLines; for(int pos = sortedChanges.size()-1; pos >= 0; --pos) { DocumentChange& change(*sortedChanges[pos]); QString encountered; if(changeIsValid(change, textLines) && //We demand this, although it should be fixed ((encountered = rangeText(change.m_range, textLines)) == change.m_oldText || change.m_ignoreOldText)) { ///Problem: This does not work if the other changes significantly alter the context @todo Use the changed context QString leftContext = QStringList(textLines.mid(0, change.m_range.start().line()+1)).join(QStringLiteral("\n")); leftContext.chop(textLines[change.m_range.start().line()].length() - change.m_range.start().column()); QString rightContext = QStringList(textLines.mid(change.m_range.end().line())).join(QStringLiteral("\n")).mid(change.m_range.end().column()); if(formatter && (formatPolicy == DocumentChangeSet::AutoFormatChanges || formatPolicy == DocumentChangeSet::AutoFormatChangesKeepIndentation)) { QString oldNewText = change.m_newText; change.m_newText = formatter->formatSource(change.m_newText, url, mime, leftContext, rightContext); if(formatPolicy == DocumentChangeSet::AutoFormatChangesKeepIndentation) { // Reproduce the previous indentation QStringList oldLines = oldNewText.split('\n'); QStringList newLines = change.m_newText.split('\n'); if(oldLines.size() == newLines.size()) { for(int line = 0; line < newLines.size(); ++line) { // Keep the previous indentation QString oldIndentation; for (int a = 0; a < oldLines[line].size(); ++a) { if (oldLines[line][a].isSpace()) { oldIndentation.append(oldLines[line][a]); } else { break; } } int newIndentationLength = 0; for(int a = 0; a < newLines[line].size(); ++a) { if(newLines[line][a].isSpace()) { newIndentationLength = a; } else { break; } } newLines[line].replace(0, newIndentationLength, oldIndentation); } change.m_newText = newLines.join(QStringLiteral("\n")); } else { qCDebug(LANGUAGE) << "Cannot keep the indentation because the line count has changed" << oldNewText; } } } QString& line = textLines[change.m_range.start().line()]; if (change.m_range.start().line() == change.m_range.end().line()) { // simply replace existing line content line.replace(change.m_range.start().column(), change.m_range.end().column()-change.m_range.start().column(), change.m_newText); } else { // replace first line contents line.replace(change.m_range.start().column(), line.length() - change.m_range.start().column(), change.m_newText); // null other lines and remember for deletion for(int i = change.m_range.start().line() + 1; i <= change.m_range.end().line(); ++i) { textLines[i].clear(); removedLines << i; } } }else{ QString warningString = i18nc("Inconsistent change in at " " = (encountered ) -> ", "Inconsistent change in %1 at %2" " = \"%3\"(encountered \"%4\") -> \"%5\"" , file.str() , printRange(change.m_range) , change.m_oldText , encountered , change.m_newText); if(replacePolicy == DocumentChangeSet::IgnoreFailedChange) { //Just don't do the replacement } else if(replacePolicy == DocumentChangeSet::WarnOnFailedChange) { qCWarning(LANGUAGE) << warningString; } else { return DocumentChangeSet::ChangeResult(warningString, sortedChanges[pos]); } } } if (!removedLines.isEmpty()) { int offset = 0; std::sort(removedLines.begin(), removedLines.end()); foreach(int l, removedLines) { textLines.removeAt(l - offset); ++offset; } } output = textLines.join(QStringLiteral("\n")); return DocumentChangeSet::ChangeResult::successfulResult(); } //Removes all duplicate changes for a single file, and then returns (via filteredChanges) the filtered duplicates DocumentChangeSet::ChangeResult DocumentChangeSetPrivate::removeDuplicates(const IndexedString& file, ChangesList& filteredChanges) { typedef QMultiMap ChangesMap; ChangesMap sortedChanges; foreach(const DocumentChangePointer &change, changes[file]) { sortedChanges.insert(change->m_range.end(), change); } //Remove duplicates ChangesMap::iterator previous = sortedChanges.begin(); for(ChangesMap::iterator it = ++sortedChanges.begin(); it != sortedChanges.end(); ) { if(( *previous ) && ( *previous )->m_range.end() > (*it)->m_range.start()) { //intersection if(duplicateChanges(( *previous ), *it)) { //duplicate, remove one it = sortedChanges.erase(it); continue; } //When two changes contain each other, and the container change is set to ignore old text, then it should be safe to //just ignore the contained change, and apply the bigger change else if((*it)->m_range.contains(( *previous )->m_range) && (*it)->m_ignoreOldText ) { qCDebug(LANGUAGE) << "Removing change: " << ( *previous )->m_oldText << "->" << ( *previous )->m_newText << ", because it is contained by change: " << (*it)->m_oldText << "->" << (*it)->m_newText; sortedChanges.erase(previous); } //This case is for when both have the same end, either of them could be the containing range else if((*previous)->m_range.contains((*it)->m_range) && (*previous)->m_ignoreOldText ) { qCDebug(LANGUAGE) << "Removing change: " << (*it)->m_oldText << "->" << (*it)->m_newText << ", because it is contained by change: " << ( *previous )->m_oldText << "->" << ( *previous )->m_newText; it = sortedChanges.erase(it); continue; } else { return DocumentChangeSet::ChangeResult( i18nc("Inconsistent change-request at :" "intersecting changes: " " -> @ & " " -> @", "Inconsistent change-request at %1; " "intersecting changes: " "\"%2\"->\"%3\"@%4 & \"%5\"->\"%6\"@%7 " , file.str() , ( *previous )->m_oldText , ( *previous )->m_newText , printRange(( *previous )->m_range) , (*it)->m_oldText , (*it)->m_newText , printRange((*it)->m_range))); } } previous = it; ++it; } filteredChanges = sortedChanges.values(); return DocumentChangeSet::ChangeResult::successfulResult(); } void DocumentChangeSetPrivate::updateFiles() { ModificationRevisionSet::clearCache(); foreach(const IndexedString& file, changes.keys()) { ModificationRevision::clearModificationCache(file); } if(updatePolicy != DocumentChangeSet::NoUpdate && ICore::self()) { // The active document should be updated first, so that the user sees the results instantly if(IDocument* activeDoc = ICore::self()->documentController()->activeDocument()) { ICore::self()->languageController()->backgroundParser()->addDocument(IndexedString(activeDoc->url())); } // If there are currently open documents that now need an update, update them too foreach(const IndexedString& doc, ICore::self()->languageController()->backgroundParser()->managedDocuments()) { DUChainReadLocker lock(DUChain::lock()); TopDUContext* top = DUChainUtils::standardContextForUrl(doc.toUrl(), true); if((top && top->parsingEnvironmentFile() && top->parsingEnvironmentFile()->needsUpdate()) || !top) { lock.unlock(); ICore::self()->languageController()->backgroundParser()->addDocument(doc); } } // Eventually update _all_ affected files foreach(const IndexedString &file, changes.keys()) { if(!file.toUrl().isValid()) { qCWarning(LANGUAGE) << "Trying to apply changes to an invalid document"; continue; } ICore::self()->languageController()->backgroundParser()->addDocument(file); } } } } diff --git a/language/codegen/duchainchangeset.cpp b/language/codegen/duchainchangeset.cpp index cb15fff0f..f104d97fb 100644 --- a/language/codegen/duchainchangeset.cpp +++ b/language/codegen/duchainchangeset.cpp @@ -1,74 +1,74 @@ /* Copyright 2008 Ramón Zarazúa 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 "duchainchangeset.h" -#include "util/debug.h" +#include #include namespace KDevelop { //DUChainRef::DUChainRef(DUChainChangeSet* set, DUChainBase* object, bool newObject) : // m_changeSet(set), m_object(object), m_objectRef(0), m_newObject(newObject) //{ //} DUChainChangeSet::DUChainChangeSet(ReferencedTopDUContext topContext) : m_topContext(topContext) { } DUChainChangeSet::~DUChainChangeSet() { foreach(DUChainRef * reference, m_objectRefs) delete reference; } DUChainChangeSet & DUChainChangeSet::operator<<(DUChainChangeSet & rhs) { //Avoid merging into self if(this == &rhs) return *this; Q_ASSERT(m_topContext == rhs.m_topContext); qCDebug(LANGUAGE) << "Merging ChangeSets for context:" << m_topContext.data()->url().str(); m_objectRefs << rhs.m_objectRefs; rhs.m_objectRefs.clear(); #ifndef NDEBUG //check for possible duplicates std::sort(m_objectRefs.begin(), m_objectRefs.end()); for(QList::iterator i = m_objectRefs.begin(); i < m_objectRefs.end() - 1; ++i ) Q_ASSERT(*i != *(i + 1)); #endif return *this; } QList DUChainChangeSet::objectRefs() const { return m_objectRefs; } const ReferencedTopDUContext & DUChainChangeSet::topDuContext() const { return m_topContext; } } diff --git a/language/codegen/sourcefiletemplate.cpp b/language/codegen/sourcefiletemplate.cpp index 3d3ab6c1e..1e4aaac1b 100644 --- a/language/codegen/sourcefiletemplate.cpp +++ b/language/codegen/sourcefiletemplate.cpp @@ -1,348 +1,347 @@ /* * This file is part of KDevelop * Copyright 2012 Miha Čančula * * 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 "sourcefiletemplate.h" #include "templaterenderer.h" -#include "util/debug.h" +#include #include #include #include #include #include #include #include #include #include #include -#include using namespace KDevelop; typedef SourceFileTemplate::ConfigOption ConfigOption; class KDevelop::SourceFileTemplatePrivate { public: KArchive* archive; QString descriptionFileName; QStringList searchLocations; ConfigOption readEntry(const QDomElement& element, TemplateRenderer* renderer); }; ConfigOption SourceFileTemplatePrivate::readEntry(const QDomElement& element, TemplateRenderer* renderer) { ConfigOption entry; entry.name = element.attribute(QStringLiteral("name")); entry.type = element.attribute(QStringLiteral("type"), QStringLiteral("String")); bool isDefaultValueSet = false; for (QDomElement e = element.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) { QString tag = e.tagName(); if (tag == QLatin1String("label")) { entry.label = e.text(); } else if (tag == QLatin1String("tooltip")) { entry.label = e.text(); } else if (tag == QLatin1String("whatsthis")) { entry.label = e.text(); } else if ( tag == QLatin1String("min") ) { entry.minValue = e.text(); } else if ( tag == QLatin1String("max") ) { entry.maxValue = e.text(); } else if ( tag == QLatin1String("default") ) { entry.value = renderer->render(e.text(), entry.name); isDefaultValueSet = true; } else if (tag == QLatin1String("choices")) { QStringList values; QDomNodeList choices = element.elementsByTagName(QStringLiteral("choice")); for (int j = 0; j < choices.size(); ++j) { QDomElement choiceElement = choices.at(j).toElement(); values << choiceElement.attribute(QStringLiteral("name")); } Q_ASSERT(!values.isEmpty()); if (values.isEmpty()) { qCWarning(LANGUAGE) << "Entry " << entry.name << "has an enum without any choices"; } entry.values = values; } } qCDebug(LANGUAGE) << "Read entry" << entry.name << "with default value" << entry.value; // preset value for enum if needed if (!entry.values.isEmpty()) { if (isDefaultValueSet) { const bool isSaneDefaultValue = entry.values.contains(entry.value.toString()); Q_ASSERT(isSaneDefaultValue); if (!isSaneDefaultValue) { qCWarning(LANGUAGE) << "Default value" << entry.value << "not in enum" << entry.values; entry.value = entry.values.at(0); } } else { entry.value = entry.values.at(0); } } return entry; } SourceFileTemplate::SourceFileTemplate (const QString& templateDescription) : d(new KDevelop::SourceFileTemplatePrivate) { d->archive = nullptr; setTemplateDescription(templateDescription); } SourceFileTemplate::SourceFileTemplate() : d(new KDevelop::SourceFileTemplatePrivate) { d->archive = nullptr; } SourceFileTemplate::SourceFileTemplate (const SourceFileTemplate& other) : d(new KDevelop::SourceFileTemplatePrivate) { d->archive = nullptr; *this = other; } SourceFileTemplate::~SourceFileTemplate() { delete d->archive; delete d; } SourceFileTemplate& SourceFileTemplate::operator=(const SourceFileTemplate& other) { if (other.d == d) { return *this; } delete d->archive; if (other.d->archive) { if (other.d->archive->fileName().endsWith(QLatin1String(".zip"))) { d->archive = new KZip(other.d->archive->fileName()); } else { d->archive = new KTar(other.d->archive->fileName()); } d->archive->open(QIODevice::ReadOnly); } else { d->archive = nullptr; } d->descriptionFileName = other.d->descriptionFileName; return *this; } void SourceFileTemplate::setTemplateDescription(const QString& templateDescription) { delete d->archive; d->descriptionFileName = templateDescription; QString archiveFileName; const QString templateBaseName = QFileInfo(templateDescription).baseName(); d->searchLocations.append(QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("/kdevfiletemplates/templates/"), QStandardPaths::LocateDirectory)); foreach(const QString& dir, d->searchLocations) { foreach(const auto& entry, QDir(dir).entryInfoList(QDir::Files)) { if (entry.baseName() == templateBaseName) { archiveFileName = entry.absoluteFilePath(); qCDebug(LANGUAGE) << "Found template archive" << archiveFileName; break; } } } if (archiveFileName.isEmpty() || !QFileInfo::exists(archiveFileName)) { qCWarning(LANGUAGE) << "Could not find a template archive for description" << templateDescription << ", archive file" << archiveFileName; d->archive = nullptr; } else { QFileInfo info(archiveFileName); if (info.suffix() == QLatin1String("zip")) { d->archive = new KZip(archiveFileName); } else { d->archive = new KTar(archiveFileName); } d->archive->open(QIODevice::ReadOnly); } } bool SourceFileTemplate::isValid() const { return d->archive; } QString SourceFileTemplate::name() const { KConfig templateConfig(d->descriptionFileName); KConfigGroup cg(&templateConfig, "General"); return cg.readEntry("Name"); } QString SourceFileTemplate::type() const { KConfig templateConfig(d->descriptionFileName); KConfigGroup cg(&templateConfig, "General"); return cg.readEntry("Type", QString()); } QString SourceFileTemplate::languageName() const { KConfig templateConfig(d->descriptionFileName); KConfigGroup cg(&templateConfig, "General"); return cg.readEntry("Language", QString()); } QStringList SourceFileTemplate::category() const { KConfig templateConfig(d->descriptionFileName); KConfigGroup cg(&templateConfig, "General"); return cg.readEntry("Category", QStringList()); } QStringList SourceFileTemplate::defaultBaseClasses() const { KConfig templateConfig(d->descriptionFileName); KConfigGroup cg(&templateConfig, "General"); return cg.readEntry("BaseClasses", QStringList()); } const KArchiveDirectory* SourceFileTemplate::directory() const { Q_ASSERT(isValid()); return d->archive->directory(); } QList< SourceFileTemplate::OutputFile > SourceFileTemplate::outputFiles() const { QList outputFiles; KConfig templateConfig(d->descriptionFileName); KConfigGroup group(&templateConfig, "General"); QStringList files = group.readEntry("Files", QStringList()); qCDebug(LANGUAGE) << "Files in template" << files; foreach (const QString& fileGroup, files) { KConfigGroup cg(&templateConfig, fileGroup); OutputFile f; f.identifier = cg.name(); f.label = cg.readEntry("Name"); f.fileName = cg.readEntry("File"); f.outputName = cg.readEntry("OutputFile"); outputFiles << f; } return outputFiles; } bool SourceFileTemplate::hasCustomOptions() const { Q_ASSERT(isValid()); KConfig templateConfig(d->descriptionFileName); KConfigGroup cg(&templateConfig, "General"); bool hasOptions = d->archive->directory()->entries().contains(cg.readEntry("OptionsFile", "options.kcfg")); qCDebug(LANGUAGE) << cg.readEntry("OptionsFile", "options.kcfg") << hasOptions; return hasOptions; } QVector SourceFileTemplate::customOptions(TemplateRenderer* renderer) const { Q_ASSERT(isValid()); KConfig templateConfig(d->descriptionFileName); KConfigGroup cg(&templateConfig, "General"); const KArchiveEntry* entry = d->archive->directory()->entry(cg.readEntry("OptionsFile", "options.kcfg")); QVector optionGroups; if (!entry->isFile()) { return optionGroups; } const KArchiveFile* file = static_cast(entry); /* * Copied from kconfig_compiler.kcfg */ QDomDocument doc; QString errorMsg; int errorRow; int errorCol; if ( !doc.setContent( file->data(), &errorMsg, &errorRow, &errorCol ) ) { qCDebug(LANGUAGE) << "Unable to load document."; qCDebug(LANGUAGE) << "Parse error in line " << errorRow << ", col " << errorCol << ": " << errorMsg; return optionGroups; } QDomElement cfgElement = doc.documentElement(); if ( cfgElement.isNull() ) { qCDebug(LANGUAGE) << "No document in kcfg file"; return optionGroups; } QDomNodeList groups = cfgElement.elementsByTagName(QStringLiteral("group")); optionGroups.reserve(groups.size()); for (int i = 0; i < groups.size(); ++i) { QDomElement group = groups.at(i).toElement(); ConfigOptionGroup optionGroup; optionGroup.name = group.attribute(QStringLiteral("name")); QDomNodeList entries = group.elementsByTagName(QStringLiteral("entry")); optionGroup.options.reserve(entries.size()); for (int j = 0; j < entries.size(); ++j) { QDomElement entry = entries.at(j).toElement(); optionGroup.options << d->readEntry(entry, renderer); } optionGroups << optionGroup; } return optionGroups; } void SourceFileTemplate::addAdditionalSearchLocation(const QString& location) { if(!d->searchLocations.contains(location)) d->searchLocations.append(location); } diff --git a/language/codegen/templateclassgenerator.cpp b/language/codegen/templateclassgenerator.cpp index 2f469c3b9..362e01749 100644 --- a/language/codegen/templateclassgenerator.cpp +++ b/language/codegen/templateclassgenerator.cpp @@ -1,333 +1,333 @@ /* This file is part of KDevelop Copyright 2012 Miha Čančula 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 "templateclassgenerator.h" #include "archivetemplateloader.h" -#include "util/debug.h" +#include #include #include "language/codegen/documentchangeset.h" #include "codedescription.h" #include "templaterenderer.h" #include "sourcefiletemplate.h" #include #include #include #include #include #include using namespace KDevelop; /// @param base String such as 'public QObject' or 'QObject' InheritanceDescription descriptionFromString(const QString& base) { QStringList splitBase = base.split(' '); QString identifier = splitBase.takeLast(); QString inheritanceMode = splitBase.join(QStringLiteral(" ")); InheritanceDescription desc; desc.baseType = identifier; desc.inheritanceMode = inheritanceMode; return desc; } class KDevelop::TemplateClassGeneratorPrivate { public: SourceFileTemplate fileTemplate; QUrl baseUrl; TemplateRenderer renderer; QString name; QString identifier; QStringList namespaces; QString license; QHash fileUrls; QHash filePositions; ClassDescription description; QList directBaseClasses; QList allBaseClasses; void fetchSuperClasses(const DeclarationPointer& declaration); }; void TemplateClassGeneratorPrivate::fetchSuperClasses(const DeclarationPointer& declaration) { DUChainReadLocker lock; //Prevent duplicity if(allBaseClasses.contains(declaration)) { return; } allBaseClasses << declaration; DUContext* context = declaration->internalContext(); if (context) { foreach (const DUContext::Import& import, context->importedParentContexts()) { if (DUContext * parentContext = import.context(context->topContext())) { if (parentContext->type() == DUContext::Class) { fetchSuperClasses( DeclarationPointer(parentContext->owner()) ); } } } } } TemplateClassGenerator::TemplateClassGenerator(const QUrl& baseUrl) : d(new TemplateClassGeneratorPrivate) { Q_ASSERT(QFileInfo(baseUrl.toLocalFile()).isDir()); // assume folder d->baseUrl = baseUrl; d->renderer.setEmptyLinesPolicy(TemplateRenderer::TrimEmptyLines); } TemplateClassGenerator::~TemplateClassGenerator() { delete d; } void TemplateClassGenerator::setTemplateDescription(const SourceFileTemplate& fileTemplate) { d->fileTemplate = fileTemplate; Q_ASSERT(fileTemplate.isValid()); } DocumentChangeSet TemplateClassGenerator::generate() { return d->renderer.renderFileTemplate(d->fileTemplate, d->baseUrl, fileUrls()); } QHash TemplateClassGenerator::fileLabels() const { Q_ASSERT(d->fileTemplate.isValid()); QHash labels; foreach (const SourceFileTemplate::OutputFile& outputFile, d->fileTemplate.outputFiles()) { labels.insert(outputFile.identifier, outputFile.label); } return labels; } TemplateClassGenerator::UrlHash TemplateClassGenerator::fileUrls() const { if (d->fileUrls.isEmpty()) { foreach (const SourceFileTemplate::OutputFile& outputFile, d->fileTemplate.outputFiles()) { QString outputName = d->renderer.render(outputFile.outputName, outputFile.identifier); QUrl url = d->baseUrl.resolved(QUrl(outputName)); d->fileUrls.insert(outputFile.identifier, url); } } return d->fileUrls; } QUrl TemplateClassGenerator::baseUrl() const { return d->baseUrl; } QUrl TemplateClassGenerator::fileUrl(const QString& outputFile) const { return fileUrls().value(outputFile); } void TemplateClassGenerator::setFileUrl(const QString& outputFile, const QUrl& url) { d->fileUrls.insert(outputFile, url); d->renderer.addVariable("output_file_" + outputFile.toLower(), QDir(d->baseUrl.path()).relativeFilePath(url.path())); d->renderer.addVariable("output_file_" + outputFile.toLower() + "_absolute", url.toLocalFile()); } KTextEditor::Cursor TemplateClassGenerator::filePosition(const QString& outputFile) const { return d->filePositions.value(outputFile); } void TemplateClassGenerator::setFilePosition(const QString& outputFile, const KTextEditor::Cursor& position) { d->filePositions.insert(outputFile, position); } void TemplateClassGenerator::addVariables(const QVariantHash& variables) { d->renderer.addVariables(variables); } QString TemplateClassGenerator::renderString(const QString& text) const { return d->renderer.render(text); } SourceFileTemplate TemplateClassGenerator::sourceFileTemplate() const { return d->fileTemplate; } TemplateRenderer* TemplateClassGenerator::renderer() const { return &(d->renderer); } QString TemplateClassGenerator::name() const { return d->name; } void TemplateClassGenerator::setName(const QString& newName) { d->name = newName; d->renderer.addVariable(QStringLiteral("name"), newName); } QString TemplateClassGenerator::identifier() const { return name(); } void TemplateClassGenerator::setIdentifier(const QString& identifier) { d->renderer.addVariable(QStringLiteral("identifier"), identifier);; QStringList separators; separators << QStringLiteral("::") << QStringLiteral(".") << QStringLiteral(":") << QStringLiteral("\\") << QStringLiteral("/"); QStringList ns; foreach (const QString& separator, separators) { ns = identifier.split(separator); if (ns.size() > 1) { break; } } setName(ns.takeLast()); setNamespaces(ns); } QStringList TemplateClassGenerator::namespaces() const { return d->namespaces; } void TemplateClassGenerator::setNamespaces(const QStringList& namespaces) const { d->namespaces = namespaces; d->renderer.addVariable(QStringLiteral("namespaces"), namespaces); } /// Specify license for this class void TemplateClassGenerator::setLicense(const QString& license) { qCDebug(LANGUAGE) << "New Class: " << d->name << "Set license: " << d->license; d->license = license; d->renderer.addVariable(QStringLiteral("license"), license); } /// Get the license specified for this classes QString TemplateClassGenerator::license() const { return d->license; } void TemplateClassGenerator::setDescription(const ClassDescription& description) { d->description = description; QVariantHash variables; variables[QStringLiteral("description")] = QVariant::fromValue(description); variables[QStringLiteral("members")] = CodeDescription::toVariantList(description.members); variables[QStringLiteral("functions")] = CodeDescription::toVariantList(description.methods); variables[QStringLiteral("base_classes")] = CodeDescription::toVariantList(description.baseClasses); d->renderer.addVariables(variables); } ClassDescription TemplateClassGenerator::description() const { return d->description; } void TemplateClassGenerator::addBaseClass(const QString& base) { const InheritanceDescription desc = descriptionFromString(base); ClassDescription cd = description(); cd.baseClasses << desc; setDescription(cd); DUChainReadLocker lock; PersistentSymbolTable::Declarations decl = PersistentSymbolTable::self().getDeclarations(IndexedQualifiedIdentifier(QualifiedIdentifier(desc.baseType))); //Search for all super classes for(PersistentSymbolTable::Declarations::Iterator it = decl.iterator(); it; ++it) { DeclarationPointer declaration = DeclarationPointer(it->declaration()); if(declaration->isForwardDeclaration()) { continue; } // Check if it's a class/struct/etc if(declaration->type()) { d->fetchSuperClasses(declaration); d->directBaseClasses << declaration; break; } } } void TemplateClassGenerator::setBaseClasses(const QList& bases) { // clear ClassDescription cd = description(); cd.baseClasses.clear(); setDescription(cd); d->directBaseClasses.clear(); d->allBaseClasses.clear(); // add all bases foreach (const QString& base, bases) { addBaseClass(base); } } QList< DeclarationPointer > TemplateClassGenerator::directBaseClasses() const { return d->directBaseClasses; } QList< DeclarationPointer > TemplateClassGenerator::allBaseClasses() const { return d->allBaseClasses; } diff --git a/language/codegen/templatepreviewicon.cpp b/language/codegen/templatepreviewicon.cpp index ec2fcc394..64c37ec6b 100644 --- a/language/codegen/templatepreviewicon.cpp +++ b/language/codegen/templatepreviewicon.cpp @@ -1,115 +1,115 @@ /* This file is part of KDevelop Copyright 2017 Friedrich W. H. Kossebau 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 "templatepreviewicon.h" -#include "util/debug.h" +#include #include #include #include #include #include #include #include using namespace KDevelop; class KDevelop::TemplatePreviewIconData : public QSharedData { public: QString iconName; QString archivePath; QString dataDir; }; TemplatePreviewIcon::TemplatePreviewIcon(const QString& iconName, const QString& archivePath, const QString& dataDir) : d(new TemplatePreviewIconData) { d->iconName = iconName; d->archivePath = archivePath; d->dataDir = dataDir; } TemplatePreviewIcon::TemplatePreviewIcon() : d(new TemplatePreviewIconData) { } TemplatePreviewIcon::TemplatePreviewIcon(const TemplatePreviewIcon& other) : d(other.d) { } TemplatePreviewIcon::~TemplatePreviewIcon() = default; TemplatePreviewIcon& TemplatePreviewIcon::operator=(const TemplatePreviewIcon& other) { if (this != &other) { d = other.d; } return *this; } QPixmap TemplatePreviewIcon::pixmap() const { if (!d->iconName.isEmpty()) { // read icon from archive QScopedPointer templateArchive; if (QFileInfo(d->archivePath).completeSuffix() == QLatin1String("zip")) { templateArchive.reset(new KZip(d->archivePath)); } else { templateArchive.reset(new KTar(d->archivePath)); } if (templateArchive->open(QIODevice::ReadOnly)) { const KArchiveFile* iconFile = templateArchive->directory()->file(d->iconName); if (iconFile) { const auto data = iconFile->data(); QPixmap pixmap; const bool loadSuccess = pixmap.loadFromData(iconFile->data()); if (loadSuccess) { return pixmap; } qCWarning(LANGUAGE) << "Could not load preview icon" << d->iconName << "from" << d->archivePath; } } // support legacy templates with image files installed separately in the filesystem const QString iconFilePath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, d->dataDir + d->iconName); if (!iconFilePath.isEmpty()) { QPixmap pixmap(iconFilePath); if (!pixmap.isNull()) { return pixmap; } qCWarning(LANGUAGE) << "Could not load preview icon" << iconFilePath << "as wanted for" << d->archivePath; } } // try theme icon or default to a kdevelop icon // QIcon::hasThemeIcon for empty string can yield true with some engines, not wanted here const bool isThemeIcon = (!d->iconName.isEmpty() && QIcon::hasThemeIcon(d->iconName)); const QString iconName = isThemeIcon ? d->iconName : QStringLiteral("kdevelop"); const QIcon icon = QIcon::fromTheme(iconName); return icon.pixmap(128, 128); } diff --git a/language/codegen/templaterenderer.cpp b/language/codegen/templaterenderer.cpp index 76c04ca1d..e8f3e07ff 100644 --- a/language/codegen/templaterenderer.cpp +++ b/language/codegen/templaterenderer.cpp @@ -1,310 +1,309 @@ /* * This file is part of KDevelop * Copyright 2012 Miha Čančula * * 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 "templaterenderer.h" #include "documentchangeset.h" #include "sourcefiletemplate.h" #include "templateengine.h" #include "templateengine_p.h" #include "archivetemplateloader.h" -#include "util/debug.h" +#include #include #include -#include #include #include #include #include using namespace Grantlee; class NoEscapeStream : public OutputStream { public: NoEscapeStream(); explicit NoEscapeStream (QTextStream* stream); QString escape (const QString& input) const override; QSharedPointer< OutputStream > clone (QTextStream* stream) const override; }; NoEscapeStream::NoEscapeStream() : OutputStream() { } NoEscapeStream::NoEscapeStream(QTextStream* stream) : OutputStream (stream) { } QString NoEscapeStream::escape(const QString& input) const { return input; } QSharedPointer NoEscapeStream::clone(QTextStream* stream) const { QSharedPointer clonedStream = QSharedPointer( new NoEscapeStream( stream ) ); return clonedStream; } using namespace KDevelop; namespace KDevelop { class TemplateRendererPrivate { public: Engine* engine; Grantlee::Context context; TemplateRenderer::EmptyLinesPolicy emptyLinesPolicy; QString errorString; }; } TemplateRenderer::TemplateRenderer() : d(new TemplateRendererPrivate) { d->engine = &TemplateEngine::self()->d->engine; d->emptyLinesPolicy = KeepEmptyLines; } TemplateRenderer::~TemplateRenderer() { delete d; } void TemplateRenderer::addVariables(const QVariantHash& variables) { QVariantHash::const_iterator it = variables.constBegin(); QVariantHash::const_iterator end = variables.constEnd(); for (; it != end; ++it) { d->context.insert(it.key(), it.value()); } } void TemplateRenderer::addVariable(const QString& name, const QVariant& value) { d->context.insert(name, value); } QVariantHash TemplateRenderer::variables() const { return d->context.stackHash(0); } QString TemplateRenderer::render(const QString& content, const QString& name) const { Template t = d->engine->newTemplate(content, name); QString output; QTextStream textStream(&output); NoEscapeStream stream(&textStream); t->render(&stream, &d->context); if (t->error() != Grantlee::NoError) { d->errorString = t->errorString(); } else { d->errorString.clear(); } if (d->emptyLinesPolicy == TrimEmptyLines && output.contains('\n')) { QStringList lines = output.split('\n', QString::KeepEmptyParts); QMutableStringListIterator it(lines); // Remove empty lines from the start of the document while (it.hasNext()) { if (it.next().trimmed().isEmpty()) { it.remove(); } else { break; } } // Remove single empty lines it.toFront(); bool prePreviousEmpty = false; bool previousEmpty = false; while (it.hasNext()) { bool currentEmpty = it.peekNext().trimmed().isEmpty(); if (!prePreviousEmpty && previousEmpty && !currentEmpty) { it.remove(); } prePreviousEmpty = previousEmpty; previousEmpty = currentEmpty; it.next(); } // Compress multiple empty lines it.toFront(); previousEmpty = false; while (it.hasNext()) { bool currentEmpty = it.next().trimmed().isEmpty(); if (currentEmpty && previousEmpty) { it.remove(); } previousEmpty = currentEmpty; } // Remove empty lines from the end it.toBack(); while (it.hasPrevious()) { if (it.previous().trimmed().isEmpty()) { it.remove(); } else { break; } } // Add a newline to the end of file it.toBack(); it.insert(QString()); output = lines.join(QStringLiteral("\n")); } else if (d->emptyLinesPolicy == RemoveEmptyLines) { QStringList lines = output.split('\n', QString::SkipEmptyParts); QMutableStringListIterator it(lines); while (it.hasNext()) { if (it.next().trimmed().isEmpty()) { it.remove(); } } it.toBack(); if (lines.size() > 1) { it.insert(QString()); } output = lines.join(QStringLiteral("\n")); } return output; } QString TemplateRenderer::renderFile(const QUrl& url, const QString& name) const { QFile file(url.toLocalFile()); file.open(QIODevice::ReadOnly); QString content(file.readAll()); qCDebug(LANGUAGE) << content; return render(content, name); } QStringList TemplateRenderer::render(const QStringList& contents) const { qCDebug(LANGUAGE) << d->context.stackHash(0); QStringList ret; foreach (const QString& content, contents) { ret << render(content); } return ret; } void TemplateRenderer::setEmptyLinesPolicy(TemplateRenderer::EmptyLinesPolicy policy) { d->emptyLinesPolicy = policy; } TemplateRenderer::EmptyLinesPolicy TemplateRenderer::emptyLinesPolicy() const { return d->emptyLinesPolicy; } DocumentChangeSet TemplateRenderer::renderFileTemplate(const SourceFileTemplate& fileTemplate, const QUrl& baseUrl, const QHash& fileUrls) { DocumentChangeSet changes; const QDir baseDir(baseUrl.path()); QRegExp nonAlphaNumeric("\\W"); for (QHash::const_iterator it = fileUrls.constBegin(); it != fileUrls.constEnd(); ++it) { QString cleanName = it.key().toLower(); cleanName.replace(nonAlphaNumeric, QStringLiteral("_")); const QString path = it.value().toLocalFile(); addVariable("output_file_" + cleanName, baseDir.relativeFilePath(path)); addVariable("output_file_" + cleanName + "_absolute", path); } const KArchiveDirectory* directory = fileTemplate.directory(); ArchiveTemplateLocation location(directory); foreach (const SourceFileTemplate::OutputFile& outputFile, fileTemplate.outputFiles()) { const KArchiveEntry* entry = directory->entry(outputFile.fileName); if (!entry) { qCWarning(LANGUAGE) << "Entry" << outputFile.fileName << "is mentioned in group" << outputFile.identifier << "but is not present in the archive"; continue; } const KArchiveFile* file = dynamic_cast(entry); if (!file) { qCWarning(LANGUAGE) << "Entry" << entry->name() << "is not a file"; continue; } QUrl url = fileUrls[outputFile.identifier]; IndexedString document(url); KTextEditor::Range range(KTextEditor::Cursor(0, 0), 0); DocumentChange change(document, range, QString(), render(file->data(), outputFile.identifier)); changes.addChange(change); qCDebug(LANGUAGE) << "Added change for file" << document.str(); } return changes; } QString TemplateRenderer::errorString() const { return d->errorString; } diff --git a/language/codegen/templatesmodel.cpp b/language/codegen/templatesmodel.cpp index af19c42a1..a8753e291 100644 --- a/language/codegen/templatesmodel.cpp +++ b/language/codegen/templatesmodel.cpp @@ -1,411 +1,411 @@ /* This file is part of KDevelop Copyright 2007 Alexander Dymo Copyright 2012 Miha Čančula 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 "templatesmodel.h" #include "templatepreviewicon.h" -#include "util/debug.h" +#include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; class KDevelop::TemplatesModelPrivate { public: explicit TemplatesModelPrivate(const QString& typePrefix); QString typePrefix; QStringList searchPaths; QMap templateItems; /** * Extracts description files from all available template archives and saves them to a location * determined by descriptionResourceSuffix(). **/ void extractTemplateDescriptions(); /** * Creates a model item for the template @p name in category @p category * * @param name the name of the new template * @param category the category of the new template * @param parent the parent item * @return the created item **/ QStandardItem *createItem(const QString& name, const QString& category, QStandardItem* parent); enum ResourceType { Description, Template, Preview }; QString resourceFilter(ResourceType type, const QString &suffix = QString()) { QString filter = typePrefix; switch(type) { case Description: filter += QLatin1String("template_descriptions/"); break; case Template: filter += QLatin1String("templates/"); break; case Preview: filter += QLatin1String("template_previews/"); break; } return filter + suffix; } }; TemplatesModelPrivate::TemplatesModelPrivate(const QString& _typePrefix) : typePrefix(_typePrefix) { if (!typePrefix.endsWith('/')) { typePrefix.append('/'); } } TemplatesModel::TemplatesModel(const QString& typePrefix, QObject* parent) : QStandardItemModel(parent) , d(new TemplatesModelPrivate(typePrefix)) { const QStringList dataPaths = {QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)}; foreach(const QString& dataPath, dataPaths) { addDataPath(dataPath); } } TemplatesModel::~TemplatesModel() { delete d; } void TemplatesModel::refresh() { clear(); d->templateItems.clear(); d->templateItems[QString()] = invisibleRootItem(); d->extractTemplateDescriptions(); QStringList templateArchives; foreach(const QString& archivePath, d->searchPaths) { const QStringList files = QDir(archivePath).entryList(QDir::Files); foreach(const QString& file, files) { templateArchives.append(archivePath + file); } } QStringList templateDescriptions; const QStringList templatePaths = {QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) +'/'+ d->resourceFilter(TemplatesModelPrivate::Description)}; foreach(const QString& templateDescription, templatePaths) { const QStringList files = QDir(templateDescription).entryList(QDir::Files); foreach(const QString& file, files) { templateDescriptions.append(templateDescription + file); } } foreach (const QString &templateDescription, templateDescriptions) { QFileInfo fi(templateDescription); bool archiveFound = false; foreach( const QString& templateArchive, templateArchives ) { if( QFileInfo(templateArchive).baseName() == fi.baseName() ) { archiveFound = true; KConfig templateConfig(templateDescription); KConfigGroup general(&templateConfig, "General"); QString name = general.readEntry("Name"); QString category = general.readEntry("Category"); QString comment = general.readEntry("Comment"); TemplatePreviewIcon icon(general.readEntry("Icon"), templateArchive, d->resourceFilter(TemplatesModelPrivate::Preview)); QStandardItem *templateItem = d->createItem(name, category, invisibleRootItem()); templateItem->setData(templateDescription, DescriptionFileRole); templateItem->setData(templateArchive, ArchiveFileRole); templateItem->setData(comment, CommentRole); templateItem->setData(QVariant::fromValue(icon), PreviewIconRole); } } if (!archiveFound) { // Template file doesn't exist anymore, so remove the description // saves us the extra lookups for templateExists on the next run QFile(templateDescription).remove(); } } } QStandardItem *TemplatesModelPrivate::createItem(const QString& name, const QString& category, QStandardItem* parent) { QStringList path = category.split('/'); QStringList currentPath; foreach (const QString& entry, path) { currentPath << entry; if (!templateItems.contains(currentPath.join(QLatin1Char('/')))) { QStandardItem *item = new QStandardItem(entry); item->setEditable(false); parent->appendRow(item); templateItems[currentPath.join(QLatin1Char('/'))] = item; parent = item; } else { parent = templateItems[currentPath.join(QLatin1Char('/'))]; } } QStandardItem *templateItem = new QStandardItem(name); templateItem->setEditable(false); parent->appendRow(templateItem); return templateItem; } void TemplatesModelPrivate::extractTemplateDescriptions() { QStringList templateArchives; searchPaths << QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, resourceFilter(Template), QStandardPaths::LocateDirectory); searchPaths.removeDuplicates(); foreach(const QString &archivePath, searchPaths) { const QStringList files = QDir(archivePath).entryList(QDir::Files); foreach(const QString& file, files) { if(file.endsWith(QLatin1String(".zip")) || file.endsWith(QLatin1String(".tar.bz2"))) { QString archfile = archivePath + file; templateArchives.append(archfile); } } } QString localDescriptionsDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) +'/'+ resourceFilter(Description); QDir dir(localDescriptionsDir); if(!dir.exists()) dir.mkpath(QStringLiteral(".")); foreach (const QString &archName, templateArchives) { qCDebug(LANGUAGE) << "processing template" << archName; QScopedPointer templateArchive; if (QFileInfo(archName).completeSuffix() == QLatin1String("zip")) { templateArchive.reset(new KZip(archName)); } else { templateArchive.reset(new KTar(archName)); } if (templateArchive->open(QIODevice::ReadOnly)) { /* * This class looks for template description files in the following order * * - "basename.kdevtemplate" * - "*.kdevtemplate" * - "basename.desktop" * - "*.desktop" * * This is done because application templates can contain .desktop files used by the application * so the kdevtemplate suffix must have priority. */ QFileInfo templateInfo(archName); const KArchiveEntry *templateEntry = templateArchive->directory()->entry(templateInfo.baseName() + ".kdevtemplate"); if (!templateEntry || !templateEntry->isFile()) { /* * First, if the .kdevtemplate file is not found by name, * we check all the files in the archive for any .kdevtemplate file * * This is needed because kde-files.org renames downloaded files */ foreach (const QString& entryName, templateArchive->directory()->entries()) { if (entryName.endsWith(QLatin1String(".kdevtemplate"))) { templateEntry = templateArchive->directory()->entry(entryName); break; } } } if (!templateEntry || !templateEntry->isFile()) { templateEntry = templateArchive->directory()->entry(templateInfo.baseName() + ".desktop"); } if (!templateEntry || !templateEntry->isFile()) { foreach (const QString& entryName, templateArchive->directory()->entries()) { if (entryName.endsWith(QLatin1String(".desktop"))) { templateEntry = templateArchive->directory()->entry(entryName); break; } } } if (!templateEntry || !templateEntry->isFile()) { qCDebug(LANGUAGE) << "template" << archName << "does not contain .kdevtemplate or .desktop file"; continue; } const KArchiveFile *templateFile = static_cast(templateEntry); qCDebug(LANGUAGE) << "copy template description to" << localDescriptionsDir; templateFile->copyTo(localDescriptionsDir); /* * Rename the extracted description * so that its basename matches the basename of the template archive */ QFileInfo descriptionInfo(localDescriptionsDir + templateEntry->name()); QString destinationName = localDescriptionsDir + templateInfo.baseName() + '.' + descriptionInfo.suffix(); QFile::rename(descriptionInfo.absoluteFilePath(), destinationName); } else { qCWarning(LANGUAGE) << "could not open template" << archName; } } } QModelIndexList TemplatesModel::templateIndexes(const QString& fileName) const { QFileInfo info(fileName); QString description = QStandardPaths::locate(QStandardPaths::GenericDataLocation, d->resourceFilter(TemplatesModelPrivate::Description, info.baseName() + ".kdevtemplate")); if (description.isEmpty()) { description = QStandardPaths::locate(QStandardPaths::GenericDataLocation, d->resourceFilter(TemplatesModelPrivate::Description, info.baseName() + ".desktop")); } QModelIndexList indexes; if (!description.isEmpty()) { KConfig templateConfig(description); KConfigGroup general(&templateConfig, "General"); QStringList categories = general.readEntry("Category").split('/'); QStringList levels; foreach (const QString& category, categories) { levels << category; indexes << d->templateItems[levels.join(QString('/'))]->index(); } if (!indexes.isEmpty()) { QString name = general.readEntry("Name"); QStandardItem* categoryItem = d->templateItems[levels.join(QString('/'))]; for (int i = 0; i < categoryItem->rowCount(); ++i) { QStandardItem* templateItem = categoryItem->child(i); if (templateItem->text() == name) { indexes << templateItem->index(); break; } } } } return indexes; } QString TemplatesModel::typePrefix() const { return d->typePrefix; } void TemplatesModel::addDataPath(const QString& path) { QString realpath = path + d->resourceFilter(TemplatesModelPrivate::Template); d->searchPaths.append(realpath); } QString TemplatesModel::loadTemplateFile(const QString& fileName) { QString saveLocation = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) +'/'+ d->resourceFilter(TemplatesModelPrivate::Template); QDir dir(saveLocation); if(!dir.exists()) dir.mkpath(QStringLiteral(".")); QFileInfo info(fileName); QString destination = saveLocation + info.baseName(); QMimeType mimeType = QMimeDatabase().mimeTypeForFile(fileName); qCDebug(LANGUAGE) << "Loaded file" << fileName << "with type" << mimeType.name(); if (mimeType.name() == QLatin1String("application/x-desktop")) { qCDebug(LANGUAGE) << "Loaded desktop file" << info.absoluteFilePath() << ", compressing"; #ifdef Q_WS_WIN destination += ".zip"; KZip archive(destination); #else destination += QLatin1String(".tar.bz2"); KTar archive(destination, QStringLiteral("application/x-bzip")); #endif //Q_WS_WIN archive.open(QIODevice::WriteOnly); QDir dir(info.absoluteDir()); QDir::Filters filter = QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot; foreach (const QFileInfo& entry, dir.entryInfoList(filter)) { if (entry.isFile()) { archive.addLocalFile(entry.absoluteFilePath(), entry.fileName()); } else if (entry.isDir()) { archive.addLocalDirectory(entry.absoluteFilePath(), entry.fileName()); } } archive.close(); } else { qCDebug(LANGUAGE) << "Copying" << fileName << "to" << saveLocation; QFile::copy(fileName, saveLocation + info.fileName()); } refresh(); return destination; } diff --git a/language/codegen/utilities.cpp b/language/codegen/utilities.cpp index 13cc35bc8..7380a9389 100644 --- a/language/codegen/utilities.cpp +++ b/language/codegen/utilities.cpp @@ -1,131 +1,131 @@ /* Copyright 2009 Ramón Zarazúa 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 "utilities.h" -#include "util/debug.h" +#include #include #include #include #include #include #include #include #include #include namespace KDevelop { namespace CodeGenUtils { IdentifierValidator::IdentifierValidator( DUContext * context) : QValidator(nullptr), m_context(context) { } IdentifierValidator::~IdentifierValidator() { } QValidator::State IdentifierValidator::validate (QString & input, int &) const { //I can't figure out why it wouln't compile when I tried to use Identifier identifier(); Identifier identifier = Identifier(IndexedString(input)); if(identifier.isUnique()) return Acceptable; DUChainReadLocker lock(DUChain::lock(), 10); return m_context->findLocalDeclarations(identifier, CursorInRevision::invalid(), nullptr, AbstractType::Ptr(), DUContext::NoFiltering).empty() ? Acceptable : Invalid; } IndexedString fetchImplementationFileForClass(const Declaration & targetClass) { DUChainReadLocker lock(DUChain::lock()); qCDebug(LANGUAGE) << "Looking for implementation file for class:" << targetClass.identifier().toString(); DUContext * context = targetClass.internalContext(); //If this declaration is not a user defined type, then ignore and return empty file if(targetClass.kind() != Declaration::Type) return IndexedString(); //If this is a forward declaration attempt to resolve it. const Declaration * realClass = &targetClass; if(const ForwardDeclaration * forward = dynamic_cast(realClass)) { if(!(realClass = forward->resolve(context->topContext()))) return IndexedString(); context = realClass->internalContext(); } QVector declarations = context->localDeclarations(); QMap implementationsInFile; foreach(Declaration * decl, declarations) { ///@todo check for static variable instantiation as well if(ClassFunctionDeclaration * classFun = dynamic_cast(decl)) if(FunctionDefinition * def = FunctionDefinition::definition(classFun)) { qCDebug(LANGUAGE) << "Definition For declaration in:" << def->url().toUrl(); ++implementationsInFile[def->url()]; } } QMultiMap sorter; foreach(const IndexedString& file, implementationsInFile.keys()) sorter.insert(implementationsInFile[file], file); QList sortedFiles = sorter.values(); //If there are no methods, then just return the file the declaration is in if(sortedFiles.empty()) return context->url(); if(sortedFiles.size() == 1) return sortedFiles[0]; const QList tiedFiles = sorter.values(sorter.end().key()); if(tiedFiles.size() > 1) { //Return the file that has the most uses QMap > uses = realClass->uses(); IndexedString mostUsesFile; unsigned int mostUses = 0; foreach(const IndexedString& currentFile, tiedFiles) if(static_cast(uses[currentFile].size()) > mostUses) { mostUses = uses[currentFile].size(); mostUsesFile = currentFile; } return mostUsesFile; } else return sortedFiles.back(); } } } diff --git a/language/duchain/classdeclaration.cpp b/language/duchain/classdeclaration.cpp index 881f1931e..04e735af7 100644 --- a/language/duchain/classdeclaration.cpp +++ b/language/duchain/classdeclaration.cpp @@ -1,198 +1,198 @@ /* This file is part of KDevelop Copyright 2007 David Nolden Copyright 2009 Lior Mualem 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 "classdeclaration.h" #include "identifier.h" #include #include #include #include "types/structuretype.h" -#include "util/debug.h" +#include namespace KDevelop { DEFINE_LIST_MEMBER_HASH(ClassDeclarationData, baseClasses, BaseClassInstance) ClassDeclaration::ClassDeclaration(const KDevelop::RangeInRevision& range, DUContext* context) : ClassMemberDeclaration(*new ClassDeclarationData, range) { d_func_dynamic()->setClassId(this); setContext(context); } ClassDeclaration::ClassDeclaration( ClassDeclarationData& data, const KDevelop::RangeInRevision& range, DUContext* context ) : ClassMemberDeclaration( data, range ) { setContext(context); } ClassDeclaration::ClassDeclaration(ClassDeclarationData& data) : ClassMemberDeclaration(data) { } REGISTER_DUCHAIN_ITEM(ClassDeclaration); void ClassDeclaration::clearBaseClasses() { d_func_dynamic()->baseClassesList().clear(); } uint ClassDeclaration::baseClassesSize() const { return d_func()->baseClassesSize(); } const BaseClassInstance* ClassDeclaration::baseClasses() const { return d_func()->baseClasses(); } void ClassDeclaration::addBaseClass(const BaseClassInstance& klass) { d_func_dynamic()->baseClassesList().append(klass); } void ClassDeclaration::replaceBaseClass(uint n, const BaseClassInstance& klass) { Q_ASSERT(n <= d_func()->baseClassesSize()); d_func_dynamic()->baseClassesList()[n] = klass; } ClassDeclaration::~ClassDeclaration() { } ClassDeclaration::ClassDeclaration(const ClassDeclaration& rhs) : ClassMemberDeclaration(*new ClassDeclarationData(*rhs.d_func())) { d_func_dynamic()->setClassId(this); } Declaration* ClassDeclaration::clonePrivate() const { return new ClassDeclaration(*this); } namespace { bool isPublicBaseClassInternal( const ClassDeclaration* self, ClassDeclaration* base, const KDevelop::TopDUContext* topContext, int* baseConversionLevels, int depth, QSet* checked ) { if(checked) { if(checked->contains(self)) return false; checked->insert(self); } else if(depth > 3) { //Too much depth, to prevent endless recursion, we control the recursion using the 'checked' set QSet checkedSet; return isPublicBaseClassInternal(self, base, topContext, baseConversionLevels, depth, &checkedSet); } if( baseConversionLevels ) *baseConversionLevels = 0; if( self->indexedType() == base->indexedType() ) return true; FOREACH_FUNCTION(const BaseClassInstance& b, self->baseClasses) { if( baseConversionLevels ) ++ (*baseConversionLevels); //qCDebug(LANGUAGE) << "public base of" << c->toString() << "is" << b.baseClass->toString(); if( b.access != KDevelop::Declaration::Private ) { int nextBaseConversion = 0; if( StructureType::Ptr c = b.baseClass.type() ) { ClassDeclaration* decl = dynamic_cast(c->declaration(topContext)); if( decl && isPublicBaseClassInternal( decl, base, topContext, &nextBaseConversion, depth+1, checked ) ) { if ( baseConversionLevels ) *baseConversionLevels += nextBaseConversion; return true; } } } if( baseConversionLevels ) -- (*baseConversionLevels); } return false; } } bool ClassDeclaration::isPublicBaseClass( ClassDeclaration* base, const KDevelop::TopDUContext* topContext, int* baseConversionLevels ) const { return isPublicBaseClassInternal( this, base, topContext, baseConversionLevels, 0, nullptr ); } QString ClassDeclaration::toString() const { QString ret; switch ( classModifier() ) { case ClassDeclarationData::None: //nothing break; case ClassDeclarationData::Abstract: ret += QLatin1String("abstract "); break; case ClassDeclarationData::Final: ret += QLatin1String("final "); break; } switch ( classType() ) { case ClassDeclarationData::Class: ret += QLatin1String("class "); break; case ClassDeclarationData::Interface: ret += QLatin1String("interface "); break; case ClassDeclarationData::Trait: ret += QLatin1String("trait "); break; case ClassDeclarationData::Union: ret += QLatin1String("union "); break; case ClassDeclarationData::Struct: ret += QLatin1String("struct "); break; } return ret + identifier().toString(); } ClassDeclarationData::ClassType ClassDeclaration::classType() const { return d_func()->m_classType; } void ClassDeclaration::setClassType(ClassDeclarationData::ClassType type) { d_func_dynamic()->m_classType = type; } ClassDeclarationData::ClassModifier ClassDeclaration::classModifier() const { return d_func()->m_classModifier; } void ClassDeclaration::setClassModifier(ClassDeclarationData::ClassModifier modifier) { d_func_dynamic()->m_classModifier = modifier; } } diff --git a/language/duchain/classfunctiondeclaration.cpp b/language/duchain/classfunctiondeclaration.cpp index 4c52afadc..d7676cdf2 100644 --- a/language/duchain/classfunctiondeclaration.cpp +++ b/language/duchain/classfunctiondeclaration.cpp @@ -1,220 +1,220 @@ /* This is part of KDevelop Copyright 2002-2005 Roberto Raggi Copyright 2006 Adam Treat Copyright 2006 Hamish Rodda Copyright 2007-2008 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "classfunctiondeclaration.h" #include "ducontext.h" #include "types/functiontype.h" #include "duchainregister.h" -#include "util/debug.h" +#include namespace KDevelop { static Identifier& conversionIdentifier() { static Identifier conversionIdentifierObject(QStringLiteral("operator{...cast...}")); return conversionIdentifierObject; } REGISTER_DUCHAIN_ITEM(ClassFunctionDeclaration); ClassFunctionDeclaration::ClassFunctionDeclaration(const ClassFunctionDeclaration& rhs) : ClassFunctionDeclarationBase(*new ClassFunctionDeclarationData( *rhs.d_func() )) { } void ClassFunctionDeclaration::setAbstractType(AbstractType::Ptr type) { ///TODO: write testcase for typealias case which used to trigger this warning: /// typedef bool (*EventFilter)(void *message, long *result); /// in e.g. qcoreapplication.h:172 if(type && !dynamic_cast(type.data()) && type->whichType() != AbstractType::TypeAlias) { qCWarning(LANGUAGE) << "WARNING: Non-function type assigned to function declaration. Type is: " << type->toString() << "whichType:" << type->whichType() << "Declaration is:" << toString() << topContext()->url().str() << range().castToSimpleRange(); } ClassMemberDeclaration::setAbstractType(type); } DEFINE_LIST_MEMBER_HASH(ClassFunctionDeclarationData, m_defaultParameters, IndexedString) ClassFunctionDeclaration::ClassFunctionDeclaration(ClassFunctionDeclarationData& data) : ClassFunctionDeclarationBase(data) { } ClassFunctionDeclaration::ClassFunctionDeclaration(const RangeInRevision& range, DUContext* context) : ClassFunctionDeclarationBase(*new ClassFunctionDeclarationData, range) { d_func_dynamic()->setClassId(this); if( context ) setContext( context ); } ClassFunctionDeclaration::ClassFunctionDeclaration(ClassFunctionDeclarationData& data, const RangeInRevision& range, DUContext* context) : ClassFunctionDeclarationBase(data, range) { if( context ) setContext( context ); } Declaration* ClassFunctionDeclaration::clonePrivate() const { return new ClassFunctionDeclaration(*this); } ClassFunctionDeclaration::~ClassFunctionDeclaration() { } bool ClassFunctionDeclaration::isFunctionDeclaration() const { return true; } QString ClassFunctionDeclaration::toString() const { if( !abstractType() ) return ClassMemberDeclaration::toString(); TypePtr function = type(); if(function) { return QStringLiteral("%1 %2 %3").arg(function->partToString( FunctionType::SignatureReturn ), identifier().toString(), function->partToString( FunctionType::SignatureArguments )); } else { QString type = abstractType() ? abstractType()->toString() : QStringLiteral(""); qCDebug(LANGUAGE) << "A function has a bad type attached:" << type; return i18n("invalid member-function %1 type %2", identifier().toString(), type); } } /*bool ClassFunctionDeclaration::isSimilar(KDevelop::CodeItem *other, bool strict ) const { if (!CppClassMemberType::isSimilar(other,strict)) return false; FunctionModelItem func = dynamic_cast(other); if (isConstant() != func->isConstant()) return false; if (arguments().count() != func->arguments().count()) return false; for (int i=0; itype() != arg2->type()) return false; } return true; }*/ uint setFlag(bool enable, uint flag, uint flags) { if(enable) return flags | flag; else return flags & (~flag); } bool ClassFunctionDeclaration::isAbstract() const { return d_func()->m_functionFlags & AbstractFunctionFlag; } void ClassFunctionDeclaration::setIsAbstract(bool abstract) { d_func_dynamic()->m_functionFlags = (ClassFunctionFlags)setFlag(abstract, AbstractFunctionFlag, d_func()->m_functionFlags); } bool ClassFunctionDeclaration::isFinal() const { return d_func()->m_functionFlags & FinalFunctionFlag; } void ClassFunctionDeclaration::setIsFinal(bool final) { d_func_dynamic()->m_functionFlags = (ClassFunctionFlags)setFlag(final, FinalFunctionFlag, d_func()->m_functionFlags); } bool ClassFunctionDeclaration::isSignal() const { return d_func()->m_functionFlags & FunctionSignalFlag; } void ClassFunctionDeclaration::setIsSignal(bool isSignal) { d_func_dynamic()->m_functionFlags = (ClassFunctionFlags)setFlag(isSignal, FunctionSignalFlag, d_func()->m_functionFlags); } bool ClassFunctionDeclaration::isSlot() const { return d_func()->m_functionFlags & FunctionSlotFlag; } void ClassFunctionDeclaration::setIsSlot(bool isSlot) { d_func_dynamic()->m_functionFlags = (ClassFunctionFlags)setFlag(isSlot, FunctionSlotFlag, d_func()->m_functionFlags); } bool ClassFunctionDeclaration::isConversionFunction() const { return identifier() == conversionIdentifier(); } bool ClassFunctionDeclaration::isConstructor() const { DUContext* ctx = context(); if (ctx && ctx->type() == DUContext::Class && ctx->localScopeIdentifier().top().nameEquals(identifier())) return true; return false; } bool ClassFunctionDeclaration::isDestructor() const { DUContext* ctx = context(); QString id = identifier().toString(); return ctx && ctx->type() == DUContext::Class && id.startsWith('~') && id.mid(1) == ctx->localScopeIdentifier().top().toString(); } uint ClassFunctionDeclaration::additionalIdentity() const { if(abstractType()) return abstractType()->hash(); else return 0; } const IndexedString* ClassFunctionDeclaration::defaultParameters() const { return d_func()->m_defaultParameters(); } unsigned int ClassFunctionDeclaration::defaultParametersSize() const { return d_func()->m_defaultParametersSize(); } void ClassFunctionDeclaration::addDefaultParameter(const IndexedString& str) { d_func_dynamic()->m_defaultParametersList().append(str); } void ClassFunctionDeclaration::clearDefaultParameters() { d_func_dynamic()->m_defaultParametersList().clear(); } } diff --git a/language/duchain/codemodel.cpp b/language/duchain/codemodel.cpp index cc5a45fc7..fdbd42b65 100644 --- a/language/duchain/codemodel.cpp +++ b/language/duchain/codemodel.cpp @@ -1,358 +1,358 @@ /* This file is part of KDevelop Copyright 2008 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "codemodel.h" #include "appendedlist.h" -#include "util/debug.h" +#include #include #include "identifier.h" #include #include #include #define ifDebug(x) namespace KDevelop { class CodeModelItemHandler { public: static int leftChild(const CodeModelItem& m_data) { return (int)m_data.referenceCount; } static void setLeftChild(CodeModelItem& m_data, int child) { m_data.referenceCount = (uint)child; } static int rightChild(const CodeModelItem& m_data) { return (int)m_data.uKind; } static void setRightChild(CodeModelItem& m_data, int child) { m_data.uKind = (uint)child; } //Copies this item into the given one static void copyTo(const CodeModelItem& m_data, CodeModelItem& data) { data = m_data; } static void createFreeItem(CodeModelItem& data) { data = CodeModelItem(); data.referenceCount = (uint)-1; data.uKind = (uint)-1; } static bool isFree(const CodeModelItem& m_data) { return !m_data.id.isValid(); } static const CodeModelItem& data(const CodeModelItem& m_data) { return m_data; } static bool equals(const CodeModelItem& m_data, const CodeModelItem& rhs) { return m_data.id == rhs.id; } }; DEFINE_LIST_MEMBER_HASH(CodeModelRepositoryItem, items, CodeModelItem) class CodeModelRepositoryItem { public: CodeModelRepositoryItem() : centralFreeItem(-1) { initializeAppendedLists(); } CodeModelRepositoryItem(const CodeModelRepositoryItem& rhs, bool dynamic = true) : file(rhs.file), centralFreeItem(rhs.centralFreeItem) { initializeAppendedLists(dynamic); copyListsFrom(rhs); } ~CodeModelRepositoryItem() { freeAppendedLists(); } unsigned int hash() const { //We only compare the declaration. This allows us implementing a map, although the item-repository //originally represents a set. return file.index(); } uint itemSize() const { return dynamicSize(); } uint classSize() const { return sizeof(CodeModelRepositoryItem); } IndexedString file; int centralFreeItem; START_APPENDED_LISTS(CodeModelRepositoryItem); APPENDED_LIST_FIRST(CodeModelRepositoryItem, CodeModelItem, items); END_APPENDED_LISTS(CodeModelRepositoryItem, items); }; class CodeModelRequestItem { public: CodeModelRequestItem(const CodeModelRepositoryItem& item) : m_item(item) { } enum { AverageSize = 30 //This should be the approximate average size of an Item }; unsigned int hash() const { return m_item.hash(); } uint itemSize() const { return m_item.itemSize(); } void createItem(CodeModelRepositoryItem* item) const { Q_ASSERT(shouldDoDUChainReferenceCounting(item)); Q_ASSERT(shouldDoDUChainReferenceCounting(((char*)item) + (itemSize()-1))); new (item) CodeModelRepositoryItem(m_item, false); } static void destroy(CodeModelRepositoryItem* item, KDevelop::AbstractItemRepository&) { Q_ASSERT(shouldDoDUChainReferenceCounting(item)); // Q_ASSERT(shouldDoDUChainReferenceCounting(((char*)item) + (itemSize()-1))); item->~CodeModelRepositoryItem(); } static bool persistent(const CodeModelRepositoryItem* item) { Q_UNUSED(item); return true; } bool equals(const CodeModelRepositoryItem* item) const { return m_item.file == item->file; } const CodeModelRepositoryItem& m_item; }; class CodeModelPrivate { public: CodeModelPrivate() : m_repository(QStringLiteral("Code Model")) { } //Maps declaration-ids to items ItemRepository m_repository; }; CodeModel::CodeModel() : d(new CodeModelPrivate()) { } CodeModel::~CodeModel() { delete d; } void CodeModel::addItem(const IndexedString& file, const IndexedQualifiedIdentifier& id, CodeModelItem::Kind kind) { ifDebug( qCDebug(LANGUAGE) << "addItem" << file.str() << id.identifier().toString() << id.index; ) if(!id.isValid()) return; CodeModelRepositoryItem item; item.file = file; CodeModelRequestItem request(item); uint index = d->m_repository.findIndex(item); CodeModelItem newItem; newItem.id = id; newItem.kind = kind; newItem.referenceCount = 1; if(index) { const CodeModelRepositoryItem* oldItem = d->m_repository.itemFromIndex(index); EmbeddedTreeAlgorithms alg(oldItem->items(), oldItem->itemsSize(), oldItem->centralFreeItem); int listIndex = alg.indexOf(newItem); QMutexLocker lock(d->m_repository.mutex()); DynamicItem editableItem = d->m_repository.dynamicItemFromIndex(index); CodeModelItem* items = const_cast(editableItem->items()); if(listIndex != -1) { //Only update the reference-count ++items[listIndex].referenceCount; items[listIndex].kind = kind; return; }else{ //Add the item to the list EmbeddedTreeAddItem add(items, editableItem->itemsSize(), editableItem->centralFreeItem, newItem); if(add.newItemCount() != editableItem->itemsSize()) { //The data needs to be transferred into a bigger list. That list is within "item". item.itemsList().resize(add.newItemCount()); add.transferData(item.itemsList().data(), item.itemsList().size(), &item.centralFreeItem); d->m_repository.deleteItem(index); }else{ //We're fine: The item fits into the existing list. return; } } }else{ //We're creating a new index item.itemsList().append(newItem); } Q_ASSERT(!d->m_repository.findIndex(request)); //This inserts the changed item volatile uint newIndex = d->m_repository.index(request); Q_UNUSED(newIndex); ifDebug( qCDebug(LANGUAGE) << "new index" << newIndex; ) Q_ASSERT(d->m_repository.findIndex(request)); } void CodeModel::updateItem(const IndexedString& file, const IndexedQualifiedIdentifier& id, CodeModelItem::Kind kind) { ifDebug( qCDebug(LANGUAGE) << file.str() << id.identifier().toString() << kind; ) if(!id.isValid()) return; CodeModelRepositoryItem item; item.file = file; CodeModelRequestItem request(item); CodeModelItem newItem; newItem.id = id; newItem.kind = kind; newItem.referenceCount = 1; uint index = d->m_repository.findIndex(item); if(index) { //Check whether the item is already in the mapped list, else copy the list into the new created item QMutexLocker lock(d->m_repository.mutex()); DynamicItem oldItem = d->m_repository.dynamicItemFromIndex(index); EmbeddedTreeAlgorithms alg(oldItem->items(), oldItem->itemsSize(), oldItem->centralFreeItem); int listIndex = alg.indexOf(newItem); Q_ASSERT(listIndex != -1); CodeModelItem* items = const_cast(oldItem->items()); Q_ASSERT(items[listIndex].id == id); items[listIndex].kind = kind; return; } Q_ASSERT(0); //The updated item as not in the symbol table! } void CodeModel::removeItem(const IndexedString& file, const IndexedQualifiedIdentifier& id) //void CodeModel::removeDeclaration(const QualifiedIdentifier& id, const IndexedDeclaration& declaration) { if(!id.isValid()) return; ifDebug( qCDebug(LANGUAGE) << "removeItem" << file.str() << id.identifier().toString(); ) CodeModelRepositoryItem item; item.file = file; CodeModelRequestItem request(item); uint index = d->m_repository.findIndex(item); if(index) { CodeModelItem searchItem; searchItem.id = id; QMutexLocker lock(d->m_repository.mutex()); DynamicItem oldItem = d->m_repository.dynamicItemFromIndex(index); EmbeddedTreeAlgorithms alg(oldItem->items(), oldItem->itemsSize(), oldItem->centralFreeItem); int listIndex = alg.indexOf(searchItem); if(listIndex == -1) return; CodeModelItem* items = const_cast(oldItem->items()); --items[listIndex].referenceCount; if(oldItem->items()[listIndex].referenceCount) return; //Nothing to remove, there's still a reference-count left //We have reduced the reference-count to zero, so remove the item from the list EmbeddedTreeRemoveItem remove(items, oldItem->itemsSize(), oldItem->centralFreeItem, searchItem); uint newItemCount = remove.newItemCount(); if(newItemCount != oldItem->itemsSize()) { if(newItemCount == 0) { //Has become empty, delete the item d->m_repository.deleteItem(index); return; }else{ //Make smaller item.itemsList().resize(newItemCount); remove.transferData(item.itemsList().data(), item.itemsSize(), &item.centralFreeItem); //Delete the old list d->m_repository.deleteItem(index); //Add the new list d->m_repository.index(request); return; } } } } void CodeModel::items(const IndexedString& file, uint& count, const CodeModelItem*& items) const { ifDebug( qCDebug(LANGUAGE) << "items" << file.str(); ) CodeModelRepositoryItem item; item.file = file; CodeModelRequestItem request(item); uint index = d->m_repository.findIndex(item); if(index) { const CodeModelRepositoryItem* repositoryItem = d->m_repository.itemFromIndex(index); ifDebug( qCDebug(LANGUAGE) << "found index" << index << repositoryItem->itemsSize(); ) count = repositoryItem->itemsSize(); items = repositoryItem->items(); }else{ ifDebug( qCDebug(LANGUAGE) << "found no index"; ) count = 0; items = nullptr; } } CodeModel& CodeModel::self() { static CodeModel ret; return ret; } } diff --git a/language/duchain/duchain.cpp b/language/duchain/duchain.cpp index 8212dad43..6ed647c02 100644 --- a/language/duchain/duchain.cpp +++ b/language/duchain/duchain.cpp @@ -1,1749 +1,1749 @@ /* This is part of KDevelop Copyright 2006-2008 Hamish Rodda Copyright 2007-2008 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "duchain.h" #include "duchainlock.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "../interfaces/ilanguagesupport.h" #include "../interfaces/icodehighlighting.h" #include "../backgroundparser/backgroundparser.h" -#include "util/debug.h" +#include #include "language-features.h" #include "topducontext.h" #include "topducontextdata.h" #include "topducontextdynamicdata.h" #include "parsingenvironment.h" #include "declaration.h" #include "definitions.h" #include "duchainutils.h" #include "use.h" #include "uses.h" #include "abstractfunctiondeclaration.h" #include "duchainregister.h" #include "persistentsymboltable.h" #include "serialization/itemrepository.h" #include "waitforupdate.h" #include "importers.h" #if HAVE_MALLOC_TRIM #include "malloc.h" #endif namespace { //Additional "soft" cleanup steps that are done before the actual cleanup. //During "soft" cleanup, the consistency is not guaranteed. The repository is //marked to be updating during soft cleanup, so if kdevelop crashes, it will be cleared. //The big advantage of the soft cleanup steps is, that the duchain is always only locked for //short times, which leads to no lockup in the UI. const int SOFT_CLEANUP_STEPS = 1; const uint cleanupEverySeconds = 200; ///Approximate maximum count of top-contexts that are checked during final cleanup const uint maxFinalCleanupCheckContexts = 2000; const uint minimumFinalCleanupCheckContextsPercentage = 10; //Check at least n% of all top-contexts during cleanup //Set to true as soon as the duchain is deleted } namespace KDevelop { bool DUChain::m_deleted = false; ///Must be locked through KDevelop::SpinLock before using chainsByIndex ///This lock should be locked only for very short times QMutex DUChain::chainsByIndexLock; std::vector DUChain::chainsByIndex; //This thing is not actually used, but it's needed for compiling DEFINE_LIST_MEMBER_HASH(EnvironmentInformationListItem, items, uint) //An entry for the item-repository that holds some meta-data. Behind this entry, the actual ParsingEnvironmentFileData is stored. class EnvironmentInformationItem { public: EnvironmentInformationItem(uint topContext, uint size) : m_topContext(topContext), m_size(size) { } ~EnvironmentInformationItem() { } unsigned int hash() const { return m_topContext; } unsigned int itemSize() const { return sizeof(*this) + m_size; } uint m_topContext; uint m_size;//Size of the data behind, that holds the actual item }; struct ItemRepositoryIndexHash { uint operator()(unsigned int __x) const { return 173*(__x>>2) + 11 * (__x >> 16); } }; class EnvironmentInformationRequest { public: ///This constructor should only be used for lookup EnvironmentInformationRequest(uint topContextIndex) : m_file(nullptr), m_index(topContextIndex) { } EnvironmentInformationRequest(const ParsingEnvironmentFile* file) : m_file(file), m_index(file->indexedTopContext().index()) { } enum { AverageSize = 32 //This should be the approximate average size of an Item }; unsigned int hash() const { return m_index; } uint itemSize() const { return sizeof(EnvironmentInformationItem) + DUChainItemSystem::self().dynamicSize(*m_file->d_func()); } void createItem(EnvironmentInformationItem* item) const { new (item) EnvironmentInformationItem(m_index, DUChainItemSystem::self().dynamicSize(*m_file->d_func())); Q_ASSERT(m_file->d_func()->m_dynamic); DUChainBaseData* data = reinterpret_cast(reinterpret_cast(item) + sizeof(EnvironmentInformationItem)); DUChainItemSystem::self().copy(*m_file->d_func(), *data, true); Q_ASSERT(data->m_range == m_file->d_func()->m_range); Q_ASSERT(data->classId == m_file->d_func()->classId); Q_ASSERT(data->m_dynamic == false); } static void destroy(EnvironmentInformationItem* item, KDevelop::AbstractItemRepository&) { item->~EnvironmentInformationItem(); //We don't need to call the destructor, because that's done in DUChainBase::makeDynamic() //We just need to make sure that every environment-file is dynamic when it's deleted // DUChainItemSystem::self().callDestructor((DUChainBaseData*)(((char*)item) + sizeof(EnvironmentInformationItem))); } static bool persistent(const EnvironmentInformationItem* ) { //Cleanup done separately return true; } bool equals(const EnvironmentInformationItem* item) const { return m_index == item->m_topContext; } const ParsingEnvironmentFile* m_file; uint m_index; }; ///A list of environment-information/top-contexts mapped to a file-name class EnvironmentInformationListItem { public: EnvironmentInformationListItem() { initializeAppendedLists(true); } EnvironmentInformationListItem(const EnvironmentInformationListItem& rhs, bool dynamic = true) { initializeAppendedLists(dynamic); m_file = rhs.m_file; copyListsFrom(rhs); } ~EnvironmentInformationListItem() { freeAppendedLists(); } unsigned int hash() const { //We only compare the declaration. This allows us implementing a map, although the item-repository //originally represents a set. return m_file.hash(); } unsigned short int itemSize() const { return dynamicSize(); } IndexedString m_file; uint classSize() const { return sizeof(*this); } START_APPENDED_LISTS(EnvironmentInformationListItem); ///Contains the index of each contained environment-item APPENDED_LIST_FIRST(EnvironmentInformationListItem, uint, items); END_APPENDED_LISTS(EnvironmentInformationListItem, items); }; class EnvironmentInformationListRequest { public: ///This constructor should only be used for lookup EnvironmentInformationListRequest(const IndexedString& file) : m_file(file), m_item(nullptr) { } ///This is used to actually construct the information in the repository EnvironmentInformationListRequest(const IndexedString& file, const EnvironmentInformationListItem& item) : m_file(file), m_item(&item) { } enum { AverageSize = 160 //This should be the approximate average size of an Item }; unsigned int hash() const { return m_file.hash(); } uint itemSize() const { return m_item->itemSize(); } void createItem(EnvironmentInformationListItem* item) const { Q_ASSERT(m_item->m_file == m_file); new (item) EnvironmentInformationListItem(*m_item, false); } static void destroy(EnvironmentInformationListItem* item, KDevelop::AbstractItemRepository&) { item->~EnvironmentInformationListItem(); } static bool persistent(const EnvironmentInformationListItem*) { //Cleanup is done separately return true; } bool equals(const EnvironmentInformationListItem* item) const { return m_file == item->m_file; } IndexedString m_file; const EnvironmentInformationListItem* m_item; }; class DUChainPrivate; static DUChainPrivate* duChainPrivateSelf = nullptr; class DUChainPrivate { class CleanupThread : public QThread { public: explicit CleanupThread(DUChainPrivate* data) : m_stopRunning(false), m_data(data) { } void stopThread() { { QMutexLocker lock(&m_waitMutex); m_stopRunning = true; m_wait.wakeAll(); //Wakes the thread up, so it notices it should exit } wait(); } private: void run() override { while(1) { for(uint s = 0; s < cleanupEverySeconds; ++s) { if(m_stopRunning) break; QMutexLocker lock(&m_waitMutex); m_wait.wait(&m_waitMutex, 1000); } if(m_stopRunning) break; //Just to make sure the cache is cleared periodically ModificationRevisionSet::clearCache(); m_data->doMoreCleanup(SOFT_CLEANUP_STEPS, TryLock); if(m_stopRunning) break; } } bool m_stopRunning; QWaitCondition m_wait; QMutex m_waitMutex; DUChainPrivate* m_data; }; public: DUChainPrivate() : m_chainsMutex(QMutex::Recursive), m_cleanupMutex(QMutex::Recursive), instance(nullptr), m_cleanupDisabled(false), m_destroyed(false), m_environmentListInfo(QStringLiteral("Environment Lists")), m_environmentInfo(QStringLiteral("Environment Information")) { #if defined(TEST_NO_CLEANUP) m_cleanupDisabled = true; #endif duChainPrivateSelf = this; qRegisterMetaType("KDevelop::DUChainBasePointer"); qRegisterMetaType("KDevelop::DUContextPointer"); qRegisterMetaType("KDevelop::TopDUContextPointer"); qRegisterMetaType("KDevelop::DeclarationPointer"); qRegisterMetaType("KDevelop::FunctionDeclarationPointer"); qRegisterMetaType("KDevelop::IndexedString"); qRegisterMetaType("KDevelop::IndexedTopDUContext"); qRegisterMetaType("KDevelop::ReferencedTopDUContext"); instance = new DUChain(); m_cleanup = new CleanupThread(this); m_cleanup->start(); DUChain::m_deleted = false; ///Loading of some static data: { ///@todo Solve this more duchain-like QFile f(globalItemRepositoryRegistry().path() + "/parsing_environment_data"); bool opened = f.open(QIODevice::ReadOnly); ///FIXME: ugh, so ugly ParsingEnvironmentFile::m_staticData = reinterpret_cast( new char[sizeof(StaticParsingEnvironmentData)]); if(opened) { qCDebug(LANGUAGE) << "reading parsing-environment static data"; //Read f.read((char*)ParsingEnvironmentFile::m_staticData, sizeof(StaticParsingEnvironmentData)); }else{ qCDebug(LANGUAGE) << "creating new parsing-environment static data"; //Initialize new (ParsingEnvironmentFile::m_staticData) StaticParsingEnvironmentData(); } } ///Read in the list of available top-context indices { QFile f(globalItemRepositoryRegistry().path() + "/available_top_context_indices"); bool opened = f.open(QIODevice::ReadOnly); if(opened) { Q_ASSERT( (f.size() % sizeof(uint)) == 0); m_availableTopContextIndices.resize(f.size()/(int)sizeof(uint)); f.read((char*)m_availableTopContextIndices.data(), f.size()); } } } ~DUChainPrivate() { qCDebug(LANGUAGE) << "Destroying"; DUChain::m_deleted = true; m_cleanup->stopThread(); delete m_cleanup; delete instance; } void clear() { if(!m_cleanupDisabled) doMoreCleanup(); DUChainWriteLocker writeLock(DUChain::lock()); QMutexLocker l(&m_chainsMutex); foreach(TopDUContext* top, m_chainsByUrl) removeDocumentChainFromMemory(top); m_indexEnvironmentInformations.clear(); m_fileEnvironmentInformations.clear(); Q_ASSERT(m_fileEnvironmentInformations.isEmpty()); Q_ASSERT(m_chainsByUrl.isEmpty()); } ///DUChain must be write-locked ///Also removes from the environment-manager if the top-context is not on disk void removeDocumentChainFromMemory(TopDUContext* context) { QMutexLocker l(&m_chainsMutex); { QMutexLocker l(&m_referenceCountsMutex); if(m_referenceCounts.contains(context)) { //This happens during shutdown, since everything is unloaded qCDebug(LANGUAGE) << "removed a top-context that was reference-counted:" << context->url().str() << context->ownIndex(); m_referenceCounts.remove(context); } } uint index = context->ownIndex(); // qCDebug(LANGUAGE) << "duchain: removing document" << context->url().str(); Q_ASSERT(hasChainForIndex(index)); Q_ASSERT(m_chainsByUrl.contains(context->url(), context)); m_chainsByUrl.remove(context->url(), context); if(!context->isOnDisk()) instance->removeFromEnvironmentManager(context); l.unlock(); //DUChain is write-locked, so we can do whatever we want on the top-context, including deleting it context->deleteSelf(); l.relock(); Q_ASSERT(hasChainForIndex(index)); QMutexLocker lock(&DUChain::chainsByIndexLock); DUChain::chainsByIndex[index] = nullptr; } ///Must be locked before accessing content of this class. ///Should be released during expensive disk-operations and such. QMutex m_chainsMutex; QMutex m_cleanupMutex; CleanupThread* m_cleanup; DUChain* instance; DUChainLock lock; QMultiMap m_chainsByUrl; //Must be locked before accessing m_referenceCounts QMutex m_referenceCountsMutex; QHash m_referenceCounts; Definitions m_definitions; Uses m_uses; QSet m_loading; bool m_cleanupDisabled; //List of available top-context indices, protected by m_chainsMutex QVector m_availableTopContextIndices; ///Used to keep alive the top-context that belong to documents loaded in the editor QSet m_openDocumentContexts; bool m_destroyed; ///The item must not be stored yet ///m_chainsMutex should not be locked, since this can trigger I/O void addEnvironmentInformation(ParsingEnvironmentFilePointer info) { Q_ASSERT(!findInformation(info->indexedTopContext().index())); Q_ASSERT(m_environmentInfo.findIndex(info->indexedTopContext().index()) == 0); QMutexLocker lock(&m_chainsMutex); m_fileEnvironmentInformations.insert(info->url(), info); m_indexEnvironmentInformations.insert(info->indexedTopContext().index(), info); Q_ASSERT(info->d_func()->classId); } ///The item must be managed currently ///m_chainsMutex does not need to be locked void removeEnvironmentInformation(ParsingEnvironmentFilePointer info) { info->makeDynamic(); //By doing this, we make sure the data is actually being destroyed in the destructor bool removed = false; bool removed2 = false; { QMutexLocker lock(&m_chainsMutex); removed = m_fileEnvironmentInformations.remove(info->url(), info); removed2 = m_indexEnvironmentInformations.remove(info->indexedTopContext().index()); } { //Remove it from the environment information lists if it was there QMutexLocker lock(m_environmentListInfo.mutex()); uint index = m_environmentListInfo.findIndex(info->url()); if(index) { EnvironmentInformationListItem item(*m_environmentListInfo.itemFromIndex(index)); if(item.itemsList().removeOne(info->indexedTopContext().index())) { m_environmentListInfo.deleteItem(index); if(!item.itemsList().isEmpty()) m_environmentListInfo.index(EnvironmentInformationListRequest(info->url(), item)); } } } QMutexLocker lock(m_environmentInfo.mutex()); uint index = m_environmentInfo.findIndex(info->indexedTopContext().index()); if(index) { m_environmentInfo.deleteItem(index); } Q_UNUSED(removed); Q_UNUSED(removed2); Q_ASSERT(index || (removed && removed2)); Q_ASSERT(!findInformation(info->indexedTopContext().index())); } ///m_chainsMutex should _not_ be locked, because this may trigger I/O QList getEnvironmentInformation(IndexedString url) { QList ret; uint listIndex = m_environmentListInfo.findIndex(url); if(listIndex) { KDevVarLengthArray topContextIndices; { //First store all the possible intices into the KDevVarLengthArray, so we can unlock the mutex before processing them. QMutexLocker lock(m_environmentListInfo.mutex()); //Lock the mutex to make sure the item isn't changed while it's being iterated const EnvironmentInformationListItem* item = m_environmentListInfo.itemFromIndex(listIndex); FOREACH_FUNCTION(uint topContextIndex, item->items) topContextIndices << topContextIndex; } //Process the indices in a separate step after copying them from the array, so we don't need m_environmentListInfo.mutex locked, //and can call loadInformation(..) safely, which else might lead to a deadlock. foreach (uint topContextIndex, topContextIndices) { QExplicitlySharedDataPointer< ParsingEnvironmentFile > p = ParsingEnvironmentFilePointer(loadInformation(topContextIndex)); if(p) { ret << p; }else{ qCDebug(LANGUAGE) << "Failed to load enviromment-information for" << TopDUContextDynamicData::loadUrl(topContextIndex).str(); } } } QMutexLocker l(&m_chainsMutex); //Add those information that have not been added to the stored lists yet foreach(const ParsingEnvironmentFilePointer& file, m_fileEnvironmentInformations.values(url)) if(!ret.contains(file)) ret << file; return ret; } ///Must be called _without_ the chainsByIndex spin-lock locked static inline bool hasChainForIndex(uint index) { QMutexLocker lock(&DUChain::chainsByIndexLock); return (DUChain::chainsByIndex.size() > index) && DUChain::chainsByIndex[index]; } ///Must be called _without_ the chainsByIndex spin-lock locked. Returns the top-context if it is loaded. static inline TopDUContext* readChainForIndex(uint index) { QMutexLocker lock(&DUChain::chainsByIndexLock); if(DUChain::chainsByIndex.size() > index) return DUChain::chainsByIndex[index]; else return nullptr; } ///Makes sure that the chain with the given index is loaded ///@warning m_chainsMutex must NOT be locked when this is called void loadChain(uint index, QSet& loaded) { QMutexLocker l(&m_chainsMutex); if(!hasChainForIndex(index)) { if(m_loading.contains(index)) { //It's probably being loaded by another thread. So wait until the load is ready while(m_loading.contains(index)) { l.unlock(); qCDebug(LANGUAGE) << "waiting for another thread to load index" << index; QThread::usleep(50000); l.relock(); } loaded.insert(index); return; } m_loading.insert(index); loaded.insert(index); l.unlock(); qCDebug(LANGUAGE) << "loading top-context" << index; TopDUContext* chain = TopDUContextDynamicData::load(index); if(chain) { chain->setParsingEnvironmentFile(loadInformation(chain->ownIndex())); if(!chain->usingImportsCache()) { //Eventually also load all the imported chains, so the import-structure is built foreach(const DUContext::Import &import, chain->DUContext::importedParentContexts()) { if(!loaded.contains(import.topContextIndex())) { loadChain(import.topContextIndex(), loaded); } } } chain->rebuildDynamicImportStructure(); chain->setInDuChain(true); instance->addDocumentChain(chain); } l.relock(); m_loading.remove(index); } } ///Stores all environment-information ///Also makes sure that all information that stays is referenced, so it stays alive. ///@param atomic If this is false, the write-lock will be released time by time void storeAllInformation(bool atomic, DUChainWriteLocker& locker) { uint cnt = 0; QList urls; { QMutexLocker lock(&m_chainsMutex); urls += m_fileEnvironmentInformations.keys(); } foreach(const IndexedString &url, urls) { QList check; { QMutexLocker lock(&m_chainsMutex); check = m_fileEnvironmentInformations.values(url); } foreach(ParsingEnvironmentFilePointer file, check) { EnvironmentInformationRequest req(file.data()); QMutexLocker lock(m_environmentInfo.mutex()); uint index = m_environmentInfo.findIndex(req); if(file->d_func()->isDynamic()) { //This item has been changed, or isn't in the repository yet //Eventually remove an old entry if(index) m_environmentInfo.deleteItem(index); //Add the new entry to the item repository index = m_environmentInfo.index(req); Q_ASSERT(index); EnvironmentInformationItem* item = const_cast(m_environmentInfo.itemFromIndex(index)); DUChainBaseData* theData = reinterpret_cast(reinterpret_cast(item) + sizeof(EnvironmentInformationItem)); Q_ASSERT(theData->m_range == file->d_func()->m_range); Q_ASSERT(theData->m_dynamic == false); Q_ASSERT(theData->classId == file->d_func()->classId); file->setData( theData ); ++cnt; }else{ m_environmentInfo.itemFromIndex(index); //Prevent unloading of the data, by accessing the item } } ///We must not release the lock while holding a reference to a ParsingEnvironmentFilePointer, else we may miss the deletion of an ///information, and will get crashes. if(!atomic && (cnt % 100 == 0)) { //Release the lock on a regular basis locker.unlock(); locker.lock(); } storeInformationList(url); //Access the data in the repository, so the bucket isn't unloaded uint index = m_environmentListInfo.findIndex(EnvironmentInformationListRequest(url)); if(index) { m_environmentListInfo.itemFromIndex(index); }else{ QMutexLocker lock(&m_chainsMutex); qCDebug(LANGUAGE) << "Did not find stored item for" << url.str() << "count:" << m_fileEnvironmentInformations.values(url); } if(!atomic) { locker.unlock(); locker.lock(); } } } QMutex& cleanupMutex() { return m_cleanupMutex; } /// defines how we interact with the ongoing language parse jobs enum LockFlag { /// no locking required, only used when we locked previously NoLock = 0, /// lock all parse jobs and block until we succeeded. required at shutdown BlockingLock = 1, /// only try to lock and abort on failure, good for the intermittent cleanups TryLock = 2, }; ///@param retries When this is nonzero, then doMoreCleanup will do the specified amount of cycles ///doing the cleanup without permanently locking the du-chain. During these steps the consistency ///of the disk-storage is not guaranteed, but only few changes will be done during these steps, ///so the final step where the duchain is permanently locked is much faster. void doMoreCleanup(int retries = 0, LockFlag lockFlag = BlockingLock) { if(m_cleanupDisabled) return; //This mutex makes sure that there's never 2 threads at he same time trying to clean up QMutexLocker lockCleanupMutex(&cleanupMutex()); if(m_destroyed || m_cleanupDisabled) return; Q_ASSERT(!instance->lock()->currentThreadHasReadLock() && !instance->lock()->currentThreadHasWriteLock()); DUChainWriteLocker writeLock(instance->lock()); //This is used to stop all parsing before starting to do the cleanup. This way less happens during the //soft cleanups, and we have a good chance that during the "hard" cleanup only few data has to be written. QList locked; if (lockFlag != NoLock) { QList languages; if (ICore* core = ICore::self()) if (ILanguageController* lc = core->languageController()) languages = lc->loadedLanguages(); writeLock.unlock(); //Here we wait for all parsing-threads to stop their processing foreach(const auto language, languages) { if (lockFlag == TryLock) { if (!language->parseLock()->tryLockForWrite()) { qCDebug(LANGUAGE) << "Aborting cleanup because language plugin is still parsing:" << language->name(); // some language is still parsing, don't interfere with the cleanup foreach(auto* lock, locked) { lock->unlock(); } return; } } else { language->parseLock()->lockForWrite(); } locked << language->parseLock(); } writeLock.lock(); globalItemRepositoryRegistry().lockForWriting(); qCDebug(LANGUAGE) << "starting cleanup"; } QTime startTime = QTime::currentTime(); PersistentSymbolTable::self().clearCache(); storeAllInformation(!retries, writeLock); //Puts environment-information into a repository //We don't need to increase the reference-count, since the cleanup-mutex is locked QSet workOnContexts; { QMutexLocker l(&m_chainsMutex); workOnContexts.reserve(m_chainsByUrl.size()); foreach(TopDUContext* top, m_chainsByUrl) { workOnContexts << top; Q_ASSERT(hasChainForIndex(top->ownIndex())); } } foreach(TopDUContext* context, workOnContexts) { context->m_dynamicData->store(); if(retries) { //Eventually give other threads a chance to access the duchain writeLock.unlock(); //Sleep to give the other threads a realistic chance to get a read-lock in between QThread::usleep(500); writeLock.lock(); } } //Unload all top-contexts that don't have a reference-count and that are not imported by a referenced one QSet unloadedNames; bool unloadedOne = true; bool unloadAllUnreferenced = !retries; //Now unload contexts, but only ones that are not imported by any other currently loaded context //The complication: Since during the lock-break new references may be added, we must never keep //the du-chain in an invalid state. Thus we can only unload contexts that are not imported by any //currently loaded contexts. In case of loops, we have to unload everything at once. while(unloadedOne) { unloadedOne = false; int hadUnloadable = 0; unloadContexts: foreach(TopDUContext* unload, workOnContexts) { bool hasReference = false; { QMutexLocker l(&m_referenceCountsMutex); //Test if the context is imported by a referenced one for (auto it = m_referenceCounts.constBegin(), end = m_referenceCounts.constEnd(); it != end; ++it) { auto* context = it.key(); if(context == unload || context->imports(unload, CursorInRevision())) { workOnContexts.remove(unload); hasReference = true; } } } if(!hasReference) ++hadUnloadable; //We have found a context that is not referenced else continue; //This context is referenced bool isImportedByLoaded = !unload->loadedImporters().isEmpty(); //If we unload a context that is imported by other contexts, we create a bad loaded state if(isImportedByLoaded && !unloadAllUnreferenced) continue; unloadedNames.insert(unload->url()); //Since we've released the write-lock in between, we've got to call store() again to be sure that none of the data is dynamic //If nothing has changed, it is only a low-cost call. unload->m_dynamicData->store(); Q_ASSERT(!unload->d_func()->m_dynamic); removeDocumentChainFromMemory(unload); workOnContexts.remove(unload); unloadedOne = true; if(!unloadAllUnreferenced) { //Eventually give other threads a chance to access the duchain writeLock.unlock(); //Sleep to give the other threads a realistic chance to get a read-lock in between QThread::usleep(500); writeLock.lock(); } } if(hadUnloadable && !unloadedOne) { Q_ASSERT(!unloadAllUnreferenced); //This can happen in case of loops. We have o unload everything at one time. qCDebug(LANGUAGE) << "found" << hadUnloadable << "unloadable contexts, but could not unload separately. Unloading atomically."; unloadAllUnreferenced = true; hadUnloadable = 0; //Reset to 0, so we cannot loop forever goto unloadContexts; } } if(retries == 0) { QMutexLocker lock(&m_chainsMutex); //Do this atomically, since we must be sure that _everything_ is already saved for(QMultiMap::iterator it = m_fileEnvironmentInformations.begin(); it != m_fileEnvironmentInformations.end(); ) { ParsingEnvironmentFile* f = it->data(); Q_ASSERT(f->d_func()->classId); if(f->ref.load() == 1) { Q_ASSERT(!f->d_func()->isDynamic()); //It cannot be dynamic, since we have stored before //The ParsingEnvironmentFilePointer is only referenced once. This means that it does not belong to any //loaded top-context, so just remove it to save some memory and processing time. ///@todo use some kind of timeout before removing it = m_fileEnvironmentInformations.erase(it); }else{ ++it; } } } if(retries) writeLock.unlock(); //This must be the last step, due to the on-disk reference counting globalItemRepositoryRegistry().store(); //Stores all repositories { //Store the static parsing-environment file data ///@todo Solve this more elegantly, using a general mechanism to store static duchain-like data Q_ASSERT(ParsingEnvironmentFile::m_staticData); QFile f(globalItemRepositoryRegistry().path() + "/parsing_environment_data"); bool opened = f.open(QIODevice::WriteOnly); Q_ASSERT(opened); Q_UNUSED(opened); f.write((char*)ParsingEnvironmentFile::m_staticData, sizeof(StaticParsingEnvironmentData)); } ///Write out the list of available top-context indices { QMutexLocker lock(&m_chainsMutex); QFile f(globalItemRepositoryRegistry().path() + "/available_top_context_indices"); bool opened = f.open(QIODevice::WriteOnly); Q_ASSERT(opened); Q_UNUSED(opened); f.write((char*)m_availableTopContextIndices.data(), m_availableTopContextIndices.size() * sizeof(uint)); } if(retries) { doMoreCleanup(retries-1, NoLock); writeLock.lock(); } if(lockFlag != NoLock) { globalItemRepositoryRegistry().unlockForWriting(); int elapsedSeconds = startTime.secsTo(QTime::currentTime()); qCDebug(LANGUAGE) << "seconds spent doing cleanup: " << elapsedSeconds << "top-contexts still open:" << m_chainsByUrl.size(); } if(!retries) { int elapesedMilliSeconds = startTime.msecsTo(QTime::currentTime()); qCDebug(LANGUAGE) << "milliseconds spent doing cleanup with locked duchain: " << elapesedMilliSeconds; } foreach(QReadWriteLock* lock, locked) lock->unlock(); #if HAVE_MALLOC_TRIM // trim unused memory but keep a pad buffer of about 50 MB // this can greatly decrease the perceived memory consumption of kdevelop // see: https://sourceware.org/bugzilla/show_bug.cgi?id=14827 malloc_trim(50 * 1024 * 1024); #endif } ///Checks whether the information is already loaded. ParsingEnvironmentFile* findInformation(uint topContextIndex) { QMutexLocker lock(&m_chainsMutex); QHash::iterator it = m_indexEnvironmentInformations.find(topContextIndex); if(it != m_indexEnvironmentInformations.end()) return (*it).data(); return nullptr; } ///Loads/gets the environment-information for the given top-context index, or returns zero if none exists ///@warning m_chainsMutex should NOT be locked when this is called, because it triggers I/O ///@warning no other mutexes should be locked, as that may lead to a dedalock ParsingEnvironmentFile* loadInformation(uint topContextIndex) { ParsingEnvironmentFile* alreadyLoaded = findInformation(topContextIndex); if(alreadyLoaded) return alreadyLoaded; //Step two: Check if it is on disk, and if is, load it uint dataIndex = m_environmentInfo.findIndex(EnvironmentInformationRequest(topContextIndex)); if(!dataIndex) { //No environment-information stored for this top-context return nullptr; } const EnvironmentInformationItem& item(*m_environmentInfo.itemFromIndex(dataIndex)); QMutexLocker lock(&m_chainsMutex); //Due to multi-threading, we must do this check after locking the mutex, so we can be sure we don't create the same item twice at the same time alreadyLoaded = findInformation(topContextIndex); if(alreadyLoaded) return alreadyLoaded; ///FIXME: ugly, and remove const_cast ParsingEnvironmentFile* ret = dynamic_cast(DUChainItemSystem::self().create( const_cast(reinterpret_cast(reinterpret_cast(&item) + sizeof(EnvironmentInformationItem))) )); if(ret) { Q_ASSERT(ret->d_func()->classId); Q_ASSERT(ret->indexedTopContext().index() == topContextIndex); ParsingEnvironmentFilePointer retPtr(ret); m_fileEnvironmentInformations.insert(ret->url(), retPtr); Q_ASSERT(!m_indexEnvironmentInformations.contains(ret->indexedTopContext().index())); m_indexEnvironmentInformations.insert(ret->indexedTopContext().index(), retPtr); } return ret; } struct CleanupListVisitor { QList checkContexts; bool operator()(const EnvironmentInformationItem* item) { checkContexts << item->m_topContext; return true; } }; ///Will check a selection of all top-contexts for up-to-date ness, and remove them if out of date void cleanupTopContexts() { DUChainWriteLocker lock( DUChain::lock() ); qCDebug(LANGUAGE) << "cleaning top-contexts"; CleanupListVisitor visitor; uint startPos = 0; m_environmentInfo.visitAllItems(visitor); int checkContextsCount = maxFinalCleanupCheckContexts; int percentageOfContexts = (visitor.checkContexts.size() * 100) / minimumFinalCleanupCheckContextsPercentage; if(checkContextsCount < percentageOfContexts) checkContextsCount = percentageOfContexts; if(visitor.checkContexts.size() > (int)checkContextsCount) startPos = qrand() % (visitor.checkContexts.size() - checkContextsCount); int endPos = startPos + maxFinalCleanupCheckContexts; if(endPos > visitor.checkContexts.size()) endPos = visitor.checkContexts.size(); QSet< uint > check; for(int a = startPos; a < endPos && check.size() < checkContextsCount; ++a) if(check.size() < checkContextsCount) addContextsForRemoval(check, IndexedTopDUContext(visitor.checkContexts[a])); foreach(uint topIndex, check) { IndexedTopDUContext top(topIndex); if(top.data()) { qCDebug(LANGUAGE) << "removing top-context for" << top.data()->url().str() << "because it is out of date"; instance->removeDocumentChain(top.data()); } } qCDebug(LANGUAGE) << "check ready"; } private: void addContextsForRemoval(QSet& topContexts, IndexedTopDUContext top) { if(topContexts.contains(top.index())) return; QExplicitlySharedDataPointer info( instance->environmentFileForDocument(top) ); ///@todo Also check if the context is "useful"(Not a duplicate context, imported by a useful one, ...) if(info && info->needsUpdate()) { //This context will be removed }else{ return; } topContexts.insert(top.index()); if(info) { //Check whether importers need to be removed as well QList< QExplicitlySharedDataPointer > importers = info->importers(); QSet< QExplicitlySharedDataPointer > checkNext; //Do breadth first search, so less imports/importers have to be loaded, and a lower depth is reached for(QList< QExplicitlySharedDataPointer >::iterator it = importers.begin(); it != importers.end(); ++it) { IndexedTopDUContext c = (*it)->indexedTopContext(); if(!topContexts.contains(c.index())) { topContexts.insert(c.index()); //Prevent useless recursion checkNext.insert(*it); } } for(QSet< QExplicitlySharedDataPointer >::const_iterator it = checkNext.begin(); it != checkNext.end(); ++it) { topContexts.remove((*it)->indexedTopContext().index()); //Enable full check again addContextsForRemoval(topContexts, (*it)->indexedTopContext()); } } } ///Stores the environment-information for the given url void storeInformationList(IndexedString url) { QMutexLocker lock(m_environmentListInfo.mutex()); EnvironmentInformationListItem newItem; newItem.m_file = url; QSet newItems; { QMutexLocker lock(&m_chainsMutex); QMultiMap::iterator start = m_fileEnvironmentInformations.lowerBound(url); QMultiMap::iterator end = m_fileEnvironmentInformations.upperBound(url); for(QMultiMap::iterator it = start; it != end; ++it) { uint topContextIndex = (*it)->indexedTopContext().index(); newItems.insert(topContextIndex); newItem.itemsList().append(topContextIndex); } } uint index = m_environmentListInfo.findIndex(url); if(index) { //We only handle adding items here, since we can never be sure whether everything is loaded //Removal is handled directly in removeEnvironmentInformation const EnvironmentInformationListItem* item = m_environmentListInfo.itemFromIndex(index); QSet oldItems; FOREACH_FUNCTION(uint topContextIndex, item->items) { oldItems.insert(topContextIndex); if(!newItems.contains(topContextIndex)) { newItems.insert(topContextIndex); newItem.itemsList().append(topContextIndex); } } if(oldItems == newItems) return; ///Update/insert a new list m_environmentListInfo.deleteItem(index); //Remove the previous item } Q_ASSERT(m_environmentListInfo.findIndex(EnvironmentInformationListRequest(url)) == 0); //Insert the new item m_environmentListInfo.index(EnvironmentInformationListRequest(url, newItem)); Q_ASSERT(m_environmentListInfo.findIndex(EnvironmentInformationListRequest(url))); } //Loaded environment information. Protected by m_chainsMutex QMultiMap m_fileEnvironmentInformations; QHash m_indexEnvironmentInformations; ///The following repositories are thread-safe, and m_chainsMutex should not be locked when using them, because ///they may trigger I/O. Still it may be required to lock their local mutexes. ///Maps filenames to a list of top-contexts/environment-information. ItemRepository m_environmentListInfo; ///Maps top-context-indices to environment-information item. ItemRepository m_environmentInfo; }; Q_GLOBAL_STATIC(DUChainPrivate, sdDUChainPrivate) DUChain::DUChain() { Q_ASSERT(ICore::self()); connect(ICore::self()->documentController(), &IDocumentController::documentLoadedPrepare, this, &DUChain::documentLoadedPrepare); connect(ICore::self()->documentController(), &IDocumentController::documentUrlChanged, this, &DUChain::documentRenamed); connect(ICore::self()->documentController(), &IDocumentController::documentActivated, this, &DUChain::documentActivated); connect(ICore::self()->documentController(), &IDocumentController::documentClosed, this, &DUChain::documentClosed); } DUChain::~DUChain() { DUChain::m_deleted = true; } DUChain* DUChain::self() { return sdDUChainPrivate->instance; } extern void initModificationRevisionSetRepository(); extern void initDeclarationRepositories(); extern void initIdentifierRepository(); extern void initTypeRepository(); extern void initInstantiationInformationRepository(); void DUChain::initialize() { // Initialize the global item repository as first thing after loading the session Q_ASSERT(ICore::self()); Q_ASSERT(ICore::self()->activeSession()); ItemRepositoryRegistry::initialize(ICore::self()->activeSessionLock()); initReferenceCounting(); // This needs to be initialized here too as the function is not threadsafe, but can // sometimes be called from different threads. This results in the underlying QFile // being 0 and hence crashes at some point later when accessing the contents via // read. See https://bugs.kde.org/show_bug.cgi?id=250779 RecursiveImportRepository::repository(); RecursiveImportCacheRepository::repository(); // similar to above, see https://bugs.kde.org/show_bug.cgi?id=255323 initDeclarationRepositories(); initModificationRevisionSetRepository(); initIdentifierRepository(); initTypeRepository(); initInstantiationInformationRepository(); Importers::self(); globalImportIdentifier(); globalIndexedImportIdentifier(); globalAliasIdentifier(); globalIndexedAliasIdentifier(); } DUChainLock* DUChain::lock() { return &sdDUChainPrivate->lock; } QList DUChain::allChains() const { QMutexLocker l(&sdDUChainPrivate->m_chainsMutex); return sdDUChainPrivate->m_chainsByUrl.values(); } void DUChain::updateContextEnvironment( TopDUContext* context, ParsingEnvironmentFile* file ) { QMutexLocker l(&sdDUChainPrivate->m_chainsMutex); removeFromEnvironmentManager( context ); context->setParsingEnvironmentFile( file ); addToEnvironmentManager( context ); } void DUChain::removeDocumentChain( TopDUContext* context ) { ENSURE_CHAIN_WRITE_LOCKED; IndexedTopDUContext indexed(context->indexed()); Q_ASSERT(indexed.data() == context); ///This assertion fails if you call removeDocumentChain(..) on a document that has not been added to the du-chain context->m_dynamicData->deleteOnDisk(); Q_ASSERT(indexed.data() == context); sdDUChainPrivate->removeDocumentChainFromMemory(context); Q_ASSERT(!indexed.data()); Q_ASSERT(!environmentFileForDocument(indexed)); QMutexLocker lock(&sdDUChainPrivate->m_chainsMutex); sdDUChainPrivate->m_availableTopContextIndices.push_back(indexed.index()); } void DUChain::addDocumentChain( TopDUContext * chain ) { QMutexLocker l(&sdDUChainPrivate->m_chainsMutex); // qCDebug(LANGUAGE) << "duchain: adding document" << chain->url().str() << " " << chain; Q_ASSERT(chain); Q_ASSERT(!sdDUChainPrivate->hasChainForIndex(chain->ownIndex())); { QMutexLocker lock(&DUChain::chainsByIndexLock); if(DUChain::chainsByIndex.size() <= chain->ownIndex()) DUChain::chainsByIndex.resize(chain->ownIndex() + 100, nullptr); DUChain::chainsByIndex[chain->ownIndex()] = chain; } { Q_ASSERT(DUChain::chainsByIndex[chain->ownIndex()]); } Q_ASSERT(sdDUChainPrivate->hasChainForIndex(chain->ownIndex())); sdDUChainPrivate->m_chainsByUrl.insert(chain->url(), chain); Q_ASSERT(sdDUChainPrivate->hasChainForIndex(chain->ownIndex())); chain->setInDuChain(true); l.unlock(); addToEnvironmentManager(chain); // This function might be called during shutdown by stale parse jobs // Make sure we don't access null-pointers here if (ICore::self() && ICore::self()->languageController() && ICore::self()->languageController()->backgroundParser()->trackerForUrl(chain->url())) { //Make sure the context stays alive at least as long as the context is open ReferencedTopDUContext ctx(chain); sdDUChainPrivate->m_openDocumentContexts.insert(ctx); } } void DUChain::addToEnvironmentManager( TopDUContext * chain ) { ParsingEnvironmentFilePointer file = chain->parsingEnvironmentFile(); if( !file ) return; //We don't need to manage Q_ASSERT(file->indexedTopContext().index() == chain->ownIndex()); if(ParsingEnvironmentFile* alreadyHave = sdDUChainPrivate->findInformation(file->indexedTopContext().index())) { ///If this triggers, there has already been another environment-information registered for this top-context. ///removeFromEnvironmentManager should have been called before to remove the old environment-information. Q_ASSERT(alreadyHave == file.data()); Q_UNUSED(alreadyHave); return; } sdDUChainPrivate->addEnvironmentInformation(file); } void DUChain::removeFromEnvironmentManager( TopDUContext * chain ) { ParsingEnvironmentFilePointer file = chain->parsingEnvironmentFile(); if( !file ) return; //We don't need to manage sdDUChainPrivate->removeEnvironmentInformation(file); } TopDUContext* DUChain::chainForDocument(const QUrl& document, bool proxyContext) const { return chainForDocument(IndexedString(document), proxyContext); } bool DUChain::isInMemory(uint topContextIndex) const { return DUChainPrivate::hasChainForIndex(topContextIndex); } IndexedString DUChain::urlForIndex(uint index) const { { TopDUContext* chain = DUChainPrivate::readChainForIndex(index); if(chain) return chain->url(); } return TopDUContextDynamicData::loadUrl(index); } TopDUContext* DUChain::loadChain(uint index) { QSet loaded; sdDUChainPrivate->loadChain(index, loaded); { QMutexLocker lock(&chainsByIndexLock); if(chainsByIndex.size() > index) { TopDUContext* top = chainsByIndex[index]; if(top) return top; } } return nullptr; } TopDUContext* DUChain::chainForDocument(const KDevelop::IndexedString& document, bool proxyContext) const { ENSURE_CHAIN_READ_LOCKED; if(sdDUChainPrivate->m_destroyed) return nullptr; QList list = sdDUChainPrivate->getEnvironmentInformation(document); foreach(const ParsingEnvironmentFilePointer &file, list) if(isInMemory(file->indexedTopContext().index()) && file->isProxyContext() == proxyContext) { return file->topContext(); } foreach(const ParsingEnvironmentFilePointer &file, list) if(proxyContext == file->isProxyContext()) { return file->topContext(); } //Allow selecting a top-context even if there is no ParsingEnvironmentFile QList< TopDUContext* > ret = chainsForDocument(document); foreach(TopDUContext* ctx, ret) { if(!ctx->parsingEnvironmentFile() || (ctx->parsingEnvironmentFile()->isProxyContext() == proxyContext)) return ctx; } return nullptr; } QList DUChain::chainsForDocument(const QUrl& document) const { return chainsForDocument(IndexedString(document)); } QList DUChain::chainsForDocument(const IndexedString& document) const { QList chains; if(sdDUChainPrivate->m_destroyed) return chains; QMutexLocker l(&sdDUChainPrivate->m_chainsMutex); // Match all parsed versions of this document for (auto it = sdDUChainPrivate->m_chainsByUrl.lowerBound(document); it != sdDUChainPrivate->m_chainsByUrl.end(); ++it) { if (it.key() == document) chains << it.value(); else break; } return chains; } TopDUContext* DUChain::chainForDocument( const QUrl& document, const KDevelop::ParsingEnvironment* environment, bool proxyContext ) const { return chainForDocument( IndexedString(document), environment, proxyContext ); } ParsingEnvironmentFilePointer DUChain::environmentFileForDocument( const IndexedString& document, const ParsingEnvironment* environment, bool proxyContext ) const { ENSURE_CHAIN_READ_LOCKED; if(sdDUChainPrivate->m_destroyed) return ParsingEnvironmentFilePointer(); QList< ParsingEnvironmentFilePointer> list = sdDUChainPrivate->getEnvironmentInformation(document); // qCDebug(LANGUAGE) << document.str() << ": matching" << list.size() << (onlyProxyContexts ? "proxy-contexts" : (noProxyContexts ? "content-contexts" : "contexts")); auto it = list.constBegin(); while(it != list.constEnd()) { if(*it && ((*it)->isProxyContext() == proxyContext) && (*it)->matchEnvironment(environment) && // Verify that the environment-file and its top-context are "good": The top-context must exist, // and there must be a content-context associated to the proxy-context. (*it)->topContext() && (!proxyContext || DUChainUtils::contentContextFromProxyContext((*it)->topContext())) ) { return *it; } ++it; } return ParsingEnvironmentFilePointer(); } QList DUChain::allEnvironmentFiles(const IndexedString& document) { return sdDUChainPrivate->getEnvironmentInformation(document); } ParsingEnvironmentFilePointer DUChain::environmentFileForDocument(IndexedTopDUContext topContext) const { if(topContext.index() == 0) return ParsingEnvironmentFilePointer(); return ParsingEnvironmentFilePointer(sdDUChainPrivate->loadInformation(topContext.index())); } TopDUContext* DUChain::chainForDocument( const IndexedString& document, const ParsingEnvironment* environment, bool proxyContext ) const { if(sdDUChainPrivate->m_destroyed) return nullptr; ParsingEnvironmentFilePointer envFile = environmentFileForDocument(document, environment, proxyContext); if(envFile) { return envFile->topContext(); }else{ return nullptr; } } QList DUChain::documents() const { QMutexLocker l(&sdDUChainPrivate->m_chainsMutex); QList ret; ret.reserve(sdDUChainPrivate->m_chainsByUrl.count()); foreach(TopDUContext* top, sdDUChainPrivate->m_chainsByUrl) { ret << top->url().toUrl(); } return ret; } QList DUChain::indexedDocuments() const { QMutexLocker l(&sdDUChainPrivate->m_chainsMutex); QList ret; ret.reserve(sdDUChainPrivate->m_chainsByUrl.count()); foreach(TopDUContext* top, sdDUChainPrivate->m_chainsByUrl) { ret << top->url(); } return ret; } void DUChain::documentActivated(KDevelop::IDocument* doc) { if(sdDUChainPrivate->m_destroyed) return; DUChainReadLocker lock( DUChain::lock() ); QMutexLocker l(&sdDUChainPrivate->m_chainsMutex); auto backgroundParser = ICore::self()->languageController()->backgroundParser(); auto addWithHighPriority = [backgroundParser, doc]() { backgroundParser->addDocument(IndexedString(doc->url()), TopDUContext::VisibleDeclarationsAndContexts, BackgroundParser::BestPriority); }; TopDUContext* ctx = DUChainUtils::standardContextForUrl(doc->url(), true); //Check whether the document has an attached environment-manager, and whether that one thinks the document needs to be updated. //If yes, update it. if (ctx && ctx->parsingEnvironmentFile() && ctx->parsingEnvironmentFile()->needsUpdate()) { qCDebug(LANGUAGE) << "Document needs update, using best priority since it just got activated:" << doc->url(); addWithHighPriority(); } else if (backgroundParser->managedDocuments().contains(IndexedString(doc->url()))) { // increase priority if there's already parse job of this document in the queue qCDebug(LANGUAGE) << "Prioritizing activated document:" << doc->url(); addWithHighPriority(); } } void DUChain::documentClosed(IDocument* document) { if(sdDUChainPrivate->m_destroyed) return; IndexedString url(document->url()); foreach(const ReferencedTopDUContext &top, sdDUChainPrivate->m_openDocumentContexts) if(top->url() == url) sdDUChainPrivate->m_openDocumentContexts.remove(top); } void DUChain::documentLoadedPrepare(KDevelop::IDocument* doc) { if(sdDUChainPrivate->m_destroyed) return; const IndexedString url(doc->url()); DUChainWriteLocker lock( DUChain::lock() ); QMutexLocker l(&sdDUChainPrivate->m_chainsMutex); TopDUContext* standardContext = DUChainUtils::standardContextForUrl(doc->url()); QList chains = chainsForDocument(url); auto languages = ICore::self()->languageController()->languagesForUrl(doc->url()); if(standardContext) { Q_ASSERT(chains.contains(standardContext)); //We have just loaded it Q_ASSERT((standardContext->url() == url)); sdDUChainPrivate->m_openDocumentContexts.insert(standardContext); bool needsUpdate = standardContext->parsingEnvironmentFile() && standardContext->parsingEnvironmentFile()->needsUpdate(); if(!needsUpdate) { //Only apply the highlighting if we don't need to update, else we might highlight total crap //Do instant highlighting only if all imports are loaded, to make sure that we don't block the user-interface too long //Else the highlighting will be done in the background-thread //This is not exactly right, as the direct imports don't necessarily equal the real imports used by uses //but it approximates the correct behavior. bool allImportsLoaded = true; foreach(const DUContext::Import& import, standardContext->importedParentContexts()) if(!import.indexedContext().indexedTopContext().isLoaded()) allImportsLoaded = false; if(allImportsLoaded) { l.unlock(); lock.unlock(); foreach(const auto language, languages) { if(language->codeHighlighting()) { language->codeHighlighting()->highlightDUChain(standardContext); } } qCDebug(LANGUAGE) << "highlighted" << doc->url() << "in foreground"; return; } }else{ qCDebug(LANGUAGE) << "not highlighting the duchain because the documents needs an update"; } if(needsUpdate || !(standardContext->features() & TopDUContext::AllDeclarationsContextsAndUses)) { ICore::self()->languageController()->backgroundParser()->addDocument(IndexedString(doc->url()), (TopDUContext::Features)(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdate)); return; } } //Add for highlighting etc. ICore::self()->languageController()->backgroundParser()->addDocument(IndexedString(doc->url()), TopDUContext::AllDeclarationsContextsAndUses); } void DUChain::documentRenamed(KDevelop::IDocument* doc) { if(sdDUChainPrivate->m_destroyed) return; if(!doc->url().isValid()) { ///Maybe this happens when a file was deleted? qCWarning(LANGUAGE) << "Strange, url of renamed document is invalid!"; }else{ ICore::self()->languageController()->backgroundParser()->addDocument(IndexedString(doc->url()), (TopDUContext::Features)(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdate)); } } Uses* DUChain::uses() { return &sdDUChainPrivate->m_uses; } Definitions* DUChain::definitions() { return &sdDUChainPrivate->m_definitions; } static void finalCleanup() { DUChainWriteLocker writeLock(DUChain::lock()); qCDebug(LANGUAGE) << "doing final cleanup"; int cleaned = 0; while((cleaned = globalItemRepositoryRegistry().finalCleanup())) { qCDebug(LANGUAGE) << "cleaned" << cleaned << "B"; if(cleaned < 1000) { qCDebug(LANGUAGE) << "cleaned enough"; break; } } qCDebug(LANGUAGE) << "final cleanup ready"; } void DUChain::shutdown() { // if core is not shutting down, we can end up in deadlocks or crashes // since language plugins might still try to access static duchain stuff Q_ASSERT(!ICore::self() || ICore::self()->shuttingDown()); qCDebug(LANGUAGE) << "Cleaning up and shutting down DUChain"; QMutexLocker lock(&sdDUChainPrivate->cleanupMutex()); { //Acquire write-lock of the repository, so when kdevelop crashes in that process, the repository is discarded //Crashes here may happen in an inconsistent state, thus this makes sense, to protect the user from more crashes globalItemRepositoryRegistry().lockForWriting(); sdDUChainPrivate->cleanupTopContexts(); globalItemRepositoryRegistry().unlockForWriting(); } sdDUChainPrivate->doMoreCleanup(); //Must be done _before_ finalCleanup, else we may be deleting yet needed data sdDUChainPrivate->m_openDocumentContexts.clear(); sdDUChainPrivate->m_destroyed = true; sdDUChainPrivate->clear(); { //Acquire write-lock of the repository, so when kdevelop crashes in that process, the repository is discarded //Crashes here may happen in an inconsistent state, thus this makes sense, to protect the user from more crashes globalItemRepositoryRegistry().lockForWriting(); finalCleanup(); globalItemRepositoryRegistry().unlockForWriting(); } globalItemRepositoryRegistry().shutdown(); } uint DUChain::newTopContextIndex() { { QMutexLocker lock(&sdDUChainPrivate->m_chainsMutex); if(!sdDUChainPrivate->m_availableTopContextIndices.isEmpty()) { uint ret = sdDUChainPrivate->m_availableTopContextIndices.back(); sdDUChainPrivate->m_availableTopContextIndices.pop_back(); if(TopDUContextDynamicData::fileExists(ret)) { qCWarning(LANGUAGE) << "Problem in the management of availalbe top-context indices"; return newTopContextIndex(); } return ret; } } static QAtomicInt& currentId( globalItemRepositoryRegistry().getCustomCounter(QStringLiteral("Top-Context Counter"), 1) ); return currentId.fetchAndAddRelaxed(1); } void DUChain::refCountUp(TopDUContext* top) { QMutexLocker l(&sdDUChainPrivate->m_referenceCountsMutex); // note: value is default-constructed to zero if it does not exist ++sdDUChainPrivate->m_referenceCounts[top]; } bool DUChain::deleted() { return m_deleted; } void DUChain::refCountDown(TopDUContext* top) { QMutexLocker l(&sdDUChainPrivate->m_referenceCountsMutex); auto it = sdDUChainPrivate->m_referenceCounts.find(top); if (it == sdDUChainPrivate->m_referenceCounts.end()) { //qCWarning(LANGUAGE) << "tried to decrease reference-count for" << top->url().str() << "but this top-context is not referenced"; return; } auto& refCount = *it; --refCount; if (!refCount) { sdDUChainPrivate->m_referenceCounts.erase(it); } } void DUChain::emitDeclarationSelected(const DeclarationPointer& decl) { if(sdDUChainPrivate->m_destroyed) return; emit declarationSelected(decl); } void DUChain::emitUpdateReady(const IndexedString& url, const ReferencedTopDUContext& topContext) { if(sdDUChainPrivate->m_destroyed) return; emit updateReady(url, topContext); } KDevelop::ReferencedTopDUContext DUChain::waitForUpdate(const KDevelop::IndexedString& document, KDevelop::TopDUContext::Features minFeatures, bool proxyContext) { Q_ASSERT(!lock()->currentThreadHasReadLock() && !lock()->currentThreadHasWriteLock()); WaitForUpdate waiter; updateContextForUrl(document, minFeatures, &waiter); // waiter.m_waitMutex.lock(); // waiter.m_dataMutex.unlock(); while(!waiter.m_ready) { // we might have been shut down in the meanwhile if (!ICore::self()) { return nullptr; } QMetaObject::invokeMethod(ICore::self()->languageController()->backgroundParser(), "parseDocuments"); QApplication::processEvents(); QThread::usleep(1000); } if(!proxyContext) { DUChainReadLocker readLock(DUChain::lock()); return DUChainUtils::contentContextFromProxyContext(waiter.m_topContext); } return waiter.m_topContext; } void DUChain::updateContextForUrl(const IndexedString& document, TopDUContext::Features minFeatures, QObject* notifyReady, int priority) const { DUChainReadLocker lock( DUChain::lock() ); TopDUContext* standardContext = DUChainUtils::standardContextForUrl(document.toUrl()); if(standardContext && standardContext->parsingEnvironmentFile() && !standardContext->parsingEnvironmentFile()->needsUpdate() && standardContext->parsingEnvironmentFile()->featuresSatisfied(minFeatures)) { lock.unlock(); if(notifyReady) QMetaObject::invokeMethod(notifyReady, "updateReady", Qt::DirectConnection, Q_ARG(KDevelop::IndexedString, document), Q_ARG(KDevelop::ReferencedTopDUContext, ReferencedTopDUContext(standardContext))); }else{ ///Start a parse-job for the given document ICore::self()->languageController()->backgroundParser()->addDocument(document, minFeatures, priority, notifyReady); } } void DUChain::disablePersistentStorage(bool disable) { sdDUChainPrivate->m_cleanupDisabled = disable; } void DUChain::storeToDisk() { bool wasDisabled = sdDUChainPrivate->m_cleanupDisabled; sdDUChainPrivate->m_cleanupDisabled = false; sdDUChainPrivate->doMoreCleanup(); sdDUChainPrivate->m_cleanupDisabled = wasDisabled; } bool DUChain::compareToDisk() { DUChainWriteLocker writeLock(DUChain::lock()); ///Step 1: Compare the repositories return true; } } diff --git a/language/duchain/duchainutils.cpp b/language/duchain/duchainutils.cpp index 0051afd90..d98d307b2 100644 --- a/language/duchain/duchainutils.cpp +++ b/language/duchain/duchainutils.cpp @@ -1,627 +1,627 @@ /* * DUChain Utilities * * Copyright 2007 Hamish Rodda * Copyright 2007-2008 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "duchainutils.h" #include #include #include #include "../interfaces/ilanguagesupport.h" #include "../assistant/staticassistantsmanager.h" -#include "util/debug.h" +#include #include "declaration.h" #include "classfunctiondeclaration.h" #include "ducontext.h" #include "duchain.h" #include "use.h" #include "duchainlock.h" #include "classmemberdeclaration.h" #include "functiondefinition.h" #include "specializationstore.h" #include "persistentsymboltable.h" #include "classdeclaration.h" #include "parsingenvironment.h" #include using namespace KDevelop; using namespace KTextEditor; CodeCompletionModel::CompletionProperties DUChainUtils::completionProperties(const Declaration* dec) { CodeCompletionModel::CompletionProperties p; if(dec->context()->type() == DUContext::Class) { if (const ClassMemberDeclaration* member = dynamic_cast(dec)) { switch (member->accessPolicy()) { case Declaration::Public: p |= CodeCompletionModel::Public; break; case Declaration::Protected: p |= CodeCompletionModel::Protected; break; case Declaration::Private: p |= CodeCompletionModel::Private; break; default: break; } if (member->isStatic()) p |= CodeCompletionModel::Static; if (member->isAuto()) {}//TODO if (member->isFriend()) p |= CodeCompletionModel::Friend; if (member->isRegister()) {}//TODO if (member->isExtern()) {}//TODO if (member->isMutable()) {}//TODO } } if (const AbstractFunctionDeclaration* function = dynamic_cast(dec)) { p |= CodeCompletionModel::Function; if (function->isVirtual()) p |= CodeCompletionModel::Virtual; if (function->isInline()) p |= CodeCompletionModel::Inline; if (function->isExplicit()) {}//TODO } if( dec->isTypeAlias() ) p |= CodeCompletionModel::TypeAlias; if (dec->abstractType()) { switch (dec->abstractType()->whichType()) { case AbstractType::TypeIntegral: p |= CodeCompletionModel::Variable; break; case AbstractType::TypePointer: p |= CodeCompletionModel::Variable; break; case AbstractType::TypeReference: p |= CodeCompletionModel::Variable; break; case AbstractType::TypeFunction: p |= CodeCompletionModel::Function; break; case AbstractType::TypeStructure: p |= CodeCompletionModel::Class; break; case AbstractType::TypeArray: p |= CodeCompletionModel::Variable; break; case AbstractType::TypeEnumeration: p |= CodeCompletionModel::Enum; break; case AbstractType::TypeEnumerator: p |= CodeCompletionModel::Variable; break; case AbstractType::TypeAbstract: case AbstractType::TypeDelayed: case AbstractType::TypeUnsure: case AbstractType::TypeAlias: // TODO break; } if( dec->abstractType()->modifiers() & AbstractType::ConstModifier ) p |= CodeCompletionModel::Const; if( dec->kind() == Declaration::Instance && !dec->isFunctionDeclaration() ) p |= CodeCompletionModel::Variable; } if (dec->context()) { if( dec->context()->type() == DUContext::Global ) p |= CodeCompletionModel::GlobalScope; else if( dec->context()->type() == DUContext::Namespace ) p |= CodeCompletionModel::NamespaceScope; else if( dec->context()->type() != DUContext::Class && dec->context()->type() != DUContext::Enum ) p |= CodeCompletionModel::LocalScope; } return p; } /**We have to construct the item from the pixmap, else the icon will be marked as "load on demand", * and for some reason will be loaded every time it's used(this function returns a QIcon marked "load on demand" * each time this is called). And the loading is very slow. Seems like a bug somewhere, it cannot be ment to be that slow. */ #define RETURN_CACHED_ICON(name) {static QIcon icon(QIcon( \ QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kdevelop/pics/" name ".png"))\ ).pixmap(QSize(16, 16)));\ return icon;} QIcon DUChainUtils::iconForProperties(KTextEditor::CodeCompletionModel::CompletionProperties p) { if( (p & CodeCompletionModel::Variable) ) if( (p & CodeCompletionModel::Protected) ) RETURN_CACHED_ICON("CVprotected_var") else if( p & CodeCompletionModel::Private ) RETURN_CACHED_ICON("CVprivate_var") else RETURN_CACHED_ICON("CVpublic_var") else if( (p & CodeCompletionModel::Union) && (p & CodeCompletionModel::Protected) ) RETURN_CACHED_ICON("protected_union") else if( p & CodeCompletionModel::Enum ) if( p & CodeCompletionModel::Protected ) RETURN_CACHED_ICON("protected_enum") else if( p & CodeCompletionModel::Private ) RETURN_CACHED_ICON("private_enum") else RETURN_CACHED_ICON("enum") else if( p & CodeCompletionModel::Struct ) if( p & CodeCompletionModel::Private ) RETURN_CACHED_ICON("private_struct") else RETURN_CACHED_ICON("struct") else if( p & CodeCompletionModel::Slot ) if( p & CodeCompletionModel::Protected ) RETURN_CACHED_ICON("CVprotected_slot") else if( p & CodeCompletionModel::Private ) RETURN_CACHED_ICON("CVprivate_slot") else if(p & CodeCompletionModel::Public ) RETURN_CACHED_ICON("CVpublic_slot") else RETURN_CACHED_ICON("slot") else if( p & CodeCompletionModel::Signal ) if( p & CodeCompletionModel::Protected ) RETURN_CACHED_ICON("CVprotected_signal") else RETURN_CACHED_ICON("signal") else if( p & CodeCompletionModel::Class ) if( (p & CodeCompletionModel::Class) && (p & CodeCompletionModel::Protected) ) RETURN_CACHED_ICON("protected_class") else if( (p & CodeCompletionModel::Class) && (p & CodeCompletionModel::Private) ) RETURN_CACHED_ICON("private_class") else RETURN_CACHED_ICON("code-class") else if( p & CodeCompletionModel::Union ) if( p & CodeCompletionModel::Private ) RETURN_CACHED_ICON("private_union") else RETURN_CACHED_ICON("union") else if( p & CodeCompletionModel::TypeAlias ) if ((p & CodeCompletionModel::Const) /*|| (p & CodeCompletionModel::Volatile)*/) RETURN_CACHED_ICON("CVtypedef") else RETURN_CACHED_ICON("typedef") else if( p & CodeCompletionModel::Function ) { if( p & CodeCompletionModel::Protected ) RETURN_CACHED_ICON("protected_function") else if( p & CodeCompletionModel::Private ) RETURN_CACHED_ICON("private_function") else RETURN_CACHED_ICON("code-function") } if( p & CodeCompletionModel::Protected ) RETURN_CACHED_ICON("protected_field") else if( p & CodeCompletionModel::Private ) RETURN_CACHED_ICON("private_field") else RETURN_CACHED_ICON("field") return QIcon(); } QIcon DUChainUtils::iconForDeclaration(const Declaration* dec) { return iconForProperties(completionProperties(dec)); } TopDUContext* DUChainUtils::contentContextFromProxyContext(TopDUContext* top) { if(!top) return nullptr; if(top->parsingEnvironmentFile() && top->parsingEnvironmentFile()->isProxyContext()) { if(!top->importedParentContexts().isEmpty()) { DUContext* ctx = top->importedParentContexts().at(0).context(nullptr); if(!ctx) return nullptr; TopDUContext* ret = ctx->topContext(); if(!ret) return nullptr; if(ret->url() != top->url()) qCDebug(LANGUAGE) << "url-mismatch between content and proxy:" << top->url().toUrl() << ret->url().toUrl(); if(ret->url() == top->url() && !ret->parsingEnvironmentFile()->isProxyContext()) return ret; } else { qCDebug(LANGUAGE) << "Proxy-context imports no content-context"; } } else return top; return nullptr; } TopDUContext* DUChainUtils::standardContextForUrl(const QUrl& url, bool preferProxyContext) { KDevelop::TopDUContext* chosen = nullptr; auto languages = ICore::self()->languageController()->languagesForUrl(url); foreach(const auto language, languages) { if(!chosen) { chosen = language->standardContext(url, preferProxyContext); } } if(!chosen) chosen = DUChain::self()->chainForDocument(IndexedString(url), preferProxyContext); if(!chosen && preferProxyContext) return standardContextForUrl(url, false); // Fall back to a normal context return chosen; } struct ItemUnderCursorInternal { Declaration* declaration; DUContext* context; RangeInRevision range; }; ItemUnderCursorInternal itemUnderCursorInternal(const CursorInRevision& c, DUContext* ctx, RangeInRevision::ContainsBehavior behavior) { //Search all collapsed sub-contexts. In C++, those can contain declarations that have ranges out of the context foreach(DUContext* subCtx, ctx->childContexts()) { //This is a little hacky, but we need it in case of foreach macros and similar stuff if(subCtx->range().contains(c, behavior) || subCtx->range().isEmpty() || subCtx->range().start.line == c.line || subCtx->range().end.line == c.line) { ItemUnderCursorInternal sub = itemUnderCursorInternal(c, subCtx, behavior); if(sub.declaration) { return sub; } } } foreach(Declaration* decl, ctx->localDeclarations()) { if(decl->range().contains(c, behavior)) { return {decl, ctx, decl->range()}; } } //Try finding a use under the cursor for(int a = 0; a < ctx->usesCount(); ++a) { if(ctx->uses()[a].m_range.contains(c, behavior)) { return {ctx->topContext()->usedDeclarationForIndex(ctx->uses()[a].m_declarationIndex), ctx, ctx->uses()[a].m_range}; } } return {nullptr, nullptr, RangeInRevision()}; } DUChainUtils::ItemUnderCursor DUChainUtils::itemUnderCursor(const QUrl& url, const KTextEditor::Cursor& cursor) { KDevelop::TopDUContext* top = standardContextForUrl(url.adjusted(QUrl::NormalizePathSegments)); if(!top) { return {nullptr, nullptr, KTextEditor::Range()}; } ItemUnderCursorInternal decl = itemUnderCursorInternal(top->transformToLocalRevision(cursor), top, RangeInRevision::Default); if (decl.declaration == nullptr) { decl = itemUnderCursorInternal(top->transformToLocalRevision(cursor), top, RangeInRevision::IncludeBackEdge); } return {decl.declaration, decl.context, top->transformFromLocalRevision(decl.range)}; } Declaration* DUChainUtils::declarationForDefinition(Declaration* definition, TopDUContext* topContext) { if(!definition) return nullptr; if(!topContext) topContext = definition->topContext(); if(dynamic_cast(definition)) { Declaration* ret = static_cast(definition)->declaration(); if(ret) return ret; } return definition; } Declaration* DUChainUtils::declarationInLine(const KTextEditor::Cursor& _cursor, DUContext* ctx) { if(!ctx) return nullptr; CursorInRevision cursor = ctx->transformToLocalRevision(_cursor); foreach(Declaration* decl, ctx->localDeclarations()) { if(decl->range().start.line == cursor.line) return decl; DUContext* funCtx = getFunctionContext(decl); if(funCtx && funCtx->range().contains(cursor)) return decl; } foreach(DUContext* child, ctx->childContexts()){ Declaration* decl = declarationInLine(_cursor, child); if(decl) return decl; } return nullptr; } DUChainUtils::DUChainItemFilter::~DUChainItemFilter() { } void DUChainUtils::collectItems( DUContext* context, DUChainItemFilter& filter ) { QVector children = context->childContexts(); QVector localDeclarations = context->localDeclarations(); QVector::const_iterator childIt = children.constBegin(); QVector::const_iterator declIt = localDeclarations.constBegin(); while(childIt != children.constEnd() || declIt != localDeclarations.constEnd()) { DUContext* child = nullptr; if(childIt != children.constEnd()) child = *childIt; Declaration* decl = nullptr; if(declIt != localDeclarations.constEnd()) decl = *declIt; if(decl) { if(child && child->range().start.line >= decl->range().start.line) child = nullptr; } if(child) { if(decl && decl->range().start >= child->range().start) decl = nullptr; } if(decl) { if( filter.accept(decl) ) { //Action is done in the filter } ++declIt; continue; } if(child) { if( filter.accept(child) ) collectItems(child, filter); ++childIt; continue; } } } KDevelop::DUContext* DUChainUtils::getArgumentContext(KDevelop::Declaration* decl) { DUContext* internal = decl->internalContext(); if( !internal ) return nullptr; if( internal->type() == DUContext::Function ) return internal; foreach( const DUContext::Import &ctx, internal->importedParentContexts() ) { if( ctx.context(decl->topContext()) ) if( ctx.context(decl->topContext())->type() == DUContext::Function ) return ctx.context(decl->topContext()); } return nullptr; } QList DUChainUtils::collectAllVersions(Declaration* decl) { QList ret; ret << IndexedDeclaration(decl); if(decl->inSymbolTable()) { uint count; const IndexedDeclaration* allDeclarations; PersistentSymbolTable::self().declarations(decl->qualifiedIdentifier(), count, allDeclarations); for(uint a = 0; a < count; ++a) if(!(allDeclarations[a] == IndexedDeclaration(decl))) ret << allDeclarations[a]; } return ret; } static QList getInheritersInternal(const Declaration* decl, uint& maxAllowedSteps, bool collectVersions) { QList ret; if(!dynamic_cast(decl)) return ret; if(maxAllowedSteps == 0) return ret; if(decl->internalContext() && decl->internalContext()->type() == DUContext::Class) { foreach (const IndexedDUContext importer, decl->internalContext()->indexedImporters()) { DUContext* imp = importer.data(); if(!imp) continue; if(imp->type() == DUContext::Class && imp->owner()) ret << imp->owner(); --maxAllowedSteps; if(maxAllowedSteps == 0) return ret; } } if(collectVersions && decl->inSymbolTable()) { uint count; const IndexedDeclaration* allDeclarations; PersistentSymbolTable::self().declarations(decl->qualifiedIdentifier(), count, allDeclarations); for(uint a = 0; a < count; ++a) { ++maxAllowedSteps; if(allDeclarations[a].data() && allDeclarations[a].data() != decl) { ret += getInheritersInternal(allDeclarations[a].data(), maxAllowedSteps, false); } if(maxAllowedSteps == 0) return ret; } } return ret; } QList DUChainUtils::getInheriters(const Declaration* decl, uint& maxAllowedSteps, bool collectVersions) { auto inheriters = getInheritersInternal(decl, maxAllowedSteps, collectVersions); // remove duplicates std::sort(inheriters.begin(), inheriters.end()); inheriters.erase(std::unique(inheriters.begin(), inheriters.end()), inheriters.end()); return inheriters; } QList DUChainUtils::getOverriders(const Declaration* currentClass, const Declaration* overriddenDeclaration, uint& maxAllowedSteps) { QList ret; if(maxAllowedSteps == 0) return ret; if(currentClass != overriddenDeclaration->context()->owner() && currentClass->internalContext()) ret += currentClass->internalContext()->findLocalDeclarations(overriddenDeclaration->identifier(), CursorInRevision::invalid(), currentClass->topContext(), overriddenDeclaration->abstractType()); foreach(Declaration* inheriter, getInheriters(currentClass, maxAllowedSteps)) ret += getOverriders(inheriter, overriddenDeclaration, maxAllowedSteps); return ret; } static bool hasUse(DUContext* context, int usedDeclarationIndex) { if(usedDeclarationIndex == std::numeric_limits::max()) return false; for(int a = 0; a < context->usesCount(); ++a) if(context->uses()[a].m_declarationIndex == usedDeclarationIndex) return true; foreach(DUContext* child, context->childContexts()) if(hasUse(child, usedDeclarationIndex)) return true; return false; } bool DUChainUtils::contextHasUse(DUContext* context, Declaration* declaration) { return hasUse(context, context->topContext()->indexForUsedDeclaration(declaration, false)); } static uint countUses(DUContext* context, int usedDeclarationIndex) { if(usedDeclarationIndex == std::numeric_limits::max()) return 0; uint ret = 0; for(int a = 0; a < context->usesCount(); ++a) if(context->uses()[a].m_declarationIndex == usedDeclarationIndex) ++ret; foreach(DUContext* child, context->childContexts()) ret += countUses(child, usedDeclarationIndex); return ret; } uint DUChainUtils::contextCountUses(DUContext* context, Declaration* declaration) { return countUses(context, context->topContext()->indexForUsedDeclaration(declaration, false)); } Declaration* DUChainUtils::getOverridden(const Declaration* decl) { const ClassFunctionDeclaration* classFunDecl = dynamic_cast(decl); if(!classFunDecl || !classFunDecl->isVirtual()) return nullptr; QList decls; foreach(const DUContext::Import &import, decl->context()->importedParentContexts()) { DUContext* ctx = import.context(decl->topContext()); if(ctx) decls += ctx->findDeclarations(QualifiedIdentifier(decl->identifier()), CursorInRevision::invalid(), decl->abstractType(), decl->topContext(), DUContext::DontSearchInParent); } foreach(Declaration* found, decls) { const ClassFunctionDeclaration* foundClassFunDecl = dynamic_cast(found); if(foundClassFunDecl && foundClassFunDecl->isVirtual()) return found; } return nullptr; } DUContext* DUChainUtils::getFunctionContext(Declaration* decl) { DUContext* functionContext = decl->internalContext(); if(functionContext && functionContext->type() != DUContext::Function) { foreach(const DUContext::Import& import, functionContext->importedParentContexts()) { DUContext* ctx = import.context(decl->topContext()); if(ctx && ctx->type() == DUContext::Function) functionContext = ctx; } } if(functionContext && functionContext->type() == DUContext::Function) return functionContext; return nullptr; } QVector KDevelop::DUChainUtils::allProblemsForContext(KDevelop::ReferencedTopDUContext top) { QVector ret; Q_FOREACH ( const auto& p, top->problems() ) { ret << p; } Q_FOREACH ( const auto& p, ICore::self()->languageController()->staticAssistantsManager()->problemsForContext(top) ) { ret << p; } return ret; } diff --git a/language/duchain/ducontext.cpp b/language/duchain/ducontext.cpp index d5f7d736f..843f3f538 100644 --- a/language/duchain/ducontext.cpp +++ b/language/duchain/ducontext.cpp @@ -1,1707 +1,1707 @@ /* This is part of KDevelop Copyright 2006 Hamish Rodda Copyright 2007-2009 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "ducontext.h" #include #include #include #include #include "ducontextdata.h" #include "declaration.h" #include "duchain.h" #include "duchainlock.h" #include "use.h" #include "identifier.h" #include "topducontext.h" #include "persistentsymboltable.h" #include "aliasdeclaration.h" #include "namespacealiasdeclaration.h" #include "abstractfunctiondeclaration.h" #include "duchainregister.h" #include "topducontextdynamicdata.h" #include "importers.h" #include "uses.h" #include "navigation/abstractdeclarationnavigationcontext.h" #include "navigation/abstractnavigationwidget.h" #include "ducontextdynamicdata.h" -#include "util/debug.h" +#include // maximum depth for DUContext::findDeclarationsInternal searches const uint maxParentDepth = 20; using namespace KTextEditor; #ifndef NDEBUG #define ENSURE_CAN_WRITE_(x) {if(x->inDUChain()) { ENSURE_CHAIN_WRITE_LOCKED }} #define ENSURE_CAN_READ_(x) {if(x->inDUChain()) { ENSURE_CHAIN_READ_LOCKED }} #else #define ENSURE_CAN_WRITE_(x) #define ENSURE_CAN_READ_(x) #endif QDebug operator<<(QDebug dbg, const KDevelop::DUContext::Import& import) { QDebugStateSaver saver(dbg); dbg.nospace() << "Import(" << import.indexedContext().data() << ')'; return dbg; } namespace KDevelop { DEFINE_LIST_MEMBER_HASH(DUContextData, m_childContexts, LocalIndexedDUContext) DEFINE_LIST_MEMBER_HASH(DUContextData, m_importers, IndexedDUContext) DEFINE_LIST_MEMBER_HASH(DUContextData, m_importedContexts, DUContext::Import) DEFINE_LIST_MEMBER_HASH(DUContextData, m_localDeclarations, LocalIndexedDeclaration) DEFINE_LIST_MEMBER_HASH(DUContextData, m_uses, Use) REGISTER_DUCHAIN_ITEM(DUContext); DUChainVisitor::~DUChainVisitor() { } /** * We leak here, to prevent a possible crash during destruction, as the destructor * of Identifier is not safe to be called after the duchain has been destroyed */ const Identifier& globalImportIdentifier() { static const Identifier globalImportIdentifierObject(QStringLiteral("{...import...}")); return globalImportIdentifierObject; } const Identifier& globalAliasIdentifier() { static const Identifier globalAliasIdentifierObject(QStringLiteral("{...alias...}")); return globalAliasIdentifierObject; } const IndexedIdentifier& globalIndexedImportIdentifier() { static const IndexedIdentifier id(globalImportIdentifier()); return id; } const IndexedIdentifier& globalIndexedAliasIdentifier() { static const IndexedIdentifier id(globalAliasIdentifier()); return id; } void DUContext::rebuildDynamicData(DUContext* parent, uint ownIndex) { Q_ASSERT(!parent || ownIndex); m_dynamicData->m_topContext = parent ? parent->topContext() : static_cast(this); m_dynamicData->m_indexInTopContext = ownIndex; m_dynamicData->m_parentContext = DUContextPointer(parent); m_dynamicData->m_context = this; m_dynamicData->m_childContexts.clear(); m_dynamicData->m_childContexts.reserve(d_func()->m_childContextsSize()); FOREACH_FUNCTION(const LocalIndexedDUContext& ctx, d_func()->m_childContexts) { m_dynamicData->m_childContexts << ctx.data(m_dynamicData->m_topContext); } m_dynamicData->m_localDeclarations.clear(); m_dynamicData->m_localDeclarations.reserve(d_func()->m_localDeclarationsSize()); FOREACH_FUNCTION(const LocalIndexedDeclaration& idx, d_func()->m_localDeclarations) { auto declaration = idx.data(m_dynamicData->m_topContext); if (!declaration) { qCWarning(LANGUAGE) << "child declaration number" << idx.localIndex() << "of" << d_func_dynamic()->m_localDeclarationsSize() << "is invalid"; continue; } m_dynamicData->m_localDeclarations << declaration; } DUChainBase::rebuildDynamicData(parent, ownIndex); } DUContextData::DUContextData() : m_inSymbolTable(false) , m_anonymousInParent(false) , m_propagateDeclarations(false) { initializeAppendedLists(); } DUContextData::~DUContextData() { freeAppendedLists(); } DUContextData::DUContextData(const DUContextData& rhs) : DUChainBaseData(rhs) , m_inSymbolTable(rhs.m_inSymbolTable) , m_anonymousInParent(rhs.m_anonymousInParent) , m_propagateDeclarations(rhs.m_propagateDeclarations) { initializeAppendedLists(); copyListsFrom(rhs); m_scopeIdentifier = rhs.m_scopeIdentifier; m_contextType = rhs.m_contextType; m_owner = rhs.m_owner; } DUContextDynamicData::DUContextDynamicData(DUContext* d) : m_topContext(nullptr) , m_indexInTopContext(0) , m_context(d) { } void DUContextDynamicData::scopeIdentifier(bool includeClasses, QualifiedIdentifier& target) const { if (m_parentContext) m_parentContext->m_dynamicData->scopeIdentifier(includeClasses, target); if (includeClasses || d_func()->m_contextType != DUContext::Class) target += d_func()->m_scopeIdentifier; } bool DUContextDynamicData::imports(const DUContext* context, const TopDUContext* source, QSet* recursionGuard) const { if( this == context->m_dynamicData ) return true; if (recursionGuard->contains(this)) { return false; } recursionGuard->insert(this); FOREACH_FUNCTION( const DUContext::Import& ctx, d_func()->m_importedContexts ) { DUContext* import = ctx.context(source); if(import == context || (import && import->m_dynamicData->imports(context, source, recursionGuard))) return true; } return false; } inline bool isContextTemporary(uint index) { return index > (0xffffffff/2); } void DUContextDynamicData::addDeclaration( Declaration * newDeclaration ) { // The definition may not have its identifier set when it's assigned... // allow dupes here, TODO catch the error elsewhere //If this context is temporary, added declarations should be as well, and viceversa Q_ASSERT(isContextTemporary(m_indexInTopContext) == isContextTemporary(newDeclaration->ownIndex())); CursorInRevision start = newDeclaration->range().start; bool inserted = false; ///@todo Do binary search to find the position for (int i = m_localDeclarations.size() - 1; i >= 0; --i) { Declaration* child = m_localDeclarations[i]; Q_ASSERT(d_func()->m_localDeclarations()[i].data(m_topContext) == child); if(child == newDeclaration) return; //TODO: All declarations in a macro will have the same empty range, and just get appended //that may not be Good Enough in complex cases. if (start >= child->range().start) { m_localDeclarations.insert(i + 1, newDeclaration); d_func_dynamic()->m_localDeclarationsList().insert(i+1, newDeclaration); Q_ASSERT(d_func()->m_localDeclarations()[i+1].data(m_topContext) == newDeclaration); inserted = true; break; } } if (!inserted) { // We haven't found any child that is before this one, so prepend it m_localDeclarations.insert(0, newDeclaration); d_func_dynamic()->m_localDeclarationsList().insert(0, newDeclaration); Q_ASSERT(d_func()->m_localDeclarations()[0].data(m_topContext) == newDeclaration); } } bool DUContextDynamicData::removeDeclaration(Declaration* declaration) { const int idx = m_localDeclarations.indexOf(declaration); if (idx != -1) { Q_ASSERT(d_func()->m_localDeclarations()[idx].data(m_topContext) == declaration); m_localDeclarations.remove(idx); d_func_dynamic()->m_localDeclarationsList().remove(idx); return true; } else { Q_ASSERT(d_func_dynamic()->m_localDeclarationsList().indexOf(LocalIndexedDeclaration(declaration)) == -1); return false; } } void DUContextDynamicData::addChildContext( DUContext * context ) { // Internal, don't need to assert a lock Q_ASSERT(!context->m_dynamicData->m_parentContext || context->m_dynamicData->m_parentContext.data()->m_dynamicData == this ); LocalIndexedDUContext indexed(context->m_dynamicData->m_indexInTopContext); //If this context is temporary, added declarations should be as well, and viceversa Q_ASSERT(isContextTemporary(m_indexInTopContext) == isContextTemporary(indexed.localIndex())); bool inserted = false; int childCount = m_childContexts.size(); for (int i = childCount-1; i >= 0; --i) {///@todo Do binary search to find the position DUContext* child = m_childContexts[i]; Q_ASSERT(d_func_dynamic()->m_childContexts()[i] == LocalIndexedDUContext(child)); if (context == child) return; if (context->range().start >= child->range().start) { m_childContexts.insert(i+1, context); d_func_dynamic()->m_childContextsList().insert(i+1, indexed); context->m_dynamicData->m_parentContext = m_context; inserted = true; break; } } if( !inserted ) { m_childContexts.insert(0, context); d_func_dynamic()->m_childContextsList().insert(0, indexed); context->m_dynamicData->m_parentContext = m_context; } } bool DUContextDynamicData::removeChildContext( DUContext* context ) { // ENSURE_CAN_WRITE const int idx = m_childContexts.indexOf(context); if (idx != -1) { m_childContexts.remove(idx); Q_ASSERT(d_func()->m_childContexts()[idx] == LocalIndexedDUContext(context)); d_func_dynamic()->m_childContextsList().remove(idx); return true; } else { Q_ASSERT(d_func_dynamic()->m_childContextsList().indexOf(LocalIndexedDUContext(context)) == -1); return false; } } void DUContextDynamicData::addImportedChildContext( DUContext * context ) { // ENSURE_CAN_WRITE DUContext::Import import(m_context, context); if(import.isDirect()) { //Direct importers are registered directly within the data if(d_func_dynamic()->m_importersList().contains(IndexedDUContext(context))) { qCDebug(LANGUAGE) << m_context->scopeIdentifier(true).toString() << "importer added multiple times:" << context->scopeIdentifier(true).toString(); return; } d_func_dynamic()->m_importersList().append(context); }else{ //Indirect importers are registered separately Importers::self().addImporter(import.indirectDeclarationId(), IndexedDUContext(context)); } } //Can also be called with a context that is not in the list void DUContextDynamicData::removeImportedChildContext( DUContext * context ) { // ENSURE_CAN_WRITE DUContext::Import import(m_context, context); if(import.isDirect()) { d_func_dynamic()->m_importersList().removeOne(IndexedDUContext(context)); }else{ //Indirect importers are registered separately Importers::self().removeImporter(import.indirectDeclarationId(), IndexedDUContext(context)); } } int DUContext::depth() const { { if (!parentContext()) return 0; return parentContext()->depth() + 1; } } DUContext::DUContext(DUContextData& data) : DUChainBase(data) , m_dynamicData(new DUContextDynamicData(this)) { } DUContext::DUContext(const RangeInRevision& range, DUContext* parent, bool anonymous) : DUChainBase(*new DUContextData(), range) , m_dynamicData(new DUContextDynamicData(this)) { d_func_dynamic()->setClassId(this); if(parent) m_dynamicData->m_topContext = parent->topContext(); else m_dynamicData->m_topContext = static_cast(this); d_func_dynamic()->setClassId(this); DUCHAIN_D_DYNAMIC(DUContext); d->m_contextType = Other; m_dynamicData->m_parentContext = nullptr; d->m_anonymousInParent = anonymous; d->m_inSymbolTable = false; if (parent) { m_dynamicData->m_indexInTopContext = parent->topContext()->m_dynamicData->allocateContextIndex(this, parent->isAnonymous() || anonymous); Q_ASSERT(m_dynamicData->m_indexInTopContext); if( !anonymous ) parent->m_dynamicData->addChildContext(this); else m_dynamicData->m_parentContext = parent; } if(parent && !anonymous && parent->inSymbolTable()) setInSymbolTable(true); } bool DUContext::isAnonymous() const { return d_func()->m_anonymousInParent || (m_dynamicData->m_parentContext && m_dynamicData->m_parentContext->isAnonymous()); } DUContext::DUContext( DUContextData& dd, const RangeInRevision& range, DUContext * parent, bool anonymous ) : DUChainBase(dd, range) , m_dynamicData(new DUContextDynamicData(this)) { if(parent) m_dynamicData->m_topContext = parent->topContext(); else m_dynamicData->m_topContext = static_cast(this); DUCHAIN_D_DYNAMIC(DUContext); d->m_contextType = Other; m_dynamicData->m_parentContext = nullptr; d->m_inSymbolTable = false; d->m_anonymousInParent = anonymous; if (parent) { m_dynamicData->m_indexInTopContext = parent->topContext()->m_dynamicData->allocateContextIndex(this, parent->isAnonymous() || anonymous); if( !anonymous ) parent->m_dynamicData->addChildContext(this); else m_dynamicData->m_parentContext = parent; } } DUContext::DUContext(DUContext& useDataFrom) : DUChainBase(useDataFrom) , m_dynamicData(useDataFrom.m_dynamicData) { } DUContext::~DUContext( ) { TopDUContext* top = topContext(); if(!top->deleting() || !top->isOnDisk()) { DUCHAIN_D_DYNAMIC(DUContext); if(d->m_owner.declaration()) d->m_owner.declaration()->setInternalContext(nullptr); while( d->m_importersSize() != 0 ) { if(d->m_importers()[0].data()) d->m_importers()[0].data()->removeImportedParentContext(this); else { qCDebug(LANGUAGE) << "importer disappeared"; d->m_importersList().removeOne(d->m_importers()[0]); } } clearImportedParentContexts(); } deleteChildContextsRecursively(); if(!topContext()->deleting() || !topContext()->isOnDisk()) deleteUses(); deleteLocalDeclarations(); //If the top-context is being delete, we don't need to spend time rebuilding the inner structure. //That's expensive, especially when the data is not dynamic. if(!top->deleting() || !top->isOnDisk()) { if (m_dynamicData->m_parentContext) m_dynamicData->m_parentContext->m_dynamicData->removeChildContext(this); } top->m_dynamicData->clearContextIndex(this); Q_ASSERT(d_func()->isDynamic() == (!top->deleting() || !top->isOnDisk() || top->m_dynamicData->isTemporaryContextIndex(m_dynamicData->m_indexInTopContext))); delete m_dynamicData; } QVector< DUContext * > DUContext::childContexts( ) const { ENSURE_CAN_READ return m_dynamicData->m_childContexts; } Declaration* DUContext::owner() const { ENSURE_CAN_READ return d_func()->m_owner.declaration(); } void DUContext::setOwner(Declaration* owner) { ENSURE_CAN_WRITE DUCHAIN_D_DYNAMIC(DUContext); if( owner == d->m_owner.declaration() ) return; Declaration* oldOwner = d->m_owner.declaration(); d->m_owner = owner; //Q_ASSERT(!oldOwner || oldOwner->internalContext() == this); if( oldOwner && oldOwner->internalContext() == this ) oldOwner->setInternalContext(nullptr); //The context set as internal context should always be the last opened context if( owner ) owner->setInternalContext(this); } DUContext* DUContext::parentContext( ) const { //ENSURE_CAN_READ Commented out for performance reasons return m_dynamicData->m_parentContext.data(); } void DUContext::setPropagateDeclarations(bool propagate) { ENSURE_CAN_WRITE DUCHAIN_D_DYNAMIC(DUContext); if(propagate == d->m_propagateDeclarations) return; d->m_propagateDeclarations = propagate; } bool DUContext::isPropagateDeclarations() const { return d_func()->m_propagateDeclarations; } QList DUContext::findLocalDeclarations( const IndexedIdentifier& identifier, const CursorInRevision& position, const TopDUContext* topContext, const AbstractType::Ptr& dataType, SearchFlags flags ) const { ENSURE_CAN_READ DeclarationList ret; findLocalDeclarationsInternal(identifier, position.isValid() ? position : range().end, dataType, ret, topContext ? topContext : this->topContext(), flags); return ret; } QList DUContext::findLocalDeclarations( const Identifier& identifier, const CursorInRevision& position, const TopDUContext* topContext, const AbstractType::Ptr& dataType, SearchFlags flags ) const { return findLocalDeclarations(IndexedIdentifier(identifier), position, topContext, dataType, flags); } namespace { bool contextIsChildOrEqual(const DUContext* childContext, const DUContext* context) { if(childContext == context) return true; if(childContext->parentContext()) return contextIsChildOrEqual(childContext->parentContext(), context); else return false; } struct Checker { Checker(DUContext::SearchFlags flags, const AbstractType::Ptr& dataType, const CursorInRevision & position, DUContext::ContextType ownType) : m_flags(flags) , m_dataType(dataType) , m_position(position) , m_ownType(ownType) { } Declaration* check(Declaration* declaration) const { ///@todo This is C++-specific if (m_ownType != DUContext::Class && m_ownType != DUContext::Template && m_position.isValid() && m_position <= declaration->range().start) { return nullptr; } if (declaration->kind() == Declaration::Alias && !(m_flags & DUContext::DontResolveAliases)) { //Apply alias declarations AliasDeclaration* alias = static_cast(declaration); if (alias->aliasedDeclaration().isValid()) { declaration = alias->aliasedDeclaration().declaration(); } else { qCDebug(LANGUAGE) << "lost aliased declaration"; } } if (declaration->kind() == Declaration::NamespaceAlias && !(m_flags & DUContext::NoFiltering)) { return nullptr; } if ((m_flags & DUContext::OnlyFunctions) && !declaration->isFunctionDeclaration()) { return nullptr; } if (m_dataType && m_dataType->indexed() != declaration->indexedType()) { return nullptr; } return declaration; } DUContext::SearchFlags m_flags; const AbstractType::Ptr m_dataType; const CursorInRevision m_position; DUContext::ContextType m_ownType; }; } void DUContext::findLocalDeclarationsInternal(const Identifier& identifier, const CursorInRevision& position, const AbstractType::Ptr& dataType, DeclarationList& ret, const TopDUContext* source, SearchFlags flags) const { return findLocalDeclarationsInternal(IndexedIdentifier(identifier), position, dataType, ret, source, flags); } void DUContext::findLocalDeclarationsInternal( const IndexedIdentifier& identifier, const CursorInRevision & position, const AbstractType::Ptr& dataType, DeclarationList& ret, const TopDUContext* /*source*/, SearchFlags flags ) const { Checker checker(flags, dataType, position, type()); DUCHAIN_D(DUContext); if (d->m_inSymbolTable && !d->m_scopeIdentifier.isEmpty() && !identifier.isEmpty()) { //This context is in the symbol table, use the symbol-table to speed up the search QualifiedIdentifier id(scopeIdentifier(true) + identifier); TopDUContext* top = topContext(); uint count; const IndexedDeclaration* declarations; PersistentSymbolTable::self().declarations(id, count, declarations); for (uint a = 0; a < count; ++a) { ///@todo Eventually do efficient iteration-free filtering if (declarations[a].topContextIndex() == top->ownIndex()) { Declaration* decl = declarations[a].declaration(); if (decl && contextIsChildOrEqual(decl->context(), this)) { Declaration* checked = checker.check(decl); if (checked) { ret.append(checked); } } } } } else { //Iterate through all declarations DUContextDynamicData::VisibleDeclarationIterator it(m_dynamicData); while (it) { Declaration* declaration = *it; if (declaration && declaration->indexedIdentifier() == identifier) { Declaration* checked = checker.check(declaration); if (checked) ret.append(checked); } ++it; } } } bool DUContext::foundEnough( const DeclarationList& ret, SearchFlags flags ) const { if( !ret.isEmpty() && !(flags & DUContext::NoFiltering)) return true; else return false; } bool DUContext::findDeclarationsInternal( const SearchItem::PtrList & baseIdentifiers, const CursorInRevision & position, const AbstractType::Ptr& dataType, DeclarationList& ret, const TopDUContext* source, SearchFlags flags, uint depth ) const { if (depth > maxParentDepth) { qCDebug(LANGUAGE) << "maximum depth reached in" << scopeIdentifier(true); return false; } DUCHAIN_D(DUContext); if (d->m_contextType != Namespace) { // If we're in a namespace, delay all the searching into the top-context, because only that has the overview to pick the correct declarations. for (int a = 0; a < baseIdentifiers.size(); ++a) { if (!baseIdentifiers[a]->isExplicitlyGlobal && baseIdentifiers[a]->next.isEmpty()) { // It makes no sense searching locally for qualified identifiers findLocalDeclarationsInternal(baseIdentifiers[a]->identifier, position, dataType, ret, source, flags); } } if (foundEnough(ret, flags)) { return true; } } ///Step 1: Apply namespace-aliases and -imports SearchItem::PtrList aliasedIdentifiers; //Because of namespace-imports and aliases, this identifier may need to be searched under multiple names applyAliases(baseIdentifiers, aliasedIdentifiers, position, false, type() != DUContext::Namespace && type() != DUContext::Global); if (d->m_importedContextsSize() != 0) { ///Step 2: Give identifiers that are not marked as explicitly-global to imported contexts(explicitly global ones are treatead in TopDUContext) SearchItem::PtrList nonGlobalIdentifiers; foreach (const SearchItem::Ptr& identifier, aliasedIdentifiers) { if (!identifier->isExplicitlyGlobal) { nonGlobalIdentifiers << identifier; } } if (!nonGlobalIdentifiers.isEmpty()) { const auto& url = this->url(); for(int import = d->m_importedContextsSize()-1; import >= 0; --import ) { if (position.isValid() && d->m_importedContexts()[import].position.isValid() && position < d->m_importedContexts()[import].position) { continue; } DUContext* context = d->m_importedContexts()[import].context(source); if (!context) { continue; } else if (context == this) { qCDebug(LANGUAGE) << "resolved self as import:" << scopeIdentifier(true); continue; } if (!context->findDeclarationsInternal(nonGlobalIdentifiers, url == context->url() ? position : context->range().end, dataType, ret, source, flags | InImportedParentContext, depth+1)) { return false; } } } } if (foundEnough(ret, flags)) { return true; } ///Step 3: Continue search in parent-context if (!(flags & DontSearchInParent) && shouldSearchInParent(flags) && m_dynamicData->m_parentContext) { applyUpwardsAliases(aliasedIdentifiers, source); return m_dynamicData->m_parentContext->findDeclarationsInternal(aliasedIdentifiers, url() == m_dynamicData->m_parentContext->url() ? position : m_dynamicData->m_parentContext->range().end, dataType, ret, source, flags, depth); } return true; } QList< QualifiedIdentifier > DUContext::fullyApplyAliases(const QualifiedIdentifier& id, const TopDUContext* source) const { ENSURE_CAN_READ if(!source) source = topContext(); SearchItem::PtrList identifiers; identifiers << SearchItem::Ptr(new SearchItem(id)); const DUContext* current = this; while(current) { SearchItem::PtrList aliasedIdentifiers; current->applyAliases(identifiers, aliasedIdentifiers, CursorInRevision::invalid(), true, false); current->applyUpwardsAliases(identifiers, source); current = current->parentContext(); } QList ret; foreach (const SearchItem::Ptr& item, identifiers) ret += item->toList(); return ret; } QList DUContext::findDeclarations( const QualifiedIdentifier & identifier, const CursorInRevision & position, const AbstractType::Ptr& dataType, const TopDUContext* topContext, SearchFlags flags) const { ENSURE_CAN_READ DeclarationList ret; SearchItem::PtrList identifiers; // optimize: we don't want to allocate the top node always // so create it on stack but ref it so its not deleted by the smart pointer SearchItem item(identifier); item.ref.ref(); identifiers << SearchItem::Ptr(&item); findDeclarationsInternal(identifiers, position.isValid() ? position : range().end, dataType, ret, topContext ? topContext : this->topContext(), flags, 0); return ret; } bool DUContext::imports(const DUContext* origin, const CursorInRevision& /*position*/ ) const { ENSURE_CAN_READ QSet recursionGuard; recursionGuard.reserve(8); return m_dynamicData->imports(origin, topContext(), &recursionGuard); } bool DUContext::addIndirectImport(const DUContext::Import& import) { ENSURE_CAN_WRITE DUCHAIN_D_DYNAMIC(DUContext); for(unsigned int a = 0; a < d->m_importedContextsSize(); ++a) { if(d->m_importedContexts()[a] == import) { d->m_importedContextsList()[a].position = import.position; return true; } } ///Do not sort the imported contexts by their own line-number, it makes no sense. ///Contexts added first, aka template-contexts, should stay in first place, so they are searched first. d->m_importedContextsList().append(import); return false; } void DUContext::addImportedParentContext( DUContext * context, const CursorInRevision& position, bool anonymous, bool /*temporary*/ ) { ENSURE_CAN_WRITE if(context == this) { qCDebug(LANGUAGE) << "Tried to import self"; return; } if(!context) { qCDebug(LANGUAGE) << "Tried to import invalid context"; return; } Import import(context, this, position); if(addIndirectImport(import)) return; if( !anonymous ) { ENSURE_CAN_WRITE_(context) context->m_dynamicData->addImportedChildContext(this); } } void DUContext::removeImportedParentContext( DUContext * context ) { ENSURE_CAN_WRITE DUCHAIN_D_DYNAMIC(DUContext); Import import(context, this, CursorInRevision::invalid()); for(unsigned int a = 0; a < d->m_importedContextsSize(); ++a) { if(d->m_importedContexts()[a] == import) { d->m_importedContextsList().remove(a); break; } } if( !context ) return; context->m_dynamicData->removeImportedChildContext(this); } KDevVarLengthArray DUContext::indexedImporters() const { KDevVarLengthArray ret; if(owner()) ret = Importers::self().importers(owner()->id()); //Add indirect importers to the list FOREACH_FUNCTION(const IndexedDUContext& ctx, d_func()->m_importers) ret.append(ctx); return ret; } QVector DUContext::importers() const { ENSURE_CAN_READ QVector ret; FOREACH_FUNCTION(const IndexedDUContext& ctx, d_func()->m_importers) ret << ctx.context(); if(owner()) { //Add indirect importers to the list KDevVarLengthArray indirect = Importers::self().importers(owner()->id()); foreach (const IndexedDUContext ctx, indirect) { ret << ctx.context(); } } return ret; } DUContext * DUContext::findContext( const CursorInRevision& position, DUContext* parent) const { ENSURE_CAN_READ if (!parent) parent = const_cast(this); foreach (DUContext* context, parent->m_dynamicData->m_childContexts) { if (context->range().contains(position)) { DUContext* ret = findContext(position, context); if (!ret) { ret = context; } return ret; } } return nullptr; } bool DUContext::parentContextOf(DUContext* context) const { if (this == context) return true; foreach (DUContext* child, m_dynamicData->m_childContexts) { if (child->parentContextOf(context)) { return true; } } return false; } QList< QPair > DUContext::allDeclarations(const CursorInRevision& position, const TopDUContext* topContext, bool searchInParents) const { ENSURE_CAN_READ QList< QPair > ret; QHash hadContexts; // Iterate back up the chain mergeDeclarationsInternal(ret, position, hadContexts, topContext ? topContext : this->topContext(), searchInParents); return ret; } QVector DUContext::localDeclarations(const TopDUContext* source) const { ENSURE_CAN_READ // TODO: remove this parameter once we kill old-cpp Q_UNUSED(source); return m_dynamicData->m_localDeclarations; } void DUContext::mergeDeclarationsInternal(QList< QPair >& definitions, const CursorInRevision& position, QHash& hadContexts, const TopDUContext* source, bool searchInParents, int currentDepth) const { ENSURE_CAN_READ if((currentDepth > 300 && currentDepth < 1000) || currentDepth > 1300) { qCDebug(LANGUAGE) << "too much depth"; return; } DUCHAIN_D(DUContext); if(hadContexts.contains(this) && !searchInParents) return; if(!hadContexts.contains(this)) { hadContexts[this] = true; if( (type() == DUContext::Namespace || type() == DUContext::Global) && currentDepth < 1000 ) currentDepth += 1000; { DUContextDynamicData::VisibleDeclarationIterator it(m_dynamicData); while(it) { Declaration* decl = *it; if ( decl && (!position.isValid() || decl->range().start <= position) ) definitions << qMakePair(decl, currentDepth); ++it; } } for(int a = d->m_importedContextsSize()-1; a >= 0; --a) { const Import* import(&d->m_importedContexts()[a]); DUContext* context = import->context(source); while( !context && a > 0 ) { --a; import = &d->m_importedContexts()[a]; context = import->context(source); } if( !context ) break; if(context == this) { qCDebug(LANGUAGE) << "resolved self as import:" << scopeIdentifier(true); continue; } if( position.isValid() && import->position.isValid() && position < import->position ) continue; context->mergeDeclarationsInternal(definitions, CursorInRevision::invalid(), hadContexts, source, searchInParents && context->shouldSearchInParent(InImportedParentContext) && context->parentContext()->type() == DUContext::Helper, currentDepth+1); } } ///Only respect the position if the parent-context is not a class(@todo this is language-dependent) if (parentContext() && searchInParents ) parentContext()->mergeDeclarationsInternal(definitions, parentContext()->type() == DUContext::Class ? parentContext()->range().end : position, hadContexts, source, searchInParents, currentDepth+1); } void DUContext::deleteLocalDeclarations() { ENSURE_CAN_WRITE // It may happen that the deletion of one declaration triggers the deletion of another one // Therefore we copy the list of indexed declarations and work on those. Indexed declarations // will return zero for already deleted declarations. KDevVarLengthArray indexedLocal; if (d_func()->m_localDeclarations()) { indexedLocal.append(d_func()->m_localDeclarations(), d_func()->m_localDeclarationsSize()); } foreach (const LocalIndexedDeclaration& indexed, m_dynamicData->m_localDeclarations) { delete indexed.data(topContext()); } m_dynamicData->m_localDeclarations.clear(); } void DUContext::deleteChildContextsRecursively() { ENSURE_CAN_WRITE // note: don't use qDeleteAll here because child ctx deletion changes m_dynamicData->m_childContexts // also note: foreach iterates on a copy, so this is safe foreach (DUContext* ctx, m_dynamicData->m_childContexts) { delete ctx; } m_dynamicData->m_childContexts.clear(); } QVector DUContext::clearLocalDeclarations( ) { auto copy = m_dynamicData->m_localDeclarations; foreach (Declaration* dec, copy) { dec->setContext(nullptr); } return copy; } QualifiedIdentifier DUContext::scopeIdentifier(bool includeClasses) const { ENSURE_CAN_READ QualifiedIdentifier ret; m_dynamicData->scopeIdentifier(includeClasses, ret); return ret; } bool DUContext::equalScopeIdentifier(const DUContext* rhs) const { ENSURE_CAN_READ const DUContext* left = this; const DUContext* right = rhs; while(left || right) { if(!left || !right) return false; if(!(left->d_func()->m_scopeIdentifier == right->d_func()->m_scopeIdentifier)) return false; left = left->parentContext(); right = right->parentContext(); } return true; } void DUContext::setLocalScopeIdentifier(const QualifiedIdentifier & identifier) { ENSURE_CAN_WRITE bool wasInSymbolTable = inSymbolTable(); setInSymbolTable(false); d_func_dynamic()->m_scopeIdentifier = identifier; setInSymbolTable(wasInSymbolTable); } QualifiedIdentifier DUContext::localScopeIdentifier() const { //ENSURE_CAN_READ Commented out for performance reasons return d_func()->m_scopeIdentifier; } IndexedQualifiedIdentifier DUContext::indexedLocalScopeIdentifier() const { return d_func()->m_scopeIdentifier; } DUContext::ContextType DUContext::type() const { //ENSURE_CAN_READ This is disabled, because type() is called very often while searching, and it costs us performance return d_func()->m_contextType; } void DUContext::setType(ContextType type) { ENSURE_CAN_WRITE d_func_dynamic()->m_contextType = type; } QList DUContext::findDeclarations(const Identifier& identifier, const CursorInRevision& position, const TopDUContext* topContext, SearchFlags flags) const { return findDeclarations(IndexedIdentifier(identifier), position, topContext, flags); } QList DUContext::findDeclarations(const IndexedIdentifier& identifier, const CursorInRevision& position, const TopDUContext* topContext, SearchFlags flags) const { ENSURE_CAN_READ DeclarationList ret; SearchItem::PtrList identifiers; identifiers << SearchItem::Ptr(new SearchItem(false, identifier, SearchItem::PtrList())); findDeclarationsInternal(identifiers, position.isValid() ? position : range().end, AbstractType::Ptr(), ret, topContext ? topContext : this->topContext(), flags, 0); return ret; } void DUContext::deleteUse(int index) { ENSURE_CAN_WRITE DUCHAIN_D_DYNAMIC(DUContext); d->m_usesList().remove(index); } void DUContext::deleteUses() { ENSURE_CAN_WRITE DUCHAIN_D_DYNAMIC(DUContext); d->m_usesList().clear(); } void DUContext::deleteUsesRecursively() { deleteUses(); foreach (DUContext* childContext, m_dynamicData->m_childContexts) { childContext->deleteUsesRecursively(); } } bool DUContext::inDUChain() const { if( d_func()->m_anonymousInParent || !m_dynamicData->m_parentContext) return false; TopDUContext* top = topContext(); return top && top->inDUChain(); } DUContext* DUContext::specialize(const IndexedInstantiationInformation& /*specialization*/, const TopDUContext* topContext, int /*upDistance*/) { if(!topContext) return nullptr; return this; } CursorInRevision DUContext::importPosition(const DUContext* target) const { ENSURE_CAN_READ DUCHAIN_D(DUContext); Import import(const_cast(target), this, CursorInRevision::invalid()); for(unsigned int a = 0; a < d->m_importedContextsSize(); ++a) if(d->m_importedContexts()[a] == import) return d->m_importedContexts()[a].position; return CursorInRevision::invalid(); } QVector DUContext::importedParentContexts() const { ENSURE_CAN_READ QVector ret; ret.reserve(d_func()->m_importedContextsSize()); FOREACH_FUNCTION(const DUContext::Import& import, d_func()->m_importedContexts) ret << import; return ret; } void DUContext::applyAliases(const SearchItem::PtrList& baseIdentifiers, SearchItem::PtrList& identifiers, const CursorInRevision& position, bool canBeNamespace, bool onlyImports) const { DeclarationList imports; findLocalDeclarationsInternal(globalIndexedImportIdentifier(), position, AbstractType::Ptr(), imports, topContext(), DUContext::NoFiltering); if(imports.isEmpty() && onlyImports) { identifiers = baseIdentifiers; return; } for ( const SearchItem::Ptr& identifier : baseIdentifiers ) { bool addUnmodified = true; if( !identifier->isExplicitlyGlobal ) { if( !imports.isEmpty() ) { //We have namespace-imports. foreach ( Declaration* importDecl, imports ) { //Search for the identifier with the import-identifier prepended if(dynamic_cast(importDecl)) { NamespaceAliasDeclaration* alias = static_cast(importDecl); identifiers.append( SearchItem::Ptr( new SearchItem( alias->importIdentifier(), identifier ) ) ) ; }else{ qCDebug(LANGUAGE) << "Declaration with namespace alias identifier has the wrong type" << importDecl->url().str() << importDecl->range().castToSimpleRange(); } } } if( !identifier->isEmpty() && (identifier->hasNext() || canBeNamespace) ) { DeclarationList aliases; findLocalDeclarationsInternal(identifier->identifier, position, AbstractType::Ptr(), imports, nullptr, DUContext::NoFiltering); if(!aliases.isEmpty()) { //The first part of the identifier has been found as a namespace-alias. //In c++, we only need the first alias. However, just to be correct, follow them all for now. foreach ( Declaration* aliasDecl, aliases ) { if(!dynamic_cast(aliasDecl)) continue; addUnmodified = false; //The un-modified identifier can be ignored, because it will be replaced with the resolved alias NamespaceAliasDeclaration* alias = static_cast(aliasDecl); //Create an identifier where namespace-alias part is replaced with the alias target identifiers.append( SearchItem::Ptr( new SearchItem( alias->importIdentifier(), identifier->next ) ) ) ; } } } } if( addUnmodified ) identifiers.append(identifier); } } void DUContext::applyUpwardsAliases(SearchItem::PtrList& identifiers, const TopDUContext* /*source*/) const { if(type() == Namespace) { if(d_func()->m_scopeIdentifier.isEmpty()) return; //Make sure we search for the items in all namespaces of the same name, by duplicating each one with the namespace-identifier prepended. //We do this by prepending items to the current identifiers that equal the local scope identifier. SearchItem::Ptr newItem( new SearchItem(d_func()->m_scopeIdentifier.identifier()) ); //This will exclude explictly global identifiers newItem->addToEachNode( identifiers ); if(!newItem->next.isEmpty()) { //Prepend the full scope before newItem DUContext* parent = m_dynamicData->m_parentContext.data(); while(parent) { newItem = SearchItem::Ptr( new SearchItem(parent->d_func()->m_scopeIdentifier, newItem) ); parent = parent->m_dynamicData->m_parentContext.data(); } newItem->isExplicitlyGlobal = true; identifiers.insert(0, newItem); } } } bool DUContext::shouldSearchInParent(SearchFlags flags) const { return (parentContext() && parentContext()->type() == DUContext::Helper && (flags & InImportedParentContext)) || !(flags & InImportedParentContext); } const Use* DUContext::uses() const { ENSURE_CAN_READ return d_func()->m_uses(); } bool DUContext::declarationHasUses(Declaration* decl) { return DUChain::uses()->hasUses(decl->id()); } int DUContext::usesCount() const { return d_func()->m_usesSize(); } bool usesRangeLessThan(const Use& left, const Use& right) { return left.m_range.start < right.m_range.start; } int DUContext::createUse(int declarationIndex, const RangeInRevision& range, int insertBefore) { DUCHAIN_D_DYNAMIC(DUContext); ENSURE_CAN_WRITE Use use(range, declarationIndex); if(insertBefore == -1) { //Find position where to insert const unsigned int size = d->m_usesSize(); const Use* uses = d->m_uses(); const Use* lowerBound = std::lower_bound(uses, uses + size, use, usesRangeLessThan); insertBefore = lowerBound - uses; // comment out to test this: /* unsigned int a = 0; for(; a < size && range.start > uses[a].m_range.start; ++a) { } Q_ASSERT(a == insertBefore); */ } d->m_usesList().insert(insertBefore, use); return insertBefore; } void DUContext::changeUseRange(int useIndex, const RangeInRevision& range) { ENSURE_CAN_WRITE d_func_dynamic()->m_usesList()[useIndex].m_range = range; } void DUContext::setUseDeclaration(int useNumber, int declarationIndex) { ENSURE_CAN_WRITE d_func_dynamic()->m_usesList()[useNumber].m_declarationIndex = declarationIndex; } DUContext * DUContext::findContextAt(const CursorInRevision & position, bool includeRightBorder) const { ENSURE_CAN_READ // qCDebug(LANGUAGE) << "searchign" << position << "in:" << scopeIdentifier(true).toString() << range() << includeRightBorder; if (!range().contains(position) && (!includeRightBorder || range().end != position)) { // qCDebug(LANGUAGE) << "mismatch"; return nullptr; } const auto childContexts = m_dynamicData->m_childContexts; for(int a = childContexts.size() - 1; a >= 0; --a) { if (DUContext* specific = childContexts[a]->findContextAt(position, includeRightBorder)) { return specific; } } return const_cast(this); } Declaration * DUContext::findDeclarationAt(const CursorInRevision & position) const { ENSURE_CAN_READ if (!range().contains(position)) return nullptr; foreach (Declaration* child, m_dynamicData->m_localDeclarations) { if (child->range().contains(position)) { return child; } } return nullptr; } DUContext* DUContext::findContextIncluding(const RangeInRevision& range) const { ENSURE_CAN_READ if (!this->range().contains(range)) return nullptr; foreach (DUContext* child, m_dynamicData->m_childContexts) { if (DUContext* specific = child->findContextIncluding(range)) { return specific; } } return const_cast(this); } int DUContext::findUseAt(const CursorInRevision & position) const { ENSURE_CAN_READ if (!range().contains(position)) return -1; for(unsigned int a = 0; a < d_func()->m_usesSize(); ++a) if (d_func()->m_uses()[a].m_range.contains(position)) return a; return -1; } bool DUContext::inSymbolTable() const { return d_func()->m_inSymbolTable; } void DUContext::setInSymbolTable(bool inSymbolTable) { d_func_dynamic()->m_inSymbolTable = inSymbolTable; } void DUContext::clearImportedParentContexts() { ENSURE_CAN_WRITE DUCHAIN_D_DYNAMIC(DUContext); while( d->m_importedContextsSize() != 0 ) { DUContext* ctx = d->m_importedContexts()[0].context(nullptr, false); if(ctx) ctx->m_dynamicData->removeImportedChildContext(this); d->m_importedContextsList().removeOne(d->m_importedContexts()[0]); } } void DUContext::cleanIfNotEncountered(const QSet& encountered) { ENSURE_CAN_WRITE // It may happen that the deletion of one declaration triggers the deletion of another one // Therefore we copy the list of indexed declarations and work on those. Indexed declarations // will return zero for already deleted declarations. KDevVarLengthArray indexedLocal; if (d_func()->m_localDeclarations()) { indexedLocal.append(d_func()->m_localDeclarations(), d_func()->m_localDeclarationsSize()); } foreach (const LocalIndexedDeclaration& indexed, m_dynamicData->m_localDeclarations) { auto dec = indexed.data(topContext()); if (dec && !encountered.contains(dec) && (!dec->isAutoDeclaration() || !dec->hasUses())) { delete dec; } } foreach (DUContext* childContext, m_dynamicData->m_childContexts) { if (!encountered.contains(childContext)) { delete childContext; } } } TopDUContext* DUContext::topContext() const { return m_dynamicData->m_topContext; } QWidget* DUContext::createNavigationWidget(Declaration* decl, TopDUContext* topContext, const QString& htmlPrefix, const QString& htmlSuffix, AbstractNavigationWidget::DisplayHints hints) const { if (decl) { AbstractNavigationWidget* widget = new AbstractNavigationWidget; widget->setDisplayHints(hints); AbstractDeclarationNavigationContext* context = new AbstractDeclarationNavigationContext(DeclarationPointer(decl), TopDUContextPointer(topContext)); context->setPrefixSuffix(htmlPrefix, htmlSuffix); widget->setContext(NavigationContextPointer(context)); return widget; } else { return nullptr; } } QList allUses(DUContext* context, int declarationIndex, bool noEmptyUses) { QList ret; for(int a = 0; a < context->usesCount(); ++a) if(context->uses()[a].m_declarationIndex == declarationIndex) if(!noEmptyUses || !context->uses()[a].m_range.isEmpty()) ret << context->uses()[a].m_range; foreach(DUContext* child, context->childContexts()) ret += allUses(child, declarationIndex, noEmptyUses); return ret; } DUContext::SearchItem::SearchItem(const QualifiedIdentifier& id, const Ptr& nextItem, int start) : isExplicitlyGlobal(start == 0 ? id.explicitlyGlobal() : false) { if(!id.isEmpty()) { if(id.count() > start) identifier = id.indexedAt(start); if(id.count() > start+1) addNext(Ptr( new SearchItem(id, nextItem, start+1) )); else if(nextItem) next.append(nextItem); }else if(nextItem) { ///If there is no prefix, just copy nextItem isExplicitlyGlobal = nextItem->isExplicitlyGlobal; identifier = nextItem->identifier; next = nextItem->next; } } DUContext::SearchItem::SearchItem(const QualifiedIdentifier& id, const PtrList& nextItems, int start) : isExplicitlyGlobal(start == 0 ? id.explicitlyGlobal() : false) { if(id.count() > start) identifier = id.indexedAt(start); if(id.count() > start+1) addNext(Ptr( new SearchItem(id, nextItems, start+1) )); else next = nextItems; } DUContext::SearchItem::SearchItem(bool explicitlyGlobal, const IndexedIdentifier& id, const PtrList& nextItems) : isExplicitlyGlobal(explicitlyGlobal) , identifier(id) , next(nextItems) { } DUContext::SearchItem::SearchItem(bool explicitlyGlobal, const IndexedIdentifier& id, const Ptr& nextItem) : isExplicitlyGlobal(explicitlyGlobal) , identifier(id) { next.append(nextItem); } bool DUContext::SearchItem::match(const QualifiedIdentifier& id, int offset) const { if(id.isEmpty()) { if(identifier.isEmpty() && next.isEmpty()) return true; else return false; } if(id.at(offset) != identifier) //The identifier is different return false; if(offset == id.count()-1) { if(next.isEmpty()) return true; //match else return false; //id is too short } for(int a = 0; a < next.size(); ++a) if(next[a]->match(id, offset+1)) return true; return false; } bool DUContext::SearchItem::isEmpty() const { return identifier.isEmpty(); } bool DUContext::SearchItem::hasNext() const { return !next.isEmpty(); } QList DUContext::SearchItem::toList(const QualifiedIdentifier& prefix) const { QList ret; QualifiedIdentifier id = prefix; if(id.isEmpty()) id.setExplicitlyGlobal(isExplicitlyGlobal); if(!identifier.isEmpty()) id.push(identifier); if(next.isEmpty()) { ret << id; } else { for(int a = 0; a < next.size(); ++a) ret += next[a]->toList(id); } return ret; } void DUContext::SearchItem::addNext(const SearchItem::Ptr& other) { next.append(other); } void DUContext::SearchItem::addToEachNode(const SearchItem::Ptr& other) { if(other->isExplicitlyGlobal) return; next.append(other); for(int a = 0; a < next.size()-1; ++a) next[a]->addToEachNode(other); } void DUContext::SearchItem::addToEachNode(const SearchItem::PtrList& other) { int added = 0; for (const SearchItem::Ptr& o : other) { if(!o->isExplicitlyGlobal) { next.append(o); ++added; } } for(int a = 0; a < next.size()-added; ++a) next[a]->addToEachNode(other); } DUContext::Import::Import(DUContext* _context, const DUContext* importer, const CursorInRevision& _position) : position(_position) { if(_context && _context->owner() && (_context->owner()->specialization().index() || (importer && importer->topContext() != _context->topContext()))) { m_declaration = _context->owner()->id(); }else{ m_context = _context; } } DUContext::Import::Import(const DeclarationId& id, const CursorInRevision& _position) : position(_position) , m_declaration(id) { } DUContext* DUContext::Import::context(const TopDUContext* topContext, bool instantiateIfRequired) const { if(m_declaration.isValid()) { Declaration* decl = m_declaration.getDeclaration(topContext, instantiateIfRequired); //This first case rests on the assumption that no context will ever import a function's expression context //More accurately, that no specialized or cross-topContext imports will, but if the former assumption fails the latter will too if (AbstractFunctionDeclaration *functionDecl = dynamic_cast(decl)) { if (functionDecl->internalFunctionContext()) { return functionDecl->internalFunctionContext(); } else { qCWarning(LANGUAGE) << "Import of function declaration without internal function context encountered!"; } } if(decl) return decl->logicalInternalContext(topContext); else return nullptr; }else{ return m_context.data(); } } bool DUContext::Import::isDirect() const { return m_context.isValid(); } void DUContext::visit(DUChainVisitor& visitor) { ENSURE_CAN_READ visitor.visit(this); foreach (Declaration* decl, m_dynamicData->m_localDeclarations) { visitor.visit(decl); } foreach (DUContext* childContext, m_dynamicData->m_childContexts) { childContext->visit(visitor); } } static bool sortByRange(const DUChainBase* lhs, const DUChainBase* rhs) { return lhs->range() < rhs->range(); } void DUContext::resortLocalDeclarations() { ENSURE_CAN_WRITE std::sort(m_dynamicData->m_localDeclarations.begin(), m_dynamicData->m_localDeclarations.end(), sortByRange); auto top = topContext(); auto& declarations = d_func_dynamic()->m_localDeclarationsList(); std::sort(declarations.begin(), declarations.end(), [top] (const LocalIndexedDeclaration& lhs, const LocalIndexedDeclaration& rhs) { return lhs.data(top)->range() < rhs.data(top)->range(); }); } void DUContext::resortChildContexts() { ENSURE_CAN_WRITE std::sort(m_dynamicData->m_childContexts.begin(), m_dynamicData->m_childContexts.end(), sortByRange); auto top = topContext(); auto& contexts = d_func_dynamic()->m_childContextsList(); std::sort(contexts.begin(), contexts.end(), [top] (const LocalIndexedDUContext& lhs, const LocalIndexedDUContext& rhs) { return lhs.data(top)->range() < rhs.data(top)->range(); }); } } diff --git a/language/duchain/functiondeclaration.cpp b/language/duchain/functiondeclaration.cpp index 0f51481cc..0ce168cfc 100644 --- a/language/duchain/functiondeclaration.cpp +++ b/language/duchain/functiondeclaration.cpp @@ -1,113 +1,113 @@ /* This is part of KDevelop Copyright 2002-2005 Roberto Raggi Copyright 2006 Adam Treat Copyright 2006-2007 Hamish Rodda Copyright 2007-2008 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "functiondeclaration.h" #include "ducontext.h" #include "duchainregister.h" #include "types/functiontype.h" -#include "util/debug.h" +#include namespace KDevelop { REGISTER_DUCHAIN_ITEM(FunctionDeclaration); DEFINE_LIST_MEMBER_HASH(FunctionDeclarationData, m_defaultParameters, IndexedString) FunctionDeclaration::FunctionDeclaration(FunctionDeclarationData& data) : FunctionDeclarationBase(data) { } FunctionDeclaration::FunctionDeclaration(FunctionDeclarationData& data, const RangeInRevision& range) : FunctionDeclarationBase(data, range) { } FunctionDeclaration::FunctionDeclaration(const FunctionDeclaration& rhs) : FunctionDeclarationBase(*new FunctionDeclarationData( *rhs.d_func() )) { } FunctionDeclaration::FunctionDeclaration(const RangeInRevision& range, DUContext* context) : FunctionDeclarationBase(*new FunctionDeclarationData, range) { d_func_dynamic()->setClassId(this); if( context ) setContext( context ); } FunctionDeclaration::~FunctionDeclaration() { } Declaration* FunctionDeclaration::clonePrivate() const { return new FunctionDeclaration(*this); } bool FunctionDeclaration::isFunctionDeclaration() const { return true; } void FunctionDeclaration::setAbstractType(AbstractType::Ptr type) { if( type && !type.cast() ) { qCDebug(LANGUAGE) << "wrong type attached to function declaration:" << type->toString(); } Declaration::setAbstractType(type); } QString FunctionDeclaration::toString() const { AbstractType::Ptr type = abstractType(); if( !type ) return Declaration::toString(); TypePtr function = type.cast(); if(function) { return QStringLiteral("%1 %2 %3").arg(function->partToString( FunctionType::SignatureReturn ), identifier().toString(), function->partToString( FunctionType::SignatureArguments )); }else{ return Declaration::toString(); } } uint FunctionDeclaration::additionalIdentity() const { if(abstractType()) return abstractType()->hash(); else return 0; } const IndexedString* FunctionDeclaration::defaultParameters() const { return d_func()->m_defaultParameters(); } unsigned int FunctionDeclaration::defaultParametersSize() const { return d_func()->m_defaultParametersSize(); } void FunctionDeclaration::addDefaultParameter(const IndexedString& str) { d_func_dynamic()->m_defaultParametersList().append(str); } void FunctionDeclaration::clearDefaultParameters() { d_func_dynamic()->m_defaultParametersList().clear(); } } diff --git a/language/duchain/identifier.cpp b/language/duchain/identifier.cpp index 92eef8de6..e71769483 100644 --- a/language/duchain/identifier.cpp +++ b/language/duchain/identifier.cpp @@ -1,1604 +1,1604 @@ /* This file is part of KDevelop Copyright 2006 Hamish Rodda Copyright 2007-2008 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "identifier.h" #include #include "stringhelpers.h" #include "appendedlist_static.h" #include "serialization/itemrepository.h" #include "util/kdevhash.h" -#include "util/debug.h" +#include #include #include #define ifDebug(x) namespace KDevelop { template class IdentifierPrivate { public: IdentifierPrivate() : m_unique(0) , m_refCount(0) , m_hash(0) { } template explicit IdentifierPrivate(const IdentifierPrivate& rhs) : m_unique(rhs.m_unique) , m_identifier(rhs.m_identifier) , m_refCount(0) , m_hash(rhs.m_hash) { copyListsFrom(rhs); } ~IdentifierPrivate() { templateIdentifiersList.free(const_cast(templateIdentifiers())); } //Flags the stored hash-value invalid void clearHash() { //This is always called on an object private to an Identifier, so there is no threading-problem. Q_ASSERT(dynamic); m_hash = 0; } uint hash() const { // Since this only needs reading and the data needs not to be private, this may be called by // multiple threads simultaneously, so computeHash() must be thread-safe. if( !m_hash && dynamic ) computeHash(); return m_hash; } int m_unique; IndexedString m_identifier; uint m_refCount; START_APPENDED_LISTS_STATIC(IdentifierPrivate) APPENDED_LIST_FIRST_STATIC(IndexedTypeIdentifier, templateIdentifiers) END_APPENDED_LISTS_STATIC(templateIdentifiers) uint itemSize() const { return sizeof(IdentifierPrivate) + lastOffsetBehind(); } void computeHash() const { Q_ASSERT(dynamic); //this must stay thread-safe(may be called by multiple threads at a time) //The thread-safety is given because all threads will have the same result, and it will only be written once at the end. KDevHash kdevhash; kdevhash << m_identifier.hash() << m_unique; FOREACH_FUNCTION_STATIC(const IndexedTypeIdentifier& templateIdentifier, templateIdentifiers) kdevhash << templateIdentifier.hash(); m_hash = kdevhash; } mutable uint m_hash; }; typedef IdentifierPrivate DynamicIdentifierPrivate; typedef IdentifierPrivate ConstantIdentifierPrivate; struct IdentifierItemRequest { IdentifierItemRequest(const DynamicIdentifierPrivate& identifier) : m_identifier(identifier) { identifier.hash(); //Make sure the hash is valid by calling this } enum { AverageSize = sizeof(IdentifierPrivate)+4 }; //Should return the hash-value associated with this request(For example the hash of a string) uint hash() const { return m_identifier.hash(); } //Should return the size of an item created with createItem uint itemSize() const { return m_identifier.itemSize(); } //Should create an item where the information of the requested item is permanently stored. The pointer //@param item equals an allocated range with the size of itemSize(). void createItem(ConstantIdentifierPrivate* item) const { new (item) ConstantIdentifierPrivate(m_identifier); } static bool persistent(const ConstantIdentifierPrivate* item) { return (bool)item->m_refCount; } static void destroy(ConstantIdentifierPrivate* item, AbstractItemRepository&) { item->~ConstantIdentifierPrivate(); } //Should return whether the here requested item equals the given item bool equals(const ConstantIdentifierPrivate* item) const { return item->m_hash == m_identifier.m_hash && item->m_unique == m_identifier.m_unique && item->m_identifier == m_identifier.m_identifier && m_identifier.listsEqual(*item); } const DynamicIdentifierPrivate& m_identifier; }; using IdentifierRepository = RepositoryManager< ItemRepository, false>; static IdentifierRepository& identifierRepository() { static IdentifierRepository identifierRepositoryObject(QStringLiteral("Identifier Repository")); return identifierRepositoryObject; } static uint emptyConstantIdentifierPrivateIndex() { static const uint index = identifierRepository()->index(DynamicIdentifierPrivate()); return index; } static const ConstantIdentifierPrivate* emptyConstantIdentifierPrivate() { static const ConstantIdentifierPrivate item; return &item; } bool IndexedIdentifier::isEmpty() const { return index == emptyConstantIdentifierPrivateIndex(); } /** * Before something is modified in QualifiedIdentifierPrivate, it must be made sure that * it is private to the QualifiedIdentifier it is used in(@see QualifiedIdentifier::prepareWrite) */ template class QualifiedIdentifierPrivate { public: QualifiedIdentifierPrivate() : m_explicitlyGlobal(false) , m_isExpression(false) , m_hash(0) , m_refCount(0) { } template explicit QualifiedIdentifierPrivate(const QualifiedIdentifierPrivate& rhs) : m_explicitlyGlobal(rhs.m_explicitlyGlobal) , m_isExpression(rhs.m_isExpression) , m_hash(rhs.m_hash) , m_refCount(0) { copyListsFrom(rhs); } ~QualifiedIdentifierPrivate() { identifiersList.free(const_cast(identifiers())); } bool m_explicitlyGlobal:1; bool m_isExpression:1; mutable uint m_hash; uint m_refCount; START_APPENDED_LISTS_STATIC(QualifiedIdentifierPrivate) APPENDED_LIST_FIRST_STATIC(IndexedIdentifier, identifiers) END_APPENDED_LISTS_STATIC(identifiers) uint itemSize() const { return sizeof(QualifiedIdentifierPrivate) + lastOffsetBehind(); } //Constructs m_identifiers void splitIdentifiers( const QString& str, int start ) { Q_ASSERT(dynamic); uint currentStart = start; while( currentStart < (uint)str.length() ) { identifiersList.append(IndexedIdentifier(Identifier( str, currentStart, ¤tStart ))); while( currentStart < (uint)str.length() && (str[currentStart] == ' ' ) ) ++currentStart; currentStart += 2; //Skip "::" } } inline void clearHash() const { m_hash = 0; } uint hash() const { if( m_hash == 0 ) { KDevHash hash; quint32 bitfields = m_explicitlyGlobal | (m_isExpression << 1); hash << bitfields << identifiersSize(); FOREACH_FUNCTION_STATIC( const IndexedIdentifier& identifier, identifiers ) { hash << identifier.getIndex(); } m_hash = hash; } return m_hash; } }; typedef QualifiedIdentifierPrivate DynamicQualifiedIdentifierPrivate; typedef QualifiedIdentifierPrivate ConstantQualifiedIdentifierPrivate; struct QualifiedIdentifierItemRequest { QualifiedIdentifierItemRequest(const DynamicQualifiedIdentifierPrivate& identifier) : m_identifier(identifier) { identifier.hash(); //Make sure the hash is valid by calling this } enum { AverageSize = sizeof(QualifiedIdentifierPrivate)+8 }; //Should return the hash-value associated with this request(For example the hash of a string) uint hash() const { return m_identifier.hash(); } //Should return the size of an item created with createItem uint itemSize() const { return m_identifier.itemSize(); } /** * Should create an item where the information of the requested item is permanently stored. The pointer * @param item equals an allocated range with the size of itemSize(). */ void createItem(ConstantQualifiedIdentifierPrivate* item) const { Q_ASSERT(shouldDoDUChainReferenceCounting(item)); Q_ASSERT(shouldDoDUChainReferenceCounting(((char*)item) + (itemSize()-1))); new (item) ConstantQualifiedIdentifierPrivate(m_identifier); } static bool persistent(const ConstantQualifiedIdentifierPrivate* item) { return (bool)item->m_refCount; } static void destroy(ConstantQualifiedIdentifierPrivate* item, AbstractItemRepository&) { Q_ASSERT(shouldDoDUChainReferenceCounting(item)); item->~ConstantQualifiedIdentifierPrivate(); } //Should return whether the here requested item equals the given item bool equals(const ConstantQualifiedIdentifierPrivate* item) const { return item->m_explicitlyGlobal == m_identifier.m_explicitlyGlobal && item->m_isExpression == m_identifier.m_isExpression && item->m_hash == m_identifier.m_hash && m_identifier.listsEqual(*item); } const DynamicQualifiedIdentifierPrivate& m_identifier; }; using QualifiedIdentifierRepository = RepositoryManager< ItemRepository, false>; static QualifiedIdentifierRepository& qualifiedidentifierRepository() { static QualifiedIdentifierRepository repo(QStringLiteral("Qualified Identifier Repository"), 1, [] () -> AbstractRepositoryManager* { return &identifierRepository(); }); return repo; } static uint emptyConstantQualifiedIdentifierPrivateIndex() { static const uint index = qualifiedidentifierRepository()->index(DynamicQualifiedIdentifierPrivate()); return index; } static const ConstantQualifiedIdentifierPrivate* emptyConstantQualifiedIdentifierPrivate() { static const ConstantQualifiedIdentifierPrivate item; return &item; } Identifier::Identifier(const Identifier& rhs) { rhs.makeConstant(); cd = rhs.cd; m_index = rhs.m_index; } Identifier::Identifier(uint index) : m_index(index) { Q_ASSERT(m_index); cd = identifierRepository()->itemFromIndex(index); } Identifier::Identifier(const IndexedString& str) { if (str.isEmpty()) { m_index = emptyConstantIdentifierPrivateIndex(); cd = emptyConstantIdentifierPrivate(); } else { m_index = 0; dd = new IdentifierPrivate; dd->m_identifier = str; } } Identifier::Identifier(const QString& id, uint start, uint* takenRange) { if (id.isEmpty()) { m_index = emptyConstantIdentifierPrivateIndex(); cd = emptyConstantIdentifierPrivate(); return; } m_index = 0; dd = new IdentifierPrivate; ///Extract template-parameters ParamIterator paramIt(QStringLiteral("<>:"), id, start); dd->m_identifier = IndexedString(paramIt.prefix().trimmed()); while( paramIt ) { appendTemplateIdentifier( IndexedTypeIdentifier(IndexedQualifiedIdentifier(QualifiedIdentifier(*paramIt))) ); ++paramIt; } if( takenRange ) *takenRange = paramIt.position(); } Identifier::Identifier() : m_index(emptyConstantIdentifierPrivateIndex()) , cd(emptyConstantIdentifierPrivate()) { } Identifier& Identifier::operator=(const Identifier& rhs) { if(dd == rhs.dd && cd == rhs.cd) return *this; if(!m_index) delete dd; dd = nullptr; rhs.makeConstant(); cd = rhs.cd; m_index = rhs.m_index; Q_ASSERT(cd); return *this; } Identifier::Identifier(Identifier&& rhs) Q_DECL_NOEXCEPT : m_index(rhs.m_index) { if (m_index) { cd = rhs.cd; } else { dd = rhs.dd; } rhs.cd = emptyConstantIdentifierPrivate(); rhs.m_index = emptyConstantIdentifierPrivateIndex(); } Identifier& Identifier::operator=(Identifier&& rhs) Q_DECL_NOEXCEPT { if(dd == rhs.dd && cd == rhs.cd) return *this; if (!m_index) { delete dd; dd = nullptr; } m_index = rhs.m_index; if (m_index) { cd = rhs.cd; } else { dd = rhs.dd; } rhs.cd = emptyConstantIdentifierPrivate(); rhs.m_index = emptyConstantIdentifierPrivateIndex(); return *this; } Identifier::~Identifier() { if(!m_index) delete dd; } bool Identifier::nameEquals(const Identifier& rhs) const { return identifier() == rhs.identifier(); } uint Identifier::hash() const { if(!m_index) return dd->hash(); else return cd->hash(); } bool Identifier::isEmpty() const { if(!m_index) return dd->m_identifier.isEmpty() && dd->m_unique == 0 && dd->templateIdentifiersSize() == 0; else return cd->m_identifier.isEmpty() && cd->m_unique == 0 && cd->templateIdentifiersSize() == 0; } Identifier Identifier::unique(int token) { Identifier ret; ret.setUnique(token); return ret; } bool Identifier::isUnique() const { if(!m_index) return dd->m_unique; else return cd->m_unique; } int Identifier::uniqueToken() const { if(!m_index) return dd->m_unique; else return cd->m_unique; } void Identifier::setUnique(int token) { if (token != uniqueToken()) { prepareWrite(); dd->m_unique = token; } } const IndexedString Identifier::identifier() const { if(!m_index) return dd->m_identifier; else return cd->m_identifier; } void Identifier::setIdentifier(const QString& identifier) { IndexedString id(identifier); if (id != this->identifier()) { prepareWrite(); dd->m_identifier = std::move(id); } } void Identifier::setIdentifier(const IndexedString& identifier) { if (identifier != this->identifier()) { prepareWrite(); dd->m_identifier = identifier; } } IndexedTypeIdentifier Identifier::templateIdentifier(int num) const { if(!m_index) return dd->templateIdentifiers()[num]; else return cd->templateIdentifiers()[num]; } uint Identifier::templateIdentifiersCount() const { if(!m_index) return dd->templateIdentifiersSize(); else return cd->templateIdentifiersSize(); } void Identifier::appendTemplateIdentifier(const IndexedTypeIdentifier& identifier) { prepareWrite(); dd->templateIdentifiersList.append(identifier); } void Identifier::clearTemplateIdentifiers() { prepareWrite(); dd->templateIdentifiersList.clear(); } uint Identifier::index() const { makeConstant(); Q_ASSERT(m_index); return m_index; } bool Identifier::inRepository() const { return m_index; } void Identifier::setTemplateIdentifiers(const QList& templateIdentifiers) { prepareWrite(); dd->templateIdentifiersList.clear(); foreach(const IndexedTypeIdentifier& id, templateIdentifiers) dd->templateIdentifiersList.append(id); } QString Identifier::toString(IdentifierStringFormattingOptions options) const { QString ret = identifier().str(); if (!options.testFlag(RemoveTemplateInformation) && templateIdentifiersCount()) { ret.append("< "); for (uint i = 0; i < templateIdentifiersCount(); ++i) { ret.append(templateIdentifier(i).toString(options)); if (i != templateIdentifiersCount() - 1) ret.append(", "); } ret.append(" >"); } return ret; } bool Identifier::operator==(const Identifier& rhs) const { return index() == rhs.index(); } bool Identifier::operator!=(const Identifier& rhs) const { return !operator==(rhs); } uint QualifiedIdentifier::index() const { makeConstant(); Q_ASSERT(m_index); return m_index; } void Identifier::makeConstant() const { if(m_index) return; m_index = identifierRepository()->index( IdentifierItemRequest(*dd) ); delete dd; cd = identifierRepository()->itemFromIndex( m_index ); } void Identifier::prepareWrite() { if(m_index) { const IdentifierPrivate* oldCc = cd; dd = new IdentifierPrivate; dd->m_hash = oldCc->m_hash; dd->m_unique = oldCc->m_unique; dd->m_identifier = oldCc->m_identifier; dd->copyListsFrom(*oldCc); m_index = 0; } dd->clearHash(); } bool QualifiedIdentifier::inRepository() const { if(m_index) return true; else return (bool)qualifiedidentifierRepository()->findIndex( QualifiedIdentifierItemRequest(*dd) ); } QualifiedIdentifier::QualifiedIdentifier(uint index) : m_index(index) , cd( qualifiedidentifierRepository()->itemFromIndex(index) ) { } QualifiedIdentifier::QualifiedIdentifier(const QString& id, bool isExpression) { if (id.isEmpty()) { m_index = emptyConstantQualifiedIdentifierPrivateIndex(); cd = emptyConstantQualifiedIdentifierPrivate(); return; } m_index = 0; dd = new DynamicQualifiedIdentifierPrivate; if(isExpression) { setIsExpression(true); if(!id.isEmpty()) { //Prevent tokenization, since we may lose information there Identifier finishedId; finishedId.setIdentifier(id); push(finishedId); } }else{ if (id.startsWith(QStringLiteral("::"))) { dd->m_explicitlyGlobal = true; dd->splitIdentifiers(id, 2); } else { dd->m_explicitlyGlobal = false; dd->splitIdentifiers(id, 0); } } } QualifiedIdentifier::QualifiedIdentifier(const Identifier& id) { if (id.isEmpty()) { m_index = emptyConstantQualifiedIdentifierPrivateIndex(); cd = emptyConstantQualifiedIdentifierPrivate(); return; } m_index = 0; dd = new DynamicQualifiedIdentifierPrivate; if (id.dd->m_identifier.str().isEmpty()) { dd->m_explicitlyGlobal = true; } else { dd->m_explicitlyGlobal = false; dd->identifiersList.append(IndexedIdentifier(id)); } } QualifiedIdentifier::QualifiedIdentifier() : m_index(emptyConstantQualifiedIdentifierPrivateIndex()) , cd(emptyConstantQualifiedIdentifierPrivate()) { } QualifiedIdentifier::QualifiedIdentifier(const QualifiedIdentifier& id) { if(id.m_index) { m_index = id.m_index; cd = id.cd; }else{ m_index = 0; dd = new QualifiedIdentifierPrivate(*id.dd); } } QualifiedIdentifier::QualifiedIdentifier(QualifiedIdentifier&& rhs) Q_DECL_NOEXCEPT : m_index(rhs.m_index) { if (m_index) { cd = rhs.cd; } else { dd = rhs.dd; } rhs.m_index = emptyConstantQualifiedIdentifierPrivateIndex(); rhs.cd = emptyConstantQualifiedIdentifierPrivate(); } QualifiedIdentifier& QualifiedIdentifier::operator=(const QualifiedIdentifier& rhs) { if(dd == rhs.dd && cd == rhs.cd) return *this; if(!m_index) delete dd; rhs.makeConstant(); cd = rhs.cd; m_index = rhs.m_index; return *this; } QualifiedIdentifier& QualifiedIdentifier::operator=(QualifiedIdentifier&& rhs) Q_DECL_NOEXCEPT { if(!m_index) delete dd; m_index = rhs.m_index; if (m_index) { cd = rhs.cd; } else { dd = rhs.dd; } rhs.cd = emptyConstantQualifiedIdentifierPrivate(); rhs.m_index = emptyConstantQualifiedIdentifierPrivateIndex(); return *this; } QualifiedIdentifier::~QualifiedIdentifier() { if(!m_index) delete dd; } QStringList QualifiedIdentifier::toStringList(IdentifierStringFormattingOptions options) const { QStringList ret; ret.reserve(explicitlyGlobal() + count()); if (explicitlyGlobal()) ret.append(QString()); if(m_index) { FOREACH_FUNCTION_STATIC(const IndexedIdentifier& index, cd->identifiers) ret << index.identifier().toString(options); }else{ FOREACH_FUNCTION_STATIC(const IndexedIdentifier& index, dd->identifiers) ret << index.identifier().toString(options); } return ret; } QString QualifiedIdentifier::toString(IdentifierStringFormattingOptions options) const { const QString doubleColon = QStringLiteral("::"); QString ret; if( !options.testFlag(RemoveExplicitlyGlobalPrefix) && explicitlyGlobal() ) ret = doubleColon; bool first = true; if(m_index) { FOREACH_FUNCTION_STATIC(const IndexedIdentifier& index, cd->identifiers) { if( !first ) ret += doubleColon; else first = false; ret += index.identifier().toString(options); } }else{ FOREACH_FUNCTION_STATIC(const IndexedIdentifier& index, dd->identifiers) { if( !first ) ret += doubleColon; else first = false; ret += index.identifier().toString(options); } } return ret; } QualifiedIdentifier QualifiedIdentifier::merge(const QualifiedIdentifier& base) const { QualifiedIdentifier ret(base); ret.push(*this); return ret; } QualifiedIdentifier QualifiedIdentifier::operator+(const QualifiedIdentifier& rhs) const { return rhs.merge(*this); } QualifiedIdentifier& QualifiedIdentifier::operator+=(const QualifiedIdentifier& rhs) { push(rhs); return *this; } QualifiedIdentifier QualifiedIdentifier::operator+(const Identifier& rhs) const { QualifiedIdentifier ret(*this); ret.push(rhs); return ret; } QualifiedIdentifier& QualifiedIdentifier::operator+=(const Identifier& rhs) { push(rhs); return *this; } QualifiedIdentifier QualifiedIdentifier::operator+(const IndexedIdentifier& rhs) const { QualifiedIdentifier ret(*this); ret.push(rhs); return ret; } QualifiedIdentifier& QualifiedIdentifier::operator+=(const IndexedIdentifier& rhs) { push(rhs); return *this; } bool QualifiedIdentifier::isExpression() const { if(m_index) return cd->m_isExpression; else return dd->m_isExpression; } void QualifiedIdentifier::setIsExpression(bool is) { if (is != isExpression()) { prepareWrite(); dd->m_isExpression = is; } } bool QualifiedIdentifier::explicitlyGlobal() const { // True if started with "::" if(m_index) return cd->m_explicitlyGlobal; else return dd->m_explicitlyGlobal; } void QualifiedIdentifier::setExplicitlyGlobal(bool eg) { if (eg != explicitlyGlobal()) { prepareWrite(); dd->m_explicitlyGlobal = eg; } } bool QualifiedIdentifier::sameIdentifiers(const QualifiedIdentifier& rhs) const { if(m_index && rhs.m_index) return cd->listsEqual(*rhs.cd); else if(m_index && !rhs.m_index) return cd->listsEqual(*rhs.dd); else if(!m_index && !rhs.m_index) return dd->listsEqual(*rhs.dd); else return dd->listsEqual(*rhs.cd); } bool QualifiedIdentifier::operator==(const QualifiedIdentifier& rhs) const { if( cd == rhs.cd ) return true; return hash() == rhs.hash() && sameIdentifiers(rhs); } bool QualifiedIdentifier::operator!=(const QualifiedIdentifier& rhs) const { return !operator==(rhs); } bool QualifiedIdentifier::beginsWith(const QualifiedIdentifier& other) const { uint c = count(); uint oc = other.count(); for (uint i = 0; i < c && i < oc; ++i) if (at(i) == other.at(i)) { continue; } else { return false; } return true; } struct Visitor { Visitor(KDevVarLengthArray& target, uint hash) : target(target) , hash(hash) { } bool operator()(const ConstantQualifiedIdentifierPrivate* item, uint index) const { if(item->m_hash == hash) target.append(QualifiedIdentifier(index)); return true; } KDevVarLengthArray& target; const uint hash; }; uint QualifiedIdentifier::hash() const { if(m_index) return cd->hash(); else return dd->hash(); } uint qHash(const IndexedTypeIdentifier& id) { return id.hash(); } uint qHash(const QualifiedIdentifier& id) { return id.hash(); } uint qHash(const Identifier& id) { return id.hash(); } bool QualifiedIdentifier::isQualified() const { return count() > 1 || explicitlyGlobal(); } void QualifiedIdentifier::push(const Identifier& id) { if(id.isEmpty()) return; push(IndexedIdentifier(id)); } void QualifiedIdentifier::push(const IndexedIdentifier& id) { if (id.isEmpty()) { return; } prepareWrite(); dd->identifiersList.append(id); } void QualifiedIdentifier::push(const QualifiedIdentifier& id) { if (id.isEmpty()) { return; } prepareWrite(); if (id.m_index) { dd->identifiersList.append(id.cd->identifiers(), id.cd->identifiersSize()); } else { dd->identifiersList.append(id.dd->identifiers(), id.dd->identifiersSize()); } if (id.explicitlyGlobal()) { setExplicitlyGlobal(true); } } void QualifiedIdentifier::pop() { prepareWrite(); if(!dd->identifiersSize()) return; dd->identifiersList.resize(dd->identifiersList.size()-1); } void QualifiedIdentifier::clear() { prepareWrite(); dd->identifiersList.clear(); dd->m_explicitlyGlobal = false; dd->m_isExpression = false; } bool QualifiedIdentifier::isEmpty() const { if(m_index) return cd->identifiersSize() == 0; else return dd->identifiersSize() == 0; } int QualifiedIdentifier::count() const { if(m_index) return cd->identifiersSize(); else return dd->identifiersSize(); } Identifier QualifiedIdentifier::first() const { return indexedFirst().identifier(); } IndexedIdentifier QualifiedIdentifier::indexedFirst() const { if( (m_index && cd->identifiersSize() == 0) || (!m_index && dd->identifiersSize() == 0) ) return IndexedIdentifier(); else return indexedAt(0); } Identifier QualifiedIdentifier::last() const { return indexedLast().identifier(); } IndexedIdentifier QualifiedIdentifier::indexedLast() const { uint c = count(); if(c) return indexedAt(c-1); else return IndexedIdentifier(); } Identifier QualifiedIdentifier::top() const { return last(); } QualifiedIdentifier QualifiedIdentifier::mid(int pos, int len) const { QualifiedIdentifier ret; if( pos == 0 ) ret.setExplicitlyGlobal(explicitlyGlobal()); int cnt = (int)count(); if( len == -1 ) len = cnt - pos; if( pos+len > cnt ) len -= cnt - (pos+len); for( int a = pos; a < pos+len; a++ ) ret.push(at(a)); return ret; } Identifier QualifiedIdentifier::at(int i) const { return indexedAt(i).identifier(); } IndexedIdentifier QualifiedIdentifier::indexedAt(int i) const { if (m_index) { Q_ASSERT(i >= 0 && i < (int)cd->identifiersSize()); return cd->identifiers()[i]; } else { Q_ASSERT(i >= 0 && i < (int)dd->identifiersSize()); return dd->identifiers()[i]; } } void QualifiedIdentifier::makeConstant() const { if(m_index) return; m_index = qualifiedidentifierRepository()->index( QualifiedIdentifierItemRequest(*dd) ); delete dd; cd = qualifiedidentifierRepository()->itemFromIndex( m_index ); } void QualifiedIdentifier::prepareWrite() { if(m_index) { const QualifiedIdentifierPrivate* oldCc = cd; dd = new QualifiedIdentifierPrivate; dd->m_explicitlyGlobal = oldCc->m_explicitlyGlobal; dd->m_isExpression = oldCc->m_isExpression; dd->m_hash = oldCc->m_hash; dd->copyListsFrom(*oldCc); m_index = 0; } dd->clearHash(); } uint IndexedTypeIdentifier::hash() const { quint32 bitfields = m_isConstant | (m_isReference << 1) | (m_isRValue << 2) | (m_isVolatile << 3) | (m_pointerDepth << 4) | (m_pointerConstMask << 9); return KDevHash() << m_identifier.getIndex() << bitfields; } bool IndexedTypeIdentifier::operator==(const IndexedTypeIdentifier& rhs) const { return m_identifier == rhs.m_identifier && m_isConstant == rhs.m_isConstant && m_isReference == rhs.m_isReference && m_isRValue == rhs.m_isRValue && m_isVolatile == rhs.m_isVolatile && m_pointerConstMask == rhs.m_pointerConstMask && m_pointerDepth == rhs.m_pointerDepth; } bool IndexedTypeIdentifier::operator!=(const IndexedTypeIdentifier& rhs) const { return !operator==(rhs); } bool IndexedTypeIdentifier::isReference() const { return m_isReference; } void IndexedTypeIdentifier::setIsReference(bool isRef) { m_isReference = isRef; } bool IndexedTypeIdentifier::isRValue() const { return m_isRValue; } void IndexedTypeIdentifier::setIsRValue(bool isRVal) { m_isRValue = isRVal; } bool IndexedTypeIdentifier::isConstant() const { return m_isConstant; } void IndexedTypeIdentifier::setIsConstant(bool isConst) { m_isConstant = isConst; } bool IndexedTypeIdentifier::isVolatile() const { return m_isVolatile; } void IndexedTypeIdentifier::setIsVolatile(bool isVolatile) { m_isVolatile = isVolatile; } int IndexedTypeIdentifier::pointerDepth() const { return m_pointerDepth; } void IndexedTypeIdentifier::setPointerDepth(int depth) { Q_ASSERT(depth <= 23 && depth >= 0); ///Clear the mask in removed fields for(int s = depth; s < (int)m_pointerDepth; ++s) setIsConstPointer(s, false); m_pointerDepth = depth; } bool IndexedTypeIdentifier::isConstPointer(int depthNumber) const { return m_pointerConstMask & (1 << depthNumber); } void IndexedTypeIdentifier::setIsConstPointer(int depthNumber, bool constant) { if(constant) m_pointerConstMask |= (1 << depthNumber); else m_pointerConstMask &= (~(1 << depthNumber)); } QString IndexedTypeIdentifier::toString(IdentifierStringFormattingOptions options) const { QString ret; if(isConstant()) ret += QLatin1String("const "); if(isVolatile()) ret += QLatin1String("volatile "); ret += m_identifier.identifier().toString(options); for(int a = 0; a < pointerDepth(); ++a) { ret += '*'; if( isConstPointer(a) ) ret += QLatin1String("const"); } if(isRValue()) ret += QLatin1String("&&"); else if(isReference()) ret += '&'; return ret; } IndexedTypeIdentifier::IndexedTypeIdentifier(const IndexedQualifiedIdentifier& identifier) : m_identifier(identifier) , m_isConstant(false) , m_isReference(false) , m_isRValue(false) , m_isVolatile(false) , m_pointerDepth(0) , m_pointerConstMask(0) { } IndexedTypeIdentifier::IndexedTypeIdentifier(const QString& identifier, bool isExpression) : m_identifier(QualifiedIdentifier(identifier, isExpression)) , m_isConstant(false) , m_isReference(false) , m_isRValue(false) , m_isVolatile(false) , m_pointerDepth(0) , m_pointerConstMask(0) { } IndexedIdentifier::IndexedIdentifier() : index(emptyConstantIdentifierPrivateIndex()) { if(shouldDoDUChainReferenceCounting(this)) { QMutexLocker lock(identifierRepository()->mutex()); increase(identifierRepository()->dynamicItemFromIndexSimple(index)->m_refCount, index); } } IndexedIdentifier::IndexedIdentifier(const Identifier& id) : index(id.index()) { if(shouldDoDUChainReferenceCounting(this)) { QMutexLocker lock(identifierRepository()->mutex()); increase(identifierRepository()->dynamicItemFromIndexSimple(index)->m_refCount, index); } } IndexedIdentifier::IndexedIdentifier(const IndexedIdentifier& rhs) : index(rhs.index) { if(shouldDoDUChainReferenceCounting(this)) { QMutexLocker lock(identifierRepository()->mutex()); increase(identifierRepository()->dynamicItemFromIndexSimple(index)->m_refCount, index); } } IndexedIdentifier::IndexedIdentifier(IndexedIdentifier&& rhs) Q_DECL_NOEXCEPT : index(rhs.index) { rhs.index = emptyConstantIdentifierPrivateIndex(); } IndexedIdentifier::~IndexedIdentifier() { if(shouldDoDUChainReferenceCounting(this)) { QMutexLocker lock(identifierRepository()->mutex()); decrease(identifierRepository()->dynamicItemFromIndexSimple(index)->m_refCount, index); } } IndexedIdentifier& IndexedIdentifier::operator=(const Identifier& id) { if(shouldDoDUChainReferenceCounting(this)) { QMutexLocker lock(identifierRepository()->mutex()); decrease(identifierRepository()->dynamicItemFromIndexSimple(index)->m_refCount, index); } index = id.index(); if(shouldDoDUChainReferenceCounting(this)) { QMutexLocker lock(identifierRepository()->mutex()); increase(identifierRepository()->dynamicItemFromIndexSimple(index)->m_refCount, index); } return *this; } IndexedIdentifier& IndexedIdentifier::operator=(IndexedIdentifier&& rhs) Q_DECL_NOEXCEPT { if(shouldDoDUChainReferenceCounting(this)) { QMutexLocker lock(identifierRepository()->mutex()); ifDebug( qCDebug(LANGUAGE) << "decreasing"; ) decrease(identifierRepository()->dynamicItemFromIndexSimple(index)->m_refCount, index); } else if (shouldDoDUChainReferenceCounting(&rhs)) { QMutexLocker lock(identifierRepository()->mutex()); ifDebug( qCDebug(LANGUAGE) << "decreasing"; ) decrease(identifierRepository()->dynamicItemFromIndexSimple(rhs.index)->m_refCount, rhs.index); } index = rhs.index; rhs.index = emptyConstantIdentifierPrivateIndex(); if(shouldDoDUChainReferenceCounting(this) && !(shouldDoDUChainReferenceCounting(&rhs))) { QMutexLocker lock(identifierRepository()->mutex()); ifDebug( qCDebug(LANGUAGE) << "increasing"; ) increase(identifierRepository()->dynamicItemFromIndexSimple(index)->m_refCount, index); } return *this; } IndexedIdentifier& IndexedIdentifier::operator=(const IndexedIdentifier& id) { if(shouldDoDUChainReferenceCounting(this)) { QMutexLocker lock(identifierRepository()->mutex()); decrease(identifierRepository()->dynamicItemFromIndexSimple(index)->m_refCount, index); } index = id.index; if(shouldDoDUChainReferenceCounting(this)) { QMutexLocker lock(identifierRepository()->mutex()); increase(identifierRepository()->dynamicItemFromIndexSimple(index)->m_refCount, index); } return *this; } bool IndexedIdentifier::operator==(const IndexedIdentifier& rhs) const { return index == rhs.index; } bool IndexedIdentifier::operator!=(const IndexedIdentifier& rhs) const { return index != rhs.index; } bool IndexedIdentifier::operator==(const Identifier& id) const { return index == id.index(); } Identifier IndexedIdentifier::identifier() const { return Identifier(index); } IndexedIdentifier::operator Identifier() const { return Identifier(index); } bool IndexedQualifiedIdentifier::isValid() const { return index != emptyConstantQualifiedIdentifierPrivateIndex(); } bool IndexedQualifiedIdentifier::isEmpty() const { return index == emptyConstantQualifiedIdentifierPrivateIndex(); } int cnt = 0; IndexedQualifiedIdentifier IndexedTypeIdentifier::identifier() const { return m_identifier; } void IndexedTypeIdentifier::setIdentifier(const IndexedQualifiedIdentifier& id) { m_identifier = id; } IndexedQualifiedIdentifier::IndexedQualifiedIdentifier() : index(emptyConstantQualifiedIdentifierPrivateIndex()) { ifDebug( qCDebug(LANGUAGE) << "(" << ++cnt << ")" << identifier().toString() << index; ) if(shouldDoDUChainReferenceCounting(this)) { ifDebug( qCDebug(LANGUAGE) << "increasing"; ) //qCDebug(LANGUAGE) << "(" << ++cnt << ")" << this << identifier().toString() << "inc" << index; QMutexLocker lock(qualifiedidentifierRepository()->mutex()); increase(qualifiedidentifierRepository()->dynamicItemFromIndexSimple(index)->m_refCount, index); } } IndexedQualifiedIdentifier::IndexedQualifiedIdentifier(const QualifiedIdentifier& id) : index(id.index()) { ifDebug( qCDebug(LANGUAGE) << "(" << ++cnt << ")" << identifier().toString() << index; ) if(shouldDoDUChainReferenceCounting(this)) { ifDebug( qCDebug(LANGUAGE) << "increasing"; ) QMutexLocker lock(qualifiedidentifierRepository()->mutex()); increase(qualifiedidentifierRepository()->dynamicItemFromIndexSimple(index)->m_refCount, index); } } IndexedQualifiedIdentifier::IndexedQualifiedIdentifier(const IndexedQualifiedIdentifier& id) : index(id.index) { ifDebug( qCDebug(LANGUAGE) << "(" << ++cnt << ")" << identifier().toString() << index; ) if(shouldDoDUChainReferenceCounting(this)) { ifDebug( qCDebug(LANGUAGE) << "increasing"; ) QMutexLocker lock(qualifiedidentifierRepository()->mutex()); increase(qualifiedidentifierRepository()->dynamicItemFromIndexSimple(index)->m_refCount, index); } } IndexedQualifiedIdentifier::IndexedQualifiedIdentifier(IndexedQualifiedIdentifier&& rhs) Q_DECL_NOEXCEPT : index(rhs.index) { rhs.index = emptyConstantQualifiedIdentifierPrivateIndex(); } IndexedQualifiedIdentifier& IndexedQualifiedIdentifier::operator=(const QualifiedIdentifier& id) { ifDebug( qCDebug(LANGUAGE) << "(" << ++cnt << ")" << identifier().toString() << index; ) if(shouldDoDUChainReferenceCounting(this)) { QMutexLocker lock(qualifiedidentifierRepository()->mutex()); ifDebug( qCDebug(LANGUAGE) << "decreasing"; ) decrease(qualifiedidentifierRepository()->dynamicItemFromIndexSimple(index)->m_refCount, index); index = id.index(); ifDebug( qCDebug(LANGUAGE) << index << "increasing"; ) increase(qualifiedidentifierRepository()->dynamicItemFromIndexSimple(index)->m_refCount, index); } else { index = id.index(); } return *this; } IndexedQualifiedIdentifier& IndexedQualifiedIdentifier::operator=(const IndexedQualifiedIdentifier& rhs) { ifDebug( qCDebug(LANGUAGE) << "(" << ++cnt << ")" << identifier().toString() << index; ) if(shouldDoDUChainReferenceCounting(this)) { QMutexLocker lock(qualifiedidentifierRepository()->mutex()); ifDebug( qCDebug(LANGUAGE) << "decreasing"; ) decrease(qualifiedidentifierRepository()->dynamicItemFromIndexSimple(index)->m_refCount, index); index = rhs.index; ifDebug( qCDebug(LANGUAGE) << index << "increasing"; ) increase(qualifiedidentifierRepository()->dynamicItemFromIndexSimple(index)->m_refCount, index); } else { index = rhs.index; } return *this; } IndexedQualifiedIdentifier& IndexedQualifiedIdentifier::operator=(IndexedQualifiedIdentifier&& rhs) Q_DECL_NOEXCEPT { if(shouldDoDUChainReferenceCounting(this)) { QMutexLocker lock(qualifiedidentifierRepository()->mutex()); ifDebug( qCDebug(LANGUAGE) << "decreasing"; ) decrease(qualifiedidentifierRepository()->dynamicItemFromIndexSimple(index)->m_refCount, index); } else if (shouldDoDUChainReferenceCounting(&rhs)) { QMutexLocker lock(qualifiedidentifierRepository()->mutex()); ifDebug( qCDebug(LANGUAGE) << "decreasing"; ) decrease(qualifiedidentifierRepository()->dynamicItemFromIndexSimple(rhs.index)->m_refCount, rhs.index); } index = rhs.index; rhs.index = emptyConstantQualifiedIdentifierPrivateIndex(); if(shouldDoDUChainReferenceCounting(this) && !(shouldDoDUChainReferenceCounting(&rhs))) { QMutexLocker lock(qualifiedidentifierRepository()->mutex()); ifDebug( qCDebug(LANGUAGE) << "increasing"; ) increase(qualifiedidentifierRepository()->dynamicItemFromIndexSimple(index)->m_refCount, index); } return *this; } IndexedQualifiedIdentifier::~IndexedQualifiedIdentifier() { ifDebug( qCDebug(LANGUAGE) << "(" << ++cnt << ")" << identifier().toString() << index; ) if(shouldDoDUChainReferenceCounting(this)) { ifDebug( qCDebug(LANGUAGE) << index << "decreasing"; ) QMutexLocker lock(qualifiedidentifierRepository()->mutex()); decrease(qualifiedidentifierRepository()->dynamicItemFromIndexSimple(index)->m_refCount, index); } } bool IndexedQualifiedIdentifier::operator==(const IndexedQualifiedIdentifier& rhs) const { return index == rhs.index; } bool IndexedQualifiedIdentifier::operator==(const QualifiedIdentifier& id) const { return index == id.index(); } QualifiedIdentifier IndexedQualifiedIdentifier::identifier() const { return QualifiedIdentifier(index); } IndexedQualifiedIdentifier::operator QualifiedIdentifier() const { return QualifiedIdentifier(index); } void initIdentifierRepository() { emptyConstantIdentifierPrivateIndex(); emptyConstantIdentifierPrivate(); emptyConstantQualifiedIdentifierPrivateIndex(); emptyConstantQualifiedIdentifierPrivate(); } } QDebug operator<<(QDebug s, const KDevelop::Identifier& identifier) { s.nospace() << identifier.toString(); return s.space(); } QDebug operator<<(QDebug s, const KDevelop::QualifiedIdentifier& identifier) { s.nospace() << identifier.toString(); return s.space(); } diff --git a/language/duchain/navigation/abstractdeclarationnavigationcontext.cpp b/language/duchain/navigation/abstractdeclarationnavigationcontext.cpp index 2374fd29d..df173dc62 100644 --- a/language/duchain/navigation/abstractdeclarationnavigationcontext.cpp +++ b/language/duchain/navigation/abstractdeclarationnavigationcontext.cpp @@ -1,788 +1,788 @@ /* 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 "util/debug.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() += "

" + 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() += ' ' + 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() += ' ' + 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('\n', QLatin1String(" ")); comment.replace(QLatin1String("
"), QLatin1String(" ")); comment.replace(QLatin1String("
"), QLatin1String(" ")); modifyHtml() += commentHighlight(comment.toHtmlEscaped()) + " "; } 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() += "

" + commentHighlight(comment) + "

"; } } 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("\n")); comment = comment.toHtmlEscaped(); comment.replace('\n', QLatin1String("
")); //Replicate newlines in html } modifyHtml() += commentHighlight(comment); modifyHtml() += QStringLiteral("

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

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

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

"; 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() += ' ' + 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() += ' ' + identifierHighlight(decls[currentArgNum]->identifier().toString().toHtmlEscaped(), d->m_declaration); } if( currentArgNum >= firstDefaultParam ) modifyHtml() += " = " + function->defaultParameters()[ currentArgNum - firstDefaultParam ].str().toHtmlEscaped(); ++currentArgNum; } modifyHtml() += QStringLiteral(" )"); } modifyHtml() += QStringLiteral("
"); } Identifier AbstractDeclarationNavigationContext::prettyIdentifier(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(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(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() += ", " + stringFromAccess(base.access) + " " + (base.virtualInheritance ? QStringLiteral("virtual") : QString()) + " "; 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("\\&|\\*"); 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(DeclarationPointer decl) { const ClassMemberDeclaration* memberDecl = dynamic_cast(decl.data()); if( memberDecl ) { return stringFromAccess(memberDecl->accessPolicy()); } return QString(); } QString AbstractDeclarationNavigationContext::declarationName( DeclarationPointer decl ) const { if( NamespaceAliasDeclaration* alias = dynamic_cast(decl.data()) ) { if( alias->identifier().isEmpty() ) return "using namespace " + alias->importIdentifier().toString(); else return "namespace " + alias->identifier().toString() + " = " + alias->importIdentifier().toString(); } if( !decl ) return i18nc("A declaration that is unknown", "Unknown"); else return prettyIdentifier(decl).toString(); } QStringList AbstractDeclarationNavigationContext::declarationDetails(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->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/language/duchain/navigation/abstractnavigationcontext.cpp b/language/duchain/navigation/abstractnavigationcontext.cpp index 58d62fa1c..ad9f7ed5a 100644 --- a/language/duchain/navigation/abstractnavigationcontext.cpp +++ b/language/duchain/navigation/abstractnavigationcontext.cpp @@ -1,555 +1,555 @@ /* Copyright 2007 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "abstractnavigationcontext.h" #include #include "abstractdeclarationnavigationcontext.h" #include "abstractnavigationwidget.h" #include "usesnavigationcontext.h" #include "../../../interfaces/icore.h" #include "../../../interfaces/idocumentcontroller.h" #include "../functiondeclaration.h" #include "../namespacealiasdeclaration.h" #include "../types/functiontype.h" #include "../types/structuretype.h" -#include "util/debug.h" +#include #include #include #include namespace KDevelop { class AbstractNavigationContextPrivate { public: QVector m_children; //Used to keep alive all children until this is deleted int m_selectedLink = 0; //The link currently selected NavigationAction m_selectedLinkAction; //Target of the currently selected link bool m_shorten = false; //A counter used while building the html-code to count the used links. int m_linkCount = -1; //Something else than -1 if the current position is represented by a line-number, not a link. int m_currentLine = 0; int m_currentPositionLine = 0; QMap m_links; QMap m_linkLines; //Holds the line for each link QMap m_intLinks; AbstractNavigationContext* m_previousContext; QString m_prefix, m_suffix; TopDUContextPointer m_topContext; QString m_currentText; //Here the text is built }; void AbstractNavigationContext::setTopContext(const TopDUContextPointer& context) { d->m_topContext = context; } TopDUContextPointer AbstractNavigationContext::topContext() const { return d->m_topContext; } AbstractNavigationContext::AbstractNavigationContext(const TopDUContextPointer& topContext, AbstractNavigationContext* previousContext) : d(new AbstractNavigationContextPrivate) { d->m_previousContext = previousContext; d->m_topContext = topContext; } AbstractNavigationContext::~AbstractNavigationContext() { } void AbstractNavigationContext::addExternalHtml( const QString& text ) { int lastPos = 0; int pos = 0; QString fileMark = QStringLiteral("KDEV_FILE_LINK{"); while( pos < text.length() && (pos = text.indexOf( fileMark, pos)) != -1 ) { modifyHtml() += text.mid(lastPos, pos-lastPos); pos += fileMark.length(); if( pos != text.length() ) { int fileEnd = text.indexOf('}', pos); if( fileEnd != -1 ) { QString file = text.mid( pos, fileEnd - pos ); pos = fileEnd + 1; const QUrl url = QUrl::fromUserInput(file); makeLink( url.fileName(), file, NavigationAction( url, KTextEditor::Cursor() ) ); } } lastPos = pos; } modifyHtml() += text.mid(lastPos, text.length()-lastPos); } void AbstractNavigationContext::makeLink( const QString& name, DeclarationPointer declaration, NavigationAction::Type actionType ) { NavigationAction action( declaration, actionType ); makeLink(name, QString(), action); } QString AbstractNavigationContext::createLink(const QString& name, QString, const NavigationAction& action) { if(d->m_shorten) { //Do not create links in shortened mode, it's only for viewing return typeHighlight(name.toHtmlEscaped()); } // NOTE: Since the by definition in the HTML standard some uri components // are case-insensitive, we define a new lowercase link-id for each // link. Otherwise Qt 5 seems to mess up the casing and the link // cannot be matched when it's executed. QString hrefId = QStringLiteral("link_%1").arg(d->m_links.count()); d->m_links[ hrefId ] = action; d->m_intLinks[ d->m_linkCount ] = action; d->m_linkLines[ d->m_linkCount ] = d->m_currentLine; if(d->m_currentPositionLine == d->m_currentLine) { d->m_currentPositionLine = -1; d->m_selectedLink = d->m_linkCount; } QString str = name.toHtmlEscaped(); if( d->m_linkCount == d->m_selectedLink ) str = "" + str + ""; QString ret = "m_linkCount == d->m_selectedLink && d->m_currentPositionLine == -1) ? QStringLiteral(" name = \"currentPosition\"") : QString()) + ">" + str + ""; if( d->m_selectedLink == d->m_linkCount ) d->m_selectedLinkAction = action; ++d->m_linkCount; return ret; } void AbstractNavigationContext::makeLink( const QString& name, QString targetId, const NavigationAction& action) { modifyHtml() += createLink(name, targetId, action); } void AbstractNavigationContext::clear() { d->m_linkCount = 0; d->m_currentLine = 0; d->m_currentText.clear(); d->m_links.clear(); d->m_intLinks.clear(); d->m_linkLines.clear(); } void AbstractNavigationContext::executeLink(const QString& link) { if(!d->m_links.contains(link)) return; execute(d->m_links[link]); } NavigationContextPointer AbstractNavigationContext::executeKeyAction(QString key){ Q_UNUSED(key); return NavigationContextPointer(this); } NavigationContextPointer AbstractNavigationContext::execute(const NavigationAction& action) { if(action.targetContext) return NavigationContextPointer(action.targetContext); if(action.type == NavigationAction::ExecuteKey) return executeKeyAction(action.key); if( !action.decl && (action.type != NavigationAction::JumpToSource || action.document.isEmpty()) ) { qCDebug(LANGUAGE) << "Navigation-action has invalid declaration" << endl; return NavigationContextPointer(this); } qRegisterMetaType("KTextEditor::Cursor"); switch( action.type ) { case NavigationAction::ExecuteKey: break; case NavigationAction::None: qCDebug(LANGUAGE) << "Tried to execute an invalid action in navigation-widget" << endl; break; case NavigationAction::NavigateDeclaration: { auto ctx = dynamic_cast(d->m_previousContext); if( ctx && ctx->declaration() == action.decl ) return NavigationContextPointer(d->m_previousContext); return registerChild(action.decl); } break; case NavigationAction::NavigateUses: { IContextBrowser* browser = ICore::self()->pluginController()->extensionForPlugin(); if (browser) { browser->showUses(action.decl); return NavigationContextPointer(this); } // fall-through } case NavigationAction::ShowUses: { return registerChild(new UsesNavigationContext(action.decl.data(), this)); } case NavigationAction::JumpToSource: { QUrl doc = action.document; KTextEditor::Cursor cursor = action.cursor; { DUChainReadLocker lock(DUChain::lock()); if(action.decl) { if(doc.isEmpty()) { doc = action.decl->url().toUrl(); /* if(action.decl->internalContext()) cursor = action.decl->internalContext()->range().start() + KTextEditor::Cursor(0, 1); else*/ cursor = action.decl->rangeInCurrentRevision().start(); } action.decl->activateSpecialization(); } } //This is used to execute the slot delayed in the event-loop, so crashes are avoided QMetaObject::invokeMethod( ICore::self()->documentController(), "openDocument", Qt::QueuedConnection, Q_ARG(QUrl, doc), Q_ARG(KTextEditor::Cursor, cursor) ); break; } case NavigationAction::ShowDocumentation: { auto doc = ICore::self()->documentationController()->documentationForDeclaration(action.decl.data()); ICore::self()->documentationController()->showDocumentation(doc); } break; } return NavigationContextPointer( this ); } AbstractNavigationContext* AbstractNavigationContext::previousContext() const { return d->m_previousContext; } void AbstractNavigationContext::setPreviousContext(AbstractNavigationContext* previous) { d->m_previousContext = previous; } NavigationContextPointer AbstractNavigationContext::registerChild(AbstractNavigationContext* context) { d->m_children << NavigationContextPointer(context); return d->m_children.last(); } NavigationContextPointer AbstractNavigationContext::registerChild(DeclarationPointer declaration) { //We create a navigation-widget here, and steal its context.. evil ;) QScopedPointer navigationWidget(declaration->context()->createNavigationWidget(declaration.data())); if (AbstractNavigationWidget* abstractNavigationWidget = dynamic_cast(navigationWidget.data()) ) { NavigationContextPointer ret = abstractNavigationWidget->context(); ret->setPreviousContext(this); d->m_children << ret; return ret; } else { return NavigationContextPointer(this); } } const int lineJump = 3; void AbstractNavigationContext::down() { //Make sure link-count is valid if( d->m_linkCount == -1 ) { DUChainReadLocker lock; html(); } int fromLine = d->m_currentPositionLine; if(d->m_selectedLink >= 0 && d->m_selectedLink < d->m_linkCount) { if(fromLine == -1) fromLine = d->m_linkLines[d->m_selectedLink]; for(int newSelectedLink = d->m_selectedLink+1; newSelectedLink < d->m_linkCount; ++newSelectedLink) { if(d->m_linkLines[newSelectedLink] > fromLine && d->m_linkLines[newSelectedLink] - fromLine <= lineJump) { d->m_selectedLink = newSelectedLink; d->m_currentPositionLine = -1; return; } } } if(fromLine == -1) fromLine = 0; d->m_currentPositionLine = fromLine + lineJump; if(d->m_currentPositionLine > d->m_currentLine) d->m_currentPositionLine = d->m_currentLine; } void AbstractNavigationContext::up() { //Make sure link-count is valid if( d->m_linkCount == -1 ) { DUChainReadLocker lock; html(); } int fromLine = d->m_currentPositionLine; if(d->m_selectedLink >= 0 && d->m_selectedLink < d->m_linkCount) { if(fromLine == -1) fromLine = d->m_linkLines[d->m_selectedLink]; for(int newSelectedLink = d->m_selectedLink-1; newSelectedLink >= 0; --newSelectedLink) { if(d->m_linkLines[newSelectedLink] < fromLine && fromLine - d->m_linkLines[newSelectedLink] <= lineJump) { d->m_selectedLink = newSelectedLink; d->m_currentPositionLine = -1; return; } } } if(fromLine == -1) fromLine = d->m_currentLine; d->m_currentPositionLine = fromLine - lineJump; if(d->m_currentPositionLine < 0) d->m_currentPositionLine = 0; } void AbstractNavigationContext::nextLink() { //Make sure link-count is valid if( d->m_linkCount == -1 ) { DUChainReadLocker lock; html(); } d->m_currentPositionLine = -1; if( d->m_linkCount > 0 ) d->m_selectedLink = (d->m_selectedLink+1) % d->m_linkCount; } void AbstractNavigationContext::previousLink() { //Make sure link-count is valid if( d->m_linkCount == -1 ) { DUChainReadLocker lock; html(); } d->m_currentPositionLine = -1; if( d->m_linkCount > 0 ) { --d->m_selectedLink; if( d->m_selectedLink < 0 ) d->m_selectedLink += d->m_linkCount; } Q_ASSERT(d->m_selectedLink >= 0); } int AbstractNavigationContext::linkCount() const { return d->m_linkCount; } QString AbstractNavigationContext::prefix() const { return d->m_prefix; } QString AbstractNavigationContext::suffix() const { return d->m_suffix; } void AbstractNavigationContext::setPrefixSuffix( const QString& prefix, const QString& suffix ) { d->m_prefix = prefix; d->m_suffix = suffix; } NavigationContextPointer AbstractNavigationContext::back() { if(d->m_previousContext) return NavigationContextPointer(d->m_previousContext); else return NavigationContextPointer(this); } NavigationContextPointer AbstractNavigationContext::accept() { if( d->m_selectedLink >= 0 && d->m_selectedLink < d->m_linkCount ) { NavigationAction action = d->m_intLinks[d->m_selectedLink]; return execute(action); } return NavigationContextPointer(this); } NavigationContextPointer AbstractNavigationContext::accept(IndexedDeclaration decl) { if(decl.data()) { NavigationAction action(DeclarationPointer(decl.data()), NavigationAction::NavigateDeclaration); return execute(action); }else{ return NavigationContextPointer(this); } } NavigationContextPointer AbstractNavigationContext::acceptLink(const QString& link) { if( !d->m_links.contains(link) ) { qCDebug(LANGUAGE) << "Executed unregistered link " << link << endl; return NavigationContextPointer(this); } return execute(d->m_links[link]); } NavigationAction AbstractNavigationContext::currentAction() const { return d->m_selectedLinkAction; } QString AbstractNavigationContext::declarationKind(DeclarationPointer decl) { const AbstractFunctionDeclaration* function = dynamic_cast(decl.data()); QString kind; if( decl->isTypeAlias() ) kind = i18n("Typedef"); else if( decl->kind() == Declaration::Type ) { if( decl->type() ) kind = i18n("Class"); } else if( decl->kind() == Declaration::Instance ) { kind = i18n("Variable"); } else if ( decl->kind() == Declaration::Namespace ) { kind = i18n("Namespace"); } if( NamespaceAliasDeclaration* alias = dynamic_cast(decl.data()) ) { if( alias->identifier().isEmpty() ) kind = i18n("Namespace import"); else kind = i18n("Namespace alias"); } if(function) kind = i18n("Function"); if( decl->isForwardDeclaration() ) kind = i18n("Forward Declaration"); return kind; } QString AbstractNavigationContext::html(bool shorten) { d->m_shorten = shorten; return QString(); } bool AbstractNavigationContext::alreadyComputed() const { return !d->m_currentText.isEmpty(); } bool AbstractNavigationContext::isWidgetMaximized() const { return true; } QWidget* AbstractNavigationContext::widget() const { return nullptr; } ///Splits the string by the given regular expression, but keeps the split-matches at the end of each line static QStringList splitAndKeep(QString str, QRegExp regExp) { QStringList ret; int place = regExp.indexIn(str); while(place != -1) { ret << str.left(place + regExp.matchedLength()); str = str.mid(place + regExp.matchedLength()); place = regExp.indexIn(str); } ret << str; return ret; } void AbstractNavigationContext::addHtml(QString html) { QRegExp newLineRegExp("
|
"); foreach(const QString& line, splitAndKeep(html, newLineRegExp)) { d->m_currentText += line; if(line.indexOf(newLineRegExp) != -1) { ++d->m_currentLine; if(d->m_currentLine == d->m_currentPositionLine) { d->m_currentText += QStringLiteral(" <-> "); // ><-> is <-> } } } } QString AbstractNavigationContext::currentHtml() const { return d->m_currentText; } QString AbstractNavigationContext::fontSizePrefix(bool /*shorten*/) const { return QString(); } QString AbstractNavigationContext::fontSizeSuffix(bool /*shorten*/) const { return QString(); } QString Colorizer::operator() ( const QString& str ) const { QString ret = "" + str + ""; if( m_formatting & Fixed ) ret = ""+ret+""; if ( m_formatting & Bold ) ret = ""+ret+""; if ( m_formatting & Italic ) ret = ""+ret+""; return ret; } const Colorizer AbstractNavigationContext::typeHighlight(QStringLiteral("006000")); const Colorizer AbstractNavigationContext::errorHighlight(QStringLiteral("990000")); const Colorizer AbstractNavigationContext::labelHighlight(QStringLiteral("000000")); const Colorizer AbstractNavigationContext::codeHighlight(QStringLiteral("005000")); const Colorizer AbstractNavigationContext::propertyHighlight(QStringLiteral("009900")); const Colorizer AbstractNavigationContext::navigationHighlight(QStringLiteral("000099")); const Colorizer AbstractNavigationContext::importantHighlight(QStringLiteral("000000"), Colorizer::Bold | Colorizer::Italic); const Colorizer AbstractNavigationContext::commentHighlight(QStringLiteral("303030")); const Colorizer AbstractNavigationContext::nameHighlight(QStringLiteral("000000"), Colorizer::Bold); } diff --git a/language/duchain/navigation/abstractnavigationwidget.cpp b/language/duchain/navigation/abstractnavigationwidget.cpp index 2040b85a5..16542b22d 100644 --- a/language/duchain/navigation/abstractnavigationwidget.cpp +++ b/language/duchain/navigation/abstractnavigationwidget.cpp @@ -1,328 +1,328 @@ /* 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 "abstractnavigationwidget.h" #include #include #include #include #include #include #include #include "../duchainlock.h" -#include "util/debug.h" +#include namespace { const int maxNavigationWidgetWidth = 580; const int maxNavigationWidgetHeight = 400; } namespace KDevelop { class AbstractNavigationWidgetPrivate { public: AbstractNavigationWidgetPrivate(AbstractNavigationWidget* q) : q(q) {} void anchorClicked(const QUrl&); AbstractNavigationWidget* q; NavigationContextPointer m_startContext; QPointer m_browser; QWidget* m_currentWidget = nullptr; QString m_currentText; mutable QSize m_idealTextSize; AbstractNavigationWidget::DisplayHints m_hints = AbstractNavigationWidget::NoHints; NavigationContextPointer m_context; }; AbstractNavigationWidget::AbstractNavigationWidget() : d(new AbstractNavigationWidgetPrivate(this)) { setPalette( QApplication::palette() ); setFocusPolicy(Qt::NoFocus); resize(100, 100); } QSize AbstractNavigationWidget::sizeHint() const { if(d->m_browser) { updateIdealSize(); QSize ret = QSize(qMin(d->m_idealTextSize.width(), maxNavigationWidgetWidth), qMin(d->m_idealTextSize.height(), maxNavigationWidgetHeight)); if(d->m_idealTextSize.height()>=maxNavigationWidgetHeight) { //make space for the scrollbar in case it's not fitting ret.rwidth() += 17; //m_browser->verticalScrollBar()->width() returns 100, for some reason } if(d->m_currentWidget) { ret.setHeight( ret.height() + d->m_currentWidget->sizeHint().height() ); if(d->m_currentWidget->sizeHint().width() > ret.width()) ret.setWidth(d->m_currentWidget->sizeHint().width()); if(ret.width() < 500) //When we embed a widget, give it some space, even if it doesn't have a large size-hint ret.setWidth(500); } return ret; } else return QWidget::sizeHint(); } void AbstractNavigationWidget::initBrowser(int height) { Q_ASSERT(!d->m_browser); Q_UNUSED(height); d->m_browser = new QTextBrowser; // since we can embed arbitrary HTML we have to make sure it stays readable by forcing a black-white palette QPalette p; p.setColor(QPalette::AlternateBase, Qt::white); p.setColor(QPalette::Base, Qt::white); p.setColor(QPalette::Text, Qt::black); d->m_browser->setPalette( p ); d->m_browser->setOpenLinks(false); d->m_browser->setOpenExternalLinks(false); QVBoxLayout* layout = new QVBoxLayout; layout->addWidget(d->m_browser); layout->setMargin(0); setLayout(layout); connect(d->m_browser.data(), &QTextBrowser::anchorClicked, this, [&](const QUrl& url) { d->anchorClicked(url); }); foreach(QWidget* w, findChildren()) w->setContextMenuPolicy(Qt::NoContextMenu); } AbstractNavigationWidget::~AbstractNavigationWidget() { if(d->m_currentWidget) layout()->removeWidget(d->m_currentWidget); } void AbstractNavigationWidget::setContext(NavigationContextPointer context, int initBrows) { if(d->m_browser == nullptr) initBrowser(initBrows); if(!context) { qCDebug(LANGUAGE) << "no new context created"; return; } if(context == d->m_context && (!context || context->alreadyComputed())) return; if (!d->m_startContext) { d->m_startContext = context; } bool wasInitial = (d->m_context == d->m_startContext); d->m_context = context; update(); emit contextChanged(wasInitial, d->m_context == d->m_startContext); emit sizeHintChanged(); } void AbstractNavigationWidget::updateIdealSize() const { if(d->m_context && !d->m_idealTextSize.isValid()) { QTextDocument doc; doc.setHtml(d->m_currentText); if(doc.idealWidth() > maxNavigationWidgetWidth) { doc.setTextWidth(maxNavigationWidgetWidth); d->m_idealTextSize.setWidth(maxNavigationWidgetWidth); }else{ d->m_idealTextSize.setWidth(doc.idealWidth()); } d->m_idealTextSize.setHeight(doc.size().height()); } } void AbstractNavigationWidget::setDisplayHints(DisplayHints hints) { d->m_hints = hints; } void AbstractNavigationWidget::update() { setUpdatesEnabled(false); Q_ASSERT( d->m_context ); QString html; { DUChainReadLocker lock; html = d->m_context->html(); } if(!html.isEmpty()) { int scrollPos = d->m_browser->verticalScrollBar()->value(); if ( !(d->m_hints & EmbeddableWidget)) { // TODO: Only show that the first time, or the first few times this context is shown? html += QStringLiteral("

"); if (d->m_context->linkCount() > 0) { html += i18n("(Hold Alt to show. Navigate via arrow keys, activate by pressing Enter)"); } else { html += i18n("(Hold Alt to show this tooltip)"); } html += QStringLiteral("

"); } d->m_browser->setHtml( html ); d->m_currentText = html; d->m_idealTextSize = QSize(); QSize hint = sizeHint(); if(hint.height() >= d->m_idealTextSize.height()) { d->m_browser->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); }else{ d->m_browser->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); } d->m_browser->verticalScrollBar()->setValue(scrollPos); d->m_browser->scrollToAnchor(QStringLiteral("currentPosition")); d->m_browser->show(); }else{ d->m_browser->hide(); } if(d->m_currentWidget) { layout()->removeWidget(d->m_currentWidget); d->m_currentWidget->setParent(nullptr); } d->m_currentWidget = d->m_context->widget(); d->m_browser->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); d->m_browser->setMaximumHeight(10000); if(d->m_currentWidget) { if (d->m_currentWidget->metaObject() ->indexOfSignal(QMetaObject::normalizedSignature("navigateDeclaration(KDevelop::IndexedDeclaration)")) != -1) { connect(d->m_currentWidget, SIGNAL(navigateDeclaration(KDevelop::IndexedDeclaration)), this, SLOT(navigateDeclaration(KDevelop::IndexedDeclaration))); } layout()->addWidget(d->m_currentWidget); if(d->m_context->isWidgetMaximized()) { //Leave unused room to the widget d->m_browser->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); d->m_browser->setMaximumHeight(d->m_idealTextSize.height()); } } setUpdatesEnabled(true); } NavigationContextPointer AbstractNavigationWidget::context() { return d->m_context; } void AbstractNavigationWidget::navigateDeclaration(const IndexedDeclaration& decl) { setContext(d->m_context->accept(decl)); } void AbstractNavigationWidgetPrivate::anchorClicked(const QUrl& url) { //We may get deleted while the call to acceptLink, so make sure we don't crash in that case QPointer thisPtr(q); NavigationContextPointer nextContext = m_context->acceptLink(url.toString()); if(thisPtr) q->setContext(nextContext); } void AbstractNavigationWidget::next() { Q_ASSERT( d->m_context ); d->m_context->nextLink(); update(); } void AbstractNavigationWidget::previous() { Q_ASSERT( d->m_context ); d->m_context->previousLink(); update(); } void AbstractNavigationWidget::accept() { Q_ASSERT( d->m_context ); QPointer thisPtr(this); NavigationContextPointer nextContext = d->m_context->accept(); if(thisPtr) setContext( nextContext ); } void AbstractNavigationWidget::back() { QPointer thisPtr(this); NavigationContextPointer nextContext = d->m_context->back(); if(thisPtr) setContext( nextContext ); } void AbstractNavigationWidget::up() { d->m_context->up(); update(); } void AbstractNavigationWidget::down() { d->m_context->down(); update(); } void AbstractNavigationWidget::embeddedWidgetAccept() { accept(); } void AbstractNavigationWidget::embeddedWidgetDown() { down(); } void AbstractNavigationWidget::embeddedWidgetRight() { next(); } void AbstractNavigationWidget::embeddedWidgetLeft() { previous(); } void AbstractNavigationWidget::embeddedWidgetUp() { up(); } void AbstractNavigationWidget::wheelEvent(QWheelEvent* event ) { QWidget::wheelEvent(event); event->accept(); } } #include "moc_abstractnavigationwidget.cpp" diff --git a/language/duchain/navigation/problemnavigationcontext.cpp b/language/duchain/navigation/problemnavigationcontext.cpp index 8f29f4614..39bd7505c 100644 --- a/language/duchain/navigation/problemnavigationcontext.cpp +++ b/language/duchain/navigation/problemnavigationcontext.cpp @@ -1,271 +1,269 @@ /* Copyright 2009 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "problemnavigationcontext.h" -#include "util/debug.h" - -#include +#include #include #include #include #include #include #include #include #include using namespace KDevelop; namespace { QString KEY_INVOKE_ACTION(int num) { return QStringLiteral("invoke_action_%1").arg(num); } QString iconForSeverity(IProblem::Severity severity) { switch (severity) { case IProblem::Hint: return QStringLiteral("dialog-information"); case IProblem::Warning: return QStringLiteral("dialog-warning"); case IProblem::Error: return QStringLiteral("dialog-error"); case IProblem::NoSeverity: return {}; } Q_UNREACHABLE(); return {}; } QString htmlImg(const QString& iconName, KIconLoader::Group group) { KIconLoader loader; const int size = loader.currentSize(group); return QStringLiteral("") .arg(size) .arg(loader.iconPath(iconName, group)); } } ProblemNavigationContext::ProblemNavigationContext(const QVector& problems, const Flags flags) : m_problems(problems) , m_flags(flags) , m_widget(nullptr) { // Sort problems vector: // 1) By severity // 2) By sourceString, if severities are equals std::sort(m_problems.begin(), m_problems.end(), [](const IProblem::Ptr a, const IProblem::Ptr b) { if (a->severity() < b->severity()) return true; if (a->severity() > b->severity()) return false; if (a->sourceString() < b->sourceString()) return true; return false; }); } ProblemNavigationContext::~ProblemNavigationContext() { delete m_widget; } QWidget* ProblemNavigationContext::widget() const { return m_widget; } bool ProblemNavigationContext::isWidgetMaximized() const { return false; } QString ProblemNavigationContext::name() const { return i18n("Problem"); } QString ProblemNavigationContext::escapedHtml(const QString& text) const { static const QString htmlStart = QStringLiteral(""); static const QString htmlEnd = QStringLiteral(""); QString result = text.trimmed(); if (!result.startsWith(htmlStart)) return result.toHtmlEscaped(); result.remove(htmlStart); result.remove(htmlEnd); return result; } void ProblemNavigationContext::html(IProblem::Ptr problem) { auto iconPath = iconForSeverity(problem->severity()); modifyHtml() += QStringLiteral(""); modifyHtml() += QStringLiteral("").arg(htmlImg(iconPath, KIconLoader::Panel)); // BEGIN: right column modifyHtml() += QStringLiteral(""); // END: right column modifyHtml() += QStringLiteral("
%1"); modifyHtml() += i18n("Problem in %1", problem->sourceString()); modifyHtml() += QStringLiteral("
"); if (m_flags & ShowLocation) { modifyHtml() += labelHighlight(i18n("Location: ")); makeLink(QStringLiteral("%1 :%2") .arg(problem->finalLocation().document.toUrl().fileName()) .arg(problem->finalLocation().start().line() + 1), QString(), NavigationAction(problem->finalLocation().document.toUrl(), problem->finalLocation().start()) ); modifyHtml() += QStringLiteral("
"); } QString description = escapedHtml(problem->description()); QString explanation = escapedHtml(problem->explanation()); modifyHtml() += description; // Add only non-empty explanation which differs from the problem description. // Skip this if we have more than one problem. if (m_problems.size() == 1 && !explanation.isEmpty() && explanation != description) modifyHtml() += "

" + explanation + "

"; modifyHtml() += QStringLiteral("
"); auto diagnostics = problem->diagnostics(); if (!diagnostics.isEmpty()) { DUChainReadLocker lock; for (auto diagnostic : diagnostics) { modifyHtml() += QStringLiteral("

"); modifyHtml() += labelHighlight(QStringLiteral("%1: ").arg(diagnostic->severityString())); modifyHtml() += escapedHtml(diagnostic->description()); const DocumentRange range = diagnostic->finalLocation(); Declaration* declaration = DUChainUtils::itemUnderCursor(range.document.toUrl(), range.start()).declaration; if (declaration) { modifyHtml() += i18n("
See: "); makeLink(declaration->toString(), DeclarationPointer(declaration), NavigationAction::NavigateDeclaration); modifyHtml() += i18n(" in "); makeLink(QStringLiteral("%1 :%2") .arg(declaration->url().toUrl().fileName()) .arg(declaration->rangeInCurrentRevision().start().line() + 1), DeclarationPointer(declaration), NavigationAction::NavigateDeclaration); } else if (range.start().isValid()) { modifyHtml() += i18n("
See: "); const auto url = range.document.toUrl(); makeLink(QStringLiteral("%1 :%2") .arg(url.fileName()) .arg(range.start().line() + 1), url.toDisplayString(QUrl::PreferLocalFile), NavigationAction(url, range.start())); } modifyHtml() += QStringLiteral("

"); } } auto assistant = problem->solutionAssistant(); if (assistant && !assistant->actions().isEmpty()) { modifyHtml() += QStringLiteral("").arg(QStringLiteral("#b3d4ff")); modifyHtml() += QStringLiteral(""); modifyHtml() += QStringLiteral("
%1").arg(htmlImg(QStringLiteral("dialog-ok-apply"), KIconLoader::Panel)); const int startIndex = m_assistantsActions.size(); int currentIndex = startIndex; foreach (auto assistantAction, assistant->actions()) { m_assistantsActions.append(assistantAction); if (currentIndex != startIndex) modifyHtml() += QStringLiteral("
"); makeLink(i18n("Solution (%1)", currentIndex + 1), KEY_INVOKE_ACTION( currentIndex ), NavigationAction(KEY_INVOKE_ACTION( currentIndex ))); modifyHtml() += ": " + assistantAction->description().toHtmlEscaped(); ++currentIndex; } modifyHtml() += QStringLiteral("
"); } } QString ProblemNavigationContext::html(bool shorten) { AbstractNavigationContext::html(shorten); clear(); m_assistantsActions.clear(); int problemIndex = 0; foreach (auto problem, m_problems) { html(problem); if (++problemIndex != m_problems.size()) modifyHtml() += QStringLiteral("
"); } return currentHtml(); } NavigationContextPointer ProblemNavigationContext::executeKeyAction(QString key) { if (key.startsWith(QLatin1String("invoke_action_"))) { const int index = key.replace(QLatin1String("invoke_action_"), QString()).toInt(); executeAction(index); } return {}; } void ProblemNavigationContext::executeAction(int index) { if (index < 0 || index >= m_assistantsActions.size()) return; auto action = m_assistantsActions.at(index); Q_ASSERT(action); if (action) { action->execute(); if ( topContext() ) DUChain::self()->updateContextForUrl(topContext()->url(), TopDUContext::ForceUpdate); } else { qCWarning(LANGUAGE()) << "No such action"; return; } } diff --git a/language/duchain/navigation/usescollector.cpp b/language/duchain/navigation/usescollector.cpp index 76a0cacf7..a172407bc 100644 --- a/language/duchain/navigation/usescollector.cpp +++ b/language/duchain/navigation/usescollector.cpp @@ -1,427 +1,427 @@ /* Copyright 2008 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "usescollector.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "../classmemberdeclaration.h" #include "../abstractfunctiondeclaration.h" #include "../functiondefinition.h" -#include "util/debug.h" +#include #include #include #include using namespace KDevelop; ///@todo make this language-neutral static Identifier destructorForName(Identifier name) { QString str = name.identifier().str(); if(str.startsWith('~')) return Identifier(str); return Identifier('~'+str); } ///@todo Only collect uses within currently loaded projects template void collectImporters(ImportanceChecker& checker, ParsingEnvironmentFile* current, QSet& visited, QSet& collected) { //Ignore proxy-contexts while collecting. Those build a parallel and much more complicated structure. if(current->isProxyContext()) return; if(visited.contains(current)) return; visited.insert(current); if(checker(current)) collected.insert(current); foreach(const ParsingEnvironmentFilePointer& importer, current->importers()) if(importer.data()) collectImporters(checker, importer.data(), visited, collected); else qCDebug(LANGUAGE) << "missing environment-file, strange"; } ///The returned set does not include the file itself ///@parm visited should be empty on each call, used to prevent endless recursion void allImportedFiles(ParsingEnvironmentFilePointer file, QSet& set, QSet& visited) { foreach(const ParsingEnvironmentFilePointer &import, file->imports()) { if(!import) { qCDebug(LANGUAGE) << "warning: missing import"; continue; } if(!visited.contains(import)) { visited.insert(import); set.insert(import->url()); allImportedFiles(import, set, visited); } } } void UsesCollector::setCollectConstructors(bool process) { m_collectConstructors = process; } void UsesCollector::setProcessDeclarations(bool process) { m_processDeclarations = process; } void UsesCollector::setCollectOverloads(bool collect) { m_collectOverloads = collect; } void UsesCollector::setCollectDefinitions(bool collect) { m_collectDefinitions = collect; } QList UsesCollector::declarations() { return m_declarations; } bool UsesCollector::isReady() const { return m_waitForUpdate.size() == m_updateReady.size(); } bool UsesCollector::shouldRespectFile(IndexedString document) { return (bool)ICore::self()->projectController()->findProjectForUrl(document.toUrl()) || (bool)ICore::self()->documentController()->documentForUrl(document.toUrl()); } struct ImportanceChecker { explicit ImportanceChecker(UsesCollector& collector) : m_collector(collector) { } bool operator ()(ParsingEnvironmentFile* file) { return m_collector.shouldRespectFile(file->url()); } UsesCollector& m_collector; }; void UsesCollector::startCollecting() { DUChainReadLocker lock(DUChain::lock()); if(Declaration* decl = m_declaration.data()) { if(m_collectDefinitions) { if(FunctionDefinition* def = dynamic_cast(decl)) { //Jump from definition to declaration Declaration* declaration = def->declaration(); if(declaration) decl = declaration; } } ///Collect all overloads into "decls" QList decls; if(m_collectOverloads && decl->context()->owner() && decl->context()->type() == DUContext::Class) { //First find the overridden base, and then all overriders of that base. while(Declaration* overridden = DUChainUtils::getOverridden(decl)) decl = overridden; uint maxAllowedSteps = 10000; decls += DUChainUtils::getOverriders( decl->context()->owner(), decl, maxAllowedSteps ); if(maxAllowedSteps == 10000) { ///@todo Fail! } } decls << decl; ///Collect all "parsed versions" or forward-declarations etc. here, into allDeclarations QSet allDeclarations; foreach(Declaration* overload, decls) { m_declarations = DUChainUtils::collectAllVersions(overload); foreach(const IndexedDeclaration &d, m_declarations) { if(!d.data() || d.data()->id() != overload->id()) continue; allDeclarations.insert(d); if(m_collectConstructors && d.data() && d.data()->internalContext() && d.data()->internalContext()->type() == DUContext::Class) { QList constructors = d.data()->internalContext()->findLocalDeclarations(d.data()->identifier(), CursorInRevision::invalid(), nullptr, AbstractType::Ptr(), DUContext::OnlyFunctions); foreach(Declaration* constructor, constructors) { ClassFunctionDeclaration* classFun = dynamic_cast(constructor); if(classFun && classFun->isConstructor()) allDeclarations.insert(IndexedDeclaration(constructor)); } Identifier destructorId = destructorForName(d.data()->identifier()); QList destructors = d.data()->internalContext()->findLocalDeclarations(destructorId, CursorInRevision::invalid(), nullptr, AbstractType::Ptr(), DUContext::OnlyFunctions); foreach(Declaration* destructor, destructors) { ClassFunctionDeclaration* classFun = dynamic_cast(destructor); if(classFun && classFun->isDestructor()) allDeclarations.insert(IndexedDeclaration(destructor)); } } } } ///Collect definitions for declarations if(m_collectDefinitions) { foreach(const IndexedDeclaration d, allDeclarations) { Declaration* definition = FunctionDefinition::definition(d.data()); if(definition) { qCDebug(LANGUAGE) << "adding definition"; allDeclarations.insert(IndexedDeclaration(definition)); } } } m_declarations.clear(); ///Step 4: Copy allDeclarations into m_declarations, build top-context list, etc. QList candidateTopContexts; foreach(const IndexedDeclaration d, allDeclarations) { m_declarations << d; m_declarationTopContexts.insert(d.indexedTopContext()); //We only collect declarations with the same type here.. candidateTopContexts << d.indexedTopContext().data(); } ImportanceChecker checker(*this); QSet visited; QSet collected; qCDebug(LANGUAGE) << "count of source candidate top-contexts:" << candidateTopContexts.size(); ///We use ParsingEnvironmentFile to collect all the relevant importers, because loading those is very cheap, compared ///to loading a whole TopDUContext. if(decl->inSymbolTable()) { //The declaration can only be used from other contexts if it is in the symbol table foreach(const ReferencedTopDUContext &top, candidateTopContexts) { if(top->parsingEnvironmentFile()) { collectImporters(checker, top->parsingEnvironmentFile().data(), visited, collected); //In C++, visibility is not handled strictly through the import-structure. //It may happen that an object is visible because of an earlier include. //We can not perfectly handle that, but we can at least handle it if the header includes //the header that contains the declaration. That header may be parsed empty due to header-guards, //but we still need to pick it up here. QList allVersions = DUChain::self()->allEnvironmentFiles(top->url()); foreach(const ParsingEnvironmentFilePointer& version, allVersions) collectImporters(checker, version.data(), visited, collected); } } } KDevelop::ParsingEnvironmentFile* file=decl->topContext()->parsingEnvironmentFile().data(); if(!file) return; if(checker(file)) collected.insert(file); { QSet filteredCollected; QMap grepCache; // Filter the collected files by performing a grep foreach(ParsingEnvironmentFile* file, collected) { IndexedString url = file->url(); QMap< IndexedString, bool >::iterator grepCacheIt = grepCache.find(url); if(grepCacheIt == grepCache.end()) { CodeRepresentation::Ptr repr = KDevelop::createCodeRepresentation( url ); if(repr) { QVector found = repr->grep(decl->identifier().identifier().str()); grepCacheIt = grepCache.insert(url, !found.isEmpty()); } } if(grepCacheIt.value()) filteredCollected << file; } qCDebug(LANGUAGE) << "Collected contexts for full re-parse, before filtering: " << collected.size() << " after filtering: " << filteredCollected.size(); collected = filteredCollected; } ///We have all importers now. However since we can tell parse-jobs to also update all their importers, we only need to ///update the "root" top-contexts that open the whole set with their imports. QSet rootFiles; QSet allFiles; foreach(ParsingEnvironmentFile* importer, collected) { QSet allImports; QSet visited; allImportedFiles(ParsingEnvironmentFilePointer(importer), allImports, visited); //Remove all files from the "root" set that are imported by this one ///@todo more intelligent rootFiles -= allImports; allFiles += allImports; allFiles.insert(importer->url()); rootFiles.insert(importer->url()); } emit maximumProgressSignal(rootFiles.size()); maximumProgress(rootFiles.size()); //If we used the AllDeclarationsContextsAndUsesRecursive flag here, we would compute way too much. This way we only //set the minimum-features selectively on the files we really require them on. foreach(ParsingEnvironmentFile* file, collected) m_staticFeaturesManipulated.insert(file->url()); m_staticFeaturesManipulated.insert(decl->url()); foreach(const IndexedString &file, m_staticFeaturesManipulated) ParseJob::setStaticMinimumFeatures(file, TopDUContext::AllDeclarationsContextsAndUses); m_waitForUpdate = rootFiles; foreach(const IndexedString &file, rootFiles) { qCDebug(LANGUAGE) << "updating root file:" << file.str(); DUChain::self()->updateContextForUrl(file, TopDUContext::AllDeclarationsContextsAndUses, this); } }else{ emit maximumProgressSignal(0); maximumProgress(0); } } void UsesCollector::maximumProgress(uint max) { Q_UNUSED(max); } UsesCollector::UsesCollector(IndexedDeclaration declaration) : m_declaration(declaration), m_collectOverloads(true), m_collectDefinitions(true), m_collectConstructors(false), m_processDeclarations(true) { } UsesCollector::~UsesCollector() { ICore::self()->languageController()->backgroundParser()->revertAllRequests(this); foreach(const IndexedString &file, m_staticFeaturesManipulated) ParseJob::unsetStaticMinimumFeatures(file, TopDUContext::AllDeclarationsContextsAndUses); } void UsesCollector::progress(uint processed, uint total) { Q_UNUSED(processed); Q_UNUSED(total); } void UsesCollector::updateReady(KDevelop::IndexedString url, KDevelop::ReferencedTopDUContext topContext) { DUChainReadLocker lock(DUChain::lock()); if(!topContext) { qCDebug(LANGUAGE) << "failed updating" << url.str(); }else{ if(topContext->parsingEnvironmentFile() && topContext->parsingEnvironmentFile()->isProxyContext()) { ///Use the attached content-context instead foreach(const DUContext::Import &import, topContext->importedParentContexts()) { if(import.context(nullptr) && import.context(nullptr)->topContext()->parsingEnvironmentFile() && !import.context(nullptr)->topContext()->parsingEnvironmentFile()->isProxyContext()) { if((import.context(nullptr)->topContext()->features() & TopDUContext::AllDeclarationsContextsAndUses)) { ReferencedTopDUContext newTop(import.context(nullptr)->topContext()); topContext = newTop; break; } } } if(topContext->parsingEnvironmentFile() && topContext->parsingEnvironmentFile()->isProxyContext()) { qCDebug(LANGUAGE) << "got bad proxy-context for" << url.str(); topContext = nullptr; } } } if(m_waitForUpdate.contains(url) && !m_updateReady.contains(url)) { m_updateReady << url; m_checked.clear(); emit progressSignal(m_updateReady.size(), m_waitForUpdate.size()); progress(m_updateReady.size(), m_waitForUpdate.size()); } if(!topContext || !topContext->parsingEnvironmentFile()) { qCDebug(LANGUAGE) << "bad top-context"; return; } if(!m_staticFeaturesManipulated.contains(url)) return; //Not interesting if(!(topContext->features() & TopDUContext::AllDeclarationsContextsAndUses)) { ///@todo With simplified environment-matching, the same file may have been imported multiple times, ///while only one of those was updated. We have to check here whether this file is just such an import, ///or whether we work on with it. ///@todo We will lose files that were edited right after their update here. qCWarning(LANGUAGE) << "WARNING: context" << topContext->url().str() << "does not have the required features!!"; ICore::self()->uiController()->showErrorMessage("Updating " + ICore::self()->projectController()->prettyFileName(topContext->url().toUrl(), KDevelop::IProjectController::FormatPlain) + " failed!", 5); return; } if(topContext->parsingEnvironmentFile()->needsUpdate()) { qCWarning(LANGUAGE) << "WARNING: context" << topContext->url().str() << "is not up to date!"; ICore::self()->uiController()->showErrorMessage(i18n("%1 still needs an update!", ICore::self()->projectController()->prettyFileName(topContext->url().toUrl(), KDevelop::IProjectController::FormatPlain)), 5); // return; } IndexedTopDUContext indexed(topContext.data()); if(m_checked.contains(indexed)) return; if(!topContext.data()) { qCDebug(LANGUAGE) << "updated top-context is zero:" << url.str(); return; } m_checked.insert(indexed); if(m_declaration.data() && ((m_processDeclarations && m_declarationTopContexts.contains(indexed)) || DUChainUtils::contextHasUse(topContext.data(), m_declaration.data()))) { if(!m_processed.contains(topContext->url())) { m_processed.insert(topContext->url()); lock.unlock(); emit processUsesSignal(topContext); processUses(topContext); lock.lock(); } } else { if(!m_declaration.data()) { qCDebug(LANGUAGE) << "declaration has become invalid"; } } QList imports; foreach(const DUContext::Import &imported, topContext->importedParentContexts()) if(imported.context(nullptr) && imported.context(nullptr)->topContext()) imports << KDevelop::ReferencedTopDUContext(imported.context(nullptr)->topContext()); foreach(const KDevelop::ReferencedTopDUContext &import, imports) { IndexedString url = import->url(); lock.unlock(); updateReady(url, import); lock.lock(); } } IndexedDeclaration UsesCollector::declaration() const { return m_declaration; } diff --git a/language/duchain/navigation/useswidget.cpp b/language/duchain/navigation/useswidget.cpp index 8835ebd45..dd998aeaf 100644 --- a/language/duchain/navigation/useswidget.cpp +++ b/language/duchain/navigation/useswidget.cpp @@ -1,699 +1,699 @@ /* Copyright 2008 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "useswidget.h" -#include "util/debug.h" +#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; namespace { QString backgroundColor(bool isHighlighted) { if (isHighlighted) { return QColor(251, 150, 242).name(); } else { return QColor(251, 250, 150).name(); } } } const int tooltipContextSize = 2; //How many lines around the use are shown in the tooltip ///The returned text is fully escaped ///@param cutOff The total count of characters that should be cut of, all in all on both sides together. ///@param range The range that is highlighted, and that will be preserved during cutting, given that there is enough room beside it. QString highlightAndEscapeUseText(QString line, int cutOff, KTextEditor::Range range) { int leftCutRoom = range.start().column(); int rightCutRoom = line.length() - range.end().column(); if(range.start().column() < 0 || range.end().column() > line.length() || cutOff > leftCutRoom + rightCutRoom) return QString(); //Not enough room for cutting off on sides int leftCut = 0; int rightCut = 0; if(leftCutRoom < rightCutRoom) { if(leftCutRoom * 2 >= cutOff) { //Enough room on both sides. Just cut. leftCut = cutOff / 2; rightCut = cutOff - leftCut; }else{ //Not enough room in left side, but enough room all together leftCut = leftCutRoom; rightCut = cutOff - leftCut; } }else{ if(rightCutRoom * 2 >= cutOff) { //Enough room on both sides. Just cut. rightCut = cutOff / 2; leftCut = cutOff - rightCut; }else{ //Not enough room in right side, but enough room all together rightCut = rightCutRoom; leftCut = cutOff - rightCut; } } Q_ASSERT(leftCut + rightCut <= cutOff); line = line.left(line.length() - rightCut); line = line.mid(leftCut); range += KTextEditor::Range(0, -leftCut, 0, -leftCut); Q_ASSERT(range.start().column() >= 0 && range.end().column() <= line.length()); //TODO: share code with context browser // mixing (255, 255, 0, 100) with white yields this: const QColor foreground(0, 0, 0); return "" + line.left(range.start().column()).toHtmlEscaped() + "" + line.mid(range.start().column(), range.end().column() - range.start().column()).toHtmlEscaped() + "" + line.mid(range.end().column(), line.length() - range.end().column()).toHtmlEscaped() + ""; } /** * Note: the links in the HTML here are only used for styling * the navigation is implemented in the mouse press event handler */ OneUseWidget::OneUseWidget(IndexedDeclaration declaration, IndexedString document, KTextEditor::Range range, const CodeRepresentation& code) : m_range(new PersistentMovingRange(range, document)), m_declaration(declaration), m_document(document) { //Make the sizing of this widget independent of the content, because we will adapt the content to the size setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); m_sourceLine = code.line(m_range->range().start().line()); m_layout = new QHBoxLayout(this); m_layout->setContentsMargins(0, 0, 0, 0); setLayout(m_layout); setCursor(Qt::PointingHandCursor); m_label = new QLabel(this); m_icon = new QLabel(this); m_icon->setPixmap(QIcon::fromTheme(QStringLiteral("code-function")).pixmap(16)); DUChainReadLocker lock(DUChain::lock()); QString text = "" + i18nc("refers to a line in source code", "Line %1:", range.start().line()) + QStringLiteral(""); if(!m_sourceLine.isEmpty() && m_sourceLine.length() > m_range->range().end().column()) { text += "  " + highlightAndEscapeUseText(m_sourceLine, 0, m_range->range()); //Useful tooltip: int start = m_range->range().start().line() - tooltipContextSize; int end = m_range->range().end().line() + tooltipContextSize + 1; QString toolTipText; for(int a = start; a < end; ++a) { QString lineText = code.line(a).toHtmlEscaped(); if (m_range->range().start().line() <= a && m_range->range().end().line() >= a) { lineText = QStringLiteral("") + lineText + QStringLiteral(""); } if(!lineText.trimmed().isEmpty()) { toolTipText += lineText + "
"; } } if ( toolTipText.endsWith(QLatin1String("
")) ) { toolTipText.remove(toolTipText.length() - 4, 4); } setToolTip(QStringLiteral("
") + toolTipText + QStringLiteral("
")); } m_label->setText(text); m_layout->addWidget(m_icon); m_layout->addWidget(m_label); m_layout->setAlignment(Qt::AlignLeft); } void OneUseWidget::setHighlighted(bool highlight) { if (highlight == m_isHighlighted) { return; } if (highlight) { m_label->setText(m_label->text().replace("background-color:" + backgroundColor(false), "background-color:" + backgroundColor(true))); m_isHighlighted = true; } else { m_label->setText(m_label->text().replace("background-color:" + backgroundColor(true), "background-color:" + backgroundColor(false))); m_isHighlighted = false; } } bool KDevelop::OneUseWidget::isHighlighted() const { return m_isHighlighted; } void OneUseWidget::activateLink() { ICore::self()->documentController()->openDocument(m_document.toUrl(), m_range->range().start()); } void OneUseWidget::mousePressEvent(QMouseEvent* event) { if (event->button() == Qt::LeftButton && !event->modifiers()) { activateLink(); event->accept(); } } OneUseWidget::~OneUseWidget() { } void OneUseWidget::resizeEvent ( QResizeEvent * event ) { ///Adapt the content QSize size = event->size(); KTextEditor::Range range = m_range->range(); int cutOff = 0; int maxCutOff = m_sourceLine.length() - (range.end().column() - range.start().column()); //Reset so we also get more context while up-sizing m_label->setText(QStringLiteral("") + i18nc("Refers to a line in source code", "Line %1:", range.start().line()+1) + QStringLiteral(" ") + highlightAndEscapeUseText(m_sourceLine, cutOff, range)); /// FIXME: this is incredibly ugly and slow... we could simply paint the text ourselves and elide it properly while(sizeHint().width() > size.width() && cutOff < maxCutOff) { //We've got to save space m_label->setText(QStringLiteral("") + i18nc("Refers to a line in source code", "Line %1:", range.start().line()+1) + QStringLiteral(" ") + highlightAndEscapeUseText(m_sourceLine, cutOff, range)); cutOff += 5; } event->accept(); QWidget::resizeEvent(event); } void NavigatableWidgetList::setShowHeader(bool show) { if(show && !m_headerLayout->parent()) m_layout->insertLayout(0, m_headerLayout); else m_headerLayout->setParent(nullptr); } NavigatableWidgetList::~NavigatableWidgetList() { delete m_headerLayout; } NavigatableWidgetList::NavigatableWidgetList(bool allowScrolling, uint maxHeight, bool vertical) : m_allowScrolling(allowScrolling) { m_layout = new QVBoxLayout; m_layout->setMargin(0); m_layout->setSizeConstraint(QLayout::SetMinAndMaxSize); m_layout->setSpacing(0); setBackgroundRole(QPalette::Base); m_useArrows = false; if(vertical) m_itemLayout = new QVBoxLayout; else m_itemLayout = new QHBoxLayout; m_itemLayout->setContentsMargins(0, 0, 0, 0); m_itemLayout->setMargin(0); m_itemLayout->setSpacing(0); // m_layout->setSizeConstraint(QLayout::SetMinAndMaxSize); // setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Maximum); setWidgetResizable(true); m_headerLayout = new QHBoxLayout; m_headerLayout->setMargin(0); m_headerLayout->setSpacing(0); if(m_useArrows) { auto previousButton = new QToolButton(); previousButton->setIcon(QIcon::fromTheme(QStringLiteral("go-previous"))); auto nextButton = new QToolButton(); nextButton->setIcon(QIcon::fromTheme(QStringLiteral("go-next"))); m_headerLayout->addWidget(previousButton); m_headerLayout->addWidget(nextButton); } //hide these buttons for now, they're senseless m_layout->addLayout(m_headerLayout); QHBoxLayout* spaceLayout = new QHBoxLayout; spaceLayout->addSpacing(10); spaceLayout->addLayout(m_itemLayout); m_layout->addLayout(spaceLayout); if(maxHeight) setMaximumHeight(maxHeight); if(m_allowScrolling) { QWidget* contentsWidget = new QWidget; contentsWidget->setLayout(m_layout); setWidget(contentsWidget); }else{ setLayout(m_layout); } } void NavigatableWidgetList::deleteItems() { foreach(QWidget* item, items()) delete item; } void NavigatableWidgetList::addItem(QWidget* widget, int pos) { if(pos == -1) m_itemLayout->addWidget(widget); else m_itemLayout->insertWidget(pos, widget); } QList NavigatableWidgetList::items() const { QList ret; for(int a = 0; a < m_itemLayout->count(); ++a) { QWidgetItem* widgetItem = dynamic_cast(m_itemLayout->itemAt(a)); if(widgetItem) { ret << widgetItem->widget(); } } return ret; } bool NavigatableWidgetList::hasItems() const { return (bool)m_itemLayout->count(); } void NavigatableWidgetList::addHeaderItem(QWidget* widget, Qt::Alignment alignment) { if(m_useArrows) { Q_ASSERT(m_headerLayout->count() >= 2); //At least the 2 back/next buttons m_headerLayout->insertWidget(m_headerLayout->count()-1, widget, alignment); }else{ //We need to do this so the header doesn't get stretched widget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); m_headerLayout->insertWidget(m_headerLayout->count(), widget, alignment); // widget->setMaximumHeight(20); } } ///Returns whether the uses in the child should be a new uses-group bool isNewGroup(DUContext* parent, DUContext* child) { if(parent->type() == DUContext::Other && child->type() == DUContext::Other) return false; else return true; } uint countUses(int usedDeclarationIndex, DUContext* context) { uint ret = 0; for(int useIndex = 0; useIndex < context->usesCount(); ++useIndex) if(context->uses()[useIndex].m_declarationIndex == usedDeclarationIndex) ++ret; foreach(DUContext* child, context->childContexts()) if(!isNewGroup(context, child)) ret += countUses(usedDeclarationIndex, child); return ret; } QList createUseWidgets(const CodeRepresentation& code, int usedDeclarationIndex, IndexedDeclaration decl, DUContext* context) { QList ret; VERIFY_FOREGROUND_LOCKED for(int useIndex = 0; useIndex < context->usesCount(); ++useIndex) if(context->uses()[useIndex].m_declarationIndex == usedDeclarationIndex) ret << new OneUseWidget(decl, context->url(), context->transformFromLocalRevision(context->uses()[useIndex].m_range), code); foreach(DUContext* child, context->childContexts()) if(!isNewGroup(context, child)) ret += createUseWidgets(code, usedDeclarationIndex, decl, child); return ret; } ContextUsesWidget::ContextUsesWidget(const CodeRepresentation& code, QList usedDeclarations, IndexedDUContext context) : m_context(context) { setFrameShape(NoFrame); DUChainReadLocker lock(DUChain::lock()); QString headerText = i18n("Unknown context"); setUpdatesEnabled(false); if(context.data()) { DUContext* ctx = context.data(); if(ctx->scopeIdentifier(true).isEmpty()) headerText = i18n("Global"); else { headerText = ctx->scopeIdentifier(true).toString(); if(ctx->type() == DUContext::Function || (ctx->owner() && ctx->owner()->isFunctionDeclaration())) headerText += QLatin1String("(...)"); } QSet hadIndices; foreach(const IndexedDeclaration usedDeclaration, usedDeclarations) { int usedDeclarationIndex = ctx->topContext()->indexForUsedDeclaration(usedDeclaration.data(), false); if(hadIndices.contains(usedDeclarationIndex)) continue; hadIndices.insert(usedDeclarationIndex); if(usedDeclarationIndex != std::numeric_limits::max()) { foreach(OneUseWidget* widget, createUseWidgets(code, usedDeclarationIndex, usedDeclaration, ctx)) addItem(widget); } } } QLabel* headerLabel = new QLabel(i18nc("%1: source file", "In %1", "" + headerText.toHtmlEscaped() + ": ")); addHeaderItem(headerLabel); setUpdatesEnabled(true); connect(headerLabel, &QLabel::linkActivated, this, &ContextUsesWidget::linkWasActivated); } void ContextUsesWidget::linkWasActivated(QString link) { if ( link == QLatin1String("navigateToFunction") ) { DUChainReadLocker lock(DUChain::lock()); DUContext* context = m_context.context(); if(context) { CursorInRevision contextStart = context->range().start; KTextEditor::Cursor cursor(contextStart.line, contextStart.column); QUrl url = context->url().toUrl(); lock.unlock(); ForegroundLock fgLock; ICore::self()->documentController()->openDocument(url, cursor); } } } DeclarationWidget::DeclarationWidget(const CodeRepresentation& code, const IndexedDeclaration& decl) { setFrameShape(NoFrame); DUChainReadLocker lock(DUChain::lock()); setUpdatesEnabled(false); if (Declaration* dec = decl.data()) { QLabel* headerLabel = new QLabel(dec->isDefinition() ? i18n("Definition") : i18n("Declaration")); addHeaderItem(headerLabel); addItem(new OneUseWidget(decl, dec->url(), dec->rangeInCurrentRevision(), code)); } setUpdatesEnabled(true); } TopContextUsesWidget::TopContextUsesWidget(IndexedDeclaration declaration, QList allDeclarations, IndexedTopDUContext topContext) : m_topContext(topContext) , m_declaration(declaration) , m_allDeclarations(allDeclarations) , m_usesCount(0) { m_itemLayout->setContentsMargins(10, 0, 0, 5); setFrameShape(NoFrame); setUpdatesEnabled(false); DUChainReadLocker lock(DUChain::lock()); QHBoxLayout * labelLayout = new QHBoxLayout; labelLayout->setContentsMargins(0, -1, 0, 0); // let's keep the spacing *above* the line QWidget* headerWidget = new QWidget; headerWidget->setLayout(labelLayout); headerWidget->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); QLabel* label = new QLabel(this); m_icon = new QLabel(this); m_toggleButton = new QLabel(this); m_icon->setPixmap(QIcon::fromTheme(QStringLiteral("code-class")).pixmap(16)); labelLayout->addWidget(m_icon); labelLayout->addWidget(label); labelLayout->addWidget(m_toggleButton); labelLayout->setAlignment(Qt::AlignLeft); if(topContext.isLoaded()) m_usesCount = DUChainUtils::contextCountUses(topContext.data(), declaration.data()); QString labelText = i18ncp("%1: number of uses, %2: filename with uses", "%2: 1 use", "%2: %1 uses", m_usesCount, ICore::self()->projectController()->prettyFileName(topContext.url().toUrl())); label->setText(labelText); m_toggleButton->setText("   [" + i18nc("Refers to closing a UI element", "Collapse") + "]"); connect(m_toggleButton, &QLabel::linkActivated, this, &TopContextUsesWidget::labelClicked); addHeaderItem(headerWidget); setUpdatesEnabled(true); } int TopContextUsesWidget::usesCount() const { return m_usesCount; } QList buildContextUses(const CodeRepresentation& code, QList declarations, DUContext* context) { QList ret; if(!context->parentContext() || isNewGroup(context->parentContext(), context)) { ContextUsesWidget* created = new ContextUsesWidget(code, declarations, context); if(created->hasItems()) ret << created; else delete created; } foreach(DUContext* child, context->childContexts()) ret += buildContextUses(code, declarations, child); return ret; } void TopContextUsesWidget::setExpanded(bool expanded) { if(!expanded) { m_toggleButton->setText("   [" + i18nc("Refers to opening a UI element", "Expand") + "]"); deleteItems(); }else{ m_toggleButton->setText("   [" + i18nc("Refers to closing a UI element", "Collapse") + "]"); if(hasItems()) return; DUChainReadLocker lock(DUChain::lock()); TopDUContext* topContext = m_topContext.data(); if(topContext && m_declaration.data()) { CodeRepresentation::Ptr code = createCodeRepresentation(topContext->url()); setUpdatesEnabled(false); IndexedTopDUContext localTopContext(topContext); foreach(const IndexedDeclaration &decl, m_allDeclarations) { if(decl.indexedTopContext() == localTopContext) { addItem(new DeclarationWidget(*code, decl)); } } foreach(ContextUsesWidget* usesWidget, buildContextUses(*code, m_allDeclarations, topContext)) { addItem(usesWidget); } setUpdatesEnabled(true); } } } void TopContextUsesWidget::labelClicked() { if(hasItems()) { setExpanded(false); }else{ setExpanded(true); } } UsesWidget::~UsesWidget() { if (m_collector) { m_collector->setWidget(nullptr); } } UsesWidget::UsesWidget(const IndexedDeclaration& declaration, QSharedPointer customCollector) : NavigatableWidgetList(true) { DUChainReadLocker lock(DUChain::lock()); setUpdatesEnabled(false); m_headerLine = new QLabel; redrawHeaderLine(); connect(m_headerLine, &QLabel::linkActivated, this, &UsesWidget::headerLinkActivated); m_layout->insertWidget(0, m_headerLine, 0, Qt::AlignTop); m_layout->setAlignment(Qt::AlignTop); m_itemLayout->setAlignment(Qt::AlignTop); m_progressBar = new QProgressBar; addHeaderItem(m_progressBar); if (!customCollector) { m_collector = QSharedPointer(new UsesWidget::UsesWidgetCollector(declaration)); } else { m_collector = customCollector; } m_collector->setProcessDeclarations(true); m_collector->setWidget(this); m_collector->startCollecting(); setUpdatesEnabled(true); } void UsesWidget::redrawHeaderLine() { m_headerLine->setText(headerLineText()); } const QString UsesWidget::headerLineText() const { return i18np("1 use found", "%1 uses found", countAllUses()) + " • " "[" + i18n("Expand all") + "] • " "[" + i18n("Collapse all") + "]"; } unsigned int UsesWidget::countAllUses() const { unsigned int totalUses = 0; foreach ( QWidget* w, items() ) { if ( TopContextUsesWidget* useWidget = dynamic_cast(w) ) { totalUses += useWidget->usesCount(); } } return totalUses; } void UsesWidget::setAllExpanded(bool expanded) { foreach ( QWidget* w, items() ) { if ( TopContextUsesWidget* useWidget = dynamic_cast(w) ) { useWidget->setExpanded(expanded); } } } void UsesWidget::headerLinkActivated(QString linkName) { if(linkName == QLatin1String("expandAll")) { setAllExpanded(true); } else if(linkName == QLatin1String("collapseAll")) { setAllExpanded(false); } } UsesWidget::UsesWidgetCollector::UsesWidgetCollector(IndexedDeclaration decl) : UsesCollector(decl), m_widget(nullptr) { } void UsesWidget::UsesWidgetCollector::setWidget(UsesWidget* widget ) { m_widget = widget; } void UsesWidget::UsesWidgetCollector::maximumProgress(uint max) { if (!m_widget) { return; } if(m_widget->m_progressBar) { m_widget->m_progressBar->setMaximum(max); m_widget->m_progressBar->setMinimum(0); m_widget->m_progressBar->setValue(0); }else{ qCWarning(LANGUAGE) << "maximumProgress called twice"; } } void UsesWidget::UsesWidgetCollector::progress(uint processed, uint total) { if (!m_widget) { return; } m_widget->redrawHeaderLine(); if(m_widget->m_progressBar) { m_widget->m_progressBar->setValue(processed); if(processed == total) { m_widget->setUpdatesEnabled(false); delete m_widget->m_progressBar; m_widget->m_progressBar = nullptr; m_widget->setShowHeader(false); m_widget->setUpdatesEnabled(true); } }else{ qCWarning(LANGUAGE) << "progress() called too often"; } } void UsesWidget::UsesWidgetCollector::processUses( KDevelop::ReferencedTopDUContext topContext ) { if (!m_widget) { return; } DUChainReadLocker lock; qCDebug(LANGUAGE) << "processing" << topContext->url().str(); TopContextUsesWidget* widget = new TopContextUsesWidget(declaration(), declarations(), topContext.data()); // move to back if it's just the declaration/definition bool toBack = widget->usesCount() == 0; // move to front the item belonging to the current open document IDocument* doc = ICore::self()->documentController()->activeDocument(); bool toFront = doc && (doc->url() == topContext->url().toUrl()); widget->setExpanded(true); m_widget->addItem(widget, toFront ? 0 : toBack ? widget->items().size() : -1); m_widget->redrawHeaderLine(); } QSize KDevelop::UsesWidget::sizeHint() const { QSize ret = QWidget::sizeHint(); if(ret.height() < 300) ret.setHeight(300); return ret; } diff --git a/language/duchain/parsingenvironment.cpp b/language/duchain/parsingenvironment.cpp index 5899cee19..f4d3dfdea 100644 --- a/language/duchain/parsingenvironment.cpp +++ b/language/duchain/parsingenvironment.cpp @@ -1,388 +1,388 @@ /* 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 "parsingenvironment.h" #include "topducontext.h" #include "duchainregister.h" #include "topducontextdynamicdata.h" #include "duchain.h" #include "duchainlock.h" #include "topducontextdata.h" -#include "util/debug.h" +#include #include #define ENSURE_READ_LOCKED if(indexedTopContext().isValid()) { ENSURE_CHAIN_READ_LOCKED } #define ENSURE_WRITE_LOCKED if(indexedTopContext().isValid()) { ENSURE_CHAIN_READ_LOCKED } namespace KDevelop { StaticParsingEnvironmentData* ParsingEnvironmentFile::m_staticData = nullptr; #if 0 ///Wrapper class around objects that are managed through the DUChain, and may contain arbitrary objects ///that support duchain-like store (IndexedString, StorableSet, and the likes). The object must not contain pointers ///or other non-persistent data. /// ///The object is stored during the normal duchain storage/cleanup cycles. template struct PersistentDUChainObject { ///@param fileName File-name that will be used to store the data of the object in the duchain directory PersistentDUChainObject(QString fileName) { object = (T*) new char[sizeof(T)]; if(!DUChain::self()->addPersistentObject(object, fileName, sizeof(T))) { //The constructor is called only if the object did not exist yet new (object) T(); } } ~PersistentDUChainObject() { DUChain::self()->unregisterPersistentObject(object); delete[] object; } T* object; }; #endif REGISTER_DUCHAIN_ITEM(ParsingEnvironmentFile); TopDUContext::Features ParsingEnvironmentFile::features() const { ENSURE_READ_LOCKED return d_func()->m_features; } ParsingEnvironment::ParsingEnvironment() { } ParsingEnvironment::~ParsingEnvironment() { } IndexedString ParsingEnvironmentFile::url() const { ENSURE_READ_LOCKED return d_func()->m_url; } bool ParsingEnvironmentFile::needsUpdate(const ParsingEnvironment* /*environment*/) const { ENSURE_READ_LOCKED return d_func()->m_allModificationRevisions.needsUpdate(); } bool ParsingEnvironmentFile::matchEnvironment(const ParsingEnvironment* /*environment*/) const { ENSURE_READ_LOCKED return true; } void ParsingEnvironmentFile::setTopContext(KDevelop::IndexedTopDUContext context) { if(d_func()->m_topContext == context) return; ENSURE_WRITE_LOCKED d_func_dynamic()->m_topContext = context; //Enforce an update of the 'features satisfied' caches TopDUContext::Features oldFeatures = features(); setFeatures(TopDUContext::Empty); setFeatures(oldFeatures); } KDevelop::IndexedTopDUContext ParsingEnvironmentFile::indexedTopContext() const { return d_func()->m_topContext; } const ModificationRevisionSet& ParsingEnvironmentFile::allModificationRevisions() const { ENSURE_READ_LOCKED return d_func()->m_allModificationRevisions; } void ParsingEnvironmentFile::addModificationRevisions(const ModificationRevisionSet& revisions) { ENSURE_WRITE_LOCKED d_func_dynamic()->m_allModificationRevisions += revisions; } ParsingEnvironmentFile::ParsingEnvironmentFile(ParsingEnvironmentFileData& data, const IndexedString& url) : DUChainBase(data) { d_func_dynamic()->m_url = url; d_func_dynamic()->m_modificationTime = ModificationRevision::revisionForFile(url); addModificationRevision(url, d_func_dynamic()->m_modificationTime); Q_ASSERT(d_func()->m_allModificationRevisions.index()); } ParsingEnvironmentFile::ParsingEnvironmentFile(const IndexedString& url) : DUChainBase(*new ParsingEnvironmentFileData()) { d_func_dynamic()->setClassId(this); d_func_dynamic()->m_url = url; d_func_dynamic()->m_modificationTime = ModificationRevision::revisionForFile(url); addModificationRevision(url, d_func_dynamic()->m_modificationTime); Q_ASSERT(d_func()->m_allModificationRevisions.index()); } TopDUContext* ParsingEnvironmentFile::topContext() const { ENSURE_READ_LOCKED return indexedTopContext().data(); } ParsingEnvironmentFile::~ParsingEnvironmentFile() { } ParsingEnvironmentFile::ParsingEnvironmentFile(ParsingEnvironmentFileData& data) : DUChainBase(data) { //If this triggers, the item has most probably not been initialized with the correct constructor that takes an IndexedString. Q_ASSERT(d_func()->m_allModificationRevisions.index()); } int ParsingEnvironment::type() const { return StandardParsingEnvironment; } int ParsingEnvironmentFile::type() const { ENSURE_READ_LOCKED return StandardParsingEnvironment; } bool ParsingEnvironmentFile::isProxyContext() const { ENSURE_READ_LOCKED return d_func()->m_isProxyContext; } void ParsingEnvironmentFile::setIsProxyContext(bool is) { ENSURE_WRITE_LOCKED d_func_dynamic()->m_isProxyContext = is; } QList< QExplicitlySharedDataPointer > ParsingEnvironmentFile::imports() const { ENSURE_READ_LOCKED QList imp; IndexedTopDUContext top = indexedTopContext(); if(top.isLoaded()) { TopDUContext* topCtx = top.data(); FOREACH_FUNCTION(const DUContext::Import& import, topCtx->d_func()->m_importedContexts) imp << import.indexedContext(); }else{ imp = TopDUContextDynamicData::loadImports(top.index()); } QList< QExplicitlySharedDataPointer > ret; foreach(const IndexedDUContext ctx, imp) { QExplicitlySharedDataPointer item = DUChain::self()->environmentFileForDocument(ctx.topContextIndex()); if(item) { ret << item; }else{ qCDebug(LANGUAGE) << url().str() << indexedTopContext().index() << ": invalid import" << ctx.topContextIndex(); } } return ret; } QList< QExplicitlySharedDataPointer > ParsingEnvironmentFile::importers() const { ENSURE_READ_LOCKED QList imp; IndexedTopDUContext top = indexedTopContext(); if(top.isLoaded()) { TopDUContext* topCtx = top.data(); FOREACH_FUNCTION(const IndexedDUContext& ctx, topCtx->d_func()->m_importers) imp << ctx; }else{ imp = TopDUContextDynamicData::loadImporters(top.index()); } QList< QExplicitlySharedDataPointer > ret; foreach(const IndexedDUContext ctx, imp) { QExplicitlySharedDataPointer f = DUChain::self()->environmentFileForDocument(ctx.topContextIndex()); if(f) ret << f; else qCDebug(LANGUAGE) << url().str() << indexedTopContext().index() << ": invalid importer context" << ctx.topContextIndex(); } return ret; } QMutex featureSatisfactionMutex; inline bool satisfied(TopDUContext::Features features, TopDUContext::Features required) { return (features & required) == required; } ///Makes sure the file has the correct features attached, and if minimumFeatures contains AllDeclarationsContextsAndUsesForRecursive, then also checks all imports. bool ParsingEnvironmentFile::featuresMatch(TopDUContext::Features minimumFeatures, QSet& checked) const { if(checked.contains(this)) return true; checked.insert(this); TopDUContext::Features localRequired = (TopDUContext::Features) (minimumFeatures | ParseJob::staticMinimumFeatures(url())); //Check other 'local' requirements localRequired = (TopDUContext::Features)(localRequired & (TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::AST)); if(!satisfied(features(), localRequired)) return false; if(ParseJob::hasStaticMinimumFeatures()) { //Do a manual recursion to check whether any of the relevant contexts has static minimum features set ///@todo Only do this if one of the imports actually has static features attached (by RecursiveImports set intersection) foreach(const ParsingEnvironmentFilePointer &import, imports()) if(!import->featuresMatch(minimumFeatures & TopDUContext::Recursive ? minimumFeatures : ((TopDUContext::Features)0), checked)) return false; }else if(minimumFeatures & TopDUContext::Recursive) { QMutexLocker lock(&featureSatisfactionMutex); TopDUContext::IndexedRecursiveImports recursiveImportIndices = d_func()->m_importsCache; if(recursiveImportIndices.isEmpty()) { //Unfortunately, we have to load the top-context TopDUContext* top = topContext(); if(top) recursiveImportIndices = top->recursiveImportIndices(); } ///@todo Do not create temporary intersected sets //Use the features-cache to efficiently check the recursive satisfaction of the features if(satisfied(minimumFeatures, TopDUContext::AST) && !((m_staticData->ASTSatisfied & recursiveImportIndices) == recursiveImportIndices)) return false; if(satisfied(minimumFeatures, TopDUContext::AllDeclarationsContextsAndUses)) return (m_staticData->allDeclarationsAndUsesSatisfied & recursiveImportIndices) == recursiveImportIndices; else if(satisfied(minimumFeatures, TopDUContext::AllDeclarationsAndContexts)) return (m_staticData->allDeclarationsSatisfied & recursiveImportIndices) == recursiveImportIndices; else if(satisfied(minimumFeatures, TopDUContext::VisibleDeclarationsAndContexts)) return (m_staticData->visibleDeclarationsSatisfied & recursiveImportIndices) == recursiveImportIndices; else if(satisfied(minimumFeatures, TopDUContext::SimplifiedVisibleDeclarationsAndContexts)) return (m_staticData->simplifiedVisibleDeclarationsSatisfied & recursiveImportIndices) == recursiveImportIndices; } return true; } void ParsingEnvironmentFile::setFeatures(TopDUContext::Features features) { if(d_func()->m_features == features) return; ENSURE_WRITE_LOCKED d_func_dynamic()->m_features = features; if(indexedTopContext().isValid()) { QMutexLocker lock(&featureSatisfactionMutex); if(!satisfied(features, TopDUContext::SimplifiedVisibleDeclarationsAndContexts)) m_staticData->simplifiedVisibleDeclarationsSatisfied.remove(indexedTopContext()); else m_staticData->simplifiedVisibleDeclarationsSatisfied.insert(indexedTopContext()); if(!satisfied(features, TopDUContext::VisibleDeclarationsAndContexts)) m_staticData->visibleDeclarationsSatisfied.remove(indexedTopContext()); else m_staticData->visibleDeclarationsSatisfied.insert(indexedTopContext()); if(!satisfied(features, TopDUContext::AllDeclarationsAndContexts)) m_staticData->allDeclarationsSatisfied.remove(indexedTopContext()); else m_staticData->allDeclarationsSatisfied.insert(indexedTopContext()); if(!satisfied(features, TopDUContext::AllDeclarationsContextsAndUses)) m_staticData->allDeclarationsAndUsesSatisfied.remove(indexedTopContext()); else m_staticData->allDeclarationsAndUsesSatisfied.insert(indexedTopContext()); if(!satisfied(features, TopDUContext::AST)) m_staticData->ASTSatisfied.remove(indexedTopContext()); else m_staticData->ASTSatisfied.insert(indexedTopContext()); } } bool ParsingEnvironmentFile::featuresSatisfied(KDevelop::TopDUContext::Features minimumFeatures) const { ENSURE_READ_LOCKED QSet checked; if(minimumFeatures & TopDUContext::ForceUpdate) return false; return featuresMatch(minimumFeatures, checked); } void ParsingEnvironmentFile::clearModificationRevisions() { ENSURE_WRITE_LOCKED d_func_dynamic()->m_allModificationRevisions.clear(); d_func_dynamic()->m_allModificationRevisions.addModificationRevision(d_func()->m_url, d_func()->m_modificationTime); } void ParsingEnvironmentFile::addModificationRevision(const IndexedString& url, const ModificationRevision& revision) { ENSURE_WRITE_LOCKED d_func_dynamic()->m_allModificationRevisions.addModificationRevision(url, revision); { //Test Q_ASSERT(d_func_dynamic()->m_allModificationRevisions.index()); bool result = d_func_dynamic()->m_allModificationRevisions.removeModificationRevision(url, revision); Q_UNUSED( result ); Q_ASSERT( result ); d_func_dynamic()->m_allModificationRevisions.addModificationRevision(url, revision); } } void ParsingEnvironmentFile::setModificationRevision( const KDevelop::ModificationRevision& rev ) { ENSURE_WRITE_LOCKED Q_ASSERT(d_func_dynamic()->m_allModificationRevisions.index()); bool result = d_func_dynamic()->m_allModificationRevisions.removeModificationRevision(d_func()->m_url, d_func()->m_modificationTime); Q_ASSERT( result ); Q_UNUSED( result ); #ifdef LEXERCACHE_DEBUG if(debugging()) { qCDebug(LANGUAGE) << id(this) << "setting modification-revision" << rev.toString(); } #endif d_func_dynamic()->m_modificationTime = rev; #ifdef LEXERCACHE_DEBUG if(debugging()) { qCDebug(LANGUAGE) << id(this) << "new modification-revision" << m_modificationTime; } #endif d_func_dynamic()->m_allModificationRevisions.addModificationRevision(d_func()->m_url, d_func()->m_modificationTime); } KDevelop::ModificationRevision ParsingEnvironmentFile::modificationRevision() const { ENSURE_READ_LOCKED return d_func()->m_modificationTime; } IndexedString ParsingEnvironmentFile::language() const { return d_func()->m_language; } void ParsingEnvironmentFile::setLanguage(IndexedString language) { d_func_dynamic()->m_language = language; } const KDevelop::TopDUContext::IndexedRecursiveImports& ParsingEnvironmentFile::importsCache() const { return d_func()->m_importsCache; } void ParsingEnvironmentFile::setImportsCache(const KDevelop::TopDUContext::IndexedRecursiveImports& importsCache) { d_func_dynamic()->m_importsCache = importsCache; } } //KDevelop diff --git a/language/duchain/stringhelpers.cpp b/language/duchain/stringhelpers.cpp index 0b43a4471..c6eeeeb4c 100644 --- a/language/duchain/stringhelpers.cpp +++ b/language/duchain/stringhelpers.cpp @@ -1,591 +1,591 @@ /* Copyright 2007 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "stringhelpers.h" #include "safetycounter.h" -#include "util/debug.h" +#include #include #include namespace { template int strip_impl(const T& str, T& from) { if( str.isEmpty() ) return 0; int i = 0; int ip = 0; int s = from.length(); for( int a = 0; a < s; a++ ) { if( QChar(from[a]).isSpace() ) { continue; } else { if( from[a] == str[i] ) { i++; ip = a+1; if( i == (int)str.length() ) break; } else { break; } } } if( ip ) { from.remove(0, ip); } return s - from.length(); } template int rStrip_impl(const T& str, T& from) { if( str.isEmpty() ) return 0; int i = 0; int ip = from.length(); int s = from.length(); for( int a = s-1; a >= 0; a-- ) { if( QChar( from[a] ).isSpace() ) { ///@todo Check whether this can cause problems in utf-8, as only one real character is treated! continue; } else { if( from[a] == str[i] ) { i++; ip = a; if( i == (int)str.length() ) break; } else { break; } } } if( ip != (int)from.length() ) { from = from.left( ip ); } return s - from.length(); } template T formatComment_impl(const T& comment) { T ret; QList lines = comment.split( '\n' ); if ( !lines.isEmpty() ) { auto it = lines.begin(); auto eit = lines.end(); // remove common leading chars from the beginning of lines for( ; it != eit; ++it ) { // don't trigger repeated temporary allocations here static const T tripleSlash("///"); static const T doubleSlash("//"); static const T doubleStar("**"); static const T slashDoubleStar("/**"); strip_impl( tripleSlash, *it ); strip_impl( doubleSlash, *it ); strip_impl( doubleStar, *it ); rStrip_impl( slashDoubleStar, *it ); } foreach(const T& line, lines) { if(!ret.isEmpty()) ret += '\n'; ret += line; } } return ret.trimmed(); } } namespace KDevelop { class ParamIteratorPrivate { public: QString m_prefix; QString m_source; QString m_parens; int m_cur; int m_curEnd; int m_end; int next() const { return findCommaOrEnd( m_source, m_cur, m_parens[ 1 ] ); } }; bool parenFits( QChar c1, QChar c2 ) { if( c1 == '<' && c2 == '>' ) return true; else if( c1 == '(' && c2 == ')' ) return true; else if( c1 == '[' && c2 == ']' ) return true; else if( c1 == '{' && c2 == '}' ) return true; else return false; } int findClose( const QString& str , int pos ) { int depth = 0; QList st; QChar last = ' '; for( int a = pos; a < (int)str.length(); a++) { switch(str[a].unicode()) { case '<': case '(': case '[': case '{': st.push_front( str[a] ); depth++; break; case '>': if( last == '-' ) break; case ')': case ']': case '}': if( !st.isEmpty() && parenFits(st.front(), str[a]) ) { depth--; st.pop_front(); } break; case '"': last = str[a]; a++; while( a < (int)str.length() && (str[a] != '"' || last == '\\')) { last = str[a]; a++; } continue; break; case '\'': last = str[a]; a++; while( a < (int)str.length() && (str[a] != '\'' || last == '\\')) { last = str[a]; a++; } continue; break; } last = str[a]; if( depth == 0 ) { return a; } } return -1; } int findCommaOrEnd( const QString& str , int pos, QChar validEnd) { for( int a = pos; a < (int)str.length(); a++) { switch(str[a].unicode()) { case '"': case '(': case '[': case '{': case '<': a = findClose( str, a ); if( a == -1 ) return str.length(); break; case ')': case ']': case '}': case '>': if( validEnd != ' ' && validEnd != str[a] ) continue; case ',': return a; } } return str.length(); } QString reverse( const QString& str ) { QString ret; int len = str.length(); for( int a = len-1; a >= 0; --a ) { switch(str[a].unicode()) { case '(': ret += ')'; continue; case '[': ret += ']'; continue; case '{': ret += '}'; continue; case '<': ret += '>'; continue; case ')': ret += '('; continue; case ']': ret += '['; continue; case '}': ret += '{'; continue; case '>': ret += '<'; continue; default: ret += str[a]; continue; } } return ret; } ///@todo this hackery sucks QString escapeForBracketMatching(QString str) { str.replace(QStringLiteral("<<"), QStringLiteral("$&")); str.replace(QStringLiteral(">>"), QStringLiteral("$$")); str.replace(QStringLiteral("\\\""), QStringLiteral("$!")); str.replace(QStringLiteral("->"), QStringLiteral("$?")); return str; } QString escapeFromBracketMatching(QString str) { str.replace(QStringLiteral("$&"), QStringLiteral("<<")); str.replace(QStringLiteral("$$"), QStringLiteral(">>")); str.replace(QStringLiteral("$!"), QStringLiteral("\\\"")); str.replace(QStringLiteral("$?"), QStringLiteral("->")); return str; } void skipFunctionArguments(QString str, QStringList& skippedArguments, int& argumentsStart ) { QString withStrings = escapeForBracketMatching(str); str = escapeForBracketMatching(clearStrings(str)); //Blank out everything that can confuse the bracket-matching algorithm QString reversed = reverse( str.left(argumentsStart) ); QString withStringsReversed = reverse( withStrings.left(argumentsStart) ); //Now we should decrease argumentStart at the end by the count of steps we go right until we find the beginning of the function SafetyCounter s( 1000 ); int pos = 0; int len = reversed.length(); //we are searching for an opening-brace, but the reversion has also reversed the brace while( pos < len && s ) { int lastPos = pos; pos = KDevelop::findCommaOrEnd( reversed, pos ) ; if( pos > lastPos ) { QString arg = reverse( withStringsReversed.mid(lastPos, pos-lastPos) ).trimmed(); if( !arg.isEmpty() ) skippedArguments.push_front( escapeFromBracketMatching(arg) ); //We are processing the reversed reverseding, so push to front } if( reversed[pos] == ')' || reversed[pos] == '>' ) break; else ++pos; } if( !s ) { qCDebug(LANGUAGE) << "skipFunctionArguments: Safety-counter triggered"; } argumentsStart -= pos; } QString reduceWhiteSpace(QString str) { str = str.trimmed(); QString ret; QChar spaceChar = ' '; bool hadSpace = false; for( int a = 0; a < str.length(); a++ ) { if( str[a].isSpace() ) { hadSpace = true; } else { if( hadSpace ) { hadSpace = false; ret += spaceChar; } ret += str[a]; } } return ret; } void fillString( QString& str, int start, int end, QChar replacement ) { for( int a = start; a < end; a++) str[a] = replacement; } QString stripFinalWhitespace(QString str) { for( int a = str.length() - 1; a >= 0; --a ) { if( !str[a].isSpace() ) return str.left( a+1 ); } return QString(); } QString clearComments( QString str, QChar replacement ) { QString withoutStrings = clearStrings(str, '$'); int pos = -1, newlinePos = -1, endCommentPos = -1, nextPos = -1, dest = -1; while ( (pos = str.indexOf('/', pos + 1)) != -1 ) { newlinePos = withoutStrings.indexOf('\n', pos); if (withoutStrings[pos + 1] == '/') { //C style comment dest = newlinePos == -1 ? str.length() : newlinePos; fillString(str, pos, dest, replacement); pos = dest; } else if (withoutStrings[pos + 1] == '*') { //CPP style comment endCommentPos = withoutStrings.indexOf(QStringLiteral("*/"), pos + 2); if (endCommentPos != -1) endCommentPos += 2; dest = endCommentPos == -1 ? str.length() : endCommentPos; while (pos < dest) { nextPos = (dest > newlinePos && newlinePos != -1) ? newlinePos : dest; fillString(str, pos, nextPos, replacement); pos = nextPos; if (pos == newlinePos) { ++pos; //Keep newlines intact, skip them newlinePos = withoutStrings.indexOf('\n', pos + 1); } } } } return str; } QString clearStrings( QString str, QChar replacement ) { bool inString = false; for(int pos = 0; pos < str.length(); ++pos) { //Skip cpp comments if(!inString && pos + 1 < str.length() && str[pos] == '/' && str[pos+1] == '*') { pos += 2; while(pos + 1 < str.length()) { if (str[pos] == '*' && str[pos + 1] == '/') { ++pos; break; } ++pos; } } //Skip cstyle comments if(!inString && pos + 1 < str.length() && str[pos] == '/' && str[pos+1] == '/') { pos += 2; while(pos < str.length() && str[pos] != '\n') { ++pos; } } //Skip a character a la 'b' if(!inString && str[pos] == '\'' && pos + 3 <= str.length()) { //skip the opening ' str[pos] = replacement; ++pos; if(str[pos] == '\\') { //Skip an escape character str[pos] = replacement; ++pos; } //Skip the actual character str[pos] = replacement; ++pos; //Skip the closing ' if(pos < str.length() && str[pos] == '\'') { str[pos] = replacement; } continue; } bool intoString = false; if(str[pos] == '"' && !inString) intoString = true; if(inString || intoString) { if(inString) { if(str[pos] == '"') inString = false; }else{ inString = true; } bool skip = false; if(str[pos] == '\\') skip = true; str[pos] = replacement; if(skip) { ++pos; if(pos < str.length()) str[pos] = replacement; } } } return str; } int strip(const QByteArray& str, QByteArray& from) { return strip_impl(str, from); } int rStrip(const QByteArray& str, QByteArray& from) { return rStrip_impl(str, from); } QByteArray formatComment(const QByteArray& comment) { return formatComment_impl(comment); } QString formatComment(const QString& comment) { return formatComment_impl(comment); } ParamIterator::~ParamIterator() { delete d; } ParamIterator::ParamIterator( QString parens, QString source, int offset ) : d(new ParamIteratorPrivate) { d->m_source = source; d->m_parens = parens; d->m_cur = offset; d->m_curEnd = offset; d->m_end = d->m_source.length(); ///The whole search should be stopped when: A) The end-sign is found on the top-level B) A closing-brace of parameters was found int parenBegin = d->m_source.indexOf( parens[ 0 ], offset ); //Search for an interrupting end-sign that comes before the found paren-begin int foundEnd = -1; if( parens.length() > 2 ) { foundEnd = d->m_source.indexOf( parens[2], offset ); if( foundEnd > parenBegin && parenBegin != -1 ) foundEnd = -1; } if( foundEnd != -1 ) { //We have to stop the search, because we found an interrupting end-sign before the opening-paren d->m_prefix = d->m_source.mid( offset, foundEnd - offset ); d->m_curEnd = d->m_end = d->m_cur = foundEnd; } else { if( parenBegin != -1 ) { //We have a valid prefix before an opening-paren. Take the prefix, and start iterating parameters. d->m_prefix = d->m_source.mid( offset, parenBegin - offset ); d->m_cur = parenBegin + 1; d->m_curEnd = d->next(); if( d->m_curEnd == d->m_source.length() ) { //The paren was not closed. It might be an identifier like "operator<", so count everything as prefix. d->m_prefix = d->m_source.mid(offset); d->m_curEnd = d->m_end = d->m_cur = d->m_source.length(); } } else { //We have neither found an ending-character, nor an opening-paren, so take the whole input and end d->m_prefix = d->m_source.mid(offset); d->m_curEnd = d->m_end = d->m_cur = d->m_source.length(); } } } ParamIterator& ParamIterator::operator ++() { if( d->m_source[d->m_curEnd] == d->m_parens[1] ) { //We have reached the end-paren. Stop iterating. d->m_cur = d->m_end = d->m_curEnd + 1; } else { //Iterate on through parameters d->m_cur = d->m_curEnd + 1; if ( d->m_cur < ( int ) d->m_source.length() ) { d->m_curEnd = d->next(); } } return *this; } QString ParamIterator::operator *() { return d->m_source.mid( d->m_cur, d->m_curEnd - d->m_cur ).trimmed(); } ParamIterator::operator bool() const { return d->m_cur < ( int ) d->m_end; } QString ParamIterator::prefix() const { return d->m_prefix; } uint ParamIterator::position() const { return (uint)d->m_cur; } } diff --git a/language/duchain/topducontext.cpp b/language/duchain/topducontext.cpp index 325a05e7c..415fbb11c 100644 --- a/language/duchain/topducontext.cpp +++ b/language/duchain/topducontext.cpp @@ -1,1167 +1,1167 @@ /* This is part of KDevelop Copyright 2006 Hamish Rodda Copyright 2007-2009 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "topducontext.h" #include "topducontextutils.h" #include #include "persistentsymboltable.h" #include "problem.h" #include "declaration.h" #include "duchain.h" #include "duchainlock.h" #include "parsingenvironment.h" #include "duchainpointer.h" #include "declarationid.h" #include "namespacealiasdeclaration.h" #include "aliasdeclaration.h" #include "uses.h" #include "topducontextdata.h" #include "duchainregister.h" #include "topducontextdynamicdata.h" -#include "util/debug.h" +#include #include // #define DEBUG_SEARCH const uint maxApplyAliasesRecursion = 100; namespace KDevelop { Utils::BasicSetRepository* RecursiveImportRepository::repository() { static Utils::BasicSetRepository recursiveImportRepositoryObject(QStringLiteral("Recursive Imports"), &KDevelop::globalItemRepositoryRegistry()); return &recursiveImportRepositoryObject; } ReferencedTopDUContext::ReferencedTopDUContext(TopDUContext* context) : m_topContext(context) { if(m_topContext) DUChain::self()->refCountUp(m_topContext); } ReferencedTopDUContext::ReferencedTopDUContext(const ReferencedTopDUContext& rhs) : m_topContext(rhs.m_topContext) { if(m_topContext) DUChain::self()->refCountUp(m_topContext); } ReferencedTopDUContext::~ReferencedTopDUContext() { if(m_topContext && !DUChain::deleted()) DUChain::self()->refCountDown(m_topContext); } ReferencedTopDUContext& ReferencedTopDUContext::operator=(const ReferencedTopDUContext& rhs) { if(m_topContext == rhs.m_topContext) return *this; if(m_topContext) DUChain::self()->refCountDown(m_topContext); m_topContext = rhs.m_topContext; if(m_topContext) DUChain::self()->refCountUp(m_topContext); return *this; } DEFINE_LIST_MEMBER_HASH(TopDUContextData, m_usedDeclarationIds, DeclarationId) DEFINE_LIST_MEMBER_HASH(TopDUContextData, m_problems, LocalIndexedProblem) REGISTER_DUCHAIN_ITEM(TopDUContext); template void removeFromVector(QVector& vec, const T& t) { for(int a =0; a < vec.size(); ++a) { if(vec[a] == t) { vec.remove(a); removeFromVector(vec, t); return; } } } QMutex importStructureMutex(QMutex::Recursive); //Contains data that is not shared among top-contexts that share their duchain entries class TopDUContextLocalPrivate { public: TopDUContextLocalPrivate (TopDUContext* ctxt, uint index) : m_ctxt(ctxt), m_ownIndex(index), m_inDuChain(false) { m_indexedRecursiveImports.insert(index); } ~TopDUContextLocalPrivate() { //Either we use some other contexts data and have no users, or we own the data and have users that share it. QMutexLocker lock(&importStructureMutex); foreach(const DUContext::Import& import, m_importedContexts) if(DUChain::self()->isInMemory(import.topContextIndex()) && dynamic_cast(import.context(nullptr))) dynamic_cast(import.context(nullptr))->m_local->m_directImporters.remove(m_ctxt); } ///@todo Make all this work consistently together with import-caching //After loading, should rebuild the links void rebuildDynamicImportStructure() { //Currently we do not store the whole data in TopDUContextLocalPrivate, so we reconstruct it from what was stored by DUContext. Q_ASSERT(m_importedContexts.isEmpty()); FOREACH_FUNCTION(const DUContext::Import& import, m_ctxt->d_func()->m_importedContexts) { if(DUChain::self()->isInMemory(import.topContextIndex())) { Q_ASSERT(import.context(nullptr)); TopDUContext* top = import.context(nullptr)->topContext(); Q_ASSERT(top); addImportedContextRecursively(top, false, true); } } FOREACH_FUNCTION(const IndexedDUContext& importer, m_ctxt->d_func()->m_importers) { if(DUChain::self()->isInMemory(importer.topContextIndex())) { Q_ASSERT(importer.context()); TopDUContext* top = importer.context()->topContext(); Q_ASSERT(top); top->m_local->addImportedContextRecursively(m_ctxt, false, true); } } } //Index of this top-context within the duchain //Since the data of top-contexts can be shared among multiple, this can be used to add imports that are local to this top-context. QVector m_importedContexts; // mutable bool m_haveImportStructure : 1; TopDUContext* m_ctxt; QSet m_directImporters; ParsingEnvironmentFilePointer m_file; QExplicitlySharedDataPointer m_ast; uint m_ownIndex; bool m_inDuChain; void clearImportedContextsRecursively() { QMutexLocker lock(&importStructureMutex); // Q_ASSERT(m_recursiveImports.size() == m_indexedRecursiveImports.count()-1); QSet > rebuild; foreach(const DUContext::Import &import, m_importedContexts) { TopDUContext* top = dynamic_cast(import.context(nullptr)); if(top) { top->m_local->m_directImporters.remove(m_ctxt); if(!m_ctxt->usingImportsCache()) { removeImportedContextRecursion(top, top, 1, rebuild); QHash > b = top->m_local->m_recursiveImports; for(RecursiveImports::const_iterator it = b.constBegin(); it != b.constEnd(); ++it) { if(m_recursiveImports.contains(it.key()) && m_recursiveImports[it.key()].second == top) removeImportedContextRecursion(top, it.key(), it->first+1, rebuild); //Remove all contexts that are imported through the context } } } } m_importedContexts.clear(); rebuildImportStructureRecursion(rebuild); Q_ASSERT(m_recursiveImports.isEmpty()); // Q_ASSERT(m_recursiveImports.size() == m_indexedRecursiveImports.count()-1); } //Adds the context to this and all contexts that import this, and manages m_recursiveImports void addImportedContextRecursively(TopDUContext* context, bool temporary, bool local) { QMutexLocker lock(&importStructureMutex); context->m_local->m_directImporters.insert(m_ctxt); if(local) m_importedContexts << DUContext::Import(context, m_ctxt); if(!m_ctxt->usingImportsCache()) { addImportedContextRecursion(context, context, 1, temporary); QHash > b = context->m_local->m_recursiveImports; for(RecursiveImports::const_iterator it = b.constBegin(); it != b.constEnd(); ++it) addImportedContextRecursion(context, it.key(), (*it).first+1, temporary); //Add contexts that were imported earlier into the given one } } //Removes the context from this and all contexts that import this, and manages m_recursiveImports void removeImportedContextRecursively(TopDUContext* context, bool local) { QMutexLocker lock(&importStructureMutex); context->m_local->m_directImporters.remove(m_ctxt); if(local) removeFromVector(m_importedContexts, DUContext::Import(context, m_ctxt)); QSet > rebuild; if(!m_ctxt->usingImportsCache()) { removeImportedContextRecursion(context, context, 1, rebuild); QHash > b = context->m_local->m_recursiveImports; for(RecursiveImports::const_iterator it = b.constBegin(); it != b.constEnd(); ++it) { if(m_recursiveImports.contains(it.key()) && m_recursiveImports[it.key()].second == context) removeImportedContextRecursion(context, it.key(), it->first+1, rebuild); //Remove all contexts that are imported through the context } } rebuildImportStructureRecursion(rebuild); } void removeImportedContextsRecursively(const QList& contexts, bool local) { QMutexLocker lock(&importStructureMutex); QSet > rebuild; foreach(TopDUContext* context, contexts) { context->m_local->m_directImporters.remove(m_ctxt); if(local) removeFromVector(m_importedContexts, DUContext::Import(context, m_ctxt)); if(!m_ctxt->usingImportsCache()) { removeImportedContextRecursion(context, context, 1, rebuild); QHash > b = context->m_local->m_recursiveImports; for(RecursiveImports::const_iterator it = b.constBegin(); it != b.constEnd(); ++it) { if(m_recursiveImports.contains(it.key()) && m_recursiveImports[it.key()].second == context) removeImportedContextRecursion(context, it.key(), it->first+1, rebuild); //Remove all contexts that are imported through the context } } } rebuildImportStructureRecursion(rebuild); } //Has an entry for every single recursively imported file, that contains the shortest path, and the next context on that path to the imported context. //This does not need to be stored to disk, because it is defined implicitly. //What makes this most complicated is the fact that loops are allowed in the import structure. typedef QHash > RecursiveImports; mutable RecursiveImports m_recursiveImports; mutable TopDUContext::IndexedRecursiveImports m_indexedRecursiveImports; private: void addImportedContextRecursion(const TopDUContext* traceNext, const TopDUContext* imported, int depth, bool temporary = false) { if(m_ctxt->usingImportsCache()) return; // if(!m_haveImportStructure) // return; if(imported == m_ctxt) return; const bool computeShortestPaths = false; ///@todo We do not compute the shortest path. Think what's right. // traceNext->m_local->needImportStructure(); // imported->m_local->needImportStructure(); RecursiveImports::iterator it = m_recursiveImports.find(imported); if(it == m_recursiveImports.end()) { //Insert new path to "imported" m_recursiveImports[imported] = qMakePair(depth, traceNext); m_indexedRecursiveImports.insert(imported->indexed()); // Q_ASSERT(m_indexedRecursiveImports.size() == m_recursiveImports.size()+1); Q_ASSERT(traceNext != m_ctxt); }else{ if(!computeShortestPaths) return; if(temporary) //For temporary imports, we don't record the best path. return; //It would be better if we would use the following code, but it creates too much cost in updateImportedContextRecursion when imports are removed again. //Check whether the new way to "imported" is shorter than the stored one if((*it).first > depth) { //Add a shorter path (*it).first = depth; Q_ASSERT(traceNext); (*it).second = traceNext; Q_ASSERT(traceNext == imported || (traceNext->m_local->m_recursiveImports.contains(imported) && traceNext->m_local->m_recursiveImports[imported].first < (*it).first)); }else{ //The imported context is already imported through a same/better path, so we can just stop processing. This saves us from endless recursion. return; } } if(temporary) return; for(QSet::const_iterator it = m_directImporters.constBegin(); it != m_directImporters.constEnd(); ++it) { TopDUContext* top = dynamic_cast(const_cast(*it)); //Avoid detaching, so use const_cast if(top) ///@todo also record this for local imports top->m_local->addImportedContextRecursion(m_ctxt, imported, depth+1); } } void removeImportedContextRecursion(const TopDUContext* traceNext, const TopDUContext* imported, int distance, QSet >& rebuild) { if(m_ctxt->usingImportsCache()) return; if(imported == m_ctxt) return; // if(!m_haveImportStructure) // return; RecursiveImports::iterator it = m_recursiveImports.find(imported); if(it == m_recursiveImports.end()) { //We don't import. Just return, this saves us from endless recursion. return; }else{ //Check whether we have imported "imported" through "traceNext". If not, return. Else find a new trace. if((*it).second == traceNext && (*it).first == distance) { //We need to remove the import through traceNext. Check whether there is another imported context that imports it. m_recursiveImports.erase(it); //In order to prevent problems, we completely remove everything, and re-add it. //Just updating these complex structures is very hard. Q_ASSERT(imported != m_ctxt); m_indexedRecursiveImports.remove(imported->indexed()); // Q_ASSERT(m_indexedRecursiveImports.size() == m_recursiveImports.size()); rebuild.insert(qMakePair(m_ctxt, imported)); //We MUST do this before finding another trace, because else we would create loops for(QSet::const_iterator childIt = m_directImporters.constBegin(); childIt != m_directImporters.constEnd(); ++childIt) { TopDUContext* top = dynamic_cast(const_cast(*childIt)); //Avoid detaching, so use const iterator if(top) top->m_local->removeImportedContextRecursion(m_ctxt, imported, distance+1, rebuild); //Don't use 'it' from here on, it may be invalid } } } } //Updates the trace to 'imported' void rebuildStructure(const TopDUContext* imported); void rebuildImportStructureRecursion(const QSet >& rebuild) { for(QSet >::const_iterator it = rebuild.constBegin(); it != rebuild.constEnd(); ++it) { //for(int a = rebuild.size()-1; a >= 0; --a) { //Find the best imported parent it->first->m_local->rebuildStructure(it->second); } } }; const TopDUContext::IndexedRecursiveImports& TopDUContext::recursiveImportIndices() const { // No lock-check for performance reasons QMutexLocker lock(&importStructureMutex); if(!d_func()->m_importsCache.isEmpty()) return d_func()->m_importsCache; return m_local->m_indexedRecursiveImports; } void TopDUContextData::updateImportCacheRecursion(uint baseIndex, IndexedTopDUContext currentContext, TopDUContext::IndexedRecursiveImports& visited) { if(visited.contains(currentContext.index())) return; Q_ASSERT(currentContext.index()); //The top-context must be in the repository when this is called if(!currentContext.data()) { qCDebug(LANGUAGE) << "importing invalid context"; return; } visited.insert(currentContext.index()); const TopDUContextData* currentData = currentContext.data()->topContext()->d_func(); if(currentData->m_importsCache.contains(baseIndex) || currentData->m_importsCache.isEmpty()) { //If we have a loop or no imports-cache is used, we have to look at each import separately. const KDevelop::DUContext::Import* imports = currentData->m_importedContexts(); uint importsSize = currentData->m_importedContextsSize(); for(uint a = 0; a < importsSize; ++a) { IndexedTopDUContext next(imports[a].topContextIndex()); if(next.isValid()) updateImportCacheRecursion(baseIndex, next, visited); } }else{ //If we don't have a loop with baseIndex, we can safely just merge with the imported importscache visited += currentData->m_importsCache; } } void TopDUContextData::updateImportCacheRecursion(IndexedTopDUContext currentContext, std::set& visited) { if(visited.find(currentContext.index()) != visited.end()) return; Q_ASSERT(currentContext.index()); //The top-context must be in the repository when this is called if(!currentContext.data()) { qCDebug(LANGUAGE) << "importing invalid context"; return; } visited.insert(currentContext.index()); const TopDUContextData* currentData = currentContext.data()->topContext()->d_func(); const KDevelop::DUContext::Import* imports = currentData->m_importedContexts(); uint importsSize = currentData->m_importedContextsSize(); for(uint a = 0; a < importsSize; ++a) { IndexedTopDUContext next(imports[a].topContextIndex()); if(next.isValid()) updateImportCacheRecursion(next, visited); } } void TopDUContext::updateImportsCache() { QMutexLocker lock(&importStructureMutex); const bool use_fully_recursive_import_cache_computation = false; if(use_fully_recursive_import_cache_computation) { std::set visited; TopDUContextData::updateImportCacheRecursion(this, visited); Q_ASSERT(visited.find(ownIndex()) != visited.end()); d_func_dynamic()->m_importsCache = IndexedRecursiveImports(visited); }else{ d_func_dynamic()->m_importsCache = IndexedRecursiveImports(); TopDUContextData::updateImportCacheRecursion(ownIndex(), this, d_func_dynamic()->m_importsCache); } Q_ASSERT(d_func_dynamic()->m_importsCache.contains(IndexedTopDUContext(this))); Q_ASSERT(usingImportsCache()); Q_ASSERT(imports(this, CursorInRevision::invalid())); if(parsingEnvironmentFile()) parsingEnvironmentFile()->setImportsCache(d_func()->m_importsCache); } bool TopDUContext::usingImportsCache() const { return !d_func()->m_importsCache.isEmpty(); } CursorInRevision TopDUContext::importPosition(const DUContext* target) const { ENSURE_CAN_READ DUCHAIN_D(DUContext); Import import(const_cast(target), const_cast(this), CursorInRevision::invalid()); for(unsigned int a = 0; a < d->m_importedContextsSize(); ++a) if(d->m_importedContexts()[a] == import) return d->m_importedContexts()[a].position; return DUContext::importPosition(target); } void TopDUContextLocalPrivate::rebuildStructure(const TopDUContext* imported) { if(m_ctxt == imported) return; for(QVector::const_iterator parentIt = m_importedContexts.constBegin(); parentIt != m_importedContexts.constEnd(); ++parentIt) { TopDUContext* top = dynamic_cast(const_cast(parentIt->context(nullptr))); //To avoid detaching, use const iterator if(top) { // top->m_local->needImportStructure(); if(top == imported) { addImportedContextRecursion(top, imported, 1); }else{ RecursiveImports::const_iterator it2 = top->m_local->m_recursiveImports.constFind(imported); if(it2 != top->m_local->m_recursiveImports.constEnd()) { addImportedContextRecursion(top, imported, (*it2).first + 1); } } } } for(unsigned int a = 0; a < m_ctxt->d_func()->m_importedContextsSize(); ++a) { TopDUContext* top = dynamic_cast(const_cast(m_ctxt->d_func()->m_importedContexts()[a].context(nullptr))); //To avoid detaching, use const iterator if(top) { // top->m_local->needImportStructure(); if(top == imported) { addImportedContextRecursion(top, imported, 1); }else{ RecursiveImports::const_iterator it2 = top->m_local->m_recursiveImports.constFind(imported); if(it2 != top->m_local->m_recursiveImports.constEnd()) { addImportedContextRecursion(top, imported, (*it2).first + 1); } } } } } void TopDUContext::rebuildDynamicImportStructure() { m_local->rebuildDynamicImportStructure(); } void TopDUContext::rebuildDynamicData(DUContext* parent, uint ownIndex) { Q_ASSERT(parent == nullptr && ownIndex != 0); m_local->m_ownIndex = ownIndex; DUContext::rebuildDynamicData(parent, 0); } IndexedTopDUContext TopDUContext::indexed() const { return IndexedTopDUContext(m_local->m_ownIndex); } uint TopDUContext::ownIndex() const { return m_local->m_ownIndex; } TopDUContext::TopDUContext(TopDUContextData& data) : DUContext(data), m_local(new TopDUContextLocalPrivate(this, data.m_ownIndex)), m_dynamicData(new TopDUContextDynamicData(this)) { } TopDUContext::TopDUContext(const IndexedString& url, const RangeInRevision& range, ParsingEnvironmentFile* file) : DUContext(*new TopDUContextData(url), range) , m_local(new TopDUContextLocalPrivate(this, DUChain::newTopContextIndex())) , m_dynamicData(new TopDUContextDynamicData(this)) { Q_ASSERT(url.toUrl().isValid() && !url.toUrl().isRelative()); d_func_dynamic()->setClassId(this); setType(Global); DUCHAIN_D_DYNAMIC(TopDUContext); d->m_features = VisibleDeclarationsAndContexts; d->m_ownIndex = m_local->m_ownIndex; setParsingEnvironmentFile(file); setInSymbolTable(true); } QExplicitlySharedDataPointer TopDUContext::parsingEnvironmentFile() const { return m_local->m_file; } TopDUContext::~TopDUContext( ) { m_dynamicData->m_deleting = true; //Clear the AST, so that the 'feature satisfaction' cache is eventually updated clearAst(); if(!isOnDisk()) { //Clear the 'feature satisfaction' cache which is managed in ParsingEnvironmentFile setFeatures(Empty); clearUsedDeclarationIndices(); } deleteChildContextsRecursively(); deleteLocalDeclarations(); m_dynamicData->clear(); } void TopDUContext::deleteSelf() { //We've got to make sure that m_dynamicData and m_local are still valid while all the sub-contexts are destroyed TopDUContextLocalPrivate* local = m_local; TopDUContextDynamicData* dynamicData = m_dynamicData; m_dynamicData->m_deleting = true; delete this; delete local; delete dynamicData; } TopDUContext::Features TopDUContext::features() const { uint ret = d_func()->m_features; if(ast()) ret |= TopDUContext::AST; return (TopDUContext::Features)ret; } void TopDUContext::setFeatures(Features features) { features = (TopDUContext::Features)(features & (~Recursive)); //Remove the "Recursive" flag since that's only for searching features = (TopDUContext::Features)(features & (~ForceUpdateRecursive)); //Remove the update flags features = (TopDUContext::Features)(features & (~AST)); //Remove the AST flag, it's only used while updating d_func_dynamic()->m_features = features; //Replicate features to ParsingEnvironmentFile if(parsingEnvironmentFile()) parsingEnvironmentFile()->setFeatures(this->features()); } void TopDUContext::setAst(QExplicitlySharedDataPointer ast) { ENSURE_CAN_WRITE m_local->m_ast = ast; if(parsingEnvironmentFile()) parsingEnvironmentFile()->setFeatures(features()); } void TopDUContext::setParsingEnvironmentFile(ParsingEnvironmentFile* file) { if(m_local->m_file) //Clear the "feature satisfaction" cache m_local->m_file->setFeatures(Empty); //We do not enforce a duchain lock here, since this is also used while loading a top-context m_local->m_file = QExplicitlySharedDataPointer(file); //Replicate features to ParsingEnvironmentFile if(file) { file->setTopContext(IndexedTopDUContext(ownIndex())); Q_ASSERT(file->indexedTopContext().isValid()); file->setFeatures(d_func()->m_features); file->setImportsCache(d_func()->m_importsCache); } } struct TopDUContext::FindDeclarationsAcceptor { FindDeclarationsAcceptor(const TopDUContext* _top, DeclarationList& _target, const DeclarationChecker& _check, SearchFlags _flags) : top(_top), target(_target), check(_check) { flags = _flags; } bool operator() (const QualifiedIdentifier& id) { #ifdef DEBUG_SEARCH qCDebug(LANGUAGE) << "accepting" << id.toString(); #endif PersistentSymbolTable::Declarations allDecls; //This iterator efficiently filters the visible declarations out of all declarations PersistentSymbolTable::FilteredDeclarationIterator filter; //This is used if filtering is disabled PersistentSymbolTable::Declarations::Iterator unchecked; if(check.flags & DUContext::NoImportsCheck) { allDecls = PersistentSymbolTable::self().getDeclarations(id); unchecked = allDecls.iterator(); } else filter = PersistentSymbolTable::self().getFilteredDeclarations(id, top->recursiveImportIndices()); while(filter || unchecked) { IndexedDeclaration iDecl; if(filter) { iDecl = *filter; ++filter; } else { iDecl = *unchecked; ++unchecked; } Declaration* decl = iDecl.data(); if(!decl) continue; if(!check(decl)) continue; if( ! (flags & DontResolveAliases) && decl->kind() == Declaration::Alias ) { //Apply alias declarations AliasDeclaration* alias = static_cast(decl); if(alias->aliasedDeclaration().isValid()) { decl = alias->aliasedDeclaration().declaration(); } else { qCDebug(LANGUAGE) << "lost aliased declaration"; } } target.append(decl); } check.createVisibleCache = nullptr; return !top->foundEnough(target, flags); } const TopDUContext* top; DeclarationList& target; const DeclarationChecker& check; QFlags< KDevelop::DUContext::SearchFlag > flags; }; bool TopDUContext::findDeclarationsInternal(const SearchItem::PtrList& identifiers, const CursorInRevision& position, const AbstractType::Ptr& dataType, DeclarationList& ret, const TopDUContext* /*source*/, SearchFlags flags, uint /*depth*/) const { ENSURE_CAN_READ #ifdef DEBUG_SEARCH for (const SearchItem::Ptr& idTree : identifiers) foreach(const QualifiedIdentifier &id, idTree->toList()) qCDebug(LANGUAGE) << "searching item" << id.toString(); #endif DeclarationChecker check(this, position, dataType, flags); FindDeclarationsAcceptor storer(this, ret, check, flags); ///The actual scopes are found within applyAliases, and each complete qualified identifier is given to FindDeclarationsAcceptor. ///That stores the found declaration to the output. applyAliases(identifiers, storer, position, false); return true; } //This is used to prevent endless recursion due to "using namespace .." declarations, by storing all imports that are already being used. struct TopDUContext::ApplyAliasesBuddyInfo { ApplyAliasesBuddyInfo(uint importChainType, ApplyAliasesBuddyInfo* predecessor, IndexedQualifiedIdentifier importId) : m_importChainType(importChainType), m_predecessor(predecessor), m_importId(importId) { if(m_predecessor && m_predecessor->m_importChainType != importChainType) m_predecessor = nullptr; } bool alreadyImporting(IndexedQualifiedIdentifier id) { ApplyAliasesBuddyInfo* current = this; while(current) { if(current->m_importId == id) return true; current = current->m_predecessor; } return false; } uint m_importChainType; ApplyAliasesBuddyInfo* m_predecessor; IndexedQualifiedIdentifier m_importId; }; ///@todo Implement a cache so at least the global import checks don't need to be done repeatedly. The cache should be thread-local, using DUChainPointer for the hashed items, and when an item was deleted, it should be discarded template bool TopDUContext::applyAliases( const QualifiedIdentifier& previous, const SearchItem::Ptr& identifier, Acceptor& accept, const CursorInRevision& position, bool canBeNamespace, ApplyAliasesBuddyInfo* buddy, uint recursionDepth ) const { if(recursionDepth > maxApplyAliasesRecursion) { QList searches = identifier->toList(); QualifiedIdentifier id; if(!searches.isEmpty()) id = searches.first(); qCDebug(LANGUAGE) << "maximum apply-aliases recursion reached while searching" << id; } bool foundAlias = false; QualifiedIdentifier id(previous); id.push(identifier->identifier); if(!id.inRepository()) return true; //If the qualified identifier is not in the identifier repository, it cannot be registered anywhere, so there's nothing we need to do if( !identifier->next.isEmpty() || canBeNamespace ) { //If it cannot be a namespace, the last part of the scope will be ignored //Search for namespace-aliases, by using globalAliasIdentifier, which is inserted into the symbol-table by NamespaceAliasDeclaration QualifiedIdentifier aliasId(id); aliasId.push(globalIndexedAliasIdentifier()); #ifdef DEBUG_SEARCH qCDebug(LANGUAGE) << "checking" << id.toString(); #endif if(aliasId.inRepository()) { //This iterator efficiently filters the visible declarations out of all declarations PersistentSymbolTable::FilteredDeclarationIterator filter = PersistentSymbolTable::self().getFilteredDeclarations(aliasId, recursiveImportIndices()); if(filter) { DeclarationChecker check(this, position, AbstractType::Ptr(), NoSearchFlags, nullptr); //The first part of the identifier has been found as a namespace-alias. //In c++, we only need the first alias. However, just to be correct, follow them all for now. for(; filter; ++filter) { Declaration* aliasDecl = filter->data(); if(!aliasDecl) continue; if(!check(aliasDecl)) continue; if(aliasDecl->kind() != Declaration::NamespaceAlias) continue; if(foundAlias) break; Q_ASSERT(dynamic_cast(aliasDecl)); NamespaceAliasDeclaration* alias = static_cast(aliasDecl); foundAlias = true; QualifiedIdentifier importIdentifier = alias->importIdentifier(); if(importIdentifier.isEmpty()) { qCDebug(LANGUAGE) << "found empty import"; continue; } if(buddy && buddy->alreadyImporting( importIdentifier )) continue; //This import has already been applied to this search ApplyAliasesBuddyInfo info(1, buddy, importIdentifier); if(identifier->next.isEmpty()) { //Just insert the aliased namespace identifier if(!accept(importIdentifier)) return false; }else{ //Create an identifiers where namespace-alias part is replaced with the alias target foreach (const SearchItem::Ptr& item, identifier->next) if(!applyAliases(importIdentifier, item, accept, position, canBeNamespace, &info, recursionDepth+1)) return false; } } } } } if(!foundAlias) { //If we haven't found an alias, put the current versions into the result list. Additionally we will compute the identifiers transformed through "using". if(identifier->next.isEmpty()) { if(!accept(id)) //We're at the end of a qualified identifier, accept it return false; } else { foreach (const SearchItem::Ptr& next, identifier->next) if(!applyAliases(id, next, accept, position, canBeNamespace, nullptr, recursionDepth+1)) return false; } } /*if( !prefix.explicitlyGlobal() || !prefix.isEmpty() ) {*/ ///@todo check iso c++ if using-directives should be respected on top-level when explicitly global ///@todo this is bad for a very big repository(the chains should be walked for the top-context instead) //Find all namespace-imports at given scope { QualifiedIdentifier importId(previous); importId.push(globalIndexedImportIdentifier()); #ifdef DEBUG_SEARCH // qCDebug(LANGUAGE) << "checking imports in" << (backPointer ? id.toString() : QStringLiteral("global")); #endif if(importId.inRepository()) { //This iterator efficiently filters the visible declarations out of all declarations PersistentSymbolTable::FilteredDeclarationIterator filter = PersistentSymbolTable::self().getFilteredDeclarations(importId, recursiveImportIndices()); if(filter) { DeclarationChecker check(this, position, AbstractType::Ptr(), NoSearchFlags, nullptr); for(; filter; ++filter) { Declaration* importDecl = filter->data(); if(!importDecl) continue; //We must never break or return from this loop, because else we might be creating a bad cache if(!check(importDecl)) continue; //Search for the identifier with the import-identifier prepended Q_ASSERT(dynamic_cast(importDecl)); NamespaceAliasDeclaration* alias = static_cast(importDecl); #ifdef DEBUG_SEARCH qCDebug(LANGUAGE) << "found import of" << alias->importIdentifier().toString(); #endif QualifiedIdentifier importIdentifier = alias->importIdentifier(); if(importIdentifier.isEmpty()) { qCDebug(LANGUAGE) << "found empty import"; continue; } if(buddy && buddy->alreadyImporting( importIdentifier )) continue; //This import has already been applied to this search ApplyAliasesBuddyInfo info(2, buddy, importIdentifier); if(previous != importIdentifier) if(!applyAliases(importIdentifier, identifier, accept, importDecl->topContext() == this ? importDecl->range().start : position, canBeNamespace, &info, recursionDepth+1)) return false; } } } } return true; } template void TopDUContext::applyAliases( const SearchItem::PtrList& identifiers, Acceptor& acceptor, const CursorInRevision& position, bool canBeNamespace ) const { QualifiedIdentifier emptyId; for (const SearchItem::Ptr& item : identifiers) applyAliases(emptyId, item, acceptor, position, canBeNamespace, nullptr, 0); } TopDUContext * TopDUContext::topContext() const { return const_cast(this); } bool TopDUContext::deleting() const { return m_dynamicData->m_deleting; } QList TopDUContext::problems() const { ENSURE_CAN_READ const auto data = d_func(); QList ret; ret.reserve(data->m_problemsSize()); for (uint i = 0; i < data->m_problemsSize(); ++i) { ret << ProblemPointer(data->m_problems()[i].data(this)); } return ret; } void TopDUContext::setProblems(const QList& problems) { ENSURE_CAN_WRITE clearProblems(); for (const auto& problem : problems) { addProblem(problem); } } void TopDUContext::addProblem(const ProblemPointer& problem) { ENSURE_CAN_WRITE Q_ASSERT(problem); auto data = d_func_dynamic(); // store for indexing LocalIndexedProblem indexedProblem(problem, this); Q_ASSERT(indexedProblem.isValid()); data->m_problemsList().append(indexedProblem); Q_ASSERT(indexedProblem.data(this)); } void TopDUContext::clearProblems() { ENSURE_CAN_WRITE d_func_dynamic()->m_problemsList().clear(); m_dynamicData->clearProblems(); } QVector TopDUContext::importers() const { ENSURE_CAN_READ return QVector::fromList( m_local->m_directImporters.toList() ); } QList TopDUContext::loadedImporters() const { ENSURE_CAN_READ return m_local->m_directImporters.toList(); } QVector TopDUContext::importedParentContexts() const { ENSURE_CAN_READ return DUContext::importedParentContexts(); } bool TopDUContext::imports(const DUContext * origin, const CursorInRevision& position) const { return importsPrivate(origin, position); } bool TopDUContext::importsPrivate(const DUContext * origin, const CursorInRevision& position) const { Q_UNUSED(position); if( const TopDUContext* top = dynamic_cast(origin) ) { QMutexLocker lock(&importStructureMutex); bool ret = recursiveImportIndices().contains(IndexedTopDUContext(const_cast(top))); if(top == this) Q_ASSERT(ret); return ret; } else { //Cannot import a non top-context return false; } } void TopDUContext::clearImportedParentContexts() { if(usingImportsCache()) { d_func_dynamic()->m_importsCache = IndexedRecursiveImports(); d_func_dynamic()->m_importsCache.insert(IndexedTopDUContext(this)); } DUContext::clearImportedParentContexts(); m_local->clearImportedContextsRecursively(); Q_ASSERT(m_local->m_recursiveImports.count() == 0); Q_ASSERT(m_local->m_indexedRecursiveImports.count() == 1); Q_ASSERT(imports(this, CursorInRevision::invalid())); } void TopDUContext::addImportedParentContext(DUContext* context, const CursorInRevision& position, bool anonymous, bool temporary) { if(context == this) return; if(!dynamic_cast(context)) { //We cannot do this, because of the extended way we treat top-context imports. qCDebug(LANGUAGE) << "tried to import a non top-context into a top-context. This is not possible."; return; } //Always make the contexts anonymous, because we care about importers in TopDUContextLocalPrivate DUContext::addImportedParentContext(context, position, anonymous, temporary); m_local->addImportedContextRecursively(static_cast(context), temporary, true); } void TopDUContext::removeImportedParentContext(DUContext* context) { DUContext::removeImportedParentContext(context); m_local->removeImportedContextRecursively(static_cast(context), true); } void TopDUContext::addImportedParentContexts(const QList >& contexts, bool temporary) { typedef QPair Pair; foreach(const Pair pair, contexts) addImportedParentContext(pair.first, pair.second, false, temporary); } void TopDUContext::removeImportedParentContexts(const QList& contexts) { foreach(TopDUContext* context, contexts) DUContext::removeImportedParentContext(context); m_local->removeImportedContextsRecursively(contexts, true); } /// Returns true if this object is registered in the du-chain. If it is not, all sub-objects(context, declarations, etc.) bool TopDUContext::inDUChain() const { return m_local->m_inDuChain; } /// This flag is only used by DUChain, never change it from outside. void TopDUContext::setInDuChain(bool b) { m_local->m_inDuChain = b; } bool TopDUContext::isOnDisk() const { ///@todo Change this to releasingToDisk, and only enable it while saving a top-context to disk. return m_dynamicData->isOnDisk(); } void TopDUContext::clearUsedDeclarationIndices() { ENSURE_CAN_WRITE for(unsigned int a = 0; a < d_func()->m_usedDeclarationIdsSize(); ++a) DUChain::uses()->removeUse(d_func()->m_usedDeclarationIds()[a], this); d_func_dynamic()->m_usedDeclarationIdsList().clear(); } void TopDUContext::deleteUsesRecursively() { clearUsedDeclarationIndices(); KDevelop::DUContext::deleteUsesRecursively(); } Declaration* TopDUContext::usedDeclarationForIndex(unsigned int declarationIndex) const { ENSURE_CAN_READ if(declarationIndex & (1<<31)) { //We use the highest bit to mark direct indices into the local declarations declarationIndex &= ~(1<<31); //unset the highest bit return m_dynamicData->getDeclarationForIndex(declarationIndex); }else if(declarationIndex < d_func()->m_usedDeclarationIdsSize()) return d_func()->m_usedDeclarationIds()[declarationIndex].getDeclaration(this); else return nullptr; } int TopDUContext::indexForUsedDeclaration(Declaration* declaration, bool create) { if(create) { ENSURE_CAN_WRITE }else{ ENSURE_CAN_READ } if(!declaration) { return std::numeric_limits::max(); } if(declaration->topContext() == this && !declaration->inSymbolTable() && !m_dynamicData->isTemporaryDeclarationIndex(declaration->ownIndex())) { uint index = declaration->ownIndex(); Q_ASSERT(!(index & (1<<31))); return (int)(index | (1<<31)); //We don't put context-local declarations into the list, that's a waste. We just use the mark them with the highest bit. } // if the declaration can not be found from this top-context, we create a direct // reference by index, to ensure that the use can be resolved in // usedDeclarationForIndex bool useDirectId = !recursiveImportIndices().contains(declaration->topContext()); DeclarationId id(declaration->id(useDirectId)); int index = -1; uint size = d_func()->m_usedDeclarationIdsSize(); const DeclarationId* ids = d_func()->m_usedDeclarationIds(); ///@todo Make m_usedDeclarationIds sorted, and find the decl. using binary search for(unsigned int a = 0; a < size; ++a) if(ids[a] == id) { index = a; break; } if(index != -1) return index; if(!create) return std::numeric_limits::max(); d_func_dynamic()->m_usedDeclarationIdsList().append(id); if(declaration->topContext() != this) DUChain::uses()->addUse(id, this); return d_func()->m_usedDeclarationIdsSize()-1; } QList allUses(TopDUContext* context, Declaration* declaration, bool noEmptyRanges) { QList ret; int declarationIndex = context->indexForUsedDeclaration(declaration, false); if(declarationIndex == std::numeric_limits::max()) return ret; return allUses(context, declarationIndex, noEmptyRanges); } QExplicitlySharedDataPointer TopDUContext::ast() const { return m_local->m_ast; } void TopDUContext::clearAst() { setAst(QExplicitlySharedDataPointer(nullptr)); } IndexedString TopDUContext::url() const { return d_func()->m_url; } } diff --git a/language/duchain/topducontextdynamicdata.cpp b/language/duchain/topducontextdynamicdata.cpp index eb753b4ef..10e817aac 100644 --- a/language/duchain/topducontextdynamicdata.cpp +++ b/language/duchain/topducontextdynamicdata.cpp @@ -1,841 +1,841 @@ /* This is part of KDevelop Copyright 2014 Milian Wolff Copyright 2008 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "topducontextdynamicdata.h" #include #include #include #include "declaration.h" #include "declarationdata.h" #include "ducontext.h" #include "topducontext.h" #include "topducontextdata.h" #include "ducontextdata.h" #include "ducontextdynamicdata.h" #include "duchainregister.h" #include "serialization/itemrepository.h" #include "problem.h" -#include "util/debug.h" +#include //#define DEBUG_DATA_INFO //This might be problematic on some systems, because really many mmaps are created #define USE_MMAP using namespace KDevelop; namespace { /** * Serialize @p item into @p data and update @p totalDataOffset. * * If @p isSharedDataItem is true, then the item's internal data pointer is not updated * to point to the serialized data. Otherwise the dynamic data is deleted and the items * data will point to the constant serialized data. * * NOTE: The above is required to support serialization of shared-data such as from ProblemPointer. * If we'd set the data to point to the constant region, we'd get crashes due to use-after-free when * we unmap the data and a shared pointer outlives that. */ void saveDUChainItem(QVector& data, DUChainBase& item, uint& totalDataOffset, bool isSharedDataItem) { if(!item.d_func()->classId) { //If this triggers, you have probably created an own DUChainBase based class, but haven't called setClassId(this) in the constructor. qCritical() << "no class-id set for data attached to a declaration of type" << typeid(item).name(); Q_ASSERT(0); } int size = DUChainItemSystem::self().dynamicSize(*item.d_func()); if(data.back().array.size() - int(data.back().position) < size) //Create a new data item data.append({QByteArray(size > 10000 ? size : 10000, 0), 0u}); uint pos = data.back().position; data.back().position += size; totalDataOffset += size; DUChainBaseData& target(*(reinterpret_cast(data.back().array.data() + pos))); if(item.d_func()->isDynamic()) { //Change from dynamic data to constant data enableDUChainReferenceCounting(data.back().array.data(), data.back().array.size()); DUChainItemSystem::self().copy(*item.d_func(), target, true); Q_ASSERT(!target.isDynamic()); if (!isSharedDataItem) { item.setData(&target); } disableDUChainReferenceCounting(data.back().array.data()); }else{ //Just copy the data into another place, expensive copy constructors are not needed memcpy(&target, item.d_func(), size); if (!isSharedDataItem) { item.setData(&target, false); } } if (!isSharedDataItem) { Q_ASSERT(item.d_func() == &target); Q_ASSERT(!item.d_func()->isDynamic()); } } uint indexForParentContext(DUContext* context) { return LocalIndexedDUContext(context->parentContext()).localIndex(); } uint indexForParentContext(Declaration* declaration) { return LocalIndexedDUContext(declaration->context()).localIndex(); } uint indexForParentContext(const ProblemPointer& /*problem*/) { // always stored in the top context return 0; } #ifndef QT_NO_DEBUG void validateItem(const DUChainBaseData* const data, const uchar* const mappedData, const size_t mappedDataSize) { Q_ASSERT(!data->isDynamic()); if (mappedData) { Q_ASSERT(((size_t)data) < ((size_t)mappedData) || ((size_t)data) > ((size_t)mappedData) + mappedDataSize); } } #endif const char* pointerInData(const QVector& data, uint totalOffset) { for(int a = 0; a < data.size(); ++a) { if(totalOffset < data[a].position) return data[a].array.constData() + totalOffset; totalOffset -= data[a].position; } Q_ASSERT_X(false, Q_FUNC_INFO, "Offset doesn't exist in the data."); return nullptr; } void verifyDataInfo(const TopDUContextDynamicData::ItemDataInfo& info, const QVector& data) { Q_UNUSED(info); Q_UNUSED(data); #ifdef DEBUG_DATA_INFO DUChainBaseData* item = (DUChainBaseData*)(pointerInData(data, info.dataOffset)); int size = DUChainItemSystem::self().dynamicSize(*item); Q_ASSERT(size); #endif } QString basePath() { return globalItemRepositoryRegistry().path() + "/topcontexts/"; } QString pathForTopContext(const uint topContextIndex) { return basePath() + QString::number(topContextIndex); } enum LoadType { PartialLoad, ///< Only load the direct member data FullLoad ///< Load everything, including appended lists }; template void loadTopDUContextData(const uint topContextIndex, LoadType loadType, F callback) { QFile file(pathForTopContext(topContextIndex)); if (!file.open(QIODevice::ReadOnly)) { return; } uint readValue; file.read((char*)&readValue, sizeof(uint)); // now readValue is filled with the top-context data size Q_ASSERT(readValue >= sizeof(TopDUContextData)); const QByteArray data = file.read(loadType == FullLoad ? readValue : sizeof(TopDUContextData)); const TopDUContextData* topData = reinterpret_cast(data.constData()); callback(topData); } template struct PtrType; template struct PtrType { using value = T*; }; template struct PtrType> { using value = T*; }; template Q_DECL_CONSTEXPR bool isSharedDataItem() { return false; } template<> Q_DECL_CONSTEXPR bool isSharedDataItem() { return true; } } //BEGIN DUChainItemStorage template TopDUContextDynamicData::DUChainItemStorage::DUChainItemStorage(TopDUContextDynamicData* data) : data(data) { } template TopDUContextDynamicData::DUChainItemStorage::~DUChainItemStorage() { clearItems(); } template void TopDUContextDynamicData::DUChainItemStorage::clearItems() { //Due to template specialization it's possible that a declaration is not reachable through the normal context structure. //For that reason we have to check here, and delete all remaining declarations. qDeleteAll(temporaryItems); temporaryItems.clear(); qDeleteAll(items); items.clear(); } namespace KDevelop { template<> void TopDUContextDynamicData::DUChainItemStorage::clearItems() { // don't delete anything - the problem is shared items.clear(); } } template void TopDUContextDynamicData::DUChainItemStorage::clearItemIndex(const Item& item, const uint index) { if(!data->m_dataLoaded) data->loadData(); if (index < (0x0fffffff/2)) { if (index == 0 || index > uint(items.size())) { return; } else { const uint realIndex = index - 1; Q_ASSERT(items[realIndex] == item); items[realIndex] = nullptr; if (realIndex < (uint)offsets.size()) { offsets[realIndex] = ItemDataInfo(); } } } else { const uint realIndex = 0x0fffffff - index; //We always keep the highest bit at zero if (realIndex == 0 || realIndex > uint(temporaryItems.size())) { return; } else { Q_ASSERT(temporaryItems[realIndex-1] == item); temporaryItems[realIndex-1] = nullptr; } } Q_UNUSED(item); } template void TopDUContextDynamicData::DUChainItemStorage::storeData(uint& currentDataOffset, const QVector& oldData) { auto const oldOffsets = offsets; offsets.clear(); for (int a = 0; a < items.size(); ++a) { auto item = items[a]; if (!item) { if (oldOffsets.size() > a && oldOffsets[a].dataOffset) { //Directly copy the old data range into the new data const DUChainBaseData* itemData = nullptr; if (data->m_mappedData) { itemData = reinterpret_cast(data->m_mappedData + oldOffsets[a].dataOffset); } else { itemData = reinterpret_cast(::pointerInData(oldData, oldOffsets[a].dataOffset)); } offsets << data->writeDataInfo(oldOffsets[a], itemData, currentDataOffset); } else { offsets << ItemDataInfo(); } } else { offsets << ItemDataInfo{currentDataOffset, indexForParentContext(item)}; saveDUChainItem(data->m_data, *item, currentDataOffset, isSharedDataItem()); } } #ifndef QT_NO_DEBUG if (!isSharedDataItem()) { for (auto item : items) { if (item) { validateItem(item->d_func(), data->m_mappedData, data->m_mappedDataSize); } } } #endif } template bool TopDUContextDynamicData::DUChainItemStorage::itemsHaveChanged() const { for (auto item : items) { if (item && item->d_func()->m_dynamic) { return true; } } return false; } template uint TopDUContextDynamicData::DUChainItemStorage::allocateItemIndex(const Item& item, const bool temporary) { if (!data->m_dataLoaded) { data->loadData(); } if (!temporary) { items.append(item); return items.size(); } else { temporaryItems.append(item); return 0x0fffffff - temporaryItems.size(); //We always keep the highest bit at zero } } template bool TopDUContextDynamicData::DUChainItemStorage::isItemForIndexLoaded(uint index) const { if (!data->m_dataLoaded) { return false; } if (index < (0x0fffffff/2)) { if (index == 0 || index > uint(items.size())) { return false; } return items[index-1]; } else { // temporary item return true; } } template Item TopDUContextDynamicData::DUChainItemStorage::getItemForIndex(uint index) const { if (index >= (0x0fffffff/2)) { index = 0x0fffffff - index; //We always keep the highest bit at zero if(index == 0 || index > uint(temporaryItems.size())) return {}; else return temporaryItems.at(index-1); } if (index == 0 || index > static_cast(items.size())) { qCWarning(LANGUAGE) << "item index out of bounds:" << index << "count:" << items.size(); return {}; } const uint realIndex = index - 1;; const auto& item = items.at(realIndex); if (item) { //Shortcut, because this is the most common case return item; } if (realIndex < (uint)offsets.size() && offsets[realIndex].dataOffset) { Q_ASSERT(!data->m_itemRetrievalForbidden); //Construct the context, and eventuall its parent first ///TODO: ugly, remove need for const_cast auto itemData = const_cast( reinterpret_cast(data->pointerInData(offsets[realIndex].dataOffset)) ); auto& item = items[realIndex]; item = dynamic_cast::value>(DUChainItemSystem::self().create(itemData)); if (!item) { //When this happens, the item has not been registered correctly. //We can stop here, because else we will get crashes later. qCritical() << "Failed to load item with identity" << itemData->classId; return {}; } if (isSharedDataItem()) { // NOTE: shared data must never point to mmapped data regions as otherwise we might end up with // use-after-free or double-deletions etc. pp. // thus, make the item always dynamic after deserialization item->makeDynamic(); } auto parent = data->getContextForIndex(offsets[realIndex].parentContext); Q_ASSERT_X(parent, Q_FUNC_INFO, "Could not find parent context for loaded item.\n" "Potentially, the context has been deleted without deleting its children."); item->rebuildDynamicData(parent, index); } else { qCWarning(LANGUAGE) << "invalid item for index" << index << offsets.size() << offsets.value(realIndex).dataOffset; } return item; } template void TopDUContextDynamicData::DUChainItemStorage::deleteOnDisk() { for (auto& item : items) { if (item) { item->makeDynamic(); } } } template void TopDUContextDynamicData::DUChainItemStorage::loadData(QFile* file) const { Q_ASSERT(offsets.isEmpty()); Q_ASSERT(items.isEmpty()); uint readValue; file->read((char*)&readValue, sizeof(uint)); offsets.resize(readValue); file->read((char*)offsets.data(), sizeof(ItemDataInfo) * offsets.size()); //Fill with zeroes for now, will be initialized on-demand items.resize(offsets.size()); } template void TopDUContextDynamicData::DUChainItemStorage::writeData(QFile* file) { uint writeValue = offsets.size(); file->write((char*)&writeValue, sizeof(uint)); file->write((char*)offsets.data(), sizeof(ItemDataInfo) * offsets.size()); } //END DUChainItemStorage const char* TopDUContextDynamicData::pointerInData(uint totalOffset) const { Q_ASSERT(!m_mappedData || m_data.isEmpty()); if(m_mappedData && m_mappedDataSize) return (char*)m_mappedData + totalOffset; return ::pointerInData(m_data, totalOffset); } TopDUContextDynamicData::TopDUContextDynamicData(TopDUContext* topContext) : m_deleting(false) , m_topContext(topContext) , m_contexts(this) , m_declarations(this) , m_problems(this) , m_onDisk(false) , m_dataLoaded(true) , m_mappedFile(nullptr) , m_mappedData(nullptr) , m_mappedDataSize(0) , m_itemRetrievalForbidden(false) { } void KDevelop::TopDUContextDynamicData::clear() { m_contexts.clearItems(); m_declarations.clearItems(); m_problems.clearItems(); } TopDUContextDynamicData::~TopDUContextDynamicData() { unmap(); } void KDevelop::TopDUContextDynamicData::unmap() { delete m_mappedFile; m_mappedFile = nullptr; m_mappedData = nullptr; m_mappedDataSize = 0; } bool TopDUContextDynamicData::fileExists(uint topContextIndex) { return QFile::exists(pathForTopContext(topContextIndex)); } QList TopDUContextDynamicData::loadImporters(uint topContextIndex) { QList ret; loadTopDUContextData(topContextIndex, FullLoad, [&ret] (const TopDUContextData* topData) { ret.reserve(topData->m_importersSize()); FOREACH_FUNCTION(const IndexedDUContext& importer, topData->m_importers) ret << importer; }); return ret; } QList TopDUContextDynamicData::loadImports(uint topContextIndex) { QList ret; loadTopDUContextData(topContextIndex, FullLoad, [&ret] (const TopDUContextData* topData) { ret.reserve(topData->m_importedContextsSize()); FOREACH_FUNCTION(const DUContext::Import& import, topData->m_importedContexts) ret << import.indexedContext(); }); return ret; } IndexedString TopDUContextDynamicData::loadUrl(uint topContextIndex) { IndexedString url; loadTopDUContextData(topContextIndex, PartialLoad, [&url] (const TopDUContextData* topData) { Q_ASSERT(topData->m_url.isEmpty() || topData->m_url.index() >> 16); url = topData->m_url; }); return url; } void TopDUContextDynamicData::loadData() const { //This function has to be protected by an additional mutex, since it can be triggered from multiple threads at the same time static QMutex mutex; QMutexLocker lock(&mutex); if(m_dataLoaded) return; Q_ASSERT(!m_dataLoaded); Q_ASSERT(m_data.isEmpty()); QFile* file = new QFile(pathForTopContext(m_topContext->ownIndex())); bool open = file->open(QIODevice::ReadOnly); Q_UNUSED(open); Q_ASSERT(open); Q_ASSERT(file->size()); //Skip the offsets, we're already read them //Skip top-context data uint readValue; file->read((char*)&readValue, sizeof(uint)); file->seek(readValue + file->pos()); m_contexts.loadData(file); m_declarations.loadData(file); m_problems.loadData(file); #ifdef USE_MMAP m_mappedData = file->map(file->pos(), file->size() - file->pos()); if(m_mappedData) { m_mappedFile = file; m_mappedDataSize = file->size() - file->pos(); file->close(); //Close the file, so there is less open file descriptors(May be problematic) }else{ qCDebug(LANGUAGE) << "Failed to map" << file->fileName(); } #endif if(!m_mappedFile) { QByteArray data = file->readAll(); m_data.append({data, (uint)data.size()}); delete file; } m_dataLoaded = true; } TopDUContext* TopDUContextDynamicData::load(uint topContextIndex) { QFile file(pathForTopContext(topContextIndex)); if(file.open(QIODevice::ReadOnly)) { if(file.size() == 0) { qCWarning(LANGUAGE) << "Top-context file is empty" << file.fileName(); return nullptr; } QVector contextDataOffsets; QVector declarationDataOffsets; uint readValue; file.read((char*)&readValue, sizeof(uint)); //now readValue is filled with the top-context data size QByteArray topContextData = file.read(readValue); DUChainBaseData* topData = reinterpret_cast(topContextData.data()); TopDUContext* ret = dynamic_cast(DUChainItemSystem::self().create(topData)); if(!ret) { qCWarning(LANGUAGE) << "Cannot load a top-context from file" << file.fileName() << "- the required language-support for handling ID" << topData->classId << "is probably not loaded"; return nullptr; } TopDUContextDynamicData& target(*ret->m_dynamicData); target.m_data.clear(); target.m_dataLoaded = false; target.m_onDisk = true; ret->rebuildDynamicData(nullptr, topContextIndex); target.m_topContextData.append({topContextData, (uint)0}); return ret; }else{ return nullptr; } } bool TopDUContextDynamicData::isOnDisk() const { return m_onDisk; } void TopDUContextDynamicData::deleteOnDisk() { if(!isOnDisk()) return; qCDebug(LANGUAGE) << "deleting" << m_topContext->ownIndex() << m_topContext->url().str(); if(!m_dataLoaded) loadData(); m_contexts.deleteOnDisk(); m_declarations.deleteOnDisk(); m_problems.deleteOnDisk(); m_topContext->makeDynamic(); m_onDisk = false; bool successfullyRemoved = QFile::remove(filePath()); Q_UNUSED(successfullyRemoved); Q_ASSERT(successfullyRemoved); qCDebug(LANGUAGE) << "deletion ready"; } QString KDevelop::TopDUContextDynamicData::filePath() const { return pathForTopContext(m_topContext->ownIndex()); } bool TopDUContextDynamicData::hasChanged() const { return !m_onDisk || m_topContext->d_func()->m_dynamic || m_contexts.itemsHaveChanged() || m_declarations.itemsHaveChanged() || m_problems.itemsHaveChanged(); } void TopDUContextDynamicData::store() { // qCDebug(LANGUAGE) << "storing" << m_topContext->url().str() << m_topContext->ownIndex() << "import-count:" << m_topContext->importedParentContexts().size(); //Check if something has changed. If nothing has changed, don't store to disk. bool contentDataChanged = hasChanged(); if (!contentDataChanged) { return; } ///@todo Save the meta-data into a repository, and only the actual content data into a file. /// This will make saving+loading more efficient, and will reduce the disk-usage. /// Then we also won't need to load the data if only the meta-data changed. if(!m_dataLoaded) loadData(); ///If the data is mapped, and we re-write the file, we must make sure that the data is copied out of the map, ///even if only metadata is changed. ///@todo If we split up data and metadata, we don't need to do this if(m_mappedData) contentDataChanged = true; m_topContext->makeDynamic(); m_topContextData.clear(); Q_ASSERT(m_topContext->d_func()->m_ownIndex == m_topContext->ownIndex()); uint topContextDataSize = DUChainItemSystem::self().dynamicSize(*m_topContext->d_func()); m_topContextData.append({QByteArray(DUChainItemSystem::self().dynamicSize(*m_topContext->d_func()), topContextDataSize), 0u}); uint actualTopContextDataSize = 0; if (contentDataChanged) { //We don't need these structures any more, since we have loaded all the declarations/contexts, and m_data //will be reset which these structures pointed into //Load all lazy declarations/contexts const auto oldData = m_data; //Keep the old data alive until everything is stored into a new data structure m_data.clear(); uint newDataSize = 0; foreach(const ArrayWithPosition &array, oldData) newDataSize += array.position; newDataSize = std::max(newDataSize, 10000u); //We always put 1 byte to the front, so we don't have zero data-offsets, since those are used for "invalid". uint currentDataOffset = 1; m_data.append({QByteArray(newDataSize, 0), currentDataOffset}); m_itemRetrievalForbidden = true; m_contexts.storeData(currentDataOffset, oldData); m_declarations.storeData(currentDataOffset, oldData); m_problems.storeData(currentDataOffset, oldData); m_itemRetrievalForbidden = false; } saveDUChainItem(m_topContextData, *m_topContext, actualTopContextDataSize, false); Q_ASSERT(actualTopContextDataSize == topContextDataSize); Q_ASSERT(m_topContextData.size() == 1); Q_ASSERT(!m_topContext->d_func()->isDynamic()); unmap(); QDir().mkpath(basePath()); QFile file(filePath()); if(file.open(QIODevice::WriteOnly)) { file.resize(0); file.write((char*)&topContextDataSize, sizeof(uint)); foreach(const ArrayWithPosition& pos, m_topContextData) file.write(pos.array.constData(), pos.position); m_contexts.writeData(&file); m_declarations.writeData(&file); m_problems.writeData(&file); foreach(const ArrayWithPosition& pos, m_data) file.write(pos.array.constData(), pos.position); m_onDisk = true; if (file.size() == 0) { qCWarning(LANGUAGE) << "Saving zero size top ducontext data"; } file.close(); } else { qCWarning(LANGUAGE) << "Cannot open top-context for writing"; } // qCDebug(LANGUAGE) << "stored" << m_topContext->url().str() << m_topContext->ownIndex() << "import-count:" << m_topContext->importedParentContexts().size(); } TopDUContextDynamicData::ItemDataInfo TopDUContextDynamicData::writeDataInfo(const ItemDataInfo& info, const DUChainBaseData* data, uint& totalDataOffset) { ItemDataInfo ret(info); Q_ASSERT(info.dataOffset); const auto size = DUChainItemSystem::self().dynamicSize(*data); Q_ASSERT(size); if(m_data.back().array.size() - m_data.back().position < size) { //Create a new m_data item m_data.append({QByteArray(std::max(size, 10000u), 0), 0u}); } ret.dataOffset = totalDataOffset; uint pos = m_data.back().position; m_data.back().position += size; totalDataOffset += size; auto target = reinterpret_cast(m_data.back().array.data() + pos); memcpy(target, data, size); verifyDataInfo(ret, m_data); return ret; } uint TopDUContextDynamicData::allocateDeclarationIndex(Declaration* decl, bool temporary) { return m_declarations.allocateItemIndex(decl, temporary); } uint TopDUContextDynamicData::allocateContextIndex(DUContext* context, bool temporary) { return m_contexts.allocateItemIndex(context, temporary); } uint TopDUContextDynamicData::allocateProblemIndex(ProblemPointer problem) { return m_problems.allocateItemIndex(problem, false); } bool TopDUContextDynamicData::isDeclarationForIndexLoaded(uint index) const { return m_declarations.isItemForIndexLoaded(index); } bool TopDUContextDynamicData::isContextForIndexLoaded(uint index) const { return m_contexts.isItemForIndexLoaded(index); } bool TopDUContextDynamicData::isTemporaryContextIndex(uint index) const { return !(index < (0x0fffffff/2)); } bool TopDUContextDynamicData::isTemporaryDeclarationIndex(uint index) const { return !(index < (0x0fffffff/2)); } DUContext* TopDUContextDynamicData::getContextForIndex(uint index) const { if(!m_dataLoaded) loadData(); if (index == 0) { return m_topContext; } return m_contexts.getItemForIndex(index); } Declaration* TopDUContextDynamicData::getDeclarationForIndex(uint index) const { if(!m_dataLoaded) loadData(); return m_declarations.getItemForIndex(index); } ProblemPointer TopDUContextDynamicData::getProblemForIndex(uint index) const { if(!m_dataLoaded) loadData(); return m_problems.getItemForIndex(index); } void TopDUContextDynamicData::clearDeclarationIndex(Declaration* decl) { m_declarations.clearItemIndex(decl, decl->m_indexInTopContext); } void TopDUContextDynamicData::clearContextIndex(DUContext* context) { m_contexts.clearItemIndex(context, context->m_dynamicData->m_indexInTopContext); } void TopDUContextDynamicData::clearProblems() { m_problems.clearItems(); } diff --git a/language/duchain/types/abstracttype.cpp b/language/duchain/types/abstracttype.cpp index b8a4ac13b..f65b1f848 100644 --- a/language/duchain/types/abstracttype.cpp +++ b/language/duchain/types/abstracttype.cpp @@ -1,150 +1,150 @@ /* This file is part of KDevelop Copyright 2006 Roberto Raggi Copyright 2006-2008 Hamish Rodda Copyright 2007-2008 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "abstracttype.h" #include "typesystemdata.h" #include "typeregister.h" #include "typesystem.h" #include "typerepository.h" -#include "util/debug.h" +#include namespace KDevelop { //REGISTER_TYPE(AbstractType); void AbstractType::makeDynamic() { if(d_ptr->m_dynamic) return; AbstractType::Ptr newType(clone()); //While cloning, all the data is cloned as well. So we use that mechanism and steal the cloned data. Q_ASSERT(newType->equals(this)); AbstractTypeData* oldData = d_ptr; d_ptr = newType->d_ptr; newType->d_ptr = oldData; Q_ASSERT(d_ptr->m_dynamic); } AbstractType::AbstractType( AbstractTypeData& dd ) : d_ptr(&dd) { } quint32 AbstractType::modifiers() const { return d_func()->m_modifiers; } void AbstractType::setModifiers(quint32 modifiers) { d_func_dynamic()->m_modifiers = modifiers; } AbstractType::AbstractType() : d_ptr(&createData()) { } AbstractType::~AbstractType() { if(!d_ptr->inRepository) { TypeSystem::self().callDestructor(d_ptr); delete[] (char*)d_ptr; } } void AbstractType::accept(TypeVisitor *v) const { if (v->preVisit (this)) this->accept0 (v); v->postVisit (this); } void AbstractType::acceptType(AbstractType::Ptr type, TypeVisitor *v) { if (! type) return; type->accept (v); } AbstractType::WhichType AbstractType::whichType() const { return TypeAbstract; } void AbstractType::exchangeTypes( TypeExchanger* /*exchanger */) { } IndexedType AbstractType::indexed() const { return IndexedType(AbstractType::Ptr(const_cast(this))); } bool AbstractType::equals(const AbstractType* rhs) const { //qCDebug(LANGUAGE) << this << rhs << modifiers() << rhs->modifiers(); return d_func()->typeClassId == rhs->d_func()->typeClassId && modifiers() == rhs->modifiers(); } uint AbstractType::hash() const { return KDevHash() << d_func()->typeClassId << d_func()->m_modifiers; } QString AbstractType::toString() const { return toString(false); } QString AbstractType::toString(bool spaceOnLeft) const { // TODO complete if(!spaceOnLeft) { if(modifiers() & ConstModifier) { if(modifiers() & VolatileModifier) { return QStringLiteral("const volatile "); }else{ return QStringLiteral("const "); } }else{ if(modifiers() & VolatileModifier) return QStringLiteral("volatile "); else return QString(); } }else{ if(modifiers() & ConstModifier) { if(modifiers() & VolatileModifier) { return QStringLiteral(" const volatile"); }else{ return QStringLiteral(" const"); } }else{ if(modifiers() & VolatileModifier) return QStringLiteral(" volatile"); else return QString(); } } } } diff --git a/language/duchain/types/constantintegraltype.cpp b/language/duchain/types/constantintegraltype.cpp index 2611a69df..c651d3b37 100644 --- a/language/duchain/types/constantintegraltype.cpp +++ b/language/duchain/types/constantintegraltype.cpp @@ -1,188 +1,186 @@ /* This file is part of KDevelop Copyright 2002-2005 Roberto Raggi Copyright 2006 Adam Treat Copyright 2006-2008 Hamish Rodda Copyright 2007-2008 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "constantintegraltype.h" -#include - -#include "util/debug.h" +#include #include "typesystemdata.h" #include "typeregister.h" namespace KDevelop { REGISTER_TYPE(ConstantIntegralType); ConstantIntegralType::ConstantIntegralType(const ConstantIntegralType& rhs) : IntegralType(copyData(*rhs.d_func())) { } ConstantIntegralType::ConstantIntegralType(ConstantIntegralTypeData& data) : IntegralType(data) { } ConstantIntegralType::ConstantIntegralType(uint type) : IntegralType(createData()) { setDataType(type); setModifiers(ConstModifier); } qint64 ConstantIntegralType::plainValue() const { return d_func()->m_value; } AbstractType* ConstantIntegralType::clone() const { return new ConstantIntegralType(*this); } bool ConstantIntegralType::equals(const AbstractType* _rhs) const { if( this == _rhs ) return true; if (!IntegralType::equals(_rhs)) return false; Q_ASSERT(fastCast(_rhs)); const ConstantIntegralType* rhs = static_cast(_rhs); return d_func()->m_value == rhs->d_func()->m_value; } QString ConstantIntegralType::toString() const { QString ret; switch(dataType()) { case TypeNone: ret += QStringLiteral("none"); break; case TypeChar: ret += QStringLiteral("char"); break; case TypeWchar_t: ret += QStringLiteral("wchar_t"); break; case TypeChar16_t: ret += QStringLiteral("char16_t"); break; case TypeChar32_t: ret += QStringLiteral("char32_t"); break; case TypeBoolean: ret += d_func()->m_value ? QStringLiteral("true") : QStringLiteral("false"); break; case TypeInt: ret += (modifiers() & UnsignedModifier) ? QStringLiteral("unsigned") : QStringLiteral("int"); break; case TypeFloat: ret += QStringLiteral("float"); break; case TypeDouble: ret += QStringLiteral("double"); break; case TypeVoid: ret += QStringLiteral("void"); break; default: ret += QStringLiteral(""); break; } return ret; } QString ConstantIntegralType::valueAsString() const { switch(dataType()) { case TypeNone: return QStringLiteral("none"); case TypeChar: return QString::number((char)d_func()->m_value); case TypeWchar_t: return QString::number((wchar_t)d_func()->m_value); case TypeChar16_t: return QString::number((char16_t)d_func()->m_value); case TypeChar32_t: return QString::number((char32_t)d_func()->m_value); case TypeBoolean: return d_func()->m_value ? QStringLiteral("true") : QStringLiteral("false"); case TypeInt: return (modifiers() & UnsignedModifier) ? QString::number((uint)d_func()->m_value) : QString::number((int)d_func()->m_value); case TypeFloat: return QString::number(value()); case TypeDouble: return QString::number(value()); default: return QString(); } } uint ConstantIntegralType::hash() const { return KDevHash(IntegralType::hash()) << d_func()->m_value; } template<> KDEVPLATFORMLANGUAGE_EXPORT void ConstantIntegralType::setValueInternal(qint64 value) { if((modifiers() & UnsignedModifier)) { qCWarning(LANGUAGE) << "setValue(signed) called on unsigned type"; } d_func_dynamic()->m_value = value; } template<> KDEVPLATFORMLANGUAGE_EXPORT void ConstantIntegralType::setValueInternal(quint64 value) { if(!(modifiers() & UnsignedModifier)) { qCWarning(LANGUAGE) << "setValue(unsigned) called on not unsigned type"; } d_func_dynamic()->m_value = (qint64)value; } template<> KDEVPLATFORMLANGUAGE_EXPORT void ConstantIntegralType::setValueInternal(float value) { if(dataType() != TypeFloat) { qCWarning(LANGUAGE) << "setValue(float) called on non-float type"; } memcpy(&d_func_dynamic()->m_value, &value, sizeof(float)); } template<> KDEVPLATFORMLANGUAGE_EXPORT void ConstantIntegralType::setValueInternal(double value) { if(dataType() != TypeDouble) { qCWarning(LANGUAGE) << "setValue(double) called on non-double type"; } memcpy(&d_func_dynamic()->m_value, &value, sizeof(double)); } } diff --git a/language/duchain/types/identifiedtype.cpp b/language/duchain/types/identifiedtype.cpp index 328106674..cfa368de5 100644 --- a/language/duchain/types/identifiedtype.cpp +++ b/language/duchain/types/identifiedtype.cpp @@ -1,96 +1,96 @@ /* This file is part of KDevelop Copyright 2002-2005 Roberto Raggi Copyright 2006 Adam Treat Copyright 2006 Hamish Rodda Copyright 2007-2008 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "identifiedtype.h" #include "../declaration.h" #include "../duchainpointer.h" #include "../declarationid.h" -#include "util/debug.h" +#include namespace KDevelop { IdentifiedType::~IdentifiedType() { } void IdentifiedType::clear() { idData()->m_id = DeclarationId(); } bool IdentifiedType::equals(const IdentifiedType* rhs) const { bool ret = false; if( idData()->m_id == rhs->idData()->m_id ) ret = true; //qCDebug(LANGUAGE) << this << rhs << true; return ret; } // QualifiedIdentifier IdentifiedType::identifier() const // { // return idData()->m_id ? idData()->m_iidData()->qualifiedIdentifier() : QualifiedIdentifier(); // } QualifiedIdentifier IdentifiedType::qualifiedIdentifier() const { return idData()->m_id.qualifiedIdentifier(); } uint IdentifiedType::hash() const { return idData()->m_id.hash(); } DeclarationId IdentifiedType::declarationId() const { return idData()->m_id; } void IdentifiedType::setDeclarationId(const DeclarationId& id) { idData()->m_id = id; } Declaration* IdentifiedType::declaration(const TopDUContext* top) const { return idData()->m_id.getDeclaration(top); } KDevelop::DUContext* IdentifiedType::internalContext(const KDevelop::TopDUContext* top) const { Declaration* decl = declaration(top); if(decl) return decl->internalContext(); else return nullptr; } void IdentifiedType::setDeclaration(Declaration* declaration) { if(declaration) idData()->m_id = declaration->id(); else idData()->m_id = DeclarationId(); } // QString IdentifiedType::idMangled() const // { // return identifier().mangled(); // } } diff --git a/language/duchain/types/typerepository.cpp b/language/duchain/types/typerepository.cpp index 6d38a6b31..14361f713 100644 --- a/language/duchain/types/typerepository.cpp +++ b/language/duchain/types/typerepository.cpp @@ -1,155 +1,155 @@ /* Copyright 2008 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "typerepository.h" #include #include -#include "util/debug.h" +#include #include "../types/typesystemdata.h" #include "../types/typeregister.h" #include #include #define DEBUG_TYPE_REPOSITORY #define ASSERT_ON_PROBLEM namespace KDevelop { class AbstractTypeDataRequest { public: AbstractTypeDataRequest(const AbstractType& type) : m_item(type) { } enum { AverageSize = sizeof(AbstractTypeData) + 12 }; unsigned int hash() const { return m_item.hash(); } uint itemSize() const { return TypeSystem::self().dynamicSize(*m_item.d_ptr); } void createItem(AbstractTypeData* item) const { TypeSystem::self().copy(*m_item.d_ptr, *item, true); Q_ASSERT(!item->m_dynamic); #ifdef DEBUG_TYPE_REPOSITORY AbstractType::Ptr otherType( TypeSystem::self().create(const_cast(item)) ); if(!otherType->equals(&m_item)) { //For debugging, so one can trace what happened qCWarning(LANGUAGE) << "created type in repository does not equal source type:" << m_item.toString() << otherType->toString(); TypeSystem::self().copy(*m_item.d_ptr, *item, true); otherType->equals(&m_item); } #ifdef ASSERT_ON_PROBLEM Q_ASSERT(otherType->equals(&m_item)); #endif #endif item->inRepository = true; } static void destroy(AbstractTypeData* item, KDevelop::AbstractItemRepository&) { TypeSystem::self().callDestructor(item); } static bool persistent(const AbstractTypeData* item) { // Don't try to delete release items for which the factory is not loaded, as that will lead to a crash/assertion later return (bool)item->refCount || !TypeSystem::self().isFactoryLoaded(*item); } bool equals(const AbstractTypeData* item) const { AbstractType::Ptr otherType( TypeSystem::self().create(const_cast(item)) ); if(!otherType) return false; return m_item.equals(otherType.data()); } const AbstractType& m_item; }; //The object is created in a function, to prevent initialization-order issues static RepositoryManager< ItemRepository, false>& typeRepository() { static RepositoryManager< ItemRepository, false> repository(QStringLiteral("Type Repository")); return repository; } void initTypeRepository() { typeRepository(); } AbstractRepositoryManager* typeRepositoryManager() { return &typeRepository(); } uint TypeRepository::indexForType(const AbstractType::Ptr input) { if(!input) return 0; uint i = typeRepository()->index(AbstractTypeDataRequest(*input)); #ifdef DEBUG_TYPE_REPOSITORY AbstractType::Ptr t = typeForIndex(i); if(!t->equals(input.data())) { qCWarning(LANGUAGE) << "found type in repository does not equal source type:" << input->toString() << t->toString(); t->equals(input.data()); } #ifdef ASSERT_ON_PROBLEM Q_ASSERT(t->equals(input.data())); Q_ASSERT(input->equals(t.data())); #endif #endif return i; } AbstractType::Ptr TypeRepository::typeForIndex(uint index) { if(index == 0) return AbstractType::Ptr(); return AbstractType::Ptr( TypeSystem::self().create(const_cast(typeRepository()->itemFromIndex(index))) ); } void TypeRepository::increaseReferenceCount(uint index, ReferenceCountManager* manager) { if(!index) return; QMutexLocker lock(typeRepository()->mutex()); AbstractTypeData* data = typeRepository()->dynamicItemFromIndexSimple(index); Q_ASSERT(data); if(manager) manager->increase(data->refCount, index); else ++data->refCount; } void TypeRepository::decreaseReferenceCount(uint index, ReferenceCountManager* manager) { if(!index) return; QMutexLocker lock(typeRepository()->mutex()); AbstractTypeData* data = typeRepository()->dynamicItemFromIndexSimple(index); Q_ASSERT(data); Q_ASSERT(data->refCount > 0); if(manager) manager->decrease(data->refCount, index); else --data->refCount; } } diff --git a/language/editor/modificationrevisionset.cpp b/language/editor/modificationrevisionset.cpp index c45c45657..851b97c75 100644 --- a/language/editor/modificationrevisionset.cpp +++ b/language/editor/modificationrevisionset.cpp @@ -1,329 +1,329 @@ /* Copyright 2008 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "modificationrevisionset.h" -#include "util/debug.h" +#include #include #include #include //When uncommented, the reason for needed updates is printed // #define DEBUG_NEEDSUPDATE namespace KDevelop { QMutex modificationRevisionSetMutex(QMutex::Recursive); struct FileModificationPair { KDevelop::IndexedString file; KDevelop::ModificationRevision revision; FileModificationPair() { } FileModificationPair(KDevelop::IndexedString _file, KDevelop::ModificationRevision _revision) : file(_file), revision(_revision) { } unsigned int hash() const { return ((file.hash() + revision.modificationTime) * 17 + revision.revision) * 73; } unsigned short int itemSize() const { return sizeof(FileModificationPair); } bool operator==(const FileModificationPair& rhs) const { return file == rhs.file && revision == rhs.revision; } }; struct FileModificationPairRequest { FileModificationPairRequest(const FileModificationPair& data) : m_data(data) { } const FileModificationPair& m_data; enum { AverageSize = sizeof(FileModificationPair) }; unsigned int hash() const { return m_data.hash(); } uint itemSize() const { return m_data.itemSize(); } void createItem(FileModificationPair* item) const { new (item) FileModificationPair(m_data); } bool equals(const FileModificationPair* item) const { return *item == m_data; } static void destroy(FileModificationPair* item, KDevelop::AbstractItemRepository&) { item->~FileModificationPair(); } static bool persistent(const FileModificationPair* /*item*/) { return true; //Reference-counting is done implicitly using the set-repository } }; typedef KDevelop::ItemRepository FileModificationPairRepository; static FileModificationPairRepository& fileModificationPairRepository() { static FileModificationPairRepository rep(QStringLiteral("file modification repository")); rep.setMutex(&modificationRevisionSetMutex); return rep; } void initModificationRevisionSetRepository() { fileModificationPairRepository(); } QHash > needsUpdateCache; void ModificationRevisionSet::clearCache() { QMutexLocker lock(&modificationRevisionSetMutex); ///@todo More intelligent clearing. We actually need to watch the directory for changes, and if there are changes, clear the cache. needsUpdateCache.clear(); } struct FileModificationSetRepository : public Utils::BasicSetRepository { FileModificationSetRepository() : Utils::BasicSetRepository(QStringLiteral("file modification sets"), &globalItemRepositoryRegistry(), true) { } void itemRemovedFromSets(uint index) override; }; //FileModificationSetRepository fileModificationSetRepository; struct FileModificationSetRepositoryRepresenter { static FileModificationSetRepository& repository() { static FileModificationSetRepository fileModificationSetRepository; return fileModificationSetRepository; } }; ModificationRevisionSet::ModificationRevisionSet(unsigned int index) : m_index(index) { } uint ModificationRevisionSet::size() const { Utils::Set set = Utils::Set(m_index, &FileModificationSetRepositoryRepresenter::repository()); return set.count(); } void ModificationRevisionSet::clear() { QMutexLocker lock(&modificationRevisionSetMutex); if(m_index) { Utils::Set oldModificationTimes = Utils::Set(m_index, &FileModificationSetRepositoryRepresenter::repository()); oldModificationTimes.staticUnref(); m_index = 0; } } void ModificationRevisionSet::addModificationRevision(const IndexedString& url, const KDevelop::ModificationRevision& revision) { QMutexLocker lock(&modificationRevisionSetMutex); if(m_index == 0) { Utils::Set set = FileModificationSetRepositoryRepresenter::repository().createSet(fileModificationPairRepository().index(FileModificationPair(url, revision))); set.staticRef(); m_index = set.setIndex(); }else{ Utils::Set oldModificationTimes = Utils::Set(m_index, &FileModificationSetRepositoryRepresenter::repository()); Utils::Set newModificationTimes = oldModificationTimes; Utils::Set tempSet = FileModificationSetRepositoryRepresenter::repository().createSet(fileModificationPairRepository().index(FileModificationPair(url, revision))); tempSet.staticRef(); newModificationTimes += tempSet; newModificationTimes.staticRef(); oldModificationTimes.staticUnref(); tempSet.staticUnref(); m_index = newModificationTimes.setIndex(); } } bool ModificationRevisionSet::removeModificationRevision(const IndexedString& url, const KDevelop::ModificationRevision& revision) { QMutexLocker lock(&modificationRevisionSetMutex); if(!m_index) return false; Utils::Set oldModificationTimes = Utils::Set(m_index, &FileModificationSetRepositoryRepresenter::repository()); Utils::Set newModificationTimes = oldModificationTimes; Utils::Set tempSet = FileModificationSetRepositoryRepresenter::repository().createSet(fileModificationPairRepository().index(FileModificationPair(url, revision))); tempSet.staticRef(); newModificationTimes -= tempSet; newModificationTimes.staticRef(); oldModificationTimes.staticUnref(); tempSet.staticUnref(); m_index = newModificationTimes.setIndex(); return m_index != oldModificationTimes.setIndex(); } // const QMap ModificationRevisionSet::allModificationTimes() const { // QMap ret; // Utils::Set::Iterator it = m_allModificationTimes.iterator(); // while(it) { // const FileModificationPair* data = fileModificationPairRepository().itemFromIndex(*it); // ret[data->file] = data->revision; // ++it; // } // return ret; // } typedef Utils::VirtualSetNode, FileModificationSetRepositoryRepresenter> ModificationRevisionSetNode; // static bool (const Utils::SetNodeData* node) { // ModificationRevisionSetNode // if(!node) // return false; // } static bool nodeNeedsUpdate(uint index) { QMutexLocker lock(&modificationRevisionSetMutex); if(!index) return false; const auto currentTime = QDateTime::currentDateTime(); auto cached = needsUpdateCache.constFind(index); if(cached != needsUpdateCache.constEnd()) { if((*cached).first.secsTo(currentTime) < cacheModificationTimesForSeconds ) { return cached->second; } } bool result = false; const Utils::SetNodeData* nodeData = FileModificationSetRepositoryRepresenter::repository().nodeFromIndex(index); if(nodeData->contiguous()) { //Do the actual checking for(unsigned int a = nodeData->start(); a < nodeData->end(); ++a) { const FileModificationPair* data = fileModificationPairRepository().itemFromIndex(a); ModificationRevision revision = KDevelop::ModificationRevision::revisionForFile( data->file ); if( revision != data->revision ) { result = true; break; } } }else{ result = nodeNeedsUpdate(nodeData->leftNode()) || nodeNeedsUpdate(nodeData->rightNode()); } needsUpdateCache.insert(index, std::make_pair(currentTime, result)); return result; } QString ModificationRevisionSet::toString() const { QMutexLocker lock(&modificationRevisionSetMutex); QString ret = QStringLiteral("["); // krazy:exclude=doublequote_chars Utils::Set set(m_index, &FileModificationSetRepositoryRepresenter::repository()); Utils::Set::Iterator it = set.iterator(); bool first = true; while(it) { if(!first) ret += QLatin1String(", "); first = false; const FileModificationPair* data = fileModificationPairRepository().itemFromIndex(*it); ret += data->file.str() + ':' + data->revision.toString(); ++it; } ret += ']'; return ret; } bool ModificationRevisionSet::needsUpdate() const { QMutexLocker lock(&modificationRevisionSetMutex); #ifdef DEBUG_NEEDSUPDATE Utils::Set set(m_index, &FileModificationSetRepositoryRepresenter::repository()); Utils::Set::Iterator it = set.iterator(); while(it) { const FileModificationPair* data = fileModificationPairRepository().itemFromIndex(*it); ModificationRevision revision = KDevelop::ModificationRevision::revisionForFile( data->file ); if( revision != data->revision ) { qCDebug(LANGUAGE) << "dependency" << data->file.str() << "has changed, stored stamp:" << data->revision << "new time:" << revision ; return true; } ++it; } return false; #else return nodeNeedsUpdate(m_index); #endif } ModificationRevisionSet& ModificationRevisionSet::operator+=(const ModificationRevisionSet& rhs) { QMutexLocker lock(&modificationRevisionSetMutex); Utils::Set oldModificationTimes = Utils::Set(m_index, &FileModificationSetRepositoryRepresenter::repository()); Utils::Set otherModificationTimes = Utils::Set(rhs.m_index, &FileModificationSetRepositoryRepresenter::repository()); Utils::Set newModificationTimes = oldModificationTimes; newModificationTimes += otherModificationTimes; newModificationTimes.staticRef(); oldModificationTimes.staticUnref(); m_index = newModificationTimes.setIndex(); return *this; } ModificationRevisionSet& ModificationRevisionSet::operator-=(const ModificationRevisionSet& rhs) { QMutexLocker lock(&modificationRevisionSetMutex); Utils::Set oldModificationTimes = Utils::Set(m_index, &FileModificationSetRepositoryRepresenter::repository()); Utils::Set otherModificationTimes = Utils::Set(rhs.m_index, &FileModificationSetRepositoryRepresenter::repository()); Utils::Set newModificationTimes = oldModificationTimes; newModificationTimes -= otherModificationTimes; newModificationTimes.staticRef(); oldModificationTimes.staticUnref(); m_index = newModificationTimes.setIndex(); return *this; } void FileModificationSetRepository::itemRemovedFromSets(uint index) { fileModificationPairRepository().deleteItem(index); needsUpdateCache.remove(index); } } diff --git a/language/highlighting/codehighlighting.cpp b/language/highlighting/codehighlighting.cpp index c47a0cf06..847dbeda9 100644 --- a/language/highlighting/codehighlighting.cpp +++ b/language/highlighting/codehighlighting.cpp @@ -1,642 +1,642 @@ /* * This file is part of KDevelop * * Copyright 2007-2010 David Nolden * Copyright 2006 Hamish Rodda * Copyright 2009 Milian Wolff * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "codehighlighting.h" #include #include "../../interfaces/icore.h" #include "../../interfaces/ilanguagecontroller.h" #include "../../interfaces/icompletionsettings.h" #include "../../util/foregroundlock.h" -#include "util/debug.h" +#include #include "../duchain/declaration.h" #include "../duchain/types/functiontype.h" #include "../duchain/types/enumeratortype.h" #include "../duchain/types/typealiastype.h" #include "../duchain/types/enumerationtype.h" #include "../duchain/types/structuretype.h" #include "../duchain/functiondefinition.h" #include "../duchain/use.h" #include "colorcache.h" #include "configurablecolors.h" #include #include #include #include using namespace KTextEditor; static const float highlightingZDepth = -500; #define ifDebug(x) namespace KDevelop { ///@todo Don't highlighting everything, only what is visible on-demand CodeHighlighting::CodeHighlighting( QObject * parent ) : QObject(parent), m_localColorization(true), m_globalColorization(true), m_dataMutex(QMutex::Recursive) { qRegisterMetaType("KDevelop::IndexedString"); adaptToColorChanges(); connect(ColorCache::self(), &ColorCache::colorsGotChanged, this, &CodeHighlighting::adaptToColorChanges); } CodeHighlighting::~CodeHighlighting( ) { qDeleteAll(m_highlights); } void CodeHighlighting::adaptToColorChanges() { QMutexLocker lock(&m_dataMutex); // disable local highlighting if the ratio is set to 0 m_localColorization = ICore::self()->languageController()->completionSettings()->localColorizationLevel() > 0; // disable global highlighting if the ratio is set to 0 m_globalColorization = ICore::self()->languageController()->completionSettings()->globalColorizationLevel() > 0; m_declarationAttributes.clear(); m_definitionAttributes.clear(); m_depthAttributes.clear(); m_referenceAttributes.clear(); } KTextEditor::Attribute::Ptr CodeHighlighting::attributeForType( Types type, Contexts context, const QColor &color ) const { QMutexLocker lock(&m_dataMutex); KTextEditor::Attribute::Ptr a; switch (context) { case DefinitionContext: a = m_definitionAttributes[type]; break; case DeclarationContext: a = m_declarationAttributes[type]; break; case ReferenceContext: a = m_referenceAttributes[type]; break; } if ( !a || color.isValid() ) { a = KTextEditor::Attribute::Ptr(new KTextEditor::Attribute(*ColorCache::self()->defaultColors()->getAttribute(type))); if ( context == DefinitionContext || context == DeclarationContext ) { if (ICore::self()->languageController()->completionSettings()->boldDeclarations()) { a->setFontBold(); } } if( color.isValid() ) { a->setForeground(color); // a->setBackground(QColor(mix(0xffffff-color, backgroundColor(), 255-backgroundTinting))); } else { switch (context) { case DefinitionContext: m_definitionAttributes.insert(type, a); break; case DeclarationContext: m_declarationAttributes.insert(type, a); break; case ReferenceContext: m_referenceAttributes.insert(type, a); break; } } } return a; } ColorMap emptyColorMap() { ColorMap ret(ColorCache::self()->validColorCount()+1, nullptr); return ret; } CodeHighlightingInstance* CodeHighlighting::createInstance() const { return new CodeHighlightingInstance(this); } bool CodeHighlighting::hasHighlighting(IndexedString url) const { DocumentChangeTracker* tracker = ICore::self()->languageController()->backgroundParser()->trackerForUrl(url); if(tracker) { QMutexLocker lock(&m_dataMutex); return m_highlights.contains(tracker) && !m_highlights[tracker]->m_highlightedRanges.isEmpty(); } return false; } void CodeHighlighting::highlightDUChain(ReferencedTopDUContext context) { ENSURE_CHAIN_NOT_LOCKED IndexedString url; { DUChainReadLocker lock; if (!context) return; url = context->url(); } // This prevents the background-parser from updating the top-context while we're working with it UrlParseLock urlLock(context->url()); DUChainReadLocker lock; qint64 revision = context->parsingEnvironmentFile()->modificationRevision().revision; qCDebug(LANGUAGE) << "highlighting du chain" << url.toUrl(); if ( !m_localColorization && !m_globalColorization ) { qCDebug(LANGUAGE) << "highlighting disabled"; QMetaObject::invokeMethod(this, "clearHighlightingForDocument", Qt::QueuedConnection, Q_ARG(KDevelop::IndexedString, url)); return; } CodeHighlightingInstance* instance = createInstance(); lock.unlock(); instance->highlightDUChain(context.data()); DocumentHighlighting* highlighting = new DocumentHighlighting; highlighting->m_document = url; highlighting->m_waitingRevision = revision; highlighting->m_waiting = instance->m_highlight; std::sort(highlighting->m_waiting.begin(), highlighting->m_waiting.end()); QMetaObject::invokeMethod(this, "applyHighlighting", Qt::QueuedConnection, Q_ARG(void*, highlighting)); delete instance; } void CodeHighlightingInstance::highlightDUChain(TopDUContext* context) { m_contextClasses.clear(); m_useClassCache = true; //Highlight highlightDUChain(context, QHash(), emptyColorMap()); m_functionColorsForDeclarations.clear(); m_functionDeclarationsForColors.clear(); m_useClassCache = false; m_contextClasses.clear(); } void CodeHighlightingInstance::highlightDUChain(DUContext* context, QHash colorsForDeclarations, ColorMap declarationsForColors) { DUChainReadLocker lock; TopDUContext* top = context->topContext(); //Merge the colors from the function arguments foreach( const DUContext::Import &imported, context->importedParentContexts() ) { if(!imported.context(top) || (imported.context(top)->type() != DUContext::Other && imported.context(top)->type() != DUContext::Function)) continue; //For now it's enough simply copying them, because we only pass on colors within function bodies. if (m_functionColorsForDeclarations.contains(imported.context(top))) colorsForDeclarations = m_functionColorsForDeclarations[imported.context(top)]; if (m_functionDeclarationsForColors.contains(imported.context(top))) declarationsForColors = m_functionDeclarationsForColors[imported.context(top)]; } QList takeFreeColors; foreach (Declaration* dec, context->localDeclarations()) { if (!useRainbowColor(dec)) { highlightDeclaration(dec, QColor(QColor::Invalid)); continue; } //Initially pick a color using the hash, so the chances are good that the same identifier gets the same color always. uint colorNum = dec->identifier().hash() % ColorCache::self()->primaryColorCount(); if( declarationsForColors[colorNum] ) { takeFreeColors << dec; //Use one of the colors that stays free continue; } colorsForDeclarations[dec] = colorNum; declarationsForColors[colorNum] = dec; highlightDeclaration(dec, ColorCache::self()->generatedColor(colorNum)); } foreach (Declaration* dec, takeFreeColors) { uint colorNum = dec->identifier().hash() % ColorCache::self()->primaryColorCount(); uint oldColorNum = colorNum; while (declarationsForColors[colorNum]) { colorNum = (colorNum + 1) % ColorCache::self()->primaryColorCount(); if (colorNum == oldColorNum) { colorNum = ColorCache::self()->primaryColorCount(); break; } } if (colorNum < ColorCache::self()->primaryColorCount()) { // Use primary color colorsForDeclarations[dec] = colorNum; declarationsForColors[colorNum] = dec; highlightDeclaration(dec, ColorCache::self()->generatedColor(colorNum)); } else { // Try to use supplementary color colorNum = ColorCache::self()->primaryColorCount(); while (declarationsForColors[colorNum]) { colorNum++; if (colorNum == ColorCache::self()->validColorCount()) { //If no color could be found, use default color highlightDeclaration(dec, QColor(QColor::Invalid)); break; } } if (colorNum < ColorCache::self()->validColorCount()) { // Use supplementary color colorsForDeclarations[dec] = colorNum; declarationsForColors[colorNum] = dec; highlightDeclaration(dec, ColorCache::self()->generatedColor(colorNum)); } } } for(int a = 0; a < context->usesCount(); ++a) { Declaration* decl = context->topContext()->usedDeclarationForIndex(context->uses()[a].m_declarationIndex); QColor color(QColor::Invalid); if( colorsForDeclarations.contains(decl) ) color = ColorCache::self()->generatedColor(colorsForDeclarations[decl]); highlightUse(context, a, color); } if(context->type() == DUContext::Other || context->type() == DUContext::Function) { m_functionColorsForDeclarations[IndexedDUContext(context)] = colorsForDeclarations; m_functionDeclarationsForColors[IndexedDUContext(context)] = declarationsForColors; } QVector< DUContext* > children = context->childContexts(); lock.unlock(); // Periodically release the lock, so that the UI won't be blocked too much foreach (DUContext* child, children) highlightDUChain(child, colorsForDeclarations, declarationsForColors ); } KTextEditor::Attribute::Ptr CodeHighlighting::attributeForDepth(int depth) const { while (depth >= m_depthAttributes.count()) { KTextEditor::Attribute::Ptr a(new KTextEditor::Attribute()); a->setBackground(QColor(Qt::white).dark(100 + (m_depthAttributes.count() * 25))); a->setBackgroundFillWhitespace(true); if (depth % 2) a->setOutline(Qt::red); m_depthAttributes.append(a); } return m_depthAttributes[depth]; } KDevelop::Declaration* CodeHighlightingInstance::localClassFromCodeContext(KDevelop::DUContext* context) const { if(!context) return nullptr; if(m_contextClasses.contains(context)) return m_contextClasses[context]; DUContext* startContext = context; while( context->type() == DUContext::Other ) { //Move context to the top context of type "Other". This is needed because every compound-statement creates a new sub-context. auto parent = context->parentContext(); if (!parent || (parent->type() != DUContext::Other && parent->type() != DUContext::Function)) { break; } context = context->parentContext(); } ///Step 1: Find the function-declaration for the function we are in Declaration* functionDeclaration = nullptr; if( FunctionDefinition* def = dynamic_cast(context->owner()) ) { if(m_contextClasses.contains(context)) return m_contextClasses[context]; functionDeclaration = def->declaration(startContext->topContext()); } if( !functionDeclaration && context->owner() ) functionDeclaration = context->owner(); if(!functionDeclaration) { if(m_useClassCache) m_contextClasses[context] = nullptr; return nullptr; } Declaration* decl = functionDeclaration->context()->owner(); if(m_useClassCache) m_contextClasses[context] = decl; return decl; } CodeHighlightingInstance::Types CodeHighlightingInstance::typeForDeclaration(Declaration * dec, DUContext* context) const { /** * We highlight in 3 steps by priority: * 1. Is the item in the local class or an inherited class? If yes, highlight. * 2. What kind of item is it? If it's a type/function/enumerator, highlight by type. * 3. Else, highlight by scope. * * */ // if(ClassMemberDeclaration* classMember = dynamic_cast(dec)) // if(!Cpp::isAccessible(context, classMember)) // return ErrorVariableType; if(!dec) return ErrorVariableType; Types type = LocalVariableType; if(dec->kind() == Declaration::Namespace) return NamespaceType; if(dec->kind() == Declaration::Macro){ return MacroType; } if (context && dec->context() && dec->context()->type() == DUContext::Class) { //It is a use. //Determine the class we're in Declaration* klass = localClassFromCodeContext(context); if(klass) { if (klass->internalContext() == dec->context()) type = LocalClassMemberType; //Using Member of the local class else if (klass->internalContext() && klass->internalContext()->imports(dec->context())) type = InheritedClassMemberType; //Using Member of an inherited class } } if (type == LocalVariableType) { if (dec->kind() == Declaration::Type || dec->type() || dec->type()) { if (dec->isForwardDeclaration()) type = ForwardDeclarationType; else if (dec->type()) type = FunctionType; else if(dec->type()) type = ClassType; else if(dec->type()) type = TypeAliasType; else if(dec->type()) type = EnumType; else if(dec->type()) type = EnumeratorType; } } if (type == LocalVariableType) { switch (dec->context()->type()) { case DUContext::Namespace: type = NamespaceVariableType; break; case DUContext::Class: type = MemberVariableType; break; case DUContext::Function: type = FunctionVariableType; break; case DUContext::Global: type = GlobalVariableType; break; default: break; } } return type; } bool CodeHighlightingInstance::useRainbowColor(Declaration* dec) const { return dec->context()->type() == DUContext::Function || (dec->context()->type() == DUContext::Other && dec->context()->owner()); } void CodeHighlightingInstance::highlightDeclaration(Declaration * declaration, const QColor &color) { HighlightedRange h; h.range = declaration->range(); h.attribute = m_highlighting->attributeForType(typeForDeclaration(declaration, nullptr), DeclarationContext, color); m_highlight.push_back(h); } void CodeHighlightingInstance::highlightUse(DUContext* context, int index, const QColor &color) { Types type = ErrorVariableType; Declaration* decl = context->topContext()->usedDeclarationForIndex(context->uses()[index].m_declarationIndex); type = typeForDeclaration(decl, context); if(type != ErrorVariableType || ICore::self()->languageController()->completionSettings()->highlightSemanticProblems()) { HighlightedRange h; h.range = context->uses()[index].m_range; h.attribute = m_highlighting->attributeForType(type, ReferenceContext, color); m_highlight.push_back(h); } } void CodeHighlightingInstance::highlightUses(DUContext* context) { for(int a = 0; a < context->usesCount(); ++a) highlightUse(context, a, QColor(QColor::Invalid)); } void CodeHighlighting::clearHighlightingForDocument(IndexedString document) { VERIFY_FOREGROUND_LOCKED QMutexLocker lock(&m_dataMutex); DocumentChangeTracker* tracker = ICore::self()->languageController()->backgroundParser()->trackerForUrl(document); if(m_highlights.contains(tracker)) { disconnect(tracker, &DocumentChangeTracker::destroyed, this, &CodeHighlighting::trackerDestroyed); qDeleteAll(m_highlights[tracker]->m_highlightedRanges); delete m_highlights[tracker]; m_highlights.remove(tracker); } } void CodeHighlighting::applyHighlighting(void* _highlighting) { CodeHighlighting::DocumentHighlighting* highlighting = static_cast(_highlighting); VERIFY_FOREGROUND_LOCKED QMutexLocker lock(&m_dataMutex); DocumentChangeTracker* tracker = ICore::self()->languageController()->backgroundParser()->trackerForUrl(highlighting->m_document); if(!tracker) { qCDebug(LANGUAGE) << "no document found for the planned highlighting of" << highlighting->m_document.str(); delete highlighting; return; } if(!tracker->holdingRevision(highlighting->m_waitingRevision)) { qCDebug(LANGUAGE) << "not holding revision" << highlighting->m_waitingRevision << "not applying highlighting;" << "probably a new parse job has already updated the context"; delete highlighting; return; } QVector< MovingRange* > oldHighlightedRanges; if(m_highlights.contains(tracker)) { oldHighlightedRanges = m_highlights[tracker]->m_highlightedRanges; delete m_highlights[tracker]; }else{ // we newly add this tracker, so add the connection // This can't use new style connect syntax since MovingInterface is not a QObject connect(tracker->document(), SIGNAL(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document*)), this, SLOT(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document*))); connect(tracker->document(), SIGNAL(aboutToRemoveText(KTextEditor::Range)), this, SLOT(aboutToRemoveText(KTextEditor::Range))); connect(tracker, &DocumentChangeTracker::destroyed, this, &CodeHighlighting::trackerDestroyed); } m_highlights[tracker] = highlighting; // Now create MovingRanges (match old ones with the incoming ranges) KTextEditor::Range tempRange; QVector::iterator movingIt = oldHighlightedRanges.begin(); QVector::iterator rangeIt = highlighting->m_waiting.begin(); while(rangeIt != highlighting->m_waiting.end()) { // Translate the range into the current revision KTextEditor::Range transformedRange = tracker->transformToCurrentRevision(rangeIt->range, highlighting->m_waitingRevision); while(movingIt != oldHighlightedRanges.end() && ((*movingIt)->start().line() < transformedRange.start().line() || ((*movingIt)->start().line() == transformedRange.start().line() && (*movingIt)->start().column() < transformedRange.start().column()))) { delete *movingIt; // Skip ranges that are in front of the current matched range ++movingIt; } tempRange = transformedRange; if(movingIt == oldHighlightedRanges.end() || transformedRange.start().line() != (*movingIt)->start().line() || transformedRange.start().column() != (*movingIt)->start().column() || transformedRange.end().line() != (*movingIt)->end().line() || transformedRange.end().column() != (*movingIt)->end().column()) { Q_ASSERT(rangeIt->attribute); // The moving range is behind or unequal, create a new range highlighting->m_highlightedRanges.push_back(tracker->documentMovingInterface()->newMovingRange(tempRange)); highlighting->m_highlightedRanges.back()->setAttribute(rangeIt->attribute); highlighting->m_highlightedRanges.back()->setZDepth(highlightingZDepth); } else { // Update the existing moving range (*movingIt)->setAttribute(rangeIt->attribute); (*movingIt)->setRange(tempRange); highlighting->m_highlightedRanges.push_back(*movingIt); ++movingIt; } ++rangeIt; } for(; movingIt != oldHighlightedRanges.end(); ++movingIt) delete *movingIt; // Delete unmatched moving ranges behind } void CodeHighlighting::trackerDestroyed(QObject* object) { // Called when a document is destroyed VERIFY_FOREGROUND_LOCKED QMutexLocker lock(&m_dataMutex); DocumentChangeTracker* tracker = static_cast(object); Q_ASSERT(m_highlights.contains(tracker)); delete m_highlights[tracker]; // No need to care about the individual ranges, as the document is being destroyed m_highlights.remove(tracker); } void CodeHighlighting::aboutToInvalidateMovingInterfaceContent(Document* doc) { clearHighlightingForDocument(IndexedString(doc->url())); } void CodeHighlighting::aboutToRemoveText( const KTextEditor::Range& range ) { if (range.onSingleLine()) // don't try to optimize this return; VERIFY_FOREGROUND_LOCKED QMutexLocker lock(&m_dataMutex); Q_ASSERT(dynamic_cast(sender())); KTextEditor::Document* doc = static_cast(sender()); DocumentChangeTracker* tracker = ICore::self()->languageController()->backgroundParser() ->trackerForUrl(IndexedString(doc->url())); if(m_highlights.contains(tracker)) { QVector& ranges = m_highlights.value(tracker)->m_highlightedRanges; QVector::iterator it = ranges.begin(); while(it != ranges.end()) { if (range.contains((*it)->toRange())) { delete (*it); it = ranges.erase(it); } else { ++it; } } } } } // kate: space-indent on; indent-width 2; replace-trailing-space-save on; show-tabs on; tab-indents on; tab-width 2; diff --git a/language/highlighting/colorcache.cpp b/language/highlighting/colorcache.cpp index 28495d231..bed78f2ce 100644 --- a/language/highlighting/colorcache.cpp +++ b/language/highlighting/colorcache.cpp @@ -1,329 +1,329 @@ /* * This file is part of KDevelop * * Copyright 2009 Milian Wolff * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "colorcache.h" #include "configurablecolors.h" #include "codehighlighting.h" #include #include "../../interfaces/icore.h" #include "../../interfaces/ilanguagecontroller.h" #include "../../interfaces/icompletionsettings.h" #include "../../interfaces/idocument.h" #include "../../interfaces/idocumentcontroller.h" #include "../interfaces/ilanguagesupport.h" #include "../duchain/duchain.h" #include "../duchain/duchainlock.h" -#include "util/debug.h" +#include #include "widgetcolorizer.h" #include #include #include #define ifDebug(x) namespace KDevelop { ColorCache* ColorCache::m_self = nullptr; ColorCache::ColorCache(QObject* parent) : QObject(parent), m_defaultColors(nullptr), m_validColorCount(0), m_colorOffset(0), m_localColorRatio(0), m_globalColorRatio(0), m_boldDeclarations(true) { Q_ASSERT(m_self == nullptr); updateColorsFromScheme(); // default / fallback updateColorsFromSettings(); connect(ICore::self()->languageController()->completionSettings(), &ICompletionSettings::settingsChanged, this, &ColorCache::updateColorsFromSettings, Qt::QueuedConnection); connect(ICore::self()->documentController(), &IDocumentController::documentActivated, this, &ColorCache::slotDocumentActivated); bool hadDoc = tryActiveDocument(); updateInternal(); m_self = this; if (!hadDoc) { // try to update later on again QMetaObject::invokeMethod(this, "tryActiveDocument", Qt::QueuedConnection); } } bool ColorCache::tryActiveDocument() { KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView(); if ( view ) { updateColorsFromView(view); return true; } return false; } ColorCache::~ColorCache() { m_self = nullptr; delete m_defaultColors; m_defaultColors = nullptr; } ColorCache* ColorCache::self() { if (!m_self) { m_self = new ColorCache; } return m_self; } void ColorCache::generateColors() { if ( m_defaultColors ) { delete m_defaultColors; } m_defaultColors = new CodeHighlightingColors(this); // Primary colors taken from: http://colorbrewer2.org/?type=qualitative&scheme=Paired&n=12 const QColor colors[] = { {"#b15928"}, {"#ff7f00"}, {"#b2df8a"}, {"#33a02c"}, {"#a6cee3"}, {"#1f78b4"}, {"#6a3d9a"}, {"#cab2d6"}, {"#e31a1c"}, {"#fb9a99"} }; // Supplementary colors generated by: http://tools.medialab.sciences-po.fr/iwanthue/ const QColor supplementaryColors[] = { {"#D33B67"}, {"#5EC764"}, {"#6CC82D"}, {"#995729"}, {"#FB4D84"}, {"#4B8828"}, {"#D847D0"}, {"#B56AC5"}, {"#E96F0C"}, {"#DC7161"}, {"#4D7279"}, {"#01AAF1"}, {"#D2A237"}, {"#F08CA5"}, {"#C83E93"}, {"#5D7DF7"}, {"#EFBB51"}, {"#108BBB"}, {"#5C84B8"}, {"#02F8BC"}, {"#A5A9F7"}, {"#F28E64"}, {"#A461E6"}, {"#6372D3"} }; m_colors.clear(); for(const auto& color: colors){ m_colors.append(blendLocalColor(color)); } m_primaryColorCount = m_colors.count(); for(const auto& color: supplementaryColors){ m_colors.append(blendLocalColor(color)); } m_validColorCount = m_colors.count(); } void ColorCache::slotDocumentActivated() { KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView(); ifDebug(qCDebug(LANGUAGE) << "doc activated:" << doc;) if ( view ) { updateColorsFromView(view); } } void ColorCache::slotViewSettingsChanged() { KTextEditor::View* view = qobject_cast(sender()); Q_ASSERT(view); ifDebug(qCDebug(LANGUAGE) << "settings changed" << view;) updateColorsFromView(view); } void ColorCache::updateColorsFromView(KTextEditor::View* view) { if ( !view ) { // yeah, the HighlightInterface methods returning an Attribute // require a View... kill me for that mess return; } QColor foreground(QColor::Invalid); QColor background(QColor::Invalid); KTextEditor::Attribute::Ptr style = view->defaultStyleAttribute(KTextEditor::dsNormal); foreground = style->foreground().color(); if (style->hasProperty(QTextFormat::BackgroundBrush)) { background = style->background().color(); } // FIXME: this is in kateview // qCDebug(LANGUAGE) << "got foreground:" << foreground.name() << "old is:" << m_foregroundColor.name(); //NOTE: this slot is defined in KatePart > 4.4, see ApiDocs of the ConfigInterface // the signal is not defined in ConfigInterface, but according to the docs it should be // can't use new signal slot syntax here, since ConfigInterface is not a QObject if ( KTextEditor::View* view = m_view.data() ) { Q_ASSERT(qobject_cast(view)); // we only listen to a single view, i.e. the active one disconnect(view, SIGNAL(configChanged()), this, SLOT(slotViewSettingsChanged())); } Q_ASSERT(qobject_cast(view)); connect(view, SIGNAL(configChanged()), this, SLOT(slotViewSettingsChanged())); m_view = view; if ( !foreground.isValid() ) { // fallback to colorscheme variant ifDebug(qCDebug(LANGUAGE) << "updating from scheme";) updateColorsFromScheme(); } else if ( m_foregroundColor != foreground || m_backgroundColor != background ) { m_foregroundColor = foreground; m_backgroundColor = background; ifDebug(qCDebug(LANGUAGE) << "updating from document";) update(); } } void ColorCache::updateColorsFromScheme() { KColorScheme scheme(QPalette::Normal, KColorScheme::View); QColor foreground = scheme.foreground(KColorScheme::NormalText).color(); QColor background = scheme.background(KColorScheme::NormalBackground).color(); if ( foreground != m_foregroundColor || background != m_backgroundColor ) { m_foregroundColor = foreground; m_backgroundColor = background; update(); } } void ColorCache::updateColorsFromSettings() { int localRatio = ICore::self()->languageController()->completionSettings()->localColorizationLevel(); int globalRatio = ICore::self()->languageController()->completionSettings()->globalColorizationLevel(); bool boldDeclartions = ICore::self()->languageController()->completionSettings()->boldDeclarations(); if ( localRatio != m_localColorRatio || globalRatio != m_globalColorRatio ) { m_localColorRatio = localRatio; m_globalColorRatio = globalRatio; update(); } if (boldDeclartions != m_boldDeclarations) { m_boldDeclarations = boldDeclartions; update(); } } void ColorCache::update() { if ( !m_self ) { ifDebug(qCDebug(LANGUAGE) << "not updating - still initializating";) // don't update on startup, updateInternal is called directly there return; } QMetaObject::invokeMethod(this, "updateInternal", Qt::QueuedConnection); } void ColorCache::updateInternal() { ifDebug(qCDebug(LANGUAGE) << "update internal" << m_self;) generateColors(); if ( !m_self ) { // don't do anything else fancy on startup return; } emit colorsGotChanged(); // rehighlight open documents if (!ICore::self() || ICore::self()->shuttingDown()) { return; } foreach (IDocument* doc, ICore::self()->documentController()->openDocuments()) { foreach (const auto lang, ICore::self()->languageController()->languagesForUrl(doc->url())) { ReferencedTopDUContext top; { DUChainReadLocker lock; top = lang->standardContext(doc->url()); } if(top) { if ( ICodeHighlighting* highlighting = lang->codeHighlighting() ) { highlighting->highlightDUChain(top); } } } } } QColor ColorCache::blend(QColor color, uchar ratio) const { Q_ASSERT(m_backgroundColor.isValid()); Q_ASSERT(m_foregroundColor.isValid()); return WidgetColorizer::blendForeground(color, float(ratio) / float(0xff), m_foregroundColor, m_backgroundColor); } QColor ColorCache::blendBackground(QColor color, uchar ratio) const { return WidgetColorizer::blendBackground(color, float(ratio) / float(0xff), m_foregroundColor, m_backgroundColor); } QColor ColorCache::blendGlobalColor(QColor color) const { return blend(color, m_globalColorRatio); } QColor ColorCache::blendLocalColor(QColor color) const { return blend(color, m_localColorRatio); } CodeHighlightingColors* ColorCache::defaultColors() const { Q_ASSERT(m_defaultColors); return m_defaultColors; } QColor ColorCache::generatedColor(uint num) const { return num > (uint)m_colors.size() ? foregroundColor() : m_colors[num]; } uint ColorCache::validColorCount() const { return m_validColorCount; } uint ColorCache::primaryColorCount() const { return m_primaryColorCount; } QColor ColorCache::foregroundColor() const { return m_foregroundColor; } } // kate: space-indent on; indent-width 2; remove-trailing-spaces all; show-tabs on; tab-indents on; tab-width 2; diff --git a/language/highlighting/configurablecolors.cpp b/language/highlighting/configurablecolors.cpp index e0671069b..f186f7a78 100644 --- a/language/highlighting/configurablecolors.cpp +++ b/language/highlighting/configurablecolors.cpp @@ -1,94 +1,94 @@ /* * This file is part of KDevelop * * Copyright 2007-2008 David Nolden * Copyright 2006 Hamish Rodda * Copyright 2009 Milian Wolff * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "configurablecolors.h" #include "codehighlighting.h" #include "colorcache.h" -#include "util/debug.h" +#include #define ifDebug(x) namespace KDevelop { KTextEditor::Attribute::Ptr ConfigurableHighlightingColors::defaultAttribute() const { return m_defaultAttribute; } void ConfigurableHighlightingColors::setDefaultAttribute(KTextEditor::Attribute::Ptr defaultAttrib) { m_defaultAttribute = defaultAttrib; } KTextEditor::Attribute::Ptr ConfigurableHighlightingColors::getAttribute(int number) const { return m_attributes[number]; } void ConfigurableHighlightingColors::addAttribute(int number, KTextEditor::Attribute::Ptr attribute) { m_attributes[number] = attribute; } ConfigurableHighlightingColors::ConfigurableHighlightingColors() { KTextEditor::Attribute::Ptr a(new KTextEditor::Attribute); setDefaultAttribute(a); } #define ADD_COLOR(type, color_) \ { \ KTextEditor::Attribute::Ptr a(new KTextEditor::Attribute); \ a->setForeground(QColor(cache->blendGlobalColor(color_))); \ addAttribute(CodeHighlighting::type, a); \ ifDebug(qCDebug(LANGUAGE) << #type << "color: " << #color_ << "->" << a->foreground().color().name();) \ } CodeHighlightingColors::CodeHighlightingColors(ColorCache* cache) : ConfigurableHighlightingColors() { // TODO: The set of colors doesn't work very well. Many colors simply too dark (even on the maximum "Global colorization intensity" they hardly distinguishable from grey) and look alike. ADD_COLOR(ClassType, 0x005912) //Dark green ADD_COLOR(TypeAliasType, 0x35938d) ADD_COLOR(EnumType, 0x6c101e) //Dark red ADD_COLOR(EnumeratorType, 0x862a38) //Greyish red ADD_COLOR(FunctionType, 0x21005A) //Navy blue ADD_COLOR(MemberVariableType, 0x443069) //Dark Burple (blue/purple) ADD_COLOR(LocalClassMemberType, 0xae7d00) //Light orange ADD_COLOR(InheritedClassMemberType, 0x705000) //Dark orange ADD_COLOR(LocalVariableType, 0x0C4D3C) ADD_COLOR(FunctionVariableType, 0x300085) //Less dark navy blue ADD_COLOR(NamespaceVariableType, 0x9F3C5F) //Rose ADD_COLOR(GlobalVariableType, 0x12762B) //Grass green ADD_COLOR(NamespaceType, 0x6B2840) //Dark rose ADD_COLOR(ErrorVariableType, 0x8b0019) //Pure red ADD_COLOR(ForwardDeclarationType, 0x5C5C5C) //Gray ADD_COLOR(MacroType, 0xA41239) ADD_COLOR(MacroFunctionLikeType, 0x008080) } } // kate: space-indent on; indent-width 2; remove-trailing-spaces all; show-tabs on; tab-indents on; tab-width 2; diff --git a/language/util/debug.cpp b/language/util/debug.cpp deleted file mode 100644 index 18b803eaa..000000000 --- a/language/util/debug.cpp +++ /dev/null @@ -1,22 +0,0 @@ -/* - * This file is part of KDevelop - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#include "debug.h" - -Q_LOGGING_CATEGORY(LANGUAGE, "kdevplatform.language") diff --git a/language/util/debug.h b/language/util/debug.h deleted file mode 100644 index 6fe68c8f0..000000000 --- a/language/util/debug.h +++ /dev/null @@ -1,26 +0,0 @@ -/* - * This file is part of KDevelop - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#ifndef KDEVPLATFORM_LANGUAGE_DEBUG_H -#define KDEVPLATFORM_LANGUAGE_DEBUG_H - -#include -Q_DECLARE_LOGGING_CATEGORY(LANGUAGE) - -#endif diff --git a/language/util/setrepository.cpp b/language/util/setrepository.cpp index ead7a25b5..37527a020 100644 --- a/language/util/setrepository.cpp +++ b/language/util/setrepository.cpp @@ -1,1122 +1,1122 @@ /*************************************************************************** Copyright 2007 David Nolden ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "setrepository.h" -#include "util/debug.h" +#include #include #include #include #include #include #include #include #include #include //#define DEBUG_SETREPOSITORY #ifdef DEBUG_SETREPOSITORY #define ifDebug(X) X #else #define ifDebug(x) #undef Q_ASSERT #define Q_ASSERT(x) #endif #ifndef DEBUG_SETREPOSITORY #define CHECK_SPLIT_POSITION(Node) #else #define CHECK_SPLIT_POSITION(node) Q_ASSERT(!(node).leftNode || (getLeftNode(&node)->end() <= splitPositionForRange((node).start, (node).end) && getRightNode(&node)->start() >= splitPositionForRange((node).start, (node).end))) #endif namespace Utils { /** * To achieve a maximum re-usage of nodes, we make sure that sub-nodes of a node always split at specific boundaries. * For each range we can compute a position where that range should be split into its child-nodes. * When creating a new node with 2 sub-nodes, we re-create those child-nodes if their boundaries don't represent those split-positions. * * We pick the split-positions deterministically, they are in order of priority: * ((1<<31)*n, n = [0,...] * ((1<<30)*n, n = [0,...] * ((1<<29)*n, n = [0,...] * ((1<<...)*n, n = [0,...] * ... * */ typedef BasicSetRepository::Index Index; ///The returned split position shall be the end of the first sub-range, and the start of the second ///@param splitBit should be initialized with 31, unless you know better. The value can then be used on while computing child split positions. ///In the end, it will contain the bit used to split the range. It will also contain zero if no split-position exists(length 1) uint splitPositionForRange(uint start, uint end, uchar& splitBit) { if(end-start == 1) { splitBit = 0; return 0; } while(true) { uint position = ((end-1) >> splitBit) << splitBit; //Round to the split-position in this interval that is smaller than end if(position > start && position < end) return position; Q_ASSERT(splitBit != 0); --splitBit; } return 0; } uint splitPositionForRange(uint start, uint end) { uchar splitBit = 31; return splitPositionForRange(start, end, splitBit); } class SetNodeDataRequest; #define getLeftNode(node) repository.itemFromIndex(node->leftNode()) #define getRightNode(node) repository.itemFromIndex(node->rightNode()) #define nodeFromIndex(index) repository.itemFromIndex(index) struct SetRepositoryAlgorithms { SetRepositoryAlgorithms(SetDataRepository& _repository, BasicSetRepository* _setRepository) : repository(_repository), setRepository(_setRepository) { } ///Expensive Index count(const SetNodeData* node) const; void localCheck(const SetNodeData* node); void check(uint node); void check(const SetNodeData* node); QString shortLabel(const SetNodeData& node) const; uint set_union(uint firstNode, uint secondNode, const SetNodeData* first, const SetNodeData* second, uchar splitBit = 31); uint createSetFromNodes(uint leftNode, uint rightNode, const SetNodeData* left = nullptr, const SetNodeData* right = nullptr); uint computeSetFromNodes(uint leftNode, uint rightNode, const SetNodeData* left, const SetNodeData* right, uchar splitBit); uint set_intersect(uint firstNode, uint secondNode, const SetNodeData* first, const SetNodeData* second, uchar splitBit = 31); bool set_contains(const SetNodeData* node, Index index); uint set_subtract(uint firstNode, uint secondNode, const SetNodeData* first, const SetNodeData* second, uchar splitBit = 31); //Required both nodes to be split correctly bool set_equals(const SetNodeData* lhs, const SetNodeData* rhs); QString dumpDotGraph(uint node) const; ///Finds or inserts the given ranges into the repository, and returns the set-index that represents them uint setForIndices(std::vector::const_iterator begin, std::vector::const_iterator end, uchar splitBit = 31) { Q_ASSERT(begin != end); uint startIndex = *begin; uint endIndex = *(end-1)+1; if(endIndex == startIndex+1) { SetNodeData data(startIndex, endIndex); return repository.index( SetNodeDataRequest(&data, repository, setRepository) ); } uint split = splitPositionForRange(startIndex, endIndex, splitBit); Q_ASSERT(split); std::vector::const_iterator splitIterator = std::lower_bound(begin, end, split); Q_ASSERT(*splitIterator >= split); Q_ASSERT(splitIterator > begin); Q_ASSERT(*(splitIterator-1) < split); return createSetFromNodes(setForIndices(begin, splitIterator, splitBit), setForIndices(splitIterator, end, splitBit)); } private: QString dumpDotGraphInternal(uint node, bool master=false) const; SetDataRepository& repository; BasicSetRepository* setRepository; }; void SetNodeDataRequest::destroy(SetNodeData* data, KDevelop::AbstractItemRepository& _repository) { SetDataRepository& repository(static_cast(_repository)); if(repository.setRepository->delayedDeletion()) { if(data->leftNode()){ SetDataRepositoryBase::MyDynamicItem left = repository.dynamicItemFromIndex(data->leftNode()); SetDataRepositoryBase::MyDynamicItem right = repository.dynamicItemFromIndex(data->rightNode()); Q_ASSERT(left->m_refCount > 0); --left->m_refCount; Q_ASSERT(right->m_refCount > 0); --right->m_refCount; }else { //Deleting a leaf Q_ASSERT(data->end() - data->start() == 1); repository.setRepository->itemRemovedFromSets(data->start()); } } } SetNodeDataRequest::SetNodeDataRequest(const SetNodeData* _data, SetDataRepository& _repository, BasicSetRepository* _setRepository) : data(*_data), m_hash(_data->hash()), repository(_repository), setRepository(_setRepository), m_created(false) { ifDebug( SetRepositoryAlgorithms alg(repository); alg.check(_data) ); } SetNodeDataRequest::~SetNodeDataRequest() { //Eventually increase the reference-count of direct children if(m_created) { if(data.leftNode()) ++repository.dynamicItemFromIndex(data.leftNode())->m_refCount; if(data.rightNode()) ++repository.dynamicItemFromIndex(data.rightNode())->m_refCount; } } //Should create an item where the information of the requested item is permanently stored. The pointer //@param item equals an allocated range with the size of itemSize(). void SetNodeDataRequest::createItem(SetNodeData* item) const { Q_ASSERT((data.rightNode() && data.leftNode()) || (!data.rightNode() && !data.leftNode())); m_created = true; *item = data; Q_ASSERT((item->rightNode() && item->leftNode()) || (!item->rightNode() && !item->leftNode())); #ifdef DEBUG_SETREPOSITORY //Make sure we split at the correct split position if(item->hasSlaves()) { uint split = splitPositionForRange(data.start, data.end); const SetNodeData* left = repository.itemFromIndex(item->leftNode()); const SetNodeData* right = repository.itemFromIndex(item->rightNode()); Q_ASSERT(split >= left->end() && split <= right->start()); } #endif if(!data.leftNode() && setRepository) { for(uint a = item->start(); a < item->end(); ++a) setRepository->itemAddedToSets(a); } } bool SetNodeDataRequest::equals(const SetNodeData* item) const { Q_ASSERT((item->rightNode() && item->leftNode()) || (!item->rightNode() && !item->leftNode())); //Just compare child nodes, since data must be correctly split, this is perfectly ok //Since this happens in very tight loops, we don't call an additional function here, but just do the check. return item->leftNode() == data.leftNode() && item->rightNode() == data.rightNode() && item->start() == data.start() && item->end() == data.end(); } class BasicSetRepository::Private { public: explicit Private(QString _name) : name(_name) { } ~Private() { } QString name; private: }; Set::Set() : m_tree(0), m_repository(nullptr) { } Set::~Set() { } unsigned int Set::count() const { if(!m_repository || !m_tree) return 0; QMutexLocker lock(m_repository->m_mutex); SetRepositoryAlgorithms alg(m_repository->dataRepository, m_repository); return alg.count(m_repository->dataRepository.itemFromIndex(m_tree)); } Set::Set(uint treeNode, BasicSetRepository* repository) : m_tree(treeNode), m_repository(repository) { } Set::Set(const Set& rhs) { m_repository = rhs.m_repository; m_tree = rhs.m_tree; } Set& Set::operator=(const Set& rhs) { m_repository = rhs.m_repository; m_tree = rhs.m_tree; return *this; } QString Set::dumpDotGraph() const { if(!m_repository || !m_tree) return QString(); QMutexLocker lock(m_repository->m_mutex); SetRepositoryAlgorithms alg(m_repository->dataRepository, m_repository); return alg.dumpDotGraph(m_tree); } Index SetRepositoryAlgorithms::count(const SetNodeData* node) const { if(node->leftNode() && node->rightNode()) return count(getLeftNode(node)) + count(getRightNode(node)); else return node->end() - node->start(); } void SetRepositoryAlgorithms::localCheck(const SetNodeData* ifDebug(node) ) { // Q_ASSERT(node->start() > 0); Q_ASSERT(node->start() < node->end()); Q_ASSERT((node->leftNode() && node->rightNode()) || (!node->leftNode() && !node->rightNode())); Q_ASSERT(!node->leftNode() || (getLeftNode(node())->start() == node->start() && getRightNode(node)->end() == node->end())); Q_ASSERT(!node->leftNode() || (getLeftNode(node())->end() <= getRightNode(node)->start())); } void SetRepositoryAlgorithms::check(uint node) { if(!node) return; check(nodeFromIndex(node)); } void SetRepositoryAlgorithms::check(const SetNodeData* node) { localCheck(node); if(node->leftNode()) check(getLeftNode(node)); if(node->rightNode()) check(getRightNode(node)); // CHECK_SPLIT_POSITION(*node); Re-enable this } QString SetRepositoryAlgorithms::shortLabel(const SetNodeData& node) const { return QStringLiteral("n%1_%2").arg(node.start()).arg(node.end()); } QString SetRepositoryAlgorithms::dumpDotGraphInternal(uint nodeIndex, bool master) const { if(!nodeIndex) return QStringLiteral("empty node"); const SetNodeData& node(*repository.itemFromIndex(nodeIndex)); QString color = QStringLiteral("blue"); if(master) color = QStringLiteral("red"); QString label = QStringLiteral("%1 -> %2").arg(node.start()).arg(node.end()); if(!node.contiguous()) label += QLatin1String(", with gaps"); QString ret = QStringLiteral("%1[label=\"%2\", color=\"%3\"];\n").arg(shortLabel(node), label, color); if(node.leftNode()) { const SetNodeData& left(*repository.itemFromIndex(node.leftNode())); const SetNodeData& right(*repository.itemFromIndex(node.rightNode())); Q_ASSERT(node.rightNode()); ret += QStringLiteral("%1 -> %2;\n").arg(shortLabel(node), shortLabel(left)); ret += QStringLiteral("%1 -> %2;\n").arg(shortLabel(node), shortLabel(right)); ret += dumpDotGraphInternal(node.leftNode()); ret += dumpDotGraphInternal(node.rightNode()); } return ret; } QString SetRepositoryAlgorithms::dumpDotGraph(uint nodeIndex) const { QString ret = QStringLiteral("digraph Repository {\n"); ret += dumpDotGraphInternal(nodeIndex, true); ret += QLatin1String("}\n"); return ret; } const int nodeStackAlloc = 500; class Set::Iterator::IteratorPrivate { public: IteratorPrivate() : nodeStackSize(0), currentIndex(0), repository(nullptr) { nodeStackData.resize(nodeStackAlloc); nodeStack = nodeStackData.data(); } IteratorPrivate(const IteratorPrivate& rhs) : nodeStackData(rhs.nodeStackData), nodeStackSize(rhs.nodeStackSize), currentIndex(rhs.currentIndex), repository(rhs.repository) { nodeStack = nodeStackData.data(); } void resizeNodeStack() { nodeStackData.resize(nodeStackSize + 1); nodeStack = nodeStackData.data(); } KDevVarLengthArray nodeStackData; const SetNodeData** nodeStack; int nodeStackSize; Index currentIndex; BasicSetRepository* repository; /** * Pushes the noed on top of the stack, changes currentIndex, and goes as deep as necessary for iteration. * */ void startAtNode(const SetNodeData* node) { Q_ASSERT(node->start() != node->end()); currentIndex = node->start(); do { nodeStack[nodeStackSize++] = node; if(nodeStackSize >= nodeStackAlloc) resizeNodeStack(); if(node->contiguous()) break; //We need no finer granularity, because the range is contiguous node = Set::Iterator::getDataRepository(repository).itemFromIndex(node->leftNode()); } while(node); Q_ASSERT(currentIndex >= nodeStack[0]->start()); } }; std::set Set::stdSet() const { Set::Iterator it = iterator(); std::set ret; while(it) { Q_ASSERT(ret.find(*it) == ret.end()); ret.insert(*it); ++it; } return ret; } Set::Iterator::Iterator(const Iterator& rhs) : d(new IteratorPrivate(*rhs.d)) { } Set::Iterator& Set::Iterator::operator=(const Iterator& rhs) { delete d; d = new IteratorPrivate(*rhs.d); return *this; } Set::Iterator::Iterator() : d(new IteratorPrivate) { } Set::Iterator::~Iterator() { delete d; } Set::Iterator::operator bool() const { return d->nodeStackSize; } Set::Iterator& Set::Iterator::operator++() { Q_ASSERT(d->nodeStackSize); if(d->repository->m_mutex) d->repository->m_mutex->lock(); ++d->currentIndex; //const SetNodeData** currentNode = &d->nodeStack[d->nodeStackSize - 1]; if(d->currentIndex >= d->nodeStack[d->nodeStackSize - 1]->end()) { //Advance to the next node while(d->nodeStackSize && d->currentIndex >= d->nodeStack[d->nodeStackSize - 1]->end()) { --d->nodeStackSize; } if(!d->nodeStackSize) { //ready }else{ //++d->nodeStackSize; //We were iterating the left slave of the node, now continue with the right. ifDebug( const SetNodeData& left = *d->repository->dataRepository.itemFromIndex(d->nodeStack[d->nodeStackSize - 1]->leftNode()); Q_ASSERT(left.end == d->currentIndex); ) const SetNodeData& right = *d->repository->dataRepository.itemFromIndex(d->nodeStack[d->nodeStackSize - 1]->rightNode()); d->startAtNode(&right); } } Q_ASSERT(d->nodeStackSize == 0 || d->currentIndex < d->nodeStack[0]->end()); if(d->repository->m_mutex) d->repository->m_mutex->unlock(); return *this; } BasicSetRepository::Index Set::Iterator::operator*() const { return d->currentIndex; } Set::Iterator Set::iterator() const { if(!m_tree || !m_repository) return Iterator(); QMutexLocker lock(m_repository->m_mutex); Iterator ret; ret.d->repository = m_repository; if(m_tree) ret.d->startAtNode(m_repository->dataRepository.itemFromIndex(m_tree)); return ret; } //Creates a set item with the given children., they must be valid, and they must be split around their split-position. uint SetRepositoryAlgorithms::createSetFromNodes(uint leftNode, uint rightNode, const SetNodeData* left, const SetNodeData* right) { if(!left) left = nodeFromIndex(leftNode); if(!right) right = nodeFromIndex(rightNode); Q_ASSERT(left->end() <= right->start()); SetNodeData set(left->start(), right->end(), leftNode, rightNode); Q_ASSERT(set.start() < set.end()); uint ret = repository.index(SetNodeDataRequest(&set, repository, setRepository)); Q_ASSERT(set.leftNode() >= 0x10000); Q_ASSERT(set.rightNode() >= 0x10000); Q_ASSERT(ret == repository.findIndex(SetNodeDataRequest(&set, repository, setRepository))); ifDebug( check(ret) ); return ret; } //Constructs a set node from the given two sub-nodes. Those must be valid, they must not intersect, and they must have a correct split-hierarchy. //The do not need to be split around their computed split-position. uint SetRepositoryAlgorithms::computeSetFromNodes(uint leftNode, uint rightNode, const SetNodeData* left, const SetNodeData* right, uchar splitBit) { Q_ASSERT(left->end() <= right->start()); uint splitPosition = splitPositionForRange(left->start(), right->end(), splitBit); Q_ASSERT(splitPosition); if(splitPosition < left->end()) { //The split-position intersects the left node uint leftLeftNode = left->leftNode(); uint leftRightNode = left->rightNode(); const SetNodeData* leftLeft = this->getLeftNode(left); const SetNodeData* leftRight = this->getRightNode(left); Q_ASSERT(splitPosition >= leftLeft->end() && splitPosition <= leftRight->start()); //Create a new set from leftLeft, and from leftRight + right. That set will have the correct split-position. uint newRightNode = computeSetFromNodes(leftRightNode, rightNode, leftRight, right, splitBit); return createSetFromNodes(leftLeftNode, newRightNode, leftLeft); }else if(splitPosition > right->start()) { //The split-position intersects the right node uint rightLeftNode = right->leftNode(); uint rightRightNode = right->rightNode(); const SetNodeData* rightLeft = this->getLeftNode(right); const SetNodeData* rightRight = this->getRightNode(right); Q_ASSERT(splitPosition >= rightLeft->end() && splitPosition <= rightRight->start()); //Create a new set from left + rightLeft, and from rightRight. That set will have the correct split-position. uint newLeftNode = computeSetFromNodes(leftNode, rightLeftNode, left, rightLeft, splitBit); return createSetFromNodes(newLeftNode, rightRightNode, nullptr, rightRight); }else{ return createSetFromNodes(leftNode, rightNode, left, right); } } uint SetRepositoryAlgorithms::set_union(uint firstNode, uint secondNode, const SetNodeData* first, const SetNodeData* second, uchar splitBit) { if(firstNode == secondNode) return firstNode; uint firstStart = first->start(), secondEnd = second->end(); if(firstStart >= secondEnd) return computeSetFromNodes(secondNode, firstNode, second, first, splitBit); uint firstEnd = first->end(), secondStart = second->start(); if(secondStart >= firstEnd) return computeSetFromNodes(firstNode, secondNode, first, second, splitBit); //The ranges of first and second do intersect uint newStart = firstStart < secondStart ? firstStart : secondStart; uint newEnd = firstEnd > secondEnd ? firstEnd : secondEnd; //Compute the split-position for the resulting merged node uint splitPosition = splitPositionForRange(newStart, newEnd, splitBit); //Since the ranges overlap, we can be sure that either first or second contain splitPosition. //The node that contains it, will also be split by it. if(splitPosition > firstStart && splitPosition < firstEnd && splitPosition > secondStart && splitPosition < secondEnd) { //The split-position intersect with both first and second. Continue the union on both sides of the split-position, and merge it. uint firstLeftNode = first->leftNode(); uint firstRightNode = first->rightNode(); uint secondLeftNode = second->leftNode(); uint secondRightNode = second->rightNode(); const SetNodeData* firstLeft = repository.itemFromIndex(firstLeftNode); const SetNodeData* firstRight = repository.itemFromIndex(firstRightNode); const SetNodeData* secondLeft = repository.itemFromIndex(secondLeftNode); const SetNodeData* secondRight = repository.itemFromIndex(secondRightNode); Q_ASSERT(splitPosition >= firstLeft->end() && splitPosition <= firstRight->start()); Q_ASSERT(splitPosition >= secondLeft->end() && splitPosition <= secondRight->start()); return createSetFromNodes( set_union(firstLeftNode, secondLeftNode, firstLeft, secondLeft, splitBit), set_union(firstRightNode, secondRightNode, firstRight, secondRight, splitBit) ); }else if(splitPosition > firstStart && splitPosition < firstEnd) { uint firstLeftNode = first->leftNode(); uint firstRightNode = first->rightNode(); const SetNodeData* firstLeft = repository.itemFromIndex(firstLeftNode); const SetNodeData* firstRight = repository.itemFromIndex(firstRightNode); Q_ASSERT(splitPosition >= firstLeft->end() && splitPosition <= firstRight->start()); //splitPosition does not intersect second. That means that second is completely on one side of it. //So we only need to union that side of first with second. if(secondEnd <= splitPosition) { return createSetFromNodes( set_union(firstLeftNode, secondNode, firstLeft, second, splitBit), firstRightNode, nullptr, firstRight ); }else{ Q_ASSERT(secondStart >= splitPosition); return createSetFromNodes( firstLeftNode, set_union(firstRightNode, secondNode, firstRight, second, splitBit), firstLeft ); } }else if(splitPosition > secondStart && splitPosition < secondEnd) { uint secondLeftNode = second->leftNode(); uint secondRightNode = second->rightNode(); const SetNodeData* secondLeft = repository.itemFromIndex(secondLeftNode); const SetNodeData* secondRight = repository.itemFromIndex(secondRightNode); Q_ASSERT(splitPosition >= secondLeft->end() && splitPosition <= secondRight->start()); if(firstEnd <= splitPosition) { return createSetFromNodes( set_union(secondLeftNode, firstNode, secondLeft, first, splitBit), secondRightNode, nullptr, secondRight ); }else{ Q_ASSERT(firstStart >= splitPosition); return createSetFromNodes( secondLeftNode, set_union(secondRightNode, firstNode, secondRight, first, splitBit), secondLeft ); } }else{ //We would have stopped earlier of first and second don't intersect ifDebug( uint test = repository.findIndex(SetNodeDataRequest(first, repository, setRepository)); qCDebug(LANGUAGE) << "found index:" << test; ) Q_ASSERT(0); return 0; } } bool SetRepositoryAlgorithms::set_equals(const SetNodeData* lhs, const SetNodeData* rhs) { if(lhs->leftNode() != rhs->leftNode() || lhs->rightNode() != rhs->rightNode()) return false; else return true; } uint SetRepositoryAlgorithms::set_intersect(uint firstNode, uint secondNode, const SetNodeData* first, const SetNodeData* second, uchar splitBit) { if(firstNode == secondNode) return firstNode; if(first->start() >= second->end()) return 0; if(second->start() >= first->end()) return 0; //The ranges of first and second do intersect uint firstStart = first->start(), firstEnd = first->end(), secondStart = second->start(), secondEnd = second->end(); uint newStart = firstStart < secondStart ? firstStart : secondStart; uint newEnd = firstEnd > secondEnd ? firstEnd : secondEnd; //Compute the split-position for the resulting merged node uint splitPosition = splitPositionForRange(newStart, newEnd, splitBit); //Since the ranges overlap, we can be sure that either first or second contain splitPosition. //The node that contains it, will also be split by it. if(splitPosition > firstStart && splitPosition < firstEnd && splitPosition > secondStart && splitPosition < secondEnd) { //The split-position intersect with both first and second. Continue the intersection on both sides uint firstLeftNode = first->leftNode(); uint firstRightNode = first->rightNode(); uint secondLeftNode = second->leftNode(); uint secondRightNode = second->rightNode(); const SetNodeData* firstLeft = repository.itemFromIndex(firstLeftNode); const SetNodeData* firstRight = repository.itemFromIndex(firstRightNode); const SetNodeData* secondLeft = repository.itemFromIndex(secondLeftNode); const SetNodeData* secondRight = repository.itemFromIndex(secondRightNode); Q_ASSERT(splitPosition >= firstLeft->end() && splitPosition <= firstRight->start()); Q_ASSERT(splitPosition >= secondLeft->end() && splitPosition <= secondRight->start()); uint newLeftNode = set_intersect(firstLeftNode, secondLeftNode, firstLeft, secondLeft, splitBit); uint newRightNode = set_intersect(firstRightNode, secondRightNode, firstRight, secondRight, splitBit); if(newLeftNode && newRightNode) return createSetFromNodes( newLeftNode, newRightNode ); else if(newLeftNode) return newLeftNode; else return newRightNode; }else if(splitPosition > firstStart && splitPosition < firstEnd) { uint firstLeftNode = first->leftNode(); uint firstRightNode = first->rightNode(); const SetNodeData* firstLeft = repository.itemFromIndex(firstLeftNode); const SetNodeData* firstRight = repository.itemFromIndex(firstRightNode); Q_ASSERT(splitPosition >= firstLeft->end() && splitPosition <= firstRight->start()); //splitPosition does not intersect second. That means that second is completely on one side of it. //So we can completely ignore the other side of first. if(secondEnd <= splitPosition) { return set_intersect(firstLeftNode, secondNode, firstLeft, second, splitBit); }else{ Q_ASSERT(secondStart >= splitPosition); return set_intersect(firstRightNode, secondNode, firstRight, second, splitBit); } }else if(splitPosition > secondStart && splitPosition < secondEnd) { uint secondLeftNode = second->leftNode(); uint secondRightNode = second->rightNode(); const SetNodeData* secondLeft = repository.itemFromIndex(secondLeftNode); const SetNodeData* secondRight = repository.itemFromIndex(secondRightNode); Q_ASSERT(splitPosition >= secondLeft->end() && splitPosition <= secondRight->start()); if(firstEnd <= splitPosition) { return set_intersect(secondLeftNode, firstNode, secondLeft, first, splitBit); }else{ Q_ASSERT(firstStart >= splitPosition); return set_intersect(secondRightNode, firstNode, secondRight, first, splitBit); } }else{ //We would have stopped earlier of first and second don't intersect Q_ASSERT(0); return 0; } Q_ASSERT(0); } bool SetRepositoryAlgorithms::set_contains(const SetNodeData* node, Index index) { while(true) { if(node->start() > index || node->end() <= index) return false; if(node->contiguous()) return true; const SetNodeData* leftNode = nodeFromIndex(node->leftNode()); if(index < leftNode->end()) node = leftNode; else { const SetNodeData* rightNode = nodeFromIndex(node->rightNode()); node = rightNode; } } return false; } uint SetRepositoryAlgorithms::set_subtract(uint firstNode, uint secondNode, const SetNodeData* first, const SetNodeData* second, uchar splitBit) { if(firstNode == secondNode) return 0; if(first->start() >= second->end() || second->start() >= first->end()) return firstNode; //The ranges of first and second do intersect uint firstStart = first->start(), firstEnd = first->end(), secondStart = second->start(), secondEnd = second->end(); uint newStart = firstStart < secondStart ? firstStart : secondStart; uint newEnd = firstEnd > secondEnd ? firstEnd : secondEnd; //Compute the split-position for the resulting merged node uint splitPosition = splitPositionForRange(newStart, newEnd, splitBit); //Since the ranges overlap, we can be sure that either first or second contain splitPosition. //The node that contains it, will also be split by it. if(splitPosition > firstStart && splitPosition < firstEnd && splitPosition > secondStart && splitPosition < secondEnd) { //The split-position intersect with both first and second. Continue the subtract on both sides of the split-position, and merge it. uint firstLeftNode = first->leftNode(); uint firstRightNode = first->rightNode(); uint secondLeftNode = second->leftNode(); uint secondRightNode = second->rightNode(); const SetNodeData* firstLeft = repository.itemFromIndex(firstLeftNode); const SetNodeData* firstRight = repository.itemFromIndex(firstRightNode); const SetNodeData* secondLeft = repository.itemFromIndex(secondLeftNode); const SetNodeData* secondRight = repository.itemFromIndex(secondRightNode); Q_ASSERT(splitPosition >= firstLeft->end() && splitPosition <= firstRight->start()); Q_ASSERT(splitPosition >= secondLeft->end() && splitPosition <= secondRight->start()); uint newLeftNode = set_subtract(firstLeftNode, secondLeftNode, firstLeft, secondLeft, splitBit); uint newRightNode = set_subtract(firstRightNode, secondRightNode, firstRight, secondRight, splitBit); if(newLeftNode && newRightNode) return createSetFromNodes(newLeftNode, newRightNode); else if(newLeftNode) return newLeftNode; else return newRightNode; }else if(splitPosition > firstStart && splitPosition < firstEnd) { // Q_ASSERT(splitPosition >= firstLeft->end() && splitPosition <= firstRight->start()); uint firstLeftNode = first->leftNode(); uint firstRightNode = first->rightNode(); const SetNodeData* firstLeft = repository.itemFromIndex(firstLeftNode); const SetNodeData* firstRight = repository.itemFromIndex(firstRightNode); //splitPosition does not intersect second. That means that second is completely on one side of it. //So we only need to subtract that side of first with second. uint newLeftNode = firstLeftNode, newRightNode = firstRightNode; if(secondEnd <= splitPosition) { newLeftNode = set_subtract(firstLeftNode, secondNode, firstLeft, second, splitBit); }else{ Q_ASSERT(secondStart >= splitPosition); newRightNode = set_subtract(firstRightNode, secondNode, firstRight, second, splitBit); } if(newLeftNode && newRightNode) return createSetFromNodes(newLeftNode, newRightNode); else if(newLeftNode) return newLeftNode; else return newRightNode; }else if(splitPosition > secondStart && splitPosition < secondEnd) { uint secondLeftNode = second->leftNode(); uint secondRightNode = second->rightNode(); const SetNodeData* secondLeft = repository.itemFromIndex(secondLeftNode); const SetNodeData* secondRight = repository.itemFromIndex(secondRightNode); Q_ASSERT(splitPosition >= secondLeft->end() && splitPosition <= secondRight->start()); if(firstEnd <= splitPosition) { return set_subtract(firstNode, secondLeftNode, first, secondLeft, splitBit); }else{ Q_ASSERT(firstStart >= splitPosition); return set_subtract(firstNode, secondRightNode, first, secondRight, splitBit); } }else{ //We would have stopped earlier of first and second don't intersect Q_ASSERT(0); return 0; } Q_ASSERT(0); } Set BasicSetRepository::createSetFromIndices(const std::vector& indices) { QMutexLocker lock(m_mutex); if(indices.empty()) return Set(); SetRepositoryAlgorithms alg(dataRepository, this); return Set(alg.setForIndices(indices.begin(), indices.end()), this); } Set BasicSetRepository::createSet(Index i) { QMutexLocker lock(m_mutex); SetNodeData data(i, i+1); return Set(dataRepository.index( SetNodeDataRequest(&data, dataRepository, this) ), this); } Set BasicSetRepository::createSet(const std::set& indices) { if(indices.empty()) return Set(); QMutexLocker lock(m_mutex); std::vector indicesVector; indicesVector.reserve(indices.size()); for( std::set::const_iterator it = indices.begin(); it != indices.end(); ++it ) indicesVector.push_back(*it); return createSetFromIndices(indicesVector); } BasicSetRepository::BasicSetRepository(QString name, KDevelop::ItemRepositoryRegistry* registry, bool delayedDeletion) : d(new Private(name)), dataRepository(this, name, registry), m_mutex(nullptr), m_delayedDeletion(delayedDeletion) { m_mutex = dataRepository.mutex(); } struct StatisticsVisitor { explicit StatisticsVisitor(const SetDataRepository& _rep) : nodeCount(0), badSplitNodeCount(0), zeroRefCountNodes(0), rep(_rep) { } bool operator() (const SetNodeData* item) { if(item->m_refCount == 0) ++zeroRefCountNodes; ++nodeCount; uint split = splitPositionForRange(item->start(), item->end()); if(item->hasSlaves()) if(split < rep.itemFromIndex(item->leftNode())->end() || split > rep.itemFromIndex(item->rightNode())->start()) ++badSplitNodeCount; return true; } uint nodeCount; uint badSplitNodeCount; uint zeroRefCountNodes; const SetDataRepository& rep; }; void BasicSetRepository::printStatistics() const { StatisticsVisitor stats(dataRepository); dataRepository.visitAllItems(stats); qCDebug(LANGUAGE) << "count of nodes:" << stats.nodeCount << "count of nodes with bad split:" << stats.badSplitNodeCount << "count of nodes with zero reference-count:" << stats.zeroRefCountNodes; } BasicSetRepository::~BasicSetRepository() { delete d; } void BasicSetRepository::itemRemovedFromSets(uint /*index*/) { } void BasicSetRepository::itemAddedToSets(uint /*index*/) { } ////////////Set convenience functions////////////////// bool Set::contains(Index index) const { if(!m_tree || !m_repository) return false; QMutexLocker lock(m_repository->m_mutex); SetRepositoryAlgorithms alg(m_repository->dataRepository, m_repository); return alg.set_contains(m_repository->dataRepository.itemFromIndex(m_tree), index); } Set Set::operator +(const Set& first) const { if(!first.m_tree) return *this; else if(!m_tree || !m_repository) return first; Q_ASSERT(m_repository == first.m_repository); QMutexLocker lock(m_repository->m_mutex); SetRepositoryAlgorithms alg(m_repository->dataRepository, m_repository); uint retNode = alg.set_union(m_tree, first.m_tree, m_repository->dataRepository.itemFromIndex(m_tree), m_repository->dataRepository.itemFromIndex(first.m_tree)); ifDebug(alg.check(retNode)); return Set(retNode, m_repository); } Set& Set::operator +=(const Set& first) { if(!first.m_tree) return *this; else if(!m_tree || !m_repository) { m_tree = first.m_tree; m_repository = first.m_repository; return *this; } QMutexLocker lock(m_repository->m_mutex); SetRepositoryAlgorithms alg(m_repository->dataRepository, m_repository); m_tree = alg.set_union(m_tree, first.m_tree, m_repository->dataRepository.itemFromIndex(m_tree), m_repository->dataRepository.itemFromIndex(first.m_tree)); ifDebug(alg.check(m_tree)); return *this; } Set Set::operator &(const Set& first) const { if(!first.m_tree || !m_tree) return Set(); Q_ASSERT(m_repository); QMutexLocker lock(m_repository->m_mutex); SetRepositoryAlgorithms alg(m_repository->dataRepository, m_repository); Set ret( alg.set_intersect(m_tree, first.m_tree, m_repository->dataRepository.itemFromIndex(m_tree), m_repository->dataRepository.itemFromIndex(first.m_tree)), m_repository ); ifDebug(alg.check(ret.m_tree)); return ret; } Set& Set::operator &=(const Set& first) { if(!first.m_tree || !m_tree) { m_tree = 0; return *this; } Q_ASSERT(m_repository); QMutexLocker lock(m_repository->m_mutex); SetRepositoryAlgorithms alg(m_repository->dataRepository, m_repository); m_tree = alg.set_intersect(m_tree, first.m_tree, m_repository->dataRepository.itemFromIndex(m_tree), m_repository->dataRepository.itemFromIndex(first.m_tree)); ifDebug(alg.check(m_tree)); return *this; } Set Set::operator -(const Set& rhs) const { if(!m_tree || !rhs.m_tree) return *this; Q_ASSERT(m_repository); QMutexLocker lock(m_repository->m_mutex); SetRepositoryAlgorithms alg(m_repository->dataRepository, m_repository); Set ret( alg.set_subtract(m_tree, rhs.m_tree, m_repository->dataRepository.itemFromIndex(m_tree), m_repository->dataRepository.itemFromIndex(rhs.m_tree)), m_repository ); ifDebug( alg.check(ret.m_tree) ); return ret; } Set& Set::operator -=(const Set& rhs) { if(!m_tree || !rhs.m_tree) return *this; Q_ASSERT(m_repository); QMutexLocker lock(m_repository->m_mutex); SetRepositoryAlgorithms alg(m_repository->dataRepository, m_repository); m_tree = alg.set_subtract(m_tree, rhs.m_tree, m_repository->dataRepository.itemFromIndex(m_tree), m_repository->dataRepository.itemFromIndex(rhs.m_tree)); ifDebug(alg.check(m_tree)); return *this; } BasicSetRepository* Set::repository() const { return m_repository; } void Set::staticRef() { if(!m_tree) return; QMutexLocker lock(m_repository->m_mutex); SetNodeData* data = m_repository->dataRepository.dynamicItemFromIndexSimple(m_tree); ++data->m_refCount; } ///Mutex must be locked void Set::unrefNode(uint current) { SetNodeData* data = m_repository->dataRepository.dynamicItemFromIndexSimple(current); Q_ASSERT(data->m_refCount); --data->m_refCount; if(!m_repository->delayedDeletion()) { if(data->m_refCount == 0) { if(data->leftNode()){ Q_ASSERT(data->rightNode()); unrefNode(data->rightNode()); unrefNode(data->leftNode()); }else { //Deleting a leaf Q_ASSERT(data->end() - data->start() == 1); m_repository->itemRemovedFromSets(data->start()); } m_repository->dataRepository.deleteItem(current); } } } ///Decrease the static reference-count of this set by one. This set must have a reference-count > 1. ///If this set reaches the reference-count zero, it will be deleted, and all sub-nodes that also reach the reference-count zero ///will be deleted as well. @warning Either protect ALL your sets by using reference-counting, or don't use it at all. void Set::staticUnref() { if(!m_tree) return; QMutexLocker lock(m_repository->m_mutex); unrefNode(m_tree); } StringSetRepository::StringSetRepository(QString name) : Utils::BasicSetRepository(name) { } void StringSetRepository::itemRemovedFromSets(uint index) { ///Call the IndexedString destructor with enabled reference-counting KDevelop::IndexedString string = KDevelop::IndexedString::fromIndex(index); KDevelop::enableDUChainReferenceCounting(&string, sizeof(KDevelop::IndexedString)); string.~IndexedString(); //Call destructor with enabled reference-counting KDevelop::disableDUChainReferenceCounting(&string); } void StringSetRepository::itemAddedToSets(uint index) { ///Call the IndexedString constructor with enabled reference-counting KDevelop::IndexedString string = KDevelop::IndexedString::fromIndex(index); char data[sizeof(KDevelop::IndexedString)]; KDevelop::enableDUChainReferenceCounting(data, sizeof(KDevelop::IndexedString)); new (data) KDevelop::IndexedString(string); //Call constructor with enabled reference-counting KDevelop::disableDUChainReferenceCounting(data); } } diff --git a/outputview/CMakeLists.txt b/outputview/CMakeLists.txt index aaa9c5b34..1de9dbb80 100644 --- a/outputview/CMakeLists.txt +++ b/outputview/CMakeLists.txt @@ -1,34 +1,39 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kdevplatform\") set(outputviewinterfaces_LIB_SRCS outputdelegate.cpp outputformats.cpp filtereditem.cpp ifilterstrategy.cpp outputmodel.cpp ioutputview.cpp ioutputviewmodel.cpp outputfilteringstrategies.cpp outputjob.cpp outputexecutejob.cpp ) +ecm_qt_declare_logging_category(outputviewinterfaces_LIB_SRCS + HEADER debug.h + IDENTIFIER OUTPUTVIEW + CATEGORY_NAME "kdevplatform.outputview" +) kdevplatform_add_library(KDevPlatformOutputView SOURCES ${outputviewinterfaces_LIB_SRCS}) target_link_libraries(KDevPlatformOutputView PRIVATE Qt5::Core KDev::Interfaces KDev::Util ) install(FILES ioutputview.h filtereditem.h outputmodel.h outputdelegate.h outputfilteringstrategies.h ioutputviewmodel.h ifilterstrategy.h outputjob.h outputexecutejob.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/kdevplatform/outputview COMPONENT Devel) add_subdirectory(tests) diff --git a/outputview/debug.h b/outputview/debug.h deleted file mode 100644 index 2fe2b206b..000000000 --- a/outputview/debug.h +++ /dev/null @@ -1,26 +0,0 @@ -/* - * This file is part of KDevelop - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#ifndef KDEVPLATFORM_OUTPUTVIEW_DEBUG_H -#define KDEVPLATFORM_OUTPUTVIEW_DEBUG_H - -#include -Q_DECLARE_LOGGING_CATEGORY(OUTPUTVIEW) - -#endif diff --git a/outputview/ioutputview.cpp b/outputview/ioutputview.cpp index 97b01956f..77579207f 100644 --- a/outputview/ioutputview.cpp +++ b/outputview/ioutputview.cpp @@ -1,33 +1,31 @@ /* KDevelop Output View * * Copyright 2006-2007 Andreas Pakulat * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * 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 "ioutputview.h" #include "debug.h" -Q_LOGGING_CATEGORY(OUTPUTVIEW, "kdevplatform.outputview") - namespace KDevelop { IOutputView::~IOutputView() {} } diff --git a/plugins/appwizard/CMakeLists.txt b/plugins/appwizard/CMakeLists.txt index 5288db488..c9ebe6fde 100644 --- a/plugins/appwizard/CMakeLists.txt +++ b/plugins/appwizard/CMakeLists.txt @@ -1,32 +1,38 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kdevappwizard\") ########### next target ############### set(kdevappwizard_PART_SRCS appwizardplugin.cpp appwizarddialog.cpp appwizardpagewidget.cpp projectselectionpage.cpp projecttemplatesmodel.cpp projectvcspage.cpp ) set(kdevappwizard_PART_UI projectselectionpage.ui projectvcspage.ui ) +ecm_qt_declare_logging_category(kdevappwizard_PART_SRCS + HEADER debug.h + IDENTIFIER PLUGIN_APPWIZARD + CATEGORY_NAME "kdevplatform.plugins.appwizard" +) + ki18n_wrap_ui(kdevappwizard_PART_SRCS ${kdevappwizard_PART_UI}) qt5_add_resources(kdevappwizard_PART_SRCS kdevappwizard.qrc) kdevplatform_add_plugin(kdevappwizard JSON kdevappwizard.json SOURCES ${kdevappwizard_PART_SRCS}) target_link_libraries(kdevappwizard KF5::KIOWidgets KF5::NewStuff KF5::Archive KDev::Interfaces KDev::Vcs KDev::Language KDev::Util) install(TARGETS kdevappwizard DESTINATION ${KDE_INSTALL_PLUGINDIR}/kdevplatform/${KDEV_PLUGIN_VERSION} ) diff --git a/plugins/appwizard/appwizardplugin.cpp b/plugins/appwizard/appwizardplugin.cpp index e254eb9f0..a86d8cc7f 100644 --- a/plugins/appwizard/appwizardplugin.cpp +++ b/plugins/appwizard/appwizardplugin.cpp @@ -1,563 +1,561 @@ /*************************************************************************** * Copyright 2001 Bernd Gehrmann * * Copyright 2004-2005 Sascha Cunz * * Copyright 2005 Ian Reinhart Geiser * * Copyright 2007 Alexander Dymo * * Copyright 2008 Evgeniy Ivanov * * * * 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 "appwizardplugin.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 #include #include #include #include #include #include #include #include #include #include "appwizarddialog.h" #include "projectselectionpage.h" #include "projectvcspage.h" #include "projecttemplatesmodel.h" #include "debug.h" using namespace KDevelop; -Q_LOGGING_CATEGORY(PLUGIN_APPWIZARD, "kdevplatform.plugins.appwizard") K_PLUGIN_FACTORY_WITH_JSON(AppWizardFactory, "kdevappwizard.json", registerPlugin();) AppWizardPlugin::AppWizardPlugin(QObject *parent, const QVariantList &) : KDevelop::IPlugin(QStringLiteral("kdevappwizard"), parent) , m_templatesModel(nullptr) { setXMLFile(QStringLiteral("kdevappwizard.rc")); m_newFromTemplate = actionCollection()->addAction(QStringLiteral("project_new")); m_newFromTemplate->setIcon(QIcon::fromTheme(QStringLiteral("project-development-new-template"))); m_newFromTemplate->setText(i18n("New From Template...")); connect(m_newFromTemplate, &QAction::triggered, this, &AppWizardPlugin::slotNewProject); m_newFromTemplate->setToolTip( i18n("Generate a new project from a template") ); m_newFromTemplate->setWhatsThis( i18n("This starts KDevelop's application wizard. " "It helps you to generate a skeleton for your " "application from a set of templates.") ); } AppWizardPlugin::~AppWizardPlugin() { } void AppWizardPlugin::slotNewProject() { model()->refresh(); AppWizardDialog dlg(core()->pluginController(), m_templatesModel); if (dlg.exec() == QDialog::Accepted) { QString project = createProject( dlg.appInfo() ); if (!project.isEmpty()) { core()->projectController()->openProject(QUrl::fromLocalFile(project)); KConfig templateConfig(dlg.appInfo().appTemplate); KConfigGroup general(&templateConfig, "General"); const QStringList fileArgs = general.readEntry("ShowFilesAfterGeneration").split(QLatin1Char(','), QString::SkipEmptyParts); for (const auto& fileArg : fileArgs) { QString file = KMacroExpander::expandMacros(fileArg.trimmed(), m_variables); if (QDir::isRelativePath(file)) { file = m_variables[QStringLiteral("PROJECTDIR")] + QLatin1Char('/') + file; } core()->documentController()->openDocument(QUrl::fromUserInput(file)); } } else { KMessageBox::error( ICore::self()->uiController()->activeMainWindow(), i18n("Could not create project from template\n"), i18n("Failed to create project") ); } } } namespace { IDistributedVersionControl* toDVCS(IPlugin* plugin) { Q_ASSERT(plugin); return plugin->extension(); } ICentralizedVersionControl* toCVCS(IPlugin* plugin) { Q_ASSERT(plugin); return plugin->extension(); } /*! Trouble while initializing version control. Show failure message to user. */ void vcsError(const QString &errorMsg, QTemporaryDir &tmpdir, const QUrl &dest, const QString &details = QString()) { QString displayDetails = details; if (displayDetails.isEmpty()) { displayDetails = i18n("Please see the Version Control toolview"); } KMessageBox::detailedError(nullptr, errorMsg, displayDetails, i18n("Version Control System Error")); KIO::del(dest, KIO::HideProgressInfo)->exec(); tmpdir.remove(); } /*! Setup distributed version control for a new project defined by @p info. Use @p scratchArea for temporary files */ bool initializeDVCS(IDistributedVersionControl* dvcs, const ApplicationInfo& info, QTemporaryDir& scratchArea) { Q_ASSERT(dvcs); qCDebug(PLUGIN_APPWIZARD) << "DVCS system is used, just initializing DVCS"; const QUrl& dest = info.location; //TODO: check if we want to handle KDevelop project files (like now) or only SRC dir VcsJob* job = dvcs->init(dest); if (!job || !job->exec() || job->status() != VcsJob::JobSucceeded) { vcsError(i18n("Could not initialize DVCS repository"), scratchArea, dest); return false; } qCDebug(PLUGIN_APPWIZARD) << "Initializing DVCS repository:" << dest; job = dvcs->add({dest}, KDevelop::IBasicVersionControl::Recursive); if (!job || !job->exec() || job->status() != VcsJob::JobSucceeded) { vcsError(i18n("Could not add files to the DVCS repository"), scratchArea, dest); return false; } job = dvcs->commit(info.importCommitMessage, {dest}, KDevelop::IBasicVersionControl::Recursive); if (!job || !job->exec() || job->status() != VcsJob::JobSucceeded) { vcsError(i18n("Could not import project into %1.", dvcs->name()), scratchArea, dest, job ? job->errorString() : QString()); return false; } return true; // We're good } /*! Setup version control for a new project defined by @p info. Use @p scratchArea for temporary files */ bool initializeCVCS(ICentralizedVersionControl* cvcs, const ApplicationInfo& info, QTemporaryDir& scratchArea) { Q_ASSERT(cvcs); qCDebug(PLUGIN_APPWIZARD) << "Importing" << info.sourceLocation << "to" << info.repository.repositoryServer(); VcsJob* job = cvcs->import( info.importCommitMessage, QUrl::fromLocalFile(scratchArea.path()), info.repository); if (!job || !job->exec() || job->status() != VcsJob::JobSucceeded ) { vcsError(i18n("Could not import project"), scratchArea, QUrl::fromUserInput(info.repository.repositoryServer())); return false; } qCDebug(PLUGIN_APPWIZARD) << "Checking out"; job = cvcs->createWorkingCopy( info.repository, info.location, IBasicVersionControl::Recursive); if (!job || !job->exec() || job->status() != VcsJob::JobSucceeded ) { vcsError(i18n("Could not checkout imported project"), scratchArea, QUrl::fromUserInput(info.repository.repositoryServer())); return false; } return true; // initialization phase complete } QString generateIdentifier( const QString& appname ) { QString tmp = appname; QRegExp re("[^a-zA-Z0-9_]"); return tmp.replace(re, QStringLiteral("_")); } } // end anonymous namespace QString AppWizardPlugin::createProject(const ApplicationInfo& info) { QFileInfo templateInfo(info.appTemplate); if (!templateInfo.exists()) { qCWarning(PLUGIN_APPWIZARD) << "Project app template does not exist:" << info.appTemplate; return QString(); } QString templateName = templateInfo.baseName(); QString templateArchive; const QStringList filters = {templateName + QStringLiteral(".*")}; const QStringList matchesPaths = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("kdevappwizard/templates/"), QStandardPaths::LocateDirectory); foreach(const QString& matchesPath, matchesPaths) { const QStringList files = QDir(matchesPath).entryList(filters); if(!files.isEmpty()) { templateArchive = matchesPath + files.first(); } } if(templateArchive.isEmpty()) { qCWarning(PLUGIN_APPWIZARD) << "Template name does not exist in the template list"; return QString(); } QUrl dest = info.location; //prepare variable substitution hash m_variables.clear(); m_variables[QStringLiteral("APPNAME")] = info.name; m_variables[QStringLiteral("APPNAMEUC")] = info.name.toUpper(); m_variables[QStringLiteral("APPNAMELC")] = info.name.toLower(); m_variables[QStringLiteral("APPNAMEID")] = generateIdentifier(info.name); m_variables[QStringLiteral("PROJECTDIR")] = dest.toLocalFile(); // backwards compatibility m_variables[QStringLiteral("dest")] = m_variables[QStringLiteral("PROJECTDIR")]; m_variables[QStringLiteral("PROJECTDIRNAME")] = dest.fileName(); m_variables[QStringLiteral("VERSIONCONTROLPLUGIN")] = info.vcsPluginName; KArchive* arch = nullptr; if( templateArchive.endsWith(QLatin1String(".zip")) ) { arch = new KZip(templateArchive); } else { arch = new KTar(templateArchive, QStringLiteral("application/x-bzip")); } if (arch->open(QIODevice::ReadOnly)) { QTemporaryDir tmpdir; QString unpackDir = tmpdir.path(); //the default value for all Centralized VCS IPlugin* plugin = core()->pluginController()->loadPlugin( info.vcsPluginName ); if( info.vcsPluginName.isEmpty() || ( plugin && plugin->extension() ) ) { if( !QFileInfo::exists( dest.toLocalFile() ) ) { QDir::root().mkpath( dest.toLocalFile() ); } unpackDir = dest.toLocalFile(); //in DVCS we unpack template directly to the project's directory } else { QUrl url = KIO::upUrl(dest); if(!QFileInfo::exists(url.toLocalFile())) { QDir::root().mkpath(url.toLocalFile()); } } // estimate metadata files which should not be copied QStringList metaDataFileNames; // try by same name const KArchiveEntry *templateEntry = arch->directory()->entry(templateName + QLatin1String(".kdevtemplate")); // but could be different name, if e.g. downloaded, so make a guess if (!templateEntry || !templateEntry->isFile()) { for (const auto& entryName : arch->directory()->entries()) { if (entryName.endsWith(QLatin1String(".kdevtemplate"))) { templateEntry = arch->directory()->entry(entryName); break; } } } if (templateEntry && templateEntry->isFile()) { metaDataFileNames << templateEntry->name(); // check if a preview file is to be ignored const KArchiveFile *templateFile = static_cast(templateEntry); QTemporaryDir temporaryDir; templateFile->copyTo(temporaryDir.path()); KConfig config(temporaryDir.path() + QLatin1Char('/') + templateEntry->name()); KConfigGroup group(&config, "General"); if (group.hasKey("Icon")) { const KArchiveEntry* iconEntry = arch->directory()->entry(group.readEntry("Icon")); if (iconEntry && iconEntry->isFile()) { metaDataFileNames << iconEntry->name(); } } } if (!unpackArchive(arch->directory(), unpackDir, metaDataFileNames)) { QString errorMsg = i18n("Could not create new project"); vcsError(errorMsg, tmpdir, QUrl::fromLocalFile(unpackDir)); return QString(); } if( !info.vcsPluginName.isEmpty() ) { if (!plugin) { // Red Alert, serious program corruption. // This should never happen, the vcs dialog presented a list of vcs // systems and now the chosen system doesn't exist anymore?? tmpdir.remove(); return QString(); } IDistributedVersionControl* dvcs = toDVCS(plugin); ICentralizedVersionControl* cvcs = toCVCS(plugin); bool success = false; if (dvcs) { success = initializeDVCS(dvcs, info, tmpdir); } else if (cvcs) { success = initializeCVCS(cvcs, info, tmpdir); } else { if (KMessageBox::Continue == KMessageBox::warningContinueCancel(nullptr, QStringLiteral("Failed to initialize version control system, " "plugin is neither VCS nor DVCS."))) success = true; } if (!success) return QString(); } tmpdir.remove(); }else { qCDebug(PLUGIN_APPWIZARD) << "failed to open template archive"; return QString(); } QString projectFileName = QDir::cleanPath( dest.toLocalFile() + '/' + info.name + ".kdev4" ); // Loop through the new project directory and try to detect the first .kdev4 file. // If one is found this file will be used. So .kdev4 file can be stored in any subdirectory and the // project templates can be more complex. QDirIterator it(QDir::cleanPath( dest.toLocalFile()), QStringList() << QStringLiteral("*.kdev4"), QDir::NoFilter, QDirIterator::Subdirectories); if(it.hasNext() == true) { projectFileName = it.next(); } qCDebug(PLUGIN_APPWIZARD) << "Returning" << projectFileName << QFileInfo::exists( projectFileName ) ; const QFileInfo projectFileInfo(projectFileName); if (!projectFileInfo.exists()) { qCDebug(PLUGIN_APPWIZARD) << "creating .kdev4 file"; KSharedConfigPtr cfg = KSharedConfig::openConfig( projectFileName, KConfig::SimpleConfig ); KConfigGroup project = cfg->group( "Project" ); project.writeEntry( "Name", info.name ); QString manager = QStringLiteral("KDevGenericManager"); QDir d( dest.toLocalFile() ); auto data = ICore::self()->pluginController()->queryExtensionPlugins(QStringLiteral("org.kdevelop.IProjectFileManager")); foreach(const KPluginMetaData& info, data) { QStringList filter = KPluginMetaData::readStringList(info.rawData(), QStringLiteral("X-KDevelop-ProjectFilesFilter")); if (!filter.isEmpty()) { if (!d.entryList(filter).isEmpty()) { manager = info.pluginId(); break; } } } project.writeEntry( "Manager", manager ); project.sync(); cfg->sync(); KConfigGroup project2 = cfg->group( "Project" ); qCDebug(PLUGIN_APPWIZARD) << "kdev4 file contents:" << project2.readEntry("Name", "") << project2.readEntry("Manager", "" ); } // create developer .kde4 file const QString developerProjectFileName = projectFileInfo.canonicalPath() + QLatin1String("/.kdev4/") + projectFileInfo.fileName(); qCDebug(PLUGIN_APPWIZARD) << "creating developer .kdev4 file:" << developerProjectFileName; KSharedConfigPtr developerCfg = KSharedConfig::openConfig(developerProjectFileName, KConfig::SimpleConfig); KConfigGroup developerProjectGroup = developerCfg->group("Project"); developerProjectGroup.writeEntry("VersionControlSupport", info.vcsPluginName); developerProjectGroup.sync(); developerCfg->sync(); return projectFileName; } bool AppWizardPlugin::unpackArchive(const KArchiveDirectory* dir, const QString& dest, const QStringList& skipList) { qCDebug(PLUGIN_APPWIZARD) << "unpacking dir:" << dir->name() << "to" << dest; const QStringList entries = dir->entries(); qCDebug(PLUGIN_APPWIZARD) << "entries:" << entries.join(QStringLiteral(",")); //This extra tempdir is needed just for the files files have special names, //which may contain macros also files contain content with macros. So the //easiest way to extract the files from the archive and then rename them //and replace the macros is to use a tempdir and copy the file (and //replacing while copying). This also allows one to easily remove all files, //by just unlinking the tempdir QTemporaryDir tdir; bool ret = true; foreach (const QString& entry, entries) { if (skipList.contains(entry)) { continue; } if (dir->entry(entry)->isDirectory()) { const KArchiveDirectory *file = (KArchiveDirectory *)dir->entry(entry); QString newdest = dest + '/' + KMacroExpander::expandMacros(file->name(), m_variables); if( !QFileInfo::exists( newdest ) ) { QDir::root().mkdir( newdest ); } ret |= unpackArchive(file, newdest); } else if (dir->entry(entry)->isFile()) { const KArchiveFile *file = (KArchiveFile *)dir->entry(entry); file->copyTo(tdir.path()); QString destName = dest + '/' + file->name(); if (!copyFileAndExpandMacros(QDir::cleanPath(tdir.path()+'/'+file->name()), KMacroExpander::expandMacros(destName, m_variables))) { KMessageBox::sorry(nullptr, i18n("The file %1 cannot be created.", dest)); return false; } } } tdir.remove(); return ret; } bool AppWizardPlugin::copyFileAndExpandMacros(const QString &source, const QString &dest) { qCDebug(PLUGIN_APPWIZARD) << "copy:" << source << "to" << dest; QMimeDatabase db; QMimeType mime = db.mimeTypeForFile(source); if( !mime.inherits(QStringLiteral("text/plain")) ) { KIO::CopyJob* job = KIO::copy( QUrl::fromUserInput(source), QUrl::fromUserInput(dest), KIO::HideProgressInfo ); if( !job->exec() ) { return false; } return true; } else { QFile inputFile(source); QFile outputFile(dest); if (inputFile.open(QFile::ReadOnly) && outputFile.open(QFile::WriteOnly)) { QTextStream input(&inputFile); input.setCodec(QTextCodec::codecForName("UTF-8")); QTextStream output(&outputFile); output.setCodec(QTextCodec::codecForName("UTF-8")); while(!input.atEnd()) { QString line = input.readLine(); output << KMacroExpander::expandMacros(line, m_variables) << "\n"; } #ifndef Q_OS_WIN // Preserve file mode... QT_STATBUF statBuf; QT_FSTAT(inputFile.handle(), &statBuf); // Unix only, won't work in Windows, maybe KIO::chmod could be used ::fchmod(outputFile.handle(), statBuf.st_mode); #endif return true; } else { inputFile.close(); outputFile.close(); return false; } } } KDevelop::ContextMenuExtension AppWizardPlugin::contextMenuExtension(KDevelop::Context* context) { KDevelop::ContextMenuExtension ext; if ( context->type() != KDevelop::Context::ProjectItemContext || !static_cast(context)->items().isEmpty() ) { return ext; } ext.addAction(KDevelop::ContextMenuExtension::ProjectGroup, m_newFromTemplate); return ext; } ProjectTemplatesModel* AppWizardPlugin::model() { if(!m_templatesModel) m_templatesModel = new ProjectTemplatesModel(this); return m_templatesModel; } QAbstractItemModel* AppWizardPlugin::templatesModel() { return model(); } QString AppWizardPlugin::knsConfigurationFile() const { return QStringLiteral("kdevappwizard.knsrc"); } QStringList AppWizardPlugin::supportedMimeTypes() const { QStringList types; types << QStringLiteral("application/x-desktop"); types << QStringLiteral("application/x-bzip-compressed-tar"); types << QStringLiteral("application/zip"); return types; } QIcon AppWizardPlugin::icon() const { return QIcon::fromTheme(QStringLiteral("project-development-new-template")); } QString AppWizardPlugin::name() const { return i18n("Project Templates"); } void AppWizardPlugin::loadTemplate(const QString& fileName) { model()->loadTemplateFile(fileName); } void AppWizardPlugin::reload() { model()->refresh(); } #include "appwizardplugin.moc" diff --git a/plugins/appwizard/debug.h b/plugins/appwizard/debug.h deleted file mode 100644 index edb47e097..000000000 --- a/plugins/appwizard/debug.h +++ /dev/null @@ -1,26 +0,0 @@ -/* - * This file is part of KDevelop - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#ifndef KDEVPLATFORM_APPWIZARD_DEBUG_H -#define KDEVPLATFORM_APPWIZARD_DEBUG_H - -#include -Q_DECLARE_LOGGING_CATEGORY(PLUGIN_APPWIZARD) - -#endif diff --git a/plugins/classbrowser/CMakeLists.txt b/plugins/classbrowser/CMakeLists.txt index 89edb6d6c..3e9fe8393 100644 --- a/plugins/classbrowser/CMakeLists.txt +++ b/plugins/classbrowser/CMakeLists.txt @@ -1,19 +1,24 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kdevclassbrowser\") include_directories( ${Boost_INCLUDE_DIRS} ) # workaround a boost bug in 1.37 and 1.38 that causes link failure when exceptions are disabled # see https://svn.boost.org/trac/boost/ticket/2947 for details if( ${Boost_MAJOR_VERSION}.${Boost_MINOR_VERSION}.${Boost_PATCH_VERSION} VERSION_GREATER 1.36.1 ) kde_enable_exceptions() endif() set(kdevclassbrowser_PART_SRCS classbrowserplugin.cpp classwidget.cpp classtree.cpp ) +ecm_qt_declare_logging_category(kdevclassbrowser_PART_SRCS + HEADER debug.h + IDENTIFIER PLUGIN_CLASSBROWSER + CATEGORY_NAME "kdevplatform.plugins.classbrowser" +) qt5_add_resources(kdevclassbrowser_PART_SRCS kdevclassbrowser.qrc) kdevplatform_add_plugin(kdevclassbrowser JSON kdevclassbrowser.json SOURCES ${kdevclassbrowser_PART_SRCS}) target_link_libraries(kdevclassbrowser KF5::TextEditor KDev::Util KDev::Language KDev::Interfaces) diff --git a/plugins/classbrowser/classbrowserplugin.cpp b/plugins/classbrowser/classbrowserplugin.cpp index 6a841ec2c..d2b896c99 100644 --- a/plugins/classbrowser/classbrowserplugin.cpp +++ b/plugins/classbrowser/classbrowserplugin.cpp @@ -1,184 +1,183 @@ /* * This file is part of KDevelop * * Copyright 2006 Adam Treat * Copyright 2006-2008 Hamish Rodda * Copyright 2009 Lior Mualem * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "classbrowserplugin.h" #include #include #include #include "interfaces/icore.h" #include "interfaces/iuicontroller.h" #include "interfaces/idocumentcontroller.h" #include "interfaces/contextmenuextension.h" #include "language/interfaces/codecontext.h" #include "language/duchain/duchainbase.h" #include "language/duchain/duchain.h" #include "language/duchain/duchainlock.h" #include "language/duchain/declaration.h" #include "debug.h" #include "classtree.h" #include "classwidget.h" #include #include #include #include #include -Q_LOGGING_CATEGORY(PLUGIN_CLASSBROWSER, "kdevplatform.plugins.classbrowser") K_PLUGIN_FACTORY_WITH_JSON(KDevClassBrowserFactory, "kdevclassbrowser.json", registerPlugin(); ) using namespace KDevelop; class ClassBrowserFactory: public KDevelop::IToolViewFactory { public: explicit ClassBrowserFactory(ClassBrowserPlugin *plugin): m_plugin(plugin) {} QWidget* create(QWidget *parent = nullptr) override { return new ClassWidget(parent, m_plugin); } Qt::DockWidgetArea defaultPosition() override { return Qt::LeftDockWidgetArea; } QString id() const override { return QStringLiteral("org.kdevelop.ClassBrowserView"); } private: ClassBrowserPlugin *m_plugin; }; ClassBrowserPlugin::ClassBrowserPlugin(QObject *parent, const QVariantList&) : KDevelop::IPlugin(QStringLiteral("kdevclassbrowser"), parent) , m_factory(new ClassBrowserFactory(this)) , m_activeClassTree(nullptr) { core()->uiController()->addToolView(i18n("Classes"), m_factory); setXMLFile( QStringLiteral("kdevclassbrowser.rc") ); m_findInBrowser = new QAction(i18n("Find in &Class Browser"), this); connect(m_findInBrowser, &QAction::triggered, this, &ClassBrowserPlugin::findInClassBrowser); } ClassBrowserPlugin::~ClassBrowserPlugin() { } void ClassBrowserPlugin::unload() { core()->uiController()->removeToolView(m_factory); } KDevelop::ContextMenuExtension ClassBrowserPlugin::contextMenuExtension( KDevelop::Context* context) { KDevelop::ContextMenuExtension menuExt = KDevelop::IPlugin::contextMenuExtension( context ); // No context menu if we don't have a class browser at hand. if ( m_activeClassTree == nullptr ) return menuExt; KDevelop::DeclarationContext *codeContext = dynamic_cast(context); if (!codeContext) return menuExt; DUChainReadLocker readLock(DUChain::lock()); Declaration* decl(codeContext->declaration().data()); if (decl) { if(decl->inSymbolTable()) { if(!ClassTree::populatingClassBrowserContextMenu() && ICore::self()->projectController()->findProjectForUrl(decl->url().toUrl()) && decl->kind() == Declaration::Type && decl->internalContext() && decl->internalContext()->type() == DUContext::Class) { //Currently "Find in Class Browser" seems to only work for classes, so only show it in that case m_findInBrowser->setData(QVariant::fromValue(DUChainBasePointer(decl))); menuExt.addAction( KDevelop::ContextMenuExtension::NavigationGroup, m_findInBrowser); } } } return menuExt; } void ClassBrowserPlugin::findInClassBrowser() { ICore::self()->uiController()->findToolView(i18n("Classes"), m_factory, KDevelop::IUiController::CreateAndRaise); Q_ASSERT(qobject_cast(sender())); if ( m_activeClassTree == nullptr ) return; DUChainReadLocker readLock(DUChain::lock()); QAction* a = static_cast(sender()); Q_ASSERT(a->data().canConvert()); DeclarationPointer decl = qvariant_cast(a->data()).dynamicCast(); if (decl) m_activeClassTree->highlightIdentifier(decl->qualifiedIdentifier()); } void ClassBrowserPlugin::showDefinition(DeclarationPointer declaration) { DUChainReadLocker readLock(DUChain::lock()); if ( !declaration ) return; Declaration* decl = declaration.data(); // If it's a function, find the function definition to go to the actual declaration. if ( decl && decl->isFunctionDeclaration() ) { FunctionDefinition* funcDefinition = dynamic_cast(decl); if ( funcDefinition == nullptr ) funcDefinition = FunctionDefinition::definition(decl); if ( funcDefinition ) decl = funcDefinition; } if (decl) { QUrl url = decl->url().toUrl(); KTextEditor::Range range = decl->rangeInCurrentRevision(); readLock.unlock(); ICore::self()->documentController()->openDocument(url, range.start()); } } #include "classbrowserplugin.moc" // kate: space-indent on; indent-width 2; tab-width 4; replace-tabs on; auto-insert-doxygen on diff --git a/plugins/classbrowser/debug.h b/plugins/classbrowser/debug.h deleted file mode 100644 index 7fe79e31f..000000000 --- a/plugins/classbrowser/debug.h +++ /dev/null @@ -1,26 +0,0 @@ -/* - * This file is part of KDevelop - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#ifndef KDEVPLATFORM_CLASSBROWSER_DEBUG_H -#define KDEVPLATFORM_CLASSBROWSER_DEBUG_H - -#include -Q_DECLARE_LOGGING_CATEGORY(PLUGIN_CLASSBROWSER) - -#endif diff --git a/plugins/codeutils/CMakeLists.txt b/plugins/codeutils/CMakeLists.txt index 2ce3f580c..1e1964430 100644 --- a/plugins/codeutils/CMakeLists.txt +++ b/plugins/codeutils/CMakeLists.txt @@ -1,21 +1,26 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kdevcodeutils\") project(codeutils) ########### install target ############### set(kdevcodeutils_PART_SRCS codeutilsplugin.cpp ) +ecm_qt_declare_logging_category(kdevcodeutils_PART_SRCS + HEADER debug.h + IDENTIFIER PLUGIN_CODEUTILS + CATEGORY_NAME "kdevplatform.plugins.codeutils" +) qt5_add_resources(kdevcodeutils_PART_SRCS kdevcodeutils.qrc) kdevplatform_add_plugin(kdevcodeutils JSON kdevcodeutils.json SOURCES ${kdevcodeutils_PART_SRCS}) target_link_libraries(kdevcodeutils KF5::Parts KF5::TextEditor KDev::Interfaces KDev::Util KDev::Language ) add_subdirectory(doc_templates) diff --git a/plugins/codeutils/codeutilsplugin.cpp b/plugins/codeutils/codeutilsplugin.cpp index 30a4e1239..4dfc7c7ca 100644 --- a/plugins/codeutils/codeutilsplugin.cpp +++ b/plugins/codeutils/codeutilsplugin.cpp @@ -1,154 +1,153 @@ /* * This file is part of KDevelop * Copyright 2010 Milian Wolff * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "codeutilsplugin.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include - -Q_LOGGING_CATEGORY(PLUGIN_CODEUTILS, "kdevplatform.plugins.codeutils") +#include using namespace KDevelop; using namespace KTextEditor; K_PLUGIN_FACTORY_WITH_JSON(CodeUtilsPluginFactory, "kdevcodeutils.json", registerPlugin(); ) CodeUtilsPlugin::CodeUtilsPlugin ( QObject* parent, const QVariantList& ) : IPlugin ( QStringLiteral("kdevcodeutils"), parent ) { setXMLFile( QStringLiteral("kdevcodeutils.rc") ); QAction* action = actionCollection()->addAction( QStringLiteral("document_declaration") ); // i18n: action name; 'Document' is a verb action->setText( i18n( "Document Declaration" ) ); actionCollection()->setDefaultShortcut(action, i18n( "Alt+Shift+d" )); connect( action, &QAction::triggered, this, &CodeUtilsPlugin::documentDeclaration ); action->setToolTip( i18n( "Add Doxygen skeleton for declaration under cursor." ) ); // i18n: translate title same as the action name action->setWhatsThis( i18n( "Adds a basic Doxygen comment skeleton in front of " "the declaration under the cursor, e.g. with all the " "parameter of a function." ) ); action->setIcon( QIcon::fromTheme( QStringLiteral("documentinfo") ) ); } void CodeUtilsPlugin::documentDeclaration() { View* view = ICore::self()->documentController()->activeTextDocumentView(); if ( !view ) { return; } DUChainReadLocker lock; TopDUContext* topCtx = DUChainUtils::standardContextForUrl(view->document()->url()); if ( !topCtx ) { return; } Declaration* dec = DUChainUtils::declarationInLine( KTextEditor::Cursor( view->cursorPosition() ), topCtx ); if ( !dec || dec->isForwardDeclaration() ) { return; } // finally - we found the declaration :) int line = dec->range().start.line; Cursor insertPos( line, 0 ); TemplateRenderer renderer; renderer.setEmptyLinesPolicy(TemplateRenderer::TrimEmptyLines); renderer.addVariable(QStringLiteral("brief"), i18n( "..." )); /* QString indentation = textDoc->line( insertPos.line() ); if (!indentation.isEmpty()) { int lastSpace = 0; while (indentation.at(lastSpace).isSpace()) { ++lastSpace; } indentation.truncate(lastSpace); } */ if (dec->isFunctionDeclaration()) { FunctionDescription description = FunctionDescription(DeclarationPointer(dec)); renderer.addVariable(QStringLiteral("function"), QVariant::fromValue(description)); qCDebug(PLUGIN_CODEUTILS) << "Found function" << description.name << "with" << description.arguments.size() << "arguments"; } lock.unlock(); // TODO: Choose the template based on the language QString templateName = QStringLiteral("doxygen_cpp"); auto languages = core()->languageController()->languagesForUrl(view->document()->url()); if (!languages.isEmpty()) { QString languageName = languages.first()->name(); if (languageName == QLatin1String("Php")) { templateName = QStringLiteral("phpdoc_php"); } else if (languageName == QLatin1String("Python")) { templateName = QStringLiteral("rest_python"); // Python docstrings appear inside functions and classes, not above them insertPos = Cursor(line+1, 0); } } QString fileName = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kdevcodeutils/templates/" + templateName + ".txt"); if (fileName.isEmpty()) { qCWarning(PLUGIN_CODEUTILS) << "No suitable template found" << fileName; return; } const QString comment = renderer.renderFile(QUrl::fromLocalFile(fileName)); view->insertTemplate(insertPos, comment); } CodeUtilsPlugin::~CodeUtilsPlugin() { } #include "codeutilsplugin.moc" diff --git a/plugins/codeutils/codeutilsplugin.h b/plugins/codeutils/codeutilsplugin.h index 99576596d..afe2ee889 100644 --- a/plugins/codeutils/codeutilsplugin.h +++ b/plugins/codeutils/codeutilsplugin.h @@ -1,43 +1,41 @@ /* * This file is part of KDevelop * Copyright 2010 Milian Wolff * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_PLUGIN_CODEUTILSPLUGIN_H #define KDEVPLATFORM_PLUGIN_CODEUTILSPLUGIN_H #include #include #include -#include -Q_DECLARE_LOGGING_CATEGORY(PLUGIN_CODEUTILS) class CodeUtilsPlugin : public KDevelop::IPlugin { Q_OBJECT public: explicit CodeUtilsPlugin( QObject *parent, const QVariantList & = QVariantList() ); ~CodeUtilsPlugin() override; private Q_SLOTS: void documentDeclaration(); }; #endif // KDEVPLATFORM_PLUGIN_CODEUTILSPLUGIN_H diff --git a/plugins/contextbrowser/CMakeLists.txt b/plugins/contextbrowser/CMakeLists.txt index fcd82f5eb..60171ad93 100644 --- a/plugins/contextbrowser/CMakeLists.txt +++ b/plugins/contextbrowser/CMakeLists.txt @@ -1,14 +1,19 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kdevcontextbrowser\") ########### next target ############### set(kdevcontextbrowser_PART_SRCS contextbrowser.cpp contextbrowserview.cpp browsemanager.cpp ) +ecm_qt_declare_logging_category(kdevcontextbrowser_PART_SRCS + HEADER debug.h + IDENTIFIER PLUGIN_CONTEXTBROWSER + CATEGORY_NAME "kdevplatform.plugins.contextbrowser" +) qt5_add_resources(kdevcontextbrowser_PART_SRCS kdevcontextbrowser.qrc) kdevplatform_add_plugin(kdevcontextbrowser JSON kdevcontextbrowser.json SOURCES ${kdevcontextbrowser_PART_SRCS}) target_link_libraries(kdevcontextbrowser KDev::Interfaces KDev::Util KDev::Language KDev::Sublime KDev::Shell KF5::TextEditor KF5::Parts) diff --git a/plugins/contextbrowser/contextbrowser.cpp b/plugins/contextbrowser/contextbrowser.cpp index 2b26d772f..ad7ed0063 100644 --- a/plugins/contextbrowser/contextbrowser.cpp +++ b/plugins/contextbrowser/contextbrowser.cpp @@ -1,1497 +1,1495 @@ /* * This file is part of KDevelop * * Copyright 2007 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "contextbrowser.h" #include "contextbrowserview.h" #include "browsemanager.h" #include "debug.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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include -Q_LOGGING_CATEGORY(PLUGIN_CONTEXTBROWSER, "kdevplatform.plugins.contextbrowser") - using KTextEditor::Attribute; using KTextEditor::View; // Helper that follows the QObject::parent() chain, and returns the highest widget that has no parent. QWidget* masterWidget(QWidget* w) { while(w && w->parent() && qobject_cast(w->parent())) w = qobject_cast(w->parent()); return w; } namespace { const unsigned int highlightingTimeout = 150; const float highlightingZDepth = -5000; const int maxHistoryLength = 30; // Helper that determines the context to use for highlighting at a specific position DUContext* contextForHighlightingAt(const KTextEditor::Cursor& position, TopDUContext* topContext) { DUContext* ctx = topContext->findContextAt(topContext->transformToLocalRevision(position)); while(ctx && ctx->parentContext() && (ctx->type() == DUContext::Template || ctx->type() == DUContext::Helper || ctx->localScopeIdentifier().isEmpty())) { ctx = ctx->parentContext(); } return ctx; } ///Duchain must be locked DUContext* getContextAt(const QUrl& url, KTextEditor::Cursor cursor) { TopDUContext* topContext = DUChainUtils::standardContextForUrl(url); if (!topContext) return nullptr; return contextForHighlightingAt(KTextEditor::Cursor(cursor), topContext); } DeclarationPointer cursorDeclaration() { KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView(); if (!view) { return DeclarationPointer(); } DUChainReadLocker lock; Declaration *decl = DUChainUtils::declarationForDefinition(DUChainUtils::itemUnderCursor(view->document()->url(), KTextEditor::Cursor(view->cursorPosition())).declaration); return DeclarationPointer(decl); } } class ContextBrowserViewFactory: public KDevelop::IToolViewFactory { public: explicit ContextBrowserViewFactory(ContextBrowserPlugin *plugin): m_plugin(plugin) {} QWidget* create(QWidget *parent = nullptr) override { ContextBrowserView* ret = new ContextBrowserView(m_plugin, parent); return ret; } Qt::DockWidgetArea defaultPosition() override { return Qt::BottomDockWidgetArea; } QString id() const override { return QStringLiteral("org.kdevelop.ContextBrowser"); } private: ContextBrowserPlugin *m_plugin; }; KXMLGUIClient* ContextBrowserPlugin::createGUIForMainWindow( Sublime::MainWindow* window ) { m_browseManager = new BrowseManager(this); KXMLGUIClient* ret = KDevelop::IPlugin::createGUIForMainWindow(window); connect(ICore::self()->documentController(), &IDocumentController::documentJumpPerformed, this, &ContextBrowserPlugin::documentJumpPerformed); m_previousButton = new QToolButton(); m_previousButton->setToolTip(i18n("Go back in context history")); m_previousButton->setAutoRaise(true); m_previousButton->setPopupMode(QToolButton::MenuButtonPopup); m_previousButton->setIcon(QIcon::fromTheme(QStringLiteral("go-previous"))); m_previousButton->setEnabled(false); m_previousButton->setFocusPolicy(Qt::NoFocus); m_previousMenu = new QMenu(); m_previousButton->setMenu(m_previousMenu); connect(m_previousButton.data(), &QToolButton::clicked, this, &ContextBrowserPlugin::historyPrevious); connect(m_previousMenu.data(), &QMenu::aboutToShow, this, &ContextBrowserPlugin::previousMenuAboutToShow); m_nextButton = new QToolButton(); m_nextButton->setToolTip(i18n("Go forward in context history")); m_nextButton->setAutoRaise(true); m_nextButton->setPopupMode(QToolButton::MenuButtonPopup); m_nextButton->setIcon(QIcon::fromTheme(QStringLiteral("go-next"))); m_nextButton->setEnabled(false); m_nextButton->setFocusPolicy(Qt::NoFocus); m_nextMenu = new QMenu(); m_nextButton->setMenu(m_nextMenu); connect(m_nextButton.data(), &QToolButton::clicked, this, &ContextBrowserPlugin::historyNext); connect(m_nextMenu.data(), &QMenu::aboutToShow, this, &ContextBrowserPlugin::nextMenuAboutToShow); IQuickOpen* quickOpen = KDevelop::ICore::self()->pluginController()->extensionForPlugin(QStringLiteral("org.kdevelop.IQuickOpen")); if(quickOpen) { m_outlineLine = quickOpen->createQuickOpenLine(QStringList(), QStringList() << i18n("Outline"), IQuickOpen::Outline); m_outlineLine->setDefaultText(i18n("Outline...")); m_outlineLine->setToolTip(i18n("Navigate outline of active document, click to browse.")); } connect(m_browseManager, &BrowseManager::startDelayedBrowsing, this, &ContextBrowserPlugin::startDelayedBrowsing); connect(m_browseManager, &BrowseManager::stopDelayedBrowsing, this, &ContextBrowserPlugin::stopDelayedBrowsing); connect(m_browseManager, &BrowseManager::invokeAction, this, &ContextBrowserPlugin::invokeAction); m_toolbarWidget = toolbarWidgetForMainWindow(window); m_toolbarWidgetLayout = new QHBoxLayout; m_toolbarWidgetLayout->setSizeConstraint(QLayout::SetMaximumSize); m_previousButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); m_nextButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); m_toolbarWidgetLayout->setMargin(0); m_toolbarWidgetLayout->addWidget(m_previousButton); if (m_outlineLine) { m_toolbarWidgetLayout->addWidget(m_outlineLine); m_outlineLine->setMaximumWidth(600); connect(ICore::self()->documentController(), &IDocumentController::documentClosed, m_outlineLine.data(), &IQuickOpenLine::clear); } m_toolbarWidgetLayout->addWidget(m_nextButton); if(m_toolbarWidget->children().isEmpty()) m_toolbarWidget->setLayout(m_toolbarWidgetLayout); connect(ICore::self()->documentController(), &IDocumentController::documentActivated, this, &ContextBrowserPlugin::documentActivated); return ret; } void ContextBrowserPlugin::createActionsForMainWindow(Sublime::MainWindow* window, QString& xmlFile, KActionCollection& actions) { xmlFile = QStringLiteral("kdevcontextbrowser.rc"); QAction* sourceBrowseMode = actions.addAction(QStringLiteral("source_browse_mode")); sourceBrowseMode->setText( i18n("Source &Browse Mode") ); sourceBrowseMode->setIcon( QIcon::fromTheme(QStringLiteral("arrow-up")) ); sourceBrowseMode->setCheckable(true); connect(sourceBrowseMode, &QAction::triggered, m_browseManager, &BrowseManager::setBrowsing); QAction* previousContext = actions.addAction(QStringLiteral("previous_context")); previousContext->setText( i18n("&Previous Visited Context") ); previousContext->setIcon( QIcon::fromTheme(QStringLiteral("go-previous-context") ) ); actions.setDefaultShortcut( previousContext, Qt::META | Qt::Key_Left ); QObject::connect(previousContext, &QAction::triggered, this, &ContextBrowserPlugin::previousContextShortcut); QAction* nextContext = actions.addAction(QStringLiteral("next_context")); nextContext->setText( i18n("&Next Visited Context") ); nextContext->setIcon( QIcon::fromTheme(QStringLiteral("go-next-context") ) ); actions.setDefaultShortcut( nextContext, Qt::META | Qt::Key_Right ); QObject::connect(nextContext, &QAction::triggered, this, &ContextBrowserPlugin::nextContextShortcut); QAction* previousUse = actions.addAction(QStringLiteral("previous_use")); previousUse->setText( i18n("&Previous Use") ); previousUse->setIcon( QIcon::fromTheme(QStringLiteral("go-previous-use")) ); actions.setDefaultShortcut( previousUse, Qt::META | Qt::SHIFT | Qt::Key_Left ); QObject::connect(previousUse, &QAction::triggered, this, &ContextBrowserPlugin::previousUseShortcut); QAction* nextUse = actions.addAction(QStringLiteral("next_use")); nextUse->setText( i18n("&Next Use") ); nextUse->setIcon( QIcon::fromTheme(QStringLiteral("go-next-use")) ); actions.setDefaultShortcut( nextUse, Qt::META | Qt::SHIFT | Qt::Key_Right ); QObject::connect(nextUse, &QAction::triggered, this, &ContextBrowserPlugin::nextUseShortcut); QWidgetAction* outline = new QWidgetAction(this); outline->setText(i18n("Context Browser")); QWidget* w = toolbarWidgetForMainWindow(window); w->setHidden(false); outline->setDefaultWidget(w); actions.addAction(QStringLiteral("outline_line"), outline); // Add to the actioncollection so one can set global shortcuts for the action actions.addAction(QStringLiteral("find_uses"), m_findUses); } void ContextBrowserPlugin::nextContextShortcut() { // TODO: cleanup historyNext(); } void ContextBrowserPlugin::previousContextShortcut() { // TODO: cleanup historyPrevious(); } K_PLUGIN_FACTORY_WITH_JSON(ContextBrowserFactory, "kdevcontextbrowser.json", registerPlugin();) ContextBrowserPlugin::ContextBrowserPlugin(QObject *parent, const QVariantList&) : KDevelop::IPlugin(QStringLiteral("kdevcontextbrowser"), parent) , m_viewFactory(new ContextBrowserViewFactory(this)) , m_nextHistoryIndex(0) , m_textHintProvider(this) { core()->uiController()->addToolView(i18n("Code Browser"), m_viewFactory); connect( core()->documentController(), &IDocumentController::textDocumentCreated, this, &ContextBrowserPlugin::textDocumentCreated ); connect( DUChain::self(), &DUChain::updateReady, this, &ContextBrowserPlugin::updateReady); connect( ColorCache::self(), &ColorCache::colorsGotChanged, this, &ContextBrowserPlugin::colorSetupChanged ); connect( DUChain::self(), &DUChain::declarationSelected, this, &ContextBrowserPlugin::declarationSelectedInUI ); m_updateTimer = new QTimer(this); m_updateTimer->setSingleShot(true); connect( m_updateTimer, &QTimer::timeout, this, &ContextBrowserPlugin::updateViews ); //Needed global action for the context-menu extensions m_findUses = new QAction(i18n("Find Uses"), this); connect(m_findUses, &QAction::triggered, this, &ContextBrowserPlugin::findUses); } ContextBrowserPlugin::~ContextBrowserPlugin() { ///TODO: QObject inheritance should suffice? delete m_nextMenu; delete m_previousMenu; delete m_toolbarWidgetLayout; delete m_previousButton; delete m_outlineLine; delete m_nextButton; } void ContextBrowserPlugin::unload() { core()->uiController()->removeToolView(m_viewFactory); } KDevelop::ContextMenuExtension ContextBrowserPlugin::contextMenuExtension(KDevelop::Context* context) { KDevelop::ContextMenuExtension menuExt = KDevelop::IPlugin::contextMenuExtension( context ); KDevelop::DeclarationContext *codeContext = dynamic_cast(context); if (!codeContext) return menuExt; DUChainReadLocker lock(DUChain::lock()); if(!codeContext->declaration().data()) return menuExt; qRegisterMetaType("KDevelop::IndexedDeclaration"); menuExt.addAction(KDevelop::ContextMenuExtension::NavigationGroup, m_findUses); return menuExt; } void ContextBrowserPlugin::showUses(const DeclarationPointer& declaration) { QMetaObject::invokeMethod(this, "showUsesDelayed", Qt::QueuedConnection, Q_ARG(KDevelop::DeclarationPointer, declaration)); } void ContextBrowserPlugin::showUsesDelayed(const DeclarationPointer& declaration) { DUChainReadLocker lock; Declaration* decl = declaration.data(); if(!decl) { return; } QWidget* toolView = ICore::self()->uiController()->findToolView(i18n("Code Browser"), m_viewFactory, KDevelop::IUiController::CreateAndRaise); if(!toolView) { return; } ContextBrowserView* view = dynamic_cast(toolView); Q_ASSERT(view); view->allowLockedUpdate(); view->setDeclaration(decl, decl->topContext(), true); //We may get deleted while the call to acceptLink, so make sure we don't crash in that case QPointer widget = dynamic_cast(view->navigationWidget()); if(widget && widget->context()) { auto nextContext = widget->context()->execute( NavigationAction(declaration, KDevelop::NavigationAction::ShowUses)); if(widget) { widget->setContext( nextContext ); } } } void ContextBrowserPlugin::findUses() { showUses(cursorDeclaration()); } ContextBrowserHintProvider::ContextBrowserHintProvider(ContextBrowserPlugin* plugin) : m_plugin(plugin) { } QString ContextBrowserHintProvider::textHint(View* view, const KTextEditor::Cursor& cursor) { m_plugin->m_mouseHoverCursor = KTextEditor::Cursor(cursor); if(!view) { qCWarning(PLUGIN_CONTEXTBROWSER) << "could not cast to view"; }else{ m_plugin->m_mouseHoverDocument = view->document()->url(); m_plugin->m_updateViews << view; } m_plugin->m_updateTimer->start(1); // triggers updateViews() m_plugin->showToolTip(view, cursor); return QString(); } void ContextBrowserPlugin::stopDelayedBrowsing() { hideToolTip(); } void ContextBrowserPlugin::invokeAction(int index) { if (!m_currentNavigationWidget) return; auto navigationWidget = qobject_cast(m_currentNavigationWidget); if (!navigationWidget) return; // TODO: Add API in AbstractNavigation{Widget,Context}? QMetaObject::invokeMethod(navigationWidget->context().data(), "executeAction", Q_ARG(int, index)); } void ContextBrowserPlugin::startDelayedBrowsing(KTextEditor::View* view) { if(!m_currentToolTip) { showToolTip(view, view->cursorPosition()); } } void ContextBrowserPlugin::hideToolTip() { if(m_currentToolTip) { m_currentToolTip->deleteLater(); m_currentToolTip = nullptr; m_currentNavigationWidget = nullptr; m_currentToolTipProblems.clear(); m_currentToolTipDeclaration = {}; } } static QVector findProblemsUnderCursor(TopDUContext* topContext, KTextEditor::Cursor position) { QVector problems; const auto modelsData = ICore::self()->languageController()->problemModelSet()->models(); for (const auto& modelData : modelsData) { foreach (const auto& problem, modelData.model->problems(topContext->url())) { DocumentRange problemRange = problem->finalLocation(); if (problemRange.contains(position) || (problemRange.isEmpty() && problemRange.boundaryAtCursor(position))) problems += problem; } } return problems; } static QVector findProblemsCloseToCursor(TopDUContext* topContext, KTextEditor::Cursor position, KTextEditor::View* view) { QVector allProblems; const auto modelsData = ICore::self()->languageController()->problemModelSet()->models(); for (const auto& modelData : modelsData) { foreach (const auto& problem, modelData.model->problems(topContext->url())) { allProblems += problem; } } if (allProblems.isEmpty()) return allProblems; std::sort(allProblems.begin(), allProblems.end(), [position](const KDevelop::IProblem::Ptr a, const KDevelop::IProblem::Ptr b) { const auto aRange = a->finalLocation(); const auto bRange = b->finalLocation(); const auto aLineDistance = qMin(qAbs(aRange.start().line() - position.line()), qAbs(aRange.end().line() - position.line())); const auto bLineDistance = qMin(qAbs(bRange.start().line() - position.line()), qAbs(bRange.end().line() - position.line())); if (aLineDistance != bLineDistance) { return aLineDistance < bLineDistance; } if (aRange.start().line() == bRange.start().line()) { return qAbs(aRange.start().column() - position.column()) < qAbs(bRange.start().column() - position.column()); } return qAbs(aRange.end().column() - position.column()) < qAbs(bRange.end().column() - position.column()); }); QVector closestProblems; // Show problems, located on the same line foreach (auto problem, allProblems) { auto r = problem->finalLocation(); if (r.onSingleLine() && r.start().line() == position.line()) closestProblems += problem; else break; } // If not, only show it in case there's only whitespace // between the current cursor position and the problem line if (closestProblems.isEmpty()) { foreach (auto problem, allProblems) { auto r = problem->finalLocation(); KTextEditor::Range dist; KTextEditor::Cursor bound(r.start().line(), 0); if (position < r.start()) dist = KTextEditor::Range(position, bound); else { bound.setLine(r.end().line() + 1); dist = KTextEditor::Range(bound, position); } if (view->document()->text(dist).trimmed().isEmpty()) closestProblems += problem; else break; } } return closestProblems; } QWidget* ContextBrowserPlugin::navigationWidgetForPosition(KTextEditor::View* view, KTextEditor::Cursor position) { QUrl viewUrl = view->document()->url(); auto languages = ICore::self()->languageController()->languagesForUrl(viewUrl); DUChainReadLocker lock(DUChain::lock()); foreach (const auto language, languages) { auto widget = language->specialLanguageObjectNavigationWidget(viewUrl, KTextEditor::Cursor(position)); auto navigationWidget = qobject_cast(widget); if(navigationWidget) return navigationWidget; } TopDUContext* topContext = DUChainUtils::standardContextForUrl(view->document()->url()); if (topContext) { // first pass: find problems under the cursor const auto problems = findProblemsUnderCursor(topContext, position); if (!problems.isEmpty()) { if (problems == m_currentToolTipProblems && m_currentToolTip) { return nullptr; } m_currentToolTipProblems = problems; auto widget = new AbstractNavigationWidget; auto context = new ProblemNavigationContext(problems); context->setTopContext(TopDUContextPointer(topContext)); widget->setContext(NavigationContextPointer(context)); return widget; } } auto declUnderCursor = DUChainUtils::itemUnderCursor(viewUrl, position).declaration; Declaration* decl = DUChainUtils::declarationForDefinition(declUnderCursor); if (decl && decl->kind() == Declaration::Alias) { AliasDeclaration* alias = dynamic_cast(decl); Q_ASSERT(alias); DUChainReadLocker lock; decl = alias->aliasedDeclaration().declaration(); } if(decl) { if(m_currentToolTipDeclaration == IndexedDeclaration(decl) && m_currentToolTip) return nullptr; m_currentToolTipDeclaration = IndexedDeclaration(decl); return decl->context()->createNavigationWidget(decl, DUChainUtils::standardContextForUrl(viewUrl)); } if (topContext) { // second pass: find closest problem to the cursor const auto problems = findProblemsCloseToCursor(topContext, position, view); if (!problems.isEmpty()) { if (problems == m_currentToolTipProblems && m_currentToolTip) { return nullptr; } m_currentToolTipProblems = problems; auto widget = new AbstractNavigationWidget; // since the problem is not under cursor: show location widget->setContext(NavigationContextPointer(new ProblemNavigationContext(problems, ProblemNavigationContext::ShowLocation))); return widget; } } return nullptr; } void ContextBrowserPlugin::showToolTip(KTextEditor::View* view, KTextEditor::Cursor position) { ContextBrowserView* contextView = browserViewForWidget(view); if(contextView && contextView->isVisible() && !contextView->isLocked()) return; // If the context-browser view is visible, it will care about updating by itself auto navigationWidget = navigationWidgetForPosition(view, position); if(navigationWidget) { // If we have an invisible context-view, assign the tooltip navigation-widget to it. // If the user makes the context-view visible, it will instantly contain the correct widget. if(contextView && !contextView->isLocked()) contextView->setNavigationWidget(navigationWidget); if(m_currentToolTip) { m_currentToolTip->deleteLater(); m_currentToolTip = nullptr; m_currentNavigationWidget = nullptr; } KDevelop::NavigationToolTip* tooltip = new KDevelop::NavigationToolTip(view, view->mapToGlobal(view->cursorToCoordinate(position)) + QPoint(20, 40), navigationWidget); KTextEditor::Range itemRange; { DUChainReadLocker lock; auto viewUrl = view->document()->url(); itemRange = DUChainUtils::itemUnderCursor(viewUrl, position).range; } tooltip->setHandleRect(KTextEditorHelpers::getItemBoundingRect(view, itemRange)); tooltip->resize( navigationWidget->sizeHint() + QSize(10, 10) ); QObject::connect( view, &KTextEditor::View::verticalScrollPositionChanged, this, &ContextBrowserPlugin::hideToolTip ); QObject::connect( view, &KTextEditor::View::horizontalScrollPositionChanged, this, &ContextBrowserPlugin::hideToolTip ); qCDebug(PLUGIN_CONTEXTBROWSER) << "tooltip size" << tooltip->size(); m_currentToolTip = tooltip; m_currentNavigationWidget = navigationWidget; ActiveToolTip::showToolTip(tooltip); if ( ! navigationWidget->property("DoNotCloseOnCursorMove").toBool() ) { connect(view, &View::cursorPositionChanged, this, &ContextBrowserPlugin::hideToolTip, Qt::UniqueConnection); } else { disconnect(view, &View::cursorPositionChanged, this, &ContextBrowserPlugin::hideToolTip); } }else{ qCDebug(PLUGIN_CONTEXTBROWSER) << "not showing tooltip, no navigation-widget"; } } void ContextBrowserPlugin::clearMouseHover() { m_mouseHoverCursor = KTextEditor::Cursor::invalid(); m_mouseHoverDocument.clear(); } Attribute::Ptr ContextBrowserPlugin::highlightedUseAttribute(KTextEditor::View* view) const { if( !m_highlightAttribute ) { m_highlightAttribute = Attribute::Ptr( new Attribute() ); m_highlightAttribute->setDefaultStyle(KTextEditor::dsNormal); m_highlightAttribute->setForeground(m_highlightAttribute->selectedForeground()); m_highlightAttribute->setBackgroundFillWhitespace(true); auto iface = qobject_cast(view); auto background = iface->configValue(QStringLiteral("search-highlight-color")).value(); m_highlightAttribute->setBackground(background); } return m_highlightAttribute; } void ContextBrowserPlugin::colorSetupChanged() { m_highlightAttribute = Attribute::Ptr(); } Attribute::Ptr ContextBrowserPlugin::highlightedSpecialObjectAttribute(KTextEditor::View* view) const { return highlightedUseAttribute(view); } void ContextBrowserPlugin::addHighlight( View* view, KDevelop::Declaration* decl ) { if( !view || !decl ) { qCDebug(PLUGIN_CONTEXTBROWSER) << "invalid view/declaration"; return; } ViewHighlights& highlights(m_highlightedRanges[view]); KDevelop::DUChainReadLocker lock; // Highlight the declaration highlights.highlights << decl->createRangeMoving(); highlights.highlights.back()->setAttribute(highlightedUseAttribute(view)); highlights.highlights.back()->setZDepth(highlightingZDepth); // Highlight uses { QMap< IndexedString, QList< KTextEditor::Range > > currentRevisionUses = decl->usesCurrentRevision(); for(QMap< IndexedString, QList< KTextEditor::Range > >::iterator fileIt = currentRevisionUses.begin(); fileIt != currentRevisionUses.end(); ++fileIt) { for(QList< KTextEditor::Range >::const_iterator useIt = (*fileIt).constBegin(); useIt != (*fileIt).constEnd(); ++useIt) { highlights.highlights << PersistentMovingRange::Ptr(new PersistentMovingRange(*useIt, fileIt.key())); highlights.highlights.back()->setAttribute(highlightedUseAttribute(view)); highlights.highlights.back()->setZDepth(highlightingZDepth); } } } if( FunctionDefinition* def = FunctionDefinition::definition(decl) ) { highlights.highlights << def->createRangeMoving(); highlights.highlights.back()->setAttribute(highlightedUseAttribute(view)); highlights.highlights.back()->setZDepth(highlightingZDepth); } } Declaration* ContextBrowserPlugin::findDeclaration(View* view, const KTextEditor::Cursor& position, bool mouseHighlight) { Q_UNUSED(mouseHighlight); Declaration* foundDeclaration = nullptr; if(m_useDeclaration.data()) { foundDeclaration = m_useDeclaration.data(); }else{ //If we haven't found a special language object, search for a use/declaration and eventually highlight it foundDeclaration = DUChainUtils::declarationForDefinition( DUChainUtils::itemUnderCursor(view->document()->url(), position).declaration ); if (foundDeclaration && foundDeclaration->kind() == Declaration::Alias) { AliasDeclaration* alias = dynamic_cast(foundDeclaration); Q_ASSERT(alias); DUChainReadLocker lock; foundDeclaration = alias->aliasedDeclaration().declaration(); } } return foundDeclaration; } ContextBrowserView* ContextBrowserPlugin::browserViewForWidget(QWidget* widget) { foreach(ContextBrowserView* contextView, m_views) { if(masterWidget(contextView) == masterWidget(widget)) { return contextView; } } return nullptr; } void ContextBrowserPlugin::updateForView(View* view) { bool allowHighlight = true; if(view->selection()) { // If something is selected, we unhighlight everything, so that we don't conflict with the // kate plugin that highlights occurrences of the selected string, and also to reduce the // overall amount of concurrent highlighting. allowHighlight = false; } if(m_highlightedRanges[view].keep) { m_highlightedRanges[view].keep = false; return; } // Clear all highlighting m_highlightedRanges.clear(); // Re-highlight ViewHighlights& highlights = m_highlightedRanges[view]; QUrl url = view->document()->url(); IDocument* activeDoc = core()->documentController()->activeDocument(); bool mouseHighlight = (url == m_mouseHoverDocument) && (m_mouseHoverCursor.isValid()); bool shouldUpdateBrowser = (mouseHighlight || (view == ICore::self()->documentController()->activeTextDocumentView() && activeDoc && activeDoc->textDocument() == view->document())); KTextEditor::Cursor highlightPosition; if (mouseHighlight) highlightPosition = m_mouseHoverCursor; else highlightPosition = KTextEditor::Cursor(view->cursorPosition()); ///Pick a language ILanguageSupport* language = nullptr; if(ICore::self()->languageController()->languagesForUrl(url).isEmpty()) { qCDebug(PLUGIN_CONTEXTBROWSER) << "found no language for document" << url; return; }else{ language = ICore::self()->languageController()->languagesForUrl(url).front(); } ///Check whether there is a special language object to highlight (for example a macro) KTextEditor::Range specialRange = language->specialLanguageObjectRange(url, highlightPosition); ContextBrowserView* updateBrowserView = shouldUpdateBrowser ? browserViewForWidget(view) : nullptr; if(specialRange.isValid()) { // Highlight a special language object if(allowHighlight) { highlights.highlights << PersistentMovingRange::Ptr(new PersistentMovingRange(specialRange, IndexedString(url))); highlights.highlights.back()->setAttribute(highlightedSpecialObjectAttribute(view)); highlights.highlights.back()->setZDepth(highlightingZDepth); } if(updateBrowserView) updateBrowserView->setSpecialNavigationWidget(language->specialLanguageObjectNavigationWidget(url, highlightPosition)); }else{ KDevelop::DUChainReadLocker lock( DUChain::lock(), 100 ); if(!lock.locked()) { qCDebug(PLUGIN_CONTEXTBROWSER) << "Failed to lock du-chain in time"; return; } TopDUContext* topContext = DUChainUtils::standardContextForUrl(view->document()->url()); if (!topContext) return; DUContext* ctx = contextForHighlightingAt(highlightPosition, topContext); if (!ctx) return; //Only update the history if this context is around the text cursor if(core()->documentController()->activeDocument() && highlightPosition == KTextEditor::Cursor(view->cursorPosition()) && view->document() == core()->documentController()->activeDocument()->textDocument()) { updateHistory(ctx, highlightPosition); } Declaration* foundDeclaration = findDeclaration(view, highlightPosition, mouseHighlight); if( foundDeclaration ) { m_lastHighlightedDeclaration = highlights.declaration = IndexedDeclaration(foundDeclaration); if(allowHighlight) addHighlight( view, foundDeclaration ); if(updateBrowserView) updateBrowserView->setDeclaration(foundDeclaration, topContext); }else{ if(updateBrowserView) updateBrowserView->setContext(ctx); } } } void ContextBrowserPlugin::updateViews() { foreach( View* view, m_updateViews ) { updateForView(view); } m_updateViews.clear(); m_useDeclaration = IndexedDeclaration(); } void ContextBrowserPlugin::declarationSelectedInUI(const DeclarationPointer& decl) { m_useDeclaration = IndexedDeclaration(decl.data()); KTextEditor::View* view = core()->documentController()->activeTextDocumentView(); if(view) m_updateViews << view; if(!m_updateViews.isEmpty()) m_updateTimer->start(highlightingTimeout); // triggers updateViews() } void ContextBrowserPlugin::updateReady(const IndexedString& file, const ReferencedTopDUContext& /*topContext*/) { const auto url = file.toUrl(); for(QMap< View*, ViewHighlights >::iterator it = m_highlightedRanges.begin(); it != m_highlightedRanges.end(); ++it) { if(it.key()->document()->url() == url) { if(!m_updateViews.contains(it.key())) { qCDebug(PLUGIN_CONTEXTBROWSER) << "adding view for update"; m_updateViews << it.key(); // Don't change the highlighted declaration after finished parse-jobs (*it).keep = true; } } } if(!m_updateViews.isEmpty()) m_updateTimer->start(highlightingTimeout); } void ContextBrowserPlugin::textDocumentCreated( KDevelop::IDocument* document ) { Q_ASSERT(document->textDocument()); connect( document->textDocument(), &KTextEditor::Document::viewCreated, this, &ContextBrowserPlugin::viewCreated ); foreach( View* view, document->textDocument()->views() ) viewCreated( document->textDocument(), view ); } void ContextBrowserPlugin::documentActivated( IDocument* doc ) { if (m_outlineLine) m_outlineLine->clear(); if (View* view = doc->activeTextView()) { cursorPositionChanged(view, view->cursorPosition()); } } void ContextBrowserPlugin::viewDestroyed( QObject* obj ) { m_highlightedRanges.remove(static_cast(obj)); m_updateViews.remove(static_cast(obj)); } void ContextBrowserPlugin::selectionChanged( View* view ) { clearMouseHover(); m_updateViews.insert(view); m_updateTimer->start(highlightingTimeout/2); // triggers updateViews() } void ContextBrowserPlugin::cursorPositionChanged( View* view, const KTextEditor::Cursor& newPosition ) { if(view->document() == m_lastInsertionDocument && newPosition == m_lastInsertionPos) { //Do not update the highlighting while typing m_lastInsertionDocument = nullptr; m_lastInsertionPos = KTextEditor::Cursor(); if(m_highlightedRanges.contains(view)) m_highlightedRanges[view].keep = true; }else{ if(m_highlightedRanges.contains(view)) m_highlightedRanges[view].keep = false; } clearMouseHover(); m_updateViews.insert(view); m_updateTimer->start(highlightingTimeout/2); // triggers updateViews() } void ContextBrowserPlugin::textInserted(KTextEditor::Document* doc, const KTextEditor::Cursor& cursor, const QString& text) { m_lastInsertionDocument = doc; m_lastInsertionPos = cursor + KTextEditor::Cursor(0, text.size()); } void ContextBrowserPlugin::viewCreated( KTextEditor::Document* , View* v ) { disconnect( v, &View::cursorPositionChanged, this, &ContextBrowserPlugin::cursorPositionChanged ); ///Just to make sure that multiple connections don't happen connect( v, &View::cursorPositionChanged, this, &ContextBrowserPlugin::cursorPositionChanged ); connect( v, &View::destroyed, this, &ContextBrowserPlugin::viewDestroyed ); disconnect( v->document(), &KTextEditor::Document::textInserted, this, &ContextBrowserPlugin::textInserted); connect(v->document(), &KTextEditor::Document::textInserted, this, &ContextBrowserPlugin::textInserted); disconnect(v, &View::selectionChanged, this, &ContextBrowserPlugin::selectionChanged); KTextEditor::TextHintInterface *iface = dynamic_cast(v); if( !iface ) return; iface->setTextHintDelay(highlightingTimeout); iface->registerTextHintProvider(&m_textHintProvider); } void ContextBrowserPlugin::registerToolView(ContextBrowserView* view) { m_views << view; } void ContextBrowserPlugin::previousUseShortcut() { switchUse(false); } void ContextBrowserPlugin::nextUseShortcut() { switchUse(true); } KTextEditor::Range cursorToRange(KTextEditor::Cursor cursor) { return KTextEditor::Range(cursor, cursor); } void ContextBrowserPlugin::switchUse(bool forward) { View* view = core()->documentController()->activeTextDocumentView(); if(view) { KTextEditor::Document* doc = view->document(); KDevelop::DUChainReadLocker lock( DUChain::lock() ); KDevelop::TopDUContext* chosen = DUChainUtils::standardContextForUrl(doc->url()); if( chosen ) { KTextEditor::Cursor cCurrent(view->cursorPosition()); KDevelop::CursorInRevision c = chosen->transformToLocalRevision(cCurrent); Declaration* decl = nullptr; //If we have a locked declaration, use that for jumping foreach(ContextBrowserView* view, m_views) { decl = view->lockedDeclaration().data(); ///@todo Somehow match the correct context-browser view if there is multiple if(decl) break; } if(!decl) //Try finding a declaration under the cursor decl = DUChainUtils::itemUnderCursor(doc->url(), cCurrent).declaration; if (decl && decl->kind() == Declaration::Alias) { AliasDeclaration* alias = dynamic_cast(decl); Q_ASSERT(alias); DUChainReadLocker lock; decl = alias->aliasedDeclaration().declaration(); } if(decl) { Declaration* target = nullptr; if(forward) //Try jumping from definition to declaration target = DUChainUtils::declarationForDefinition(decl, chosen); else if(decl->url().toUrl() == doc->url() && decl->range().contains(c)) //Try jumping from declaration to definition target = FunctionDefinition::definition(decl); if(target && target != decl) { KTextEditor::Cursor jumpTo = target->rangeInCurrentRevision().start(); QUrl document = target->url().toUrl(); lock.unlock(); core()->documentController()->openDocument( document, cursorToRange(jumpTo) ); return; }else{ //Always work with the declaration instead of the definition decl = DUChainUtils::declarationForDefinition(decl, chosen); } } if(!decl) { //Pick the last use we have highlighted decl = m_lastHighlightedDeclaration.data(); } if(decl) { KDevVarLengthArray usingFiles = DUChain::uses()->uses(decl->id()); if(DUChainUtils::contextHasUse(decl->topContext(), decl) && usingFiles.indexOf(decl->topContext()) == -1) usingFiles.insert(0, decl->topContext()); if(decl->range().contains(c) && decl->url() == chosen->url()) { //The cursor is directly on the declaration. Jump to the first or last use. if(!usingFiles.isEmpty()) { TopDUContext* top = (forward ? usingFiles[0] : usingFiles.back()).data(); if(top) { QList useRanges = allUses(top, decl, true); std::sort(useRanges.begin(), useRanges.end()); if(!useRanges.isEmpty()) { QUrl url = top->url().toUrl(); KTextEditor::Range selectUse = chosen->transformFromLocalRevision(forward ? useRanges.first() : useRanges.back()); lock.unlock(); core()->documentController()->openDocument(url, cursorToRange(selectUse.start())); } } } return; } //Check whether we are within a use QList localUses = allUses(chosen, decl, true); std::sort(localUses.begin(), localUses.end()); for(int a = 0; a < localUses.size(); ++a) { int nextUse = (forward ? a+1 : a-1); bool pick = localUses[a].contains(c); if(!pick && forward && a+1 < localUses.size() && localUses[a].end <= c && localUses[a+1].start > c) { //Special case: We aren't on a use, but we are jumping forward, and are behind this and the next use pick = true; } if(!pick && !forward && a-1 >= 0 && c < localUses[a].start && c >= localUses[a-1].end) { //Special case: We aren't on a use, but we are jumping backward, and are in front of this use, but behind the previous one pick = true; } if(!pick && a == 0 && c < localUses[a].start) { if(!forward) { //Will automatically jump to previous file }else{ nextUse = 0; //We are before the first use, so jump to it. } pick = true; } if(!pick && a == localUses.size()-1 && c >= localUses[a].end) { if(forward) { //Will automatically jump to next file }else{ //We are behind the last use, but moving backward. So pick the last use. nextUse = a; } pick = true; } if(pick) { //Make sure we end up behind the use if(nextUse != a) while(forward && nextUse < localUses.size() && (localUses[nextUse].start <= localUses[a].end || localUses[nextUse].isEmpty())) ++nextUse; //Make sure we end up before the use if(nextUse != a) while(!forward && nextUse >= 0 && (localUses[nextUse].start >= localUses[a].start || localUses[nextUse].isEmpty())) --nextUse; //Jump to the next use qCDebug(PLUGIN_CONTEXTBROWSER) << "count of uses:" << localUses.size() << "nextUse" << nextUse; if(nextUse < 0 || nextUse == localUses.size()) { qCDebug(PLUGIN_CONTEXTBROWSER) << "jumping to next file"; //Jump to the first use in the next using top-context int indexInFiles = usingFiles.indexOf(chosen); if(indexInFiles != -1) { int nextFile = (forward ? indexInFiles+1 : indexInFiles-1); qCDebug(PLUGIN_CONTEXTBROWSER) << "current file" << indexInFiles << "nextFile" << nextFile; if(nextFile < 0 || nextFile >= usingFiles.size()) { //Open the declaration, or the definition if(nextFile >= usingFiles.size()) { Declaration* definition = FunctionDefinition::definition(decl); if(definition) decl = definition; } QUrl u = decl->url().toUrl(); KTextEditor::Range range = decl->rangeInCurrentRevision(); range.setEnd(range.start()); lock.unlock(); core()->documentController()->openDocument(u, range); return; }else{ TopDUContext* nextTop = usingFiles[nextFile].data(); QUrl u = nextTop->url().toUrl(); QList nextTopUses = allUses(nextTop, decl, true); std::sort(nextTopUses.begin(), nextTopUses.end()); if(!nextTopUses.isEmpty()) { KTextEditor::Range range = chosen->transformFromLocalRevision(forward ? nextTopUses.front() : nextTopUses.back()); range.setEnd(range.start()); lock.unlock(); core()->documentController()->openDocument(u, range); } return; } }else{ qCDebug(PLUGIN_CONTEXTBROWSER) << "not found own file in use list"; } }else{ QUrl url = chosen->url().toUrl(); KTextEditor::Range range = chosen->transformFromLocalRevision(localUses[nextUse]); range.setEnd(range.start()); lock.unlock(); core()->documentController()->openDocument(url, range); return; } } } } } } } void ContextBrowserPlugin::unRegisterToolView(ContextBrowserView* view) { m_views.removeAll(view); } // history browsing QWidget* ContextBrowserPlugin::toolbarWidgetForMainWindow( Sublime::MainWindow* window ) { //TODO: support multiple windows (if that ever gets revived) if (!m_toolbarWidget) { m_toolbarWidget = new QWidget(window); } return m_toolbarWidget; } void ContextBrowserPlugin::documentJumpPerformed( KDevelop::IDocument* newDocument, const KTextEditor::Cursor& newCursor, KDevelop::IDocument* previousDocument, const KTextEditor::Cursor& previousCursor) { DUChainReadLocker lock(DUChain::lock()); /*TODO: support multiple windows if that ever gets revived if(newDocument && newDocument->textDocument() && newDocument->textDocument()->activeView() && masterWidget(newDocument->textDocument()->activeView()) != masterWidget(this)) return; */ if(previousDocument && previousCursor.isValid()) { qCDebug(PLUGIN_CONTEXTBROWSER) << "updating jump source"; DUContext* context = getContextAt(previousDocument->url(), previousCursor); if(context) { updateHistory(context, KTextEditor::Cursor(previousCursor), true); }else{ //We just want this place in the history m_history.resize(m_nextHistoryIndex); // discard forward history m_history.append(HistoryEntry(DocumentCursor(IndexedString(previousDocument->url()), KTextEditor::Cursor(previousCursor)))); ++m_nextHistoryIndex; } } qCDebug(PLUGIN_CONTEXTBROWSER) << "new doc: " << newDocument << " new cursor: " << newCursor; if(newDocument && newCursor.isValid()) { qCDebug(PLUGIN_CONTEXTBROWSER) << "updating jump target"; DUContext* context = getContextAt(newDocument->url(), newCursor); if(context) { updateHistory(context, KTextEditor::Cursor(newCursor), true); }else{ //We just want this place in the history m_history.resize(m_nextHistoryIndex); // discard forward history m_history.append(HistoryEntry(DocumentCursor(IndexedString(newDocument->url()), KTextEditor::Cursor(newCursor)))); ++m_nextHistoryIndex; if (m_outlineLine) m_outlineLine->clear(); } } } void ContextBrowserPlugin::updateButtonState() { m_nextButton->setEnabled( m_nextHistoryIndex < m_history.size() ); m_previousButton->setEnabled( m_nextHistoryIndex >= 2 ); } void ContextBrowserPlugin::historyNext() { if(m_nextHistoryIndex >= m_history.size()) { return; } openDocument(m_nextHistoryIndex); // opening the document at given position // will update the widget for us ++m_nextHistoryIndex; updateButtonState(); } void ContextBrowserPlugin::openDocument(int historyIndex) { Q_ASSERT_X(historyIndex >= 0, "openDocument", "negative history index"); Q_ASSERT_X(historyIndex < m_history.size(), "openDocument", "history index out of range"); DocumentCursor c = m_history[historyIndex].computePosition(); if (c.isValid() && !c.document.str().isEmpty()) { disconnect(ICore::self()->documentController(), &IDocumentController::documentJumpPerformed, this, &ContextBrowserPlugin::documentJumpPerformed); ICore::self()->documentController()->openDocument(c.document.toUrl(), c); connect(ICore::self()->documentController(), &IDocumentController::documentJumpPerformed, this, &ContextBrowserPlugin::documentJumpPerformed); KDevelop::DUChainReadLocker lock( KDevelop::DUChain::lock() ); updateDeclarationListBox(m_history[historyIndex].context.data()); } } void ContextBrowserPlugin::historyPrevious() { if(m_nextHistoryIndex < 2) { return; } --m_nextHistoryIndex; openDocument(m_nextHistoryIndex-1); // opening the document at given position // will update the widget for us updateButtonState(); } QString ContextBrowserPlugin::actionTextFor(int historyIndex) const { const HistoryEntry& entry = m_history.at(historyIndex); QString actionText = entry.context.data() ? entry.context.data()->scopeIdentifier(true).toString() : QString(); if(actionText.isEmpty()) actionText = entry.alternativeString; if(actionText.isEmpty()) actionText = QStringLiteral(""); actionText += QLatin1String(" @ "); QString fileName = entry.absoluteCursorPosition.document.toUrl().fileName(); actionText += QStringLiteral("%1:%2").arg(fileName).arg(entry.absoluteCursorPosition.line()+1); return actionText; } /* inline QDebug operator<<(QDebug debug, const ContextBrowserPlugin::HistoryEntry &he) { DocumentCursor c = he.computePosition(); debug << "\n\tHistoryEntry " << c.line << " " << c.document.str(); return debug; } */ void ContextBrowserPlugin::nextMenuAboutToShow() { QList indices; for(int a = m_nextHistoryIndex; a < m_history.size(); ++a) { indices << a; } fillHistoryPopup(m_nextMenu, indices); } void ContextBrowserPlugin::previousMenuAboutToShow() { QList indices; for(int a = m_nextHistoryIndex-2; a >= 0; --a) { indices << a; } fillHistoryPopup(m_previousMenu, indices); } void ContextBrowserPlugin::fillHistoryPopup(QMenu* menu, const QList& historyIndices) { menu->clear(); KDevelop::DUChainReadLocker lock( KDevelop::DUChain::lock() ); foreach(int index, historyIndices) { QAction* action = new QAction(actionTextFor(index), menu); action->setData(index); menu->addAction(action); connect(action, &QAction::triggered, this, &ContextBrowserPlugin::actionTriggered); } } bool ContextBrowserPlugin::isPreviousEntry(KDevelop::DUContext* context, const KTextEditor::Cursor& /*position*/) const { if (m_nextHistoryIndex == 0) return false; Q_ASSERT(m_nextHistoryIndex <= m_history.count()); const HistoryEntry& he = m_history.at(m_nextHistoryIndex-1); KDevelop::DUChainReadLocker lock( KDevelop::DUChain::lock() ); // is this necessary?? Q_ASSERT(context); return IndexedDUContext(context) == he.context; } void ContextBrowserPlugin::updateHistory(KDevelop::DUContext* context, const KTextEditor::Cursor& position, bool force) { qCDebug(PLUGIN_CONTEXTBROWSER) << "updating history"; if(m_outlineLine && m_outlineLine->isVisible()) updateDeclarationListBox(context); if(!context || (!context->owner() && !force)) { return; //Only add history-entries for contexts that have owners, which in practice should be functions and classes //This keeps the history cleaner } if (isPreviousEntry(context, position)) { if(m_nextHistoryIndex) { HistoryEntry& he = m_history[m_nextHistoryIndex-1]; he.setCursorPosition(position); } return; } else { // Append new history entry m_history.resize(m_nextHistoryIndex); // discard forward history m_history.append(HistoryEntry(IndexedDUContext(context), position)); ++m_nextHistoryIndex; updateButtonState(); if(m_history.size() > (maxHistoryLength + 5)) { m_history = m_history.mid(m_history.size() - maxHistoryLength); m_nextHistoryIndex = m_history.size(); } } } void ContextBrowserPlugin::updateDeclarationListBox(DUContext* context) { if(!context || !context->owner()) { qCDebug(PLUGIN_CONTEXTBROWSER) << "not updating box"; m_listUrl = IndexedString(); ///@todo Compute the context in the document here if (m_outlineLine) m_outlineLine->clear(); return; } Declaration* decl = context->owner(); m_listUrl = context->url(); Declaration* specialDecl = SpecializationStore::self().applySpecialization(decl, decl->topContext()); FunctionType::Ptr function = specialDecl->type(); QString text = specialDecl->qualifiedIdentifier().toString(); if(function) text += function->partToString(KDevelop::FunctionType::SignatureArguments); if(m_outlineLine && !m_outlineLine->hasFocus()) { m_outlineLine->setText(text); m_outlineLine->setCursorPosition(0); } qCDebug(PLUGIN_CONTEXTBROWSER) << "updated" << text; } void ContextBrowserPlugin::actionTriggered() { QAction* action = qobject_cast(sender()); Q_ASSERT(action); Q_ASSERT(action->data().type() == QVariant::Int); int historyPosition = action->data().toInt(); // qCDebug(PLUGIN_CONTEXTBROWSER) << "history pos" << historyPosition << m_history.size() << m_history; if(historyPosition >= 0 && historyPosition < m_history.size()) { m_nextHistoryIndex = historyPosition + 1; openDocument(historyPosition); updateButtonState(); } } void ContextBrowserPlugin::doNavigate(NavigationActionType action) { KTextEditor::View* view = qobject_cast(sender()); if(!view) { qCWarning(PLUGIN_CONTEXTBROWSER) << "sender is not a view"; return; } KTextEditor::CodeCompletionInterface* iface = qobject_cast(view); if(!iface || iface->isCompletionActive()) return; // If code completion is active, the actions should be handled by the completion widget QWidget* widget = m_currentNavigationWidget.data(); if(!widget || !widget->isVisible()) { ContextBrowserView* contextView = browserViewForWidget(view); if(contextView) widget = contextView->navigationWidget(); } if(widget) { AbstractNavigationWidget* navWidget = qobject_cast(widget); if (navWidget) { switch(action) { case Accept: navWidget->accept(); break; case Back: navWidget->back(); break; case Left: navWidget->previous(); break; case Right: navWidget->next(); break; case Up: navWidget->up(); break; case Down: navWidget->down(); break; } } } } void ContextBrowserPlugin::navigateAccept() { doNavigate(Accept); } void ContextBrowserPlugin::navigateBack() { doNavigate(Back); } void ContextBrowserPlugin::navigateDown() { doNavigate(Down); } void ContextBrowserPlugin::navigateLeft() { doNavigate(Left); } void ContextBrowserPlugin::navigateRight() { doNavigate(Right); } void ContextBrowserPlugin::navigateUp() { doNavigate(Up); } //BEGIN HistoryEntry ContextBrowserPlugin::HistoryEntry::HistoryEntry(KDevelop::DocumentCursor pos) : absoluteCursorPosition(pos) { } ContextBrowserPlugin::HistoryEntry::HistoryEntry(IndexedDUContext ctx, const KTextEditor::Cursor& cursorPosition) : context(ctx) { //Use a position relative to the context setCursorPosition(cursorPosition); if(ctx.data()) alternativeString = ctx.data()->scopeIdentifier(true).toString(); if(!alternativeString.isEmpty()) alternativeString += i18n("(changed)"); //This is used when the context was deleted in between } DocumentCursor ContextBrowserPlugin::HistoryEntry::computePosition() const { KDevelop::DUChainReadLocker lock( KDevelop::DUChain::lock() ); DocumentCursor ret; if(context.data()) { ret = DocumentCursor(context.data()->url(), relativeCursorPosition); ret.setLine(ret.line() + context.data()->range().start.line); }else{ ret = absoluteCursorPosition; } return ret; } void ContextBrowserPlugin::HistoryEntry::setCursorPosition(const KTextEditor::Cursor& cursorPosition) { KDevelop::DUChainReadLocker lock( KDevelop::DUChain::lock() ); if(context.data()) { absoluteCursorPosition = DocumentCursor(context.data()->url(), cursorPosition); relativeCursorPosition = cursorPosition; relativeCursorPosition.setLine(relativeCursorPosition.line() - context.data()->range().start.line); } } // kate: space-indent on; indent-width 2; tab-width 4; replace-tabs on; auto-insert-doxygen on #include "contextbrowser.moc" diff --git a/plugins/contextbrowser/debug.h b/plugins/contextbrowser/debug.h deleted file mode 100644 index 8ec62aed0..000000000 --- a/plugins/contextbrowser/debug.h +++ /dev/null @@ -1,26 +0,0 @@ -/* - * This file is part of KDevelop - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#ifndef KDEVPLATFORM_CONTEXTBROWSER_DEBUG_H -#define KDEVPLATFORM_CONTEXTBROWSER_DEBUG_H - -#include -Q_DECLARE_LOGGING_CATEGORY(PLUGIN_CONTEXTBROWSER) - -#endif diff --git a/plugins/cvs/CMakeLists.txt b/plugins/cvs/CMakeLists.txt index 2ef636e76..af1efa7e8 100644 --- a/plugins/cvs/CMakeLists.txt +++ b/plugins/cvs/CMakeLists.txt @@ -1,46 +1,53 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kdevcvs\") -add_subdirectory(tests) - ########### next target ############### +ecm_qt_declare_logging_category(kdevcvs_LOG_PART_SRCS + HEADER debug.h + IDENTIFIER PLUGIN_CVS + CATEGORY_NAME "kdevplatform.plugins.cvs" +) + set(kdevcvs_PART_SRCS cvsplugin.cpp cvsmainview.cpp cvsgenericoutputview.cpp cvsjob.cpp cvsproxy.cpp editorsview.cpp commitdialog.cpp importmetadatawidget.cpp importdialog.cpp checkoutdialog.cpp cvsannotatejob.cpp cvslogjob.cpp cvsdiffjob.cpp cvsstatusjob.cpp + ${kdevcvs_LOG_PART_SRCS} ) set(kdevcvs_PART_UI cvsmainview.ui cvsgenericoutputview.ui editorsview.ui commitdialog.ui importmetadatawidget.ui checkoutdialog.ui ) ki18n_wrap_ui(kdevcvs_PART_SRCS ${kdevcvs_PART_UI}) qt5_add_resources(kdevcvs_PART_SRCS kdevcvs.qrc) kdevplatform_add_plugin(kdevcvs JSON kdevcvs.json SOURCES ${kdevcvs_PART_SRCS}) target_link_libraries(kdevcvs KF5::KIOWidgets KF5::Parts KDev::Util KDev::Interfaces KDev::Vcs KDev::Project KDev::Language ) + +add_subdirectory(tests) diff --git a/plugins/cvs/cvsplugin.cpp b/plugins/cvs/cvsplugin.cpp index 0b2f7ceba..11dc3c0a8 100644 --- a/plugins/cvs/cvsplugin.cpp +++ b/plugins/cvs/cvsplugin.cpp @@ -1,493 +1,492 @@ /*************************************************************************** * Copyright 2007 Robert Gruber * * * * 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 "cvsplugin.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cvsmainview.h" #include "cvsproxy.h" #include "cvsjob.h" #include "editorsview.h" #include "commitdialog.h" #include "cvsgenericoutputview.h" #include "checkoutdialog.h" #include "importdialog.h" #include "importmetadatawidget.h" #include "debug.h" #include #include #include -Q_LOGGING_CATEGORY(PLUGIN_CVS, "kdevplatform.plugins.cvs") K_PLUGIN_FACTORY(KDevCvsFactory, registerPlugin();) // K_EXPORT_PLUGIN(KDevCvsFactory(KAboutData("kdevcvs", "kdevcvs", ki18n("CVS"), "0.1", ki18n("Support for CVS version control system"), KAboutData::License_GPL))) class KDevCvsViewFactory: public KDevelop::IToolViewFactory { public: explicit KDevCvsViewFactory(CvsPlugin *plugin): m_plugin(plugin) {} QWidget* create(QWidget *parent = nullptr) override { return new CvsMainView(m_plugin, parent); } Qt::DockWidgetArea defaultPosition() override { return Qt::BottomDockWidgetArea; } QString id() const override { return QStringLiteral("org.kdevelop.CVSView"); } private: CvsPlugin *m_plugin; }; class CvsPluginPrivate { public: explicit CvsPluginPrivate(CvsPlugin *pThis) : m_factory(new KDevCvsViewFactory(pThis)) , m_proxy(new CvsProxy(pThis)) , m_common(new KDevelop::VcsPluginHelper(pThis, pThis)) {} KDevCvsViewFactory* m_factory; QPointer m_proxy; QScopedPointer m_common; }; CvsPlugin::CvsPlugin(QObject *parent, const QVariantList &) : KDevelop::IPlugin(QStringLiteral("kdevcvs"), parent) , d(new CvsPluginPrivate(this)) { core()->uiController()->addToolView(i18n("CVS"), d->m_factory); setXMLFile(QStringLiteral("kdevcvs.rc")); setupActions(); } CvsPlugin::~CvsPlugin() { } void CvsPlugin::unload() { core()->uiController()->removeToolView( d->m_factory ); } CvsProxy* CvsPlugin::proxy() { return d->m_proxy; } void CvsPlugin::setupActions() { QAction *action; action = actionCollection()->addAction(QStringLiteral("cvs_import")); action->setText(i18n("Import Directory...")); connect(action, &QAction::triggered, this, &CvsPlugin::slotImport); action = actionCollection()->addAction(QStringLiteral("cvs_checkout")); action->setText(i18n("Checkout...")); connect(action, &QAction::triggered, this, &CvsPlugin::slotCheckout); action = actionCollection()->addAction(QStringLiteral("cvs_status")); action->setText(i18n("Status...")); connect(action, &QAction::triggered, this, &CvsPlugin::slotStatus); } const QUrl CvsPlugin::urlFocusedDocument() const { KParts::ReadOnlyPart *plugin = dynamic_cast(core()->partController()->activePart()); if (plugin) { if (plugin->url().isLocalFile()) { return plugin->url(); } } return QUrl(); } void CvsPlugin::slotImport() { QUrl url = urlFocusedDocument(); ImportDialog dlg(this, url); dlg.exec(); } void CvsPlugin::slotCheckout() { ///@todo don't use proxy directly; use interface instead CheckoutDialog dlg(this); dlg.exec(); } void CvsPlugin::slotStatus() { QUrl url = urlFocusedDocument(); QList urls; urls << url; KDevelop::VcsJob* j = status(urls, KDevelop::IBasicVersionControl::Recursive); CvsJob* job = dynamic_cast(j); if (job) { CvsGenericOutputView* view = new CvsGenericOutputView(job); emit addNewTabToMainView(view, i18n("Status")); KDevelop::ICore::self()->runController()->registerJob(job); } } KDevelop::ContextMenuExtension CvsPlugin::contextMenuExtension(KDevelop::Context* context) { d->m_common->setupFromContext(context); QList const & ctxUrlList = d->m_common->contextUrlList(); bool hasVersionControlledEntries = false; foreach(const QUrl &url, ctxUrlList) { if (d->m_proxy->isValidDirectory(url)) { hasVersionControlledEntries = true; break; } } qCDebug(PLUGIN_CVS) << "version controlled?" << hasVersionControlledEntries; if (!hasVersionControlledEntries) return IPlugin::contextMenuExtension(context); QMenu* menu = d->m_common->commonActions(); menu->addSeparator(); QAction *action; // Just add actions which are not covered by the cvscommon plugin action = new QAction(i18n("Edit"), this); connect(action, &QAction::triggered, this, &CvsPlugin::ctxEdit); menu->addAction(action); action = new QAction(i18n("Unedit"), this); connect(action, &QAction::triggered, this, &CvsPlugin::ctxUnEdit); menu->addAction(action); action = new QAction(i18n("Show Editors"), this); connect(action, &QAction::triggered, this, &CvsPlugin::ctxEditors); menu->addAction(action); KDevelop::ContextMenuExtension menuExt; menuExt.addAction(KDevelop::ContextMenuExtension::VcsGroup, menu->menuAction()); return menuExt; } void CvsPlugin::ctxEdit() { QList const & urls = d->m_common->contextUrlList(); Q_ASSERT(!urls.empty()); KDevelop::VcsJob* j = edit(urls.front()); CvsJob* job = dynamic_cast(j); if (job) { connect(job, &CvsJob::result, this, &CvsPlugin::jobFinished); KDevelop::ICore::self()->runController()->registerJob(job); } } void CvsPlugin::ctxUnEdit() { QList const & urls = d->m_common->contextUrlList(); Q_ASSERT(!urls.empty()); KDevelop::VcsJob* j = unedit(urls.front()); CvsJob* job = dynamic_cast(j); if (job) { connect(job, &CvsJob::result, this, &CvsPlugin::jobFinished); KDevelop::ICore::self()->runController()->registerJob(job); } } void CvsPlugin::ctxEditors() { QList const & urls = d->m_common->contextUrlList(); Q_ASSERT(!urls.empty()); CvsJob* job = d->m_proxy->editors(findWorkingDir(urls.front()), urls); if (job) { KDevelop::ICore::self()->runController()->registerJob(job); EditorsView* view = new EditorsView(job); emit addNewTabToMainView(view, i18n("Editors")); } } QString CvsPlugin::findWorkingDir(const QUrl& location) { QFileInfo fileInfo(location.toLocalFile()); // find out correct working directory if (fileInfo.isFile()) { return fileInfo.absolutePath(); } else { return fileInfo.absoluteFilePath(); } } // Begin: KDevelop::IBasicVersionControl bool CvsPlugin::isValidRemoteRepositoryUrl(const QUrl& remoteLocation) { Q_UNUSED(remoteLocation); // TODO return false; } bool CvsPlugin::isVersionControlled(const QUrl & localLocation) { return d->m_proxy->isVersionControlled(localLocation); } KDevelop::VcsJob * CvsPlugin::repositoryLocation(const QUrl & localLocation) { Q_UNUSED(localLocation); return nullptr; } KDevelop::VcsJob * CvsPlugin::add(const QList & localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion) { CvsJob* job = d->m_proxy->add(findWorkingDir(localLocations[0]), localLocations, (recursion == KDevelop::IBasicVersionControl::Recursive) ? true : false); return job; } KDevelop::VcsJob * CvsPlugin::remove(const QList & localLocations) { CvsJob* job = d->m_proxy->remove(findWorkingDir(localLocations[0]), localLocations); return job; } KDevelop::VcsJob * CvsPlugin::localRevision(const QUrl & localLocation, KDevelop::VcsRevision::RevisionType) { Q_UNUSED(localLocation) return nullptr; } KDevelop::VcsJob * CvsPlugin::status(const QList & localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion) { CvsJob* job = d->m_proxy->status(findWorkingDir(localLocations[0]), localLocations, (recursion == KDevelop::IBasicVersionControl::Recursive) ? true : false); return job; } KDevelop::VcsJob * CvsPlugin::unedit(const QUrl& localLocation) { CvsJob* job = d->m_proxy->unedit(findWorkingDir(localLocation), QList() << localLocation); return job; } KDevelop::VcsJob * CvsPlugin::edit(const QUrl& localLocation) { CvsJob* job = d->m_proxy->edit(findWorkingDir(localLocation), QList() << localLocation); return job; } KDevelop::VcsJob * CvsPlugin::copy(const QUrl & localLocationSrc, const QUrl & localLocationDstn) { bool ok = QFile::copy(localLocationSrc.toLocalFile(), localLocationDstn.path()); if (!ok) { return nullptr; } QList listDstn; listDstn << localLocationDstn; CvsJob* job = d->m_proxy->add(findWorkingDir(localLocationDstn), listDstn, true); return job; } KDevelop::VcsJob * CvsPlugin::move(const QUrl &, const QUrl &) { return nullptr; } KDevelop::VcsJob * CvsPlugin::revert(const QList & localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion) { KDevelop::VcsRevision rev; CvsJob* job = d->m_proxy->update(findWorkingDir(localLocations[0]), localLocations, rev, QStringLiteral("-C"), (recursion == KDevelop::IBasicVersionControl::Recursive) ? true : false, false, false); return job; } KDevelop::VcsJob * CvsPlugin::update(const QList & localLocations, const KDevelop::VcsRevision & rev, KDevelop::IBasicVersionControl::RecursionMode recursion) { CvsJob* job = d->m_proxy->update(findWorkingDir(localLocations[0]), localLocations, rev, QString(), (recursion == KDevelop::IBasicVersionControl::Recursive) ? true : false, false, false); return job; } KDevelop::VcsJob * CvsPlugin::commit(const QString & message, const QList & localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion) { Q_UNUSED(recursion); QString msg = message; if (msg.isEmpty()) { CommitDialog dlg; if (dlg.exec() == QDialog::Accepted) { msg = dlg.message(); } } CvsJob* job = d->m_proxy->commit(findWorkingDir(localLocations[0]), localLocations, msg); return job; } KDevelop::VcsJob * CvsPlugin::diff(const QUrl & fileOrDirectory, const KDevelop::VcsRevision & srcRevision, const KDevelop::VcsRevision & dstRevision, KDevelop::VcsDiff::Type, KDevelop::IBasicVersionControl::RecursionMode) { CvsJob* job = d->m_proxy->diff(fileOrDirectory, srcRevision, dstRevision, QStringLiteral("-uN")/*always unified*/); return job; } KDevelop::VcsJob * CvsPlugin::log(const QUrl & localLocation, const KDevelop::VcsRevision & rev, unsigned long limit) { Q_UNUSED(limit) CvsJob* job = d->m_proxy->log(localLocation, rev); return job; } KDevelop::VcsJob * CvsPlugin::log(const QUrl & localLocation, const KDevelop::VcsRevision & rev, const KDevelop::VcsRevision & limit) { Q_UNUSED(limit) return log(localLocation, rev, 0); } KDevelop::VcsJob * CvsPlugin::annotate(const QUrl & localLocation, const KDevelop::VcsRevision & rev) { CvsJob* job = d->m_proxy->annotate(localLocation, rev); return job; } KDevelop::VcsJob * CvsPlugin::resolve(const QList & localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion) { Q_UNUSED(localLocations); Q_UNUSED(recursion); return nullptr; } KDevelop::VcsJob * CvsPlugin::import(const QString& commitMessage, const QUrl& sourceDirectory, const KDevelop::VcsLocation& destinationRepository) { if (commitMessage.isEmpty() || !sourceDirectory.isLocalFile() || !destinationRepository.isValid() || destinationRepository.type() != KDevelop::VcsLocation::RepositoryLocation) { return nullptr; } qCDebug(PLUGIN_CVS) << "CVS Import requested " << "src:" << sourceDirectory.toLocalFile() << "srv:" << destinationRepository.repositoryServer() << "module:" << destinationRepository.repositoryModule(); CvsJob* job = d->m_proxy->import(sourceDirectory, destinationRepository.repositoryServer(), destinationRepository.repositoryModule(), destinationRepository.userData().toString(), destinationRepository.repositoryTag(), commitMessage); return job; } KDevelop::VcsJob * CvsPlugin::createWorkingCopy(const KDevelop::VcsLocation & sourceRepository, const QUrl & destinationDirectory, KDevelop::IBasicVersionControl::RecursionMode recursion) { Q_UNUSED(recursion); if (!destinationDirectory.isLocalFile() || !sourceRepository.isValid() || sourceRepository.type() != KDevelop::VcsLocation::RepositoryLocation) { return nullptr; } qCDebug(PLUGIN_CVS) << "CVS Checkout requested " << "dest:" << destinationDirectory.toLocalFile() << "srv:" << sourceRepository.repositoryServer() << "module:" << sourceRepository.repositoryModule() << "branch:" << sourceRepository.repositoryBranch() << endl; CvsJob* job = d->m_proxy->checkout(destinationDirectory, sourceRepository.repositoryServer(), sourceRepository.repositoryModule(), QString(), sourceRepository.repositoryBranch(), true, true); return job; } QString CvsPlugin::name() const { return i18n("CVS"); } KDevelop::VcsImportMetadataWidget* CvsPlugin::createImportMetadataWidget(QWidget* parent) { return new ImportMetadataWidget(parent); } KDevelop::VcsLocationWidget* CvsPlugin::vcsLocation(QWidget* parent) const { return new KDevelop::StandardVcsLocationWidget(parent); } // End: KDevelop::IBasicVersionControl #include "cvsplugin.moc" diff --git a/plugins/cvs/cvsproxy.cpp b/plugins/cvs/cvsproxy.cpp index 287cb3efe..812d13067 100644 --- a/plugins/cvs/cvsproxy.cpp +++ b/plugins/cvs/cvsproxy.cpp @@ -1,480 +1,480 @@ /*************************************************************************** * Copyright 2007 Robert Gruber * * * * 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 "cvsproxy.h" #include #include #include #include #include #include #include #include "cvsjob.h" #include "cvsannotatejob.h" #include "cvslogjob.h" #include "cvsstatusjob.h" #include "cvsdiffjob.h" -#include "debug.h" +#include #include CvsProxy::CvsProxy(KDevelop::IPlugin* parent) : QObject(parent), vcsplugin(parent) { } CvsProxy::~CvsProxy() { } bool CvsProxy::isValidDirectory(QUrl dirPath) const { const QFileInfo fsObject( dirPath.toLocalFile() ); QDir dir = fsObject.isDir() ? fsObject.absoluteDir() : fsObject.dir(); return dir.exists(QStringLiteral("CVS")); } bool CvsProxy::isVersionControlled(QUrl filePath) const { const QFileInfo fsObject( filePath.toLocalFile() ); QDir dir = fsObject.isDir() ? fsObject.absoluteDir() : fsObject.dir(); if( !dir.cd(QStringLiteral("CVS")) ) return false; if( fsObject.isDir() ) return true; QFile cvsEntries( dir.absoluteFilePath(QStringLiteral("Entries")) ); cvsEntries.open( QIODevice::ReadOnly ); QString cvsEntriesData = cvsEntries.readAll(); cvsEntries.close(); return ( cvsEntriesData.indexOf( fsObject.fileName() ) != -1 ); } bool CvsProxy::prepareJob(CvsJob* job, const QString& repository, enum RequestedOperation op) { // Only do this check if it's a normal operation like diff, log ... // For other operations like "cvs import" isValidDirectory() would fail as the // directory is not yet under CVS control if (op == CvsProxy::NormalOperation && !isValidDirectory(QUrl::fromLocalFile(repository))) { qCDebug(PLUGIN_CVS) << repository << " is not a valid CVS repository"; return false; } // clear commands and args from a possible previous run job->clear(); // setup the working directory for the new job job->setDirectory(repository); return true; } bool CvsProxy::addFileList(CvsJob* job, const QString& repository, const QList& urls) { QStringList args; QDir repoDir(repository); foreach(const QUrl &url, urls) { ///@todo this is ok for now, but what if some of the urls are not /// to the given repository const QString file = repoDir.relativeFilePath(url.toLocalFile()); args << KShell::quoteArg( file ); } *job << args; return true; } QString CvsProxy::convertVcsRevisionToString(const KDevelop::VcsRevision & rev) { QString str; switch (rev.revisionType()) { case KDevelop::VcsRevision::Special: break; case KDevelop::VcsRevision::FileNumber: if (rev.revisionValue().isValid()) str = "-r"+rev.revisionValue().toString(); break; case KDevelop::VcsRevision::Date: if (rev.revisionValue().isValid()) str = "-D"+rev.revisionValue().toString(); break; case KDevelop::VcsRevision::GlobalNumber: // !! NOT SUPPORTED BY CVS !! default: break; } return str; } QString CvsProxy::convertRevisionToPrevious(const KDevelop::VcsRevision& rev) { QString str; // this only works if the revision is a real revisionnumber and not a date or special switch (rev.revisionType()) { case KDevelop::VcsRevision::FileNumber: if (rev.revisionValue().isValid()) { QString orig = rev.revisionValue().toString(); // First we need to find the base (aka branch-part) of the revision number which will not change QString base(orig); base.truncate(orig.lastIndexOf(QLatin1Char('.'))); // next we need to cut off the last part of the revision number // this number is a count of revisions with a branch // so if we want to diff to the previous we just need to lower it by one int number = orig.midRef(orig.lastIndexOf(QLatin1Char('.'))+1).toInt(); if (number > 1) // of course this is only possible if our revision is not the first on the branch number--; str = QStringLiteral("-r") + base + '.' + QString::number(number); qCDebug(PLUGIN_CVS) << "Converted revision "<(); if (specialtype == KDevelop::VcsRevision::Previous) { rA = convertRevisionToPrevious(revB); } } else { rA = convertVcsRevisionToString(revA); } if (!rA.isEmpty()) *job << rA; QString rB = convertVcsRevisionToString(revB); if (!rB.isEmpty()) *job << rB; // in case the QUrl is a directory there is no filename if (!info.fileName().isEmpty()) *job << KShell::quoteArg(info.fileName()); return job; } delete job; return nullptr; } CvsJob * CvsProxy::annotate(const QUrl& url, const KDevelop::VcsRevision& rev) { QFileInfo info(url.toLocalFile()); CvsAnnotateJob* job = new CvsAnnotateJob(vcsplugin); if ( prepareJob(job, info.absolutePath()) ) { *job << "cvs"; *job << "annotate"; QString revision = convertVcsRevisionToString(rev); if (!revision.isEmpty()) *job << revision; *job << KShell::quoteArg(info.fileName()); return job; } delete job; return nullptr; } CvsJob* CvsProxy::edit(const QString& repo, const QList& files) { CvsJob* job = new CvsJob(vcsplugin); if ( prepareJob(job, repo) ) { *job << "cvs"; *job << "edit"; addFileList(job, repo, files); return job; } delete job; return nullptr; } CvsJob* CvsProxy::unedit(const QString& repo, const QList& files) { CvsJob* job = new CvsJob(vcsplugin); if ( prepareJob(job, repo) ) { *job << "cvs"; *job << "unedit"; addFileList(job, repo, files); return job; } delete job; return nullptr; } CvsJob* CvsProxy::editors(const QString& repo, const QList& files) { CvsJob* job = new CvsJob(vcsplugin); if ( prepareJob(job, repo) ) { *job << "cvs"; *job << "editors"; addFileList(job, repo, files); return job; } delete job; return nullptr; } CvsJob* CvsProxy::commit(const QString& repo, const QList& files, const QString& message) { CvsJob* job = new CvsJob(vcsplugin); if ( prepareJob(job, repo) ) { *job << "cvs"; *job << "commit"; *job << "-m"; *job << KShell::quoteArg( message ); addFileList(job, repo, files); return job; } delete job; return nullptr; } CvsJob* CvsProxy::add(const QString& repo, const QList& files, bool recursiv, bool binary) { Q_UNUSED(recursiv); // FIXME recursiv is not implemented yet CvsJob* job = new CvsJob(vcsplugin); if ( prepareJob(job, repo) ) { *job << "cvs"; *job << "add"; if (binary) { *job << "-kb"; } addFileList(job, repo, files); return job; } delete job; return nullptr; } CvsJob * CvsProxy::remove(const QString& repo, const QList& files) { CvsJob* job = new CvsJob(vcsplugin); if ( prepareJob(job, repo) ) { *job << "cvs"; *job << "remove"; *job << "-f"; //existing files will be deleted addFileList(job, repo, files); return job; } delete job; return nullptr; } CvsJob * CvsProxy::update(const QString& repo, const QList& files, const KDevelop::VcsRevision & rev, const QString & updateOptions, bool recursive, bool pruneDirs, bool createDirs) { CvsJob* job = new CvsJob(vcsplugin); if ( prepareJob(job, repo) ) { *job << "cvs"; *job << "update"; if (recursive) *job << "-R"; else *job << "-L"; if (pruneDirs) *job << "-P"; if (createDirs) *job << "-d"; if (!updateOptions.isEmpty()) *job << updateOptions; QString revision = convertVcsRevisionToString(rev); if (!revision.isEmpty()) *job << revision; addFileList(job, repo, files); return job; } delete job; return nullptr; } CvsJob * CvsProxy::import(const QUrl& directory, const QString & server, const QString & repositoryName, const QString & vendortag, const QString & releasetag, const QString& message) { CvsJob* job = new CvsJob(vcsplugin); if ( prepareJob(job, directory.toLocalFile(), CvsProxy::Import) ) { *job << "cvs"; *job << "-q"; // don't print directory changes *job << "-d"; *job << server; *job << "import"; *job << "-m"; *job << KShell::quoteArg( message ); *job << repositoryName; *job << vendortag; *job << releasetag; return job; } delete job; return nullptr; } CvsJob * CvsProxy::checkout(const QUrl& targetDir, const QString & server, const QString & module, const QString & checkoutOptions, const QString & revision, bool recursive, bool pruneDirs) { CvsJob* job = new CvsJob(vcsplugin); ///@todo when doing a checkout we don't have the targetdir yet, /// for now it'll work to just run the command from the root if ( prepareJob(job, QStringLiteral("/"), CvsProxy::CheckOut) ) { *job << "cvs"; *job << "-q"; // don't print directory changes *job << "-d" << server; *job << "checkout"; if (!checkoutOptions.isEmpty()) *job << checkoutOptions; if (!revision.isEmpty()) { *job << "-r" << revision; } if (pruneDirs) *job << "-P"; if (!recursive) *job << "-l"; *job << "-d" << targetDir.toString(QUrl::PreferLocalFile | QUrl::StripTrailingSlash); *job << module; return job; } delete job; return nullptr; } CvsJob * CvsProxy::status(const QString & repo, const QList & files, bool recursive, bool taginfo) { CvsStatusJob* job = new CvsStatusJob(vcsplugin); job->setCommunicationMode( KProcess::MergedChannels ); if ( prepareJob(job, repo) ) { *job << "cvs"; *job << "status"; if (recursive) *job << "-R"; else *job << "-l"; if (taginfo) *job << "-v"; addFileList(job, repo, files); return job; } delete job; return nullptr; } diff --git a/plugins/cvs/debug.h b/plugins/cvs/debug.h deleted file mode 100644 index 244554103..000000000 --- a/plugins/cvs/debug.h +++ /dev/null @@ -1,26 +0,0 @@ -/* - * This file is part of KDevelop - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#ifndef KDEVPLATFORM_CVS_DEBUG_H -#define KDEVPLATFORM_CVS_DEBUG_H - -#include -Q_DECLARE_LOGGING_CATEGORY(PLUGIN_CVS) - -#endif diff --git a/plugins/cvs/tests/CMakeLists.txt b/plugins/cvs/tests/CMakeLists.txt index d0a03321c..244fe7c4d 100644 --- a/plugins/cvs/tests/CMakeLists.txt +++ b/plugins/cvs/tests/CMakeLists.txt @@ -1,43 +1,45 @@ # Due to the use of system() and some unix-style paths this test will only run # under Linux. (Maybe this can be fixed later) # # Moreover, I'm not sure if there is a cvs commandline client for windows # (need to check this out ...) if (UNIX) # Running the test only makes sense if the cvs command line client # is present. So check for it before adding the test ... find_program(CVS NAMES cvs PATHS /bin /usr/bin /usr/local/bin ) if (CVS) include_directories( - ${KDevPlatform_SOURCE_DIR}/plugins/cvs + .. + ${CMAKE_CURRENT_BINARY_DIR}/.. ) set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) set(test_cvs_SRCS test_cvs.cpp ../cvsjob.cpp ../cvsproxy.cpp ../cvsannotatejob.cpp ../cvslogjob.cpp ../cvsstatusjob.cpp ../cvsdiffjob.cpp + ${kdevcvs_LOG_PART_SRCS} ) ecm_add_test(${test_cvs_SRCS} TEST_NAME test_cvs LINK_LIBRARIES Qt5::Test KDev::Util KDev::Vcs KDev::Tests ) endif () endif () diff --git a/plugins/cvs/tests/test_cvs.cpp b/plugins/cvs/tests/test_cvs.cpp index 0000f8705..86332b186 100644 --- a/plugins/cvs/tests/test_cvs.cpp +++ b/plugins/cvs/tests/test_cvs.cpp @@ -1,148 +1,144 @@ /*************************************************************************** * Copyright 2007 Robert Gruber * * * * 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 "test_cvs.h" #include -#include #include #include #include #include #include #include #define CVSTEST_BASEDIR "/tmp/kdevcvs_testdir/" #define CVS_REPO CVSTEST_BASEDIR "repo/" #define CVS_IMPORT CVSTEST_BASEDIR "import/" #define CVS_TESTFILE_NAME "testfile" #define CVS_CHECKOUT CVSTEST_BASEDIR "working/" -// we need to add this since it is declared in cvsplugin.cpp which we don't compile here -Q_LOGGING_CATEGORY(PLUGIN_CVS, "kdevplatform.plugins.cvs") - void TestCvs::initTestCase() { KDevelop::AutoTestShell::init(); KDevelop::TestCore::initialize(KDevelop::Core::NoUi); m_proxy = new CvsProxy; // If the basedir for this cvs test exists from a // previous run; remove it... cleanup(); } void TestCvs::cleanupTestCase() { KDevelop::TestCore::shutdown(); delete m_proxy; } void TestCvs::init() { // Now create the basic directory structure QDir tmpdir(QStringLiteral("/tmp")); tmpdir.mkdir(CVSTEST_BASEDIR); tmpdir.mkdir(CVS_REPO); tmpdir.mkdir(CVS_IMPORT); } void TestCvs::cleanup() { if ( QFileInfo::exists(CVSTEST_BASEDIR) ) KIO::del(QUrl::fromLocalFile(QStringLiteral(CVSTEST_BASEDIR)))->exec(); } void TestCvs::repoInit() { // make job that creates the local repository CvsJob* j = new CvsJob(0); QVERIFY( j ); j->setDirectory(CVSTEST_BASEDIR); *j << "cvs" << "-d" << CVS_REPO << "init"; // try to start the job QVERIFY( j->exec() ); //check if the CVSROOT directory in the new local repository exists now QVERIFY( QFileInfo::exists(QString(CVS_REPO "/CVSROOT")) ); } void TestCvs::importTestData() { // create a file so we don't import an empty dir QFile f(CVS_IMPORT "" CVS_TESTFILE_NAME); if(f.open(QIODevice::WriteOnly)) { QTextStream input( &f ); input << "HELLO WORLD"; } f.flush(); CvsJob* j = m_proxy->import(QUrl::fromLocalFile(CVS_IMPORT), CVS_REPO, QStringLiteral("test"), QStringLiteral("vendor"), QStringLiteral("release"), QStringLiteral("test import message")); QVERIFY( j ); // try to start the job QVERIFY( j->exec() ); //check if the directory has been added to the repository QString testdir(CVS_REPO "/test"); QVERIFY( QFileInfo::exists(testdir) ); //check if the file has been added to the repository QString testfile(CVS_REPO "/test/" CVS_TESTFILE_NAME ",v"); QVERIFY( QFileInfo::exists(testfile) ); } void TestCvs::checkoutTestData() { CvsJob* j = m_proxy->checkout(QUrl::fromLocalFile(CVS_CHECKOUT), CVS_REPO, QStringLiteral("test")); QVERIFY( j ); // try to start the job QVERIFY( j->exec() ); //check if the directory is there QString testdir(CVS_CHECKOUT); QVERIFY( QFileInfo::exists(testdir) ); //check if the file is there QString testfile(CVS_CHECKOUT "" CVS_TESTFILE_NAME); QVERIFY( QFileInfo::exists(testfile) ); } void TestCvs::testInitAndImport() { repoInit(); importTestData(); checkoutTestData(); } void TestCvs::testLogFolder() { repoInit(); importTestData(); checkoutTestData(); QString testdir(CVS_CHECKOUT); KDevelop::VcsRevision rev = KDevelop::VcsRevision::createSpecialRevision(KDevelop::VcsRevision::Head); CvsJob* job = m_proxy->log(QUrl::fromLocalFile(testdir), rev); QVERIFY(job); } QTEST_MAIN(TestCvs) diff --git a/plugins/documentswitcher/CMakeLists.txt b/plugins/documentswitcher/CMakeLists.txt index eb8ba8a7b..30da6fa65 100644 --- a/plugins/documentswitcher/CMakeLists.txt +++ b/plugins/documentswitcher/CMakeLists.txt @@ -1,14 +1,20 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kdevdocumentswitcher\") project(documentswitcher) ########### next target ############### set(kdevdocumentswitcher_PART_SRCS documentswitcherplugin.cpp documentswitchertreeview.cpp ) +ecm_qt_declare_logging_category(kdevdocumentswitcher_PART_SRCS + HEADER debug.h + IDENTIFIER PLUGIN_DOCUMENTSWITCHER + CATEGORY_NAME "kdevplatform.plugins.documentswitcher" +) + qt5_add_resources(kdevdocumentswitcher_PART_SRCS kdevdocumentswitcher.qrc) kdevplatform_add_plugin(kdevdocumentswitcher JSON kdevdocumentswitcher.json SOURCES ${kdevdocumentswitcher_PART_SRCS}) target_link_libraries(kdevdocumentswitcher KDev::Interfaces KDev::Sublime KDev::Interfaces KDev::Util KDev::Project ) diff --git a/plugins/documentswitcher/documentswitcherplugin.cpp b/plugins/documentswitcher/documentswitcherplugin.cpp index e2958c489..ae57861f5 100644 --- a/plugins/documentswitcher/documentswitcherplugin.cpp +++ b/plugins/documentswitcher/documentswitcherplugin.cpp @@ -1,396 +1,395 @@ /*************************************************************************** * Copyright 2009,2013 Andreas Pakulat * * Copyright 2013 Jarosław Sierant * * * * 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 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 "documentswitcherplugin.h" #include -#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "documentswitchertreeview.h" +#include "debug.h" #include #include #include #include -Q_LOGGING_CATEGORY(PLUGIN_DOCUMENTSWITCHER, "kdevplatform.plugins.documentswitcher") K_PLUGIN_FACTORY_WITH_JSON(DocumentSwitcherFactory, "kdevdocumentswitcher.json", registerPlugin();) //TODO: Show frame around view's widget while walking through //TODO: Make the widget transparent DocumentSwitcherPlugin::DocumentSwitcherPlugin(QObject *parent, const QVariantList &/*args*/) :KDevelop::IPlugin(QStringLiteral("kdevdocumentswitcher"), parent), view(nullptr) { setXMLFile(QStringLiteral("kdevdocumentswitcher.rc")); qCDebug(PLUGIN_DOCUMENTSWITCHER) << "Adding active mainwindow from constructor" << KDevelop::ICore::self()->uiController()->activeMainWindow(); addMainWindow( qobject_cast( KDevelop::ICore::self()->uiController()->activeMainWindow() ) ); connect( KDevelop::ICore::self()->uiController()->controller(), &Sublime::Controller::mainWindowAdded, this, &DocumentSwitcherPlugin::addMainWindow ); forwardAction = actionCollection()->addAction ( QStringLiteral( "last_used_views_forward" ) ); forwardAction->setText( i18n( "Last Used Views" ) ); forwardAction->setIcon( QIcon::fromTheme( QStringLiteral( "go-next-view-page") ) ); actionCollection()->setDefaultShortcut( forwardAction, Qt::CTRL | Qt::Key_Tab ); forwardAction->setWhatsThis( i18n( "Opens a list to walk through the list of last used views." ) ); forwardAction->setStatusTip( i18n( "Walk through the list of last used views" ) ); connect( forwardAction, &QAction::triggered, this, &DocumentSwitcherPlugin::walkForward ); backwardAction = actionCollection()->addAction ( QStringLiteral( "last_used_views_backward" ) ); backwardAction->setText( i18n( "Last Used Views (Reverse)" ) ); backwardAction->setIcon( QIcon::fromTheme( QStringLiteral( "go-previous-view-page") ) ); actionCollection()->setDefaultShortcut( backwardAction, Qt::CTRL | Qt::SHIFT | Qt::Key_Tab ); backwardAction->setWhatsThis( i18n( "Opens a list to walk through the list of last used views in reverse." ) ); backwardAction->setStatusTip( i18n( "Walk through the list of last used views" ) ); connect( backwardAction, &QAction::triggered, this, &DocumentSwitcherPlugin::walkBackward ); view = new DocumentSwitcherTreeView( this ); view->setSelectionBehavior( QAbstractItemView::SelectRows ); view->setSelectionMode( QAbstractItemView::SingleSelection ); view->setUniformRowHeights( true ); view->setTextElideMode( Qt::ElideMiddle ); view->setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); view->addAction( forwardAction ); view->addAction( backwardAction ); view->setHeaderHidden( true ); view->setIndentation( 10 ); connect( view, &QListView::pressed, this, &DocumentSwitcherPlugin::switchToClicked ); connect( view, &QListView::activated, this, &DocumentSwitcherPlugin::itemActivated ); model = new QStandardItemModel( view ); view->setModel( model ); } void DocumentSwitcherPlugin::setViewGeometry(Sublime::MainWindow* window) { const QSize centralSize = window->centralWidget()->size(); // Maximum size of the view is 3/4th of the central widget (the editor area) so the view does not overlap the // mainwindow since that looks awkward. const QSize viewMaxSize( centralSize.width() * 3/4, centralSize.height() * 3/4 ); // The actual view size should be as big as the columns/rows need it, but smaller than the max-size. This means // the view will get quite high with many open files but I think that is ok. Otherwise one can easily tweak the // max size to be only 1/2th of the central widget size const int rowHeight = view->sizeHintForRow(0); const int frameWidth = view->frameWidth(); const QSize viewSize( std::min( view->sizeHintForColumn(0) + 2 * frameWidth + view->verticalScrollBar()->width(), viewMaxSize.width() ), std::min( std::max( rowHeight * view->model()->rowCount() + 2 * frameWidth, rowHeight * 6 ), viewMaxSize.height() ) ); // Position should be central over the editor area, so map to global from parent of central widget since // the view is positioned in global coords QPoint centralWidgetPos = window->mapToGlobal( window->centralWidget()->pos() ); const int xPos = std::max(0, centralWidgetPos.x() + (centralSize.width() - viewSize.width() ) / 2); const int yPos = std::max(0, centralWidgetPos.y() + (centralSize.height() - viewSize.height() ) / 2); view->setFixedSize(viewSize); view->move(xPos, yPos); } void DocumentSwitcherPlugin::walk(const int from, const int to) { Sublime::MainWindow* window = qobject_cast( KDevelop::ICore::self()->uiController()->activeMainWindow() ); if( !window || !documentLists.contains( window ) || !documentLists[window].contains( window->area() ) ) { qCWarning(PLUGIN_DOCUMENTSWITCHER) << "This should not happen, tried to walk through document list of an unknown mainwindow!"; return; } QModelIndex idx; const int step = from < to ? 1 : -1; if(!view->isVisible()) { fillModel(window); setViewGeometry(window); idx = model->index(from + step, 0); if(!idx.isValid()) { idx = model->index(0, 0); } view->show(); } else { int newRow = view->selectionModel()->currentIndex().row() + step; if(newRow == to + step) { newRow = from; } idx = model->index(newRow, 0); } view->selectionModel()->select(idx, QItemSelectionModel::Rows | QItemSelectionModel::ClearAndSelect); view->selectionModel()->setCurrentIndex(idx, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); } void DocumentSwitcherPlugin::walkForward() { walk(0, model->rowCount()-1); } void DocumentSwitcherPlugin::walkBackward() { walk(model->rowCount()-1, 0); } void DocumentSwitcherPlugin::fillModel( Sublime::MainWindow* window ) { model->clear(); auto projectController = KDevelop::ICore::self()->projectController(); foreach( Sublime::View* v, documentLists[window][window->area()] ) { using namespace KDevelop; Sublime::Document const* const slDoc = v->document(); if( !slDoc ) { continue; } QString itemText = slDoc->title();// file name IDocument const* const doc = dynamic_cast(v->document()); IProject* project = nullptr; if( doc ) { QString path = projectController->prettyFilePath(doc->url(), IProjectController::FormatPlain); const bool isPartOfOpenProject = QDir::isRelativePath(path); if( path.endsWith('/') ) { path.remove(path.length() - 1, 1); } if( isPartOfOpenProject ) { const int projectNameSize = path.indexOf(QLatin1Char(':')); // first: project name, second: path to file in project (might be just '/' when the file is in the project root dir) const QPair fileInProjectInfo = (projectNameSize < 0) ? qMakePair(path, QStringLiteral("/")) : qMakePair(path.left(projectNameSize), path.mid(projectNameSize + 1)); itemText = QStringLiteral("%1 (%2:%3)").arg(itemText, fileInProjectInfo.first, fileInProjectInfo.second); } else { itemText = itemText + " (" + path + ')'; } project = projectController->findProjectForUrl(doc->url()); } auto item = new QStandardItem( slDoc->icon(), itemText ); item->setData(QVariant::fromValue(project), DocumentSwitcherTreeView::ProjectRole); model->appendRow( item ); } } DocumentSwitcherPlugin::~DocumentSwitcherPlugin() { } void DocumentSwitcherPlugin::switchToClicked( const QModelIndex& idx ) { view->selectionModel()->select(idx, QItemSelectionModel::ClearAndSelect); itemActivated(idx); } void DocumentSwitcherPlugin::itemActivated( const QModelIndex& idx ) { Q_UNUSED( idx ); if( view->selectionModel()->selectedRows().isEmpty() ) { return; } int row = view->selectionModel()->selectedRows().first().row(); Sublime::MainWindow* window = qobject_cast( KDevelop::ICore::self()->uiController()->activeMainWindow() ); Sublime::View* activatedView = nullptr; if( window && documentLists.contains( window ) && documentLists[window].contains( window->area() ) ) { const QList l = documentLists[window][window->area()]; if( row >= 0 && row < l.size() ) { activatedView = l.at( row ); } } if( activatedView ) { if( QApplication::mouseButtons() & Qt::MiddleButton ) { window->area()->closeView( activatedView ); fillModel( window ); if ( model->rowCount() == 0 ) { view->hide(); } else { view->selectionModel()->select( view->model()->index(0, 0), QItemSelectionModel::ClearAndSelect ); } } else { window->activateView( activatedView ); view->hide(); } } } void DocumentSwitcherPlugin::unload() { foreach( QObject* mw, documentLists.keys() ) { removeMainWindow( mw ); } delete forwardAction; delete backwardAction; view->deleteLater(); } void DocumentSwitcherPlugin::storeAreaViewList( Sublime::MainWindow* mainwindow, Sublime::Area* area ) { if( !documentLists.contains( mainwindow ) || !documentLists[mainwindow].contains(area) ) { QHash > areas; qCDebug(PLUGIN_DOCUMENTSWITCHER) << "adding area views for area:" << area << area->title() << "mainwindow:" << mainwindow << mainwindow->windowTitle(); foreach( Sublime::View* v, area->views() ) { qCDebug(PLUGIN_DOCUMENTSWITCHER) << "view:" << v << v->document()->title(); } qCDebug(PLUGIN_DOCUMENTSWITCHER) << "done"; areas.insert( area, area->views() ); documentLists.insert( mainwindow, areas ); } } void DocumentSwitcherPlugin::addMainWindow( Sublime::MainWindow* mainwindow ) { if( !mainwindow ) { return; } qCDebug(PLUGIN_DOCUMENTSWITCHER) << "adding mainwindow:" << mainwindow << mainwindow->windowTitle(); qCDebug(PLUGIN_DOCUMENTSWITCHER) << "storing all views from area:" << mainwindow->area()->title() << mainwindow->area(); storeAreaViewList( mainwindow, mainwindow->area() ); qCDebug(PLUGIN_DOCUMENTSWITCHER) << "connecting signals on mainwindow"; connect( mainwindow, &Sublime::MainWindow::areaChanged, this, &DocumentSwitcherPlugin::changeArea ); connect( mainwindow, &Sublime::MainWindow::activeViewChanged, this, &DocumentSwitcherPlugin::changeView ); connect( mainwindow, &Sublime::MainWindow::viewAdded, this, &DocumentSwitcherPlugin::addView ); connect( mainwindow, &Sublime::MainWindow::aboutToRemoveView, this, &DocumentSwitcherPlugin::removeView ); connect( mainwindow, &Sublime::MainWindow::destroyed, this, &DocumentSwitcherPlugin::removeMainWindow); mainwindow->installEventFilter( this ); } bool DocumentSwitcherPlugin::eventFilter( QObject* watched, QEvent* ev ) { Sublime::MainWindow* mw = dynamic_cast( watched ); if( mw && ev->type() == QEvent::WindowActivate ) { enableActions(); } return QObject::eventFilter( watched, ev ); } void DocumentSwitcherPlugin::addView( Sublime::View* view ) { Sublime::MainWindow* mainwindow = qobject_cast( sender() ); if( !mainwindow ) return; qCDebug(PLUGIN_DOCUMENTSWITCHER) << "got signal from mainwindow:" << mainwindow << mainwindow->windowTitle() << "its area is:" << mainwindow->area() << mainwindow->area()->title() << "adding view:" << view << view->document()->title(); enableActions(); documentLists[mainwindow][mainwindow->area()].append( view ); } void DocumentSwitcherPlugin::enableActions() { forwardAction->setEnabled(true); backwardAction->setEnabled(true); } void DocumentSwitcherPlugin::removeMainWindow( QObject* obj ) { if( !obj || !documentLists.contains(obj) ) { return; } obj->removeEventFilter( this ); disconnect( obj, nullptr, this, nullptr ); documentLists.remove( obj ); } void DocumentSwitcherPlugin::changeArea( Sublime::Area* area ) { Sublime::MainWindow* mainwindow = qobject_cast( sender() ); Q_ASSERT( mainwindow ); qCDebug(PLUGIN_DOCUMENTSWITCHER) << "area changed:" << area << area->title() << "mainwindow:" << mainwindow << mainwindow->windowTitle(); //Since the main-window only emits aboutToRemoveView for views within the current area, we must forget all areas except the active one documentLists.remove(mainwindow); if( !documentLists[mainwindow].contains( area ) ) { qCDebug(PLUGIN_DOCUMENTSWITCHER) << "got area change, storing its views"; storeAreaViewList( mainwindow, area ); } enableActions(); } void DocumentSwitcherPlugin::changeView( Sublime::View* view ) { if( !view ) return; Sublime::MainWindow* mainwindow = qobject_cast( sender() ); Q_ASSERT( mainwindow ); Sublime::Area* area = mainwindow->area(); int idx = documentLists[mainwindow][area].indexOf( view ); if( idx != -1 ) { documentLists[mainwindow][area].removeAt( idx ); } qCDebug(PLUGIN_DOCUMENTSWITCHER) << "moving view to front, list should now not contain this view anymore" << view << view->document()->title(); qCDebug(PLUGIN_DOCUMENTSWITCHER) << "current area is:" << area << area->title() << "mainwnidow:" << mainwindow << mainwindow->windowTitle();; qCDebug(PLUGIN_DOCUMENTSWITCHER) << "idx of this view in list:" << documentLists[mainwindow][area].indexOf( view ); documentLists[mainwindow][area].prepend( view ); enableActions(); } void DocumentSwitcherPlugin::removeView( Sublime::View* view ) { if( !view ) return; Sublime::MainWindow* mainwindow = qobject_cast( sender() ); Q_ASSERT( mainwindow ); Sublime::Area* area = mainwindow->area(); int idx = documentLists[mainwindow][area].indexOf( view ); if( idx != -1 ) { documentLists[mainwindow][area].removeAt( idx ); } qCDebug(PLUGIN_DOCUMENTSWITCHER) << "removing view, list should now not contain this view anymore" << view << view->document()->title(); qCDebug(PLUGIN_DOCUMENTSWITCHER) << "current area is:" << area << area->title() << "mainwnidow:" << mainwindow << mainwindow->windowTitle();; qCDebug(PLUGIN_DOCUMENTSWITCHER) << "idx of this view in list:" << documentLists[mainwindow][area].indexOf( view ); enableActions(); } #include "documentswitcherplugin.moc" diff --git a/plugins/documentswitcher/documentswitcherplugin.h b/plugins/documentswitcher/documentswitcherplugin.h index a5bbceb82..c4e303817 100644 --- a/plugins/documentswitcher/documentswitcherplugin.h +++ b/plugins/documentswitcher/documentswitcherplugin.h @@ -1,78 +1,76 @@ /*************************************************************************** * Copyright 2009 Andreas Pakulat * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef KDEVPLATFORM_PLUGIN_DOCUMENTSWITCHERPLUGIN_H #define KDEVPLATFORM_PLUGIN_DOCUMENTSWITCHERPLUGIN_H #include #include -#include -Q_DECLARE_LOGGING_CATEGORY(PLUGIN_DOCUMENTSWITCHER) class QStandardItemModel; namespace Sublime { class View; class MainWindow; class Area; class MainWindow; } class DocumentSwitcherTreeView; class QModelIndex; class QAction; class DocumentSwitcherPlugin: public KDevelop::IPlugin { Q_OBJECT public: explicit DocumentSwitcherPlugin( QObject *parent, const QVariantList &args = QVariantList() ); ~DocumentSwitcherPlugin() override; void unload() override; public Q_SLOTS: void itemActivated( const QModelIndex& ); void switchToClicked(const QModelIndex& ); private Q_SLOTS: void addView( Sublime::View* ); void changeView( Sublime::View* ); void addMainWindow( Sublime::MainWindow* ); void changeArea( Sublime::Area* ); void removeView( Sublime::View* ); void removeMainWindow(QObject*); void walkForward(); void walkBackward(); protected: bool eventFilter( QObject*, QEvent* ) override; private: void setViewGeometry(Sublime::MainWindow* window); void storeAreaViewList( Sublime::MainWindow* mainwindow, Sublime::Area* area ); void enableActions(); void fillModel( Sublime::MainWindow* window ); void walk(const int from, const int to); // Need to use QObject here as we only have a QObject* in // the removeMainWindow method and cannot cast it to the mainwindow anymore QMap > > documentLists; DocumentSwitcherTreeView* view; QStandardItemModel* model; QAction* forwardAction; QAction* backwardAction; }; #endif diff --git a/plugins/execute/CMakeLists.txt b/plugins/execute/CMakeLists.txt index 6f115e65a..b0b8c47a4 100644 --- a/plugins/execute/CMakeLists.txt +++ b/plugins/execute/CMakeLists.txt @@ -1,27 +1,33 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kdevexecute\") ########### next target ############### set(kdevexecute_PART_SRCS projecttargetscombobox.cpp executeplugin.cpp nativeappconfig.cpp nativeappjob.cpp ) +ecm_qt_declare_logging_category(kdevexecute_PART_SRCS + HEADER debug.h + IDENTIFIER PLUGIN_EXECUTE + CATEGORY_NAME "kdevplatform.plugins.execute" +) + ki18n_wrap_ui( kdevexecute_PART_SRCS nativeappconfig.ui) kdevplatform_add_plugin(kdevexecute JSON kdevexecute.json SOURCES ${kdevexecute_PART_SRCS}) target_link_libraries(kdevexecute KF5::KCMUtils KDev::Interfaces KDev::Util KDev::Project KDev::OutputView KDev::Shell ) ########### install files ############### install(FILES iexecuteplugin.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/kdevplatform/execute COMPONENT Devel) diff --git a/plugins/execute/debug.h b/plugins/execute/debug.h deleted file mode 100644 index 8cab14df8..000000000 --- a/plugins/execute/debug.h +++ /dev/null @@ -1,26 +0,0 @@ -/* - * This file is part of KDevelop - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#ifndef KDEVPLATFORM_EXECUTE_DEBUG_H -#define KDEVPLATFORM_EXECUTE_DEBUG_H - -#include -Q_DECLARE_LOGGING_CATEGORY(PLUGIN_EXECUTE) - -#endif diff --git a/plugins/execute/executeplugin.cpp b/plugins/execute/executeplugin.cpp index 146444550..f834ea03a 100644 --- a/plugins/execute/executeplugin.cpp +++ b/plugins/execute/executeplugin.cpp @@ -1,251 +1,248 @@ /* * This file is part of KDevelop * * Copyright 2007 Hamish Rodda * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "executeplugin.h" -#include - #include #include #include #include #include #include #include #include #include #include #include #include #include "nativeappconfig.h" #include "debug.h" #include #include #include QString ExecutePlugin::_nativeAppConfigTypeId = QStringLiteral("Native Application"); QString ExecutePlugin::workingDirEntry = QStringLiteral("Working Directory"); QString ExecutePlugin::executableEntry = QStringLiteral("Executable"); QString ExecutePlugin::argumentsEntry = QStringLiteral("Arguments"); QString ExecutePlugin::isExecutableEntry = QStringLiteral("isExecutable"); QString ExecutePlugin::dependencyEntry = QStringLiteral("Dependencies"); // TODO: migrate to more consistent key term "EnvironmentProfile" QString ExecutePlugin::environmentProfileEntry = QStringLiteral("EnvironmentGroup"); QString ExecutePlugin::useTerminalEntry = QStringLiteral("Use External Terminal"); QString ExecutePlugin::terminalEntry = QStringLiteral("External Terminal"); QString ExecutePlugin::userIdToRunEntry = QStringLiteral("User Id to Run"); QString ExecutePlugin::dependencyActionEntry = QStringLiteral("Dependency Action"); QString ExecutePlugin::projectTargetEntry = QStringLiteral("Project Target"); using namespace KDevelop; -Q_LOGGING_CATEGORY(PLUGIN_EXECUTE, "kdevplatform.plugins.execute") K_PLUGIN_FACTORY_WITH_JSON(KDevExecuteFactory, "kdevexecute.json", registerPlugin();) ExecutePlugin::ExecutePlugin(QObject *parent, const QVariantList&) : KDevelop::IPlugin(QStringLiteral("kdevexecute"), parent) { m_configType = new NativeAppConfigType(); m_configType->addLauncher( new NativeAppLauncher() ); qCDebug(PLUGIN_EXECUTE) << "adding native app launch config"; core()->runController()->addConfigurationType( m_configType ); } ExecutePlugin::~ExecutePlugin() { } void ExecutePlugin::unload() { core()->runController()->removeConfigurationType( m_configType ); delete m_configType; m_configType = nullptr; } QStringList ExecutePlugin::arguments( KDevelop::ILaunchConfiguration* cfg, QString& err_ ) const { if( !cfg ) { return QStringList(); } KShell::Errors err; QStringList args = KShell::splitArgs( cfg->config().readEntry( ExecutePlugin::argumentsEntry, "" ), KShell::TildeExpand | KShell::AbortOnMeta, &err ); if( err != KShell::NoError ) { if( err == KShell::BadQuoting ) { err_ = i18n("There is a quoting error in the arguments for " "the launch configuration '%1'. Aborting start.", cfg->name() ); } else { err_ = i18n("A shell meta character was included in the " "arguments for the launch configuration '%1', " "this is not supported currently. Aborting start.", cfg->name() ); } args = QStringList(); qCWarning(PLUGIN_EXECUTE) << "Launch Configuration:" << cfg->name() << "arguments have meta characters"; } return args; } KJob* ExecutePlugin::dependencyJob( KDevelop::ILaunchConfiguration* cfg ) const { QVariantList deps = KDevelop::stringToQVariant( cfg->config().readEntry( dependencyEntry, QString() ) ).toList(); QString depAction = cfg->config().readEntry( dependencyActionEntry, "Nothing" ); if( depAction != QLatin1String("Nothing") && !deps.isEmpty() ) { KDevelop::ProjectModel* model = KDevelop::ICore::self()->projectController()->projectModel(); QList items; foreach( const QVariant& dep, deps ) { KDevelop::ProjectBaseItem* item = model->itemFromIndex( model->pathToIndex( dep.toStringList() ) ); if( item ) { items << item; } else { KMessageBox::error(core()->uiController()->activeMainWindow(), i18n("Couldn't resolve the dependency: %1", dep.toString())); } } KDevelop::BuilderJob* job = new KDevelop::BuilderJob(); if( depAction == QLatin1String("Build") ) { job->addItems( KDevelop::BuilderJob::Build, items ); } else if( depAction == QLatin1String("Install") ) { job->addItems( KDevelop::BuilderJob::Install, items ); } job->updateJobName(); return job; } return nullptr; } QString ExecutePlugin::environmentProfileName(KDevelop::ILaunchConfiguration* cfg) const { if( !cfg ) { return QLatin1String(""); } return cfg->config().readEntry(ExecutePlugin::environmentProfileEntry, QString()); } QUrl ExecutePlugin::executable( KDevelop::ILaunchConfiguration* cfg, QString& err ) const { QUrl executable; if( !cfg ) { return executable; } KConfigGroup grp = cfg->config(); if( grp.readEntry(ExecutePlugin::isExecutableEntry, false ) ) { executable = grp.readEntry( ExecutePlugin::executableEntry, QUrl() ); } else { QStringList prjitem = grp.readEntry( ExecutePlugin::projectTargetEntry, QStringList() ); KDevelop::ProjectModel* model = KDevelop::ICore::self()->projectController()->projectModel(); KDevelop::ProjectBaseItem* item = model->itemFromIndex( model->pathToIndex(prjitem) ); if( item && item->executable() ) { // TODO: Need an option in the gui to choose between installed and builddir url here, currently cmake only supports builddir url executable = item->executable()->builtUrl(); } } if( executable.isEmpty() ) { err = i18n("No valid executable specified"); qCWarning(PLUGIN_EXECUTE) << "Launch Configuration:" << cfg->name() << "no valid executable set"; } else { KShell::Errors err_; if( KShell::splitArgs( executable.toLocalFile(), KShell::TildeExpand | KShell::AbortOnMeta, &err_ ).isEmpty() || err_ != KShell::NoError ) { executable = QUrl(); if( err_ == KShell::BadQuoting ) { err = i18n("There is a quoting error in the executable " "for the launch configuration '%1'. " "Aborting start.", cfg->name() ); } else { err = i18n("A shell meta character was included in the " "executable for the launch configuration '%1', " "this is not supported currently. Aborting start.", cfg->name() ); } qCWarning(PLUGIN_EXECUTE) << "Launch Configuration:" << cfg->name() << "executable has meta characters"; } } return executable; } bool ExecutePlugin::useTerminal( KDevelop::ILaunchConfiguration* cfg ) const { if( !cfg ) { return false; } return cfg->config().readEntry( ExecutePlugin::useTerminalEntry, false ); } QString ExecutePlugin::terminal( KDevelop::ILaunchConfiguration* cfg ) const { if( !cfg ) { return QString(); } return cfg->config().readEntry( ExecutePlugin::terminalEntry, QString() ); } QUrl ExecutePlugin::workingDirectory( KDevelop::ILaunchConfiguration* cfg ) const { if( !cfg ) { return QUrl(); } return cfg->config().readEntry( ExecutePlugin::workingDirEntry, QUrl() ); } QString ExecutePlugin::nativeAppConfigTypeId() const { return _nativeAppConfigTypeId; } #include "executeplugin.moc" diff --git a/plugins/execute/nativeappconfig.cpp b/plugins/execute/nativeappconfig.cpp index aef218f61..95995f505 100644 --- a/plugins/execute/nativeappconfig.cpp +++ b/plugins/execute/nativeappconfig.cpp @@ -1,389 +1,388 @@ /* This file is part of KDevelop Copyright 2009 Andreas Pakulat Copyright 2010 Aleix Pol Gonzalez 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 "nativeappconfig.h" #include #include #include #include #include #include "nativeappjob.h" #include #include #include #include #include "executeplugin.h" #include "debug.h" #include #include "projecttargetscombobox.h" -#include #include #include #include #include #include #include using namespace KDevelop; QIcon NativeAppConfigPage::icon() const { return QIcon::fromTheme(QStringLiteral("system-run")); } static KDevelop::ProjectBaseItem* itemForPath(const QStringList& path, KDevelop::ProjectModel* model) { return model->itemFromIndex(model->pathToIndex(path)); } //TODO: Make sure to auto-add the executable target to the dependencies when its used. void NativeAppConfigPage::loadFromConfiguration(const KConfigGroup& cfg, KDevelop::IProject* project ) { QSignalBlocker blocker(this); projectTarget->setBaseItem( project ? project->projectItem() : nullptr, true); projectTarget->setCurrentItemPath( cfg.readEntry( ExecutePlugin::projectTargetEntry, QStringList() ) ); QUrl exe = cfg.readEntry( ExecutePlugin::executableEntry, QUrl()); if( !exe.isEmpty() || project ){ executablePath->setUrl( !exe.isEmpty() ? exe : project->path().toUrl() ); }else{ KDevelop::IProjectController* pc = KDevelop::ICore::self()->projectController(); if( pc ){ executablePath->setUrl( pc->projects().isEmpty() ? QUrl() : pc->projects().at(0)->path().toUrl() ); } } dependencies->setSuggestion(project); //executablePath->setFilter("application/x-executable"); executableRadio->setChecked( true ); if ( !cfg.readEntry( ExecutePlugin::isExecutableEntry, false ) && projectTarget->count() ){ projectTargetRadio->setChecked( true ); } arguments->setClearButtonEnabled( true ); arguments->setText( cfg.readEntry( ExecutePlugin::argumentsEntry, "" ) ); workingDirectory->setUrl( cfg.readEntry( ExecutePlugin::workingDirEntry, QUrl() ) ); environment->setCurrentProfile(cfg.readEntry(ExecutePlugin::environmentProfileEntry, QString())); runInTerminal->setChecked( cfg.readEntry( ExecutePlugin::useTerminalEntry, false ) ); terminal->setEditText( cfg.readEntry( ExecutePlugin::terminalEntry, terminal->itemText(0) ) ); dependencies->setDependencies(KDevelop::stringToQVariant( cfg.readEntry( ExecutePlugin::dependencyEntry, QString() ) ).toList()); dependencyAction->setCurrentIndex( dependencyAction->findData( cfg.readEntry( ExecutePlugin::dependencyActionEntry, "Nothing" ) ) ); } NativeAppConfigPage::NativeAppConfigPage( QWidget* parent ) : LaunchConfigurationPage( parent ) { setupUi(this); //Setup data info for combobox dependencyAction->setItemData(0, "Nothing" ); dependencyAction->setItemData(1, "Build" ); dependencyAction->setItemData(2, "Install" ); dependencyAction->setItemData(3, "SudoInstall" ); //Set workingdirectory widget to ask for directories rather than files workingDirectory->setMode(KFile::Directory | KFile::ExistingOnly | KFile::LocalOnly); configureEnvironment->setSelectionWidget(environment); //connect signals to changed signal connect( projectTarget, static_cast(&ProjectTargetsComboBox::currentIndexChanged), this, &NativeAppConfigPage::changed ); connect( projectTargetRadio, &QRadioButton::toggled, this, &NativeAppConfigPage::changed ); connect( executableRadio, &QRadioButton::toggled, this, &NativeAppConfigPage::changed ); connect( executablePath->lineEdit(), &KLineEdit::textEdited, this, &NativeAppConfigPage::changed ); connect( executablePath, &KUrlRequester::urlSelected, this, &NativeAppConfigPage::changed ); connect( arguments, &QLineEdit::textEdited, this, &NativeAppConfigPage::changed ); connect( workingDirectory, &KUrlRequester::urlSelected, this, &NativeAppConfigPage::changed ); connect( workingDirectory->lineEdit(), &KLineEdit::textEdited, this, &NativeAppConfigPage::changed ); connect( environment, &EnvironmentSelectionWidget::currentProfileChanged, this, &NativeAppConfigPage::changed ); connect( dependencyAction, static_cast(&KComboBox::currentIndexChanged), this, &NativeAppConfigPage::changed ); connect( runInTerminal, &QCheckBox::toggled, this, &NativeAppConfigPage::changed ); connect( terminal, &KComboBox::editTextChanged, this, &NativeAppConfigPage::changed ); connect( terminal, static_cast(&KComboBox::currentIndexChanged), this, &NativeAppConfigPage::changed ); connect( dependencyAction, static_cast(&KComboBox::currentIndexChanged), this, &NativeAppConfigPage::activateDeps ); connect( dependencies, &DependenciesWidget::changed, this, &NativeAppConfigPage::changed ); } void NativeAppConfigPage::activateDeps( int idx ) { dependencies->setEnabled( dependencyAction->itemData( idx ).toString() != QLatin1String("Nothing") ); } void NativeAppConfigPage::saveToConfiguration( KConfigGroup cfg, KDevelop::IProject* project ) const { Q_UNUSED( project ); cfg.writeEntry( ExecutePlugin::isExecutableEntry, executableRadio->isChecked() ); cfg.writeEntry( ExecutePlugin::executableEntry, executablePath->url() ); cfg.writeEntry( ExecutePlugin::projectTargetEntry, projectTarget->currentItemPath() ); cfg.writeEntry( ExecutePlugin::argumentsEntry, arguments->text() ); cfg.writeEntry( ExecutePlugin::workingDirEntry, workingDirectory->url() ); cfg.writeEntry( ExecutePlugin::environmentProfileEntry, environment->currentProfile() ); cfg.writeEntry( ExecutePlugin::useTerminalEntry, runInTerminal->isChecked() ); cfg.writeEntry( ExecutePlugin::terminalEntry, terminal->currentText() ); cfg.writeEntry( ExecutePlugin::dependencyActionEntry, dependencyAction->itemData( dependencyAction->currentIndex() ).toString() ); QVariantList deps = dependencies->dependencies(); cfg.writeEntry( ExecutePlugin::dependencyEntry, KDevelop::qvariantToString( QVariant( deps ) ) ); } QString NativeAppConfigPage::title() const { return i18n("Configure Native Application"); } QList< KDevelop::LaunchConfigurationPageFactory* > NativeAppLauncher::configPages() const { return QList(); } QString NativeAppLauncher::description() const { return i18n("Executes Native Applications"); } QString NativeAppLauncher::id() { return QStringLiteral("nativeAppLauncher"); } QString NativeAppLauncher::name() const { return i18n("Native Application"); } NativeAppLauncher::NativeAppLauncher() { } KJob* NativeAppLauncher::start(const QString& launchMode, KDevelop::ILaunchConfiguration* cfg) { Q_ASSERT(cfg); if( !cfg ) { return nullptr; } if( launchMode == QLatin1String("execute") ) { IExecutePlugin* iface = KDevelop::ICore::self()->pluginController()->pluginForExtension(QStringLiteral("org.kdevelop.IExecutePlugin"), QStringLiteral("kdevexecute"))->extension(); Q_ASSERT(iface); KJob* depjob = iface->dependencyJob( cfg ); QList l; if( depjob ) { l << depjob; } l << new NativeAppJob( KDevelop::ICore::self()->runController(), cfg ); return new KDevelop::ExecuteCompositeJob( KDevelop::ICore::self()->runController(), l ); } qWarning() << "Unknown launch mode " << launchMode << "for config:" << cfg->name(); return nullptr; } QStringList NativeAppLauncher::supportedModes() const { return QStringList() << QStringLiteral("execute"); } KDevelop::LaunchConfigurationPage* NativeAppPageFactory::createWidget(QWidget* parent) { return new NativeAppConfigPage( parent ); } NativeAppPageFactory::NativeAppPageFactory() { } NativeAppConfigType::NativeAppConfigType() { factoryList.append( new NativeAppPageFactory() ); } NativeAppConfigType::~NativeAppConfigType() { qDeleteAll(factoryList); factoryList.clear(); } QString NativeAppConfigType::name() const { return i18n("Compiled Binary"); } QList NativeAppConfigType::configPages() const { return factoryList; } QString NativeAppConfigType::id() const { return ExecutePlugin::_nativeAppConfigTypeId; } QIcon NativeAppConfigType::icon() const { return QIcon::fromTheme(QStringLiteral("application-x-executable")); } bool NativeAppConfigType::canLaunch ( KDevelop::ProjectBaseItem* item ) const { if( item->target() && item->target()->executable() ) { return canLaunch( item->target()->executable()->builtUrl() ); } return false; } bool NativeAppConfigType::canLaunch ( const QUrl& file ) const { return ( file.isLocalFile() && QFileInfo( file.toLocalFile() ).isExecutable() ); } void NativeAppConfigType::configureLaunchFromItem ( KConfigGroup cfg, KDevelop::ProjectBaseItem* item ) const { cfg.writeEntry( ExecutePlugin::isExecutableEntry, false ); KDevelop::ProjectModel* model = KDevelop::ICore::self()->projectController()->projectModel(); cfg.writeEntry( ExecutePlugin::projectTargetEntry, model->pathFromIndex( model->indexFromItem( item ) ) ); cfg.writeEntry( ExecutePlugin::workingDirEntry, item->executable()->builtUrl().adjusted(QUrl::RemoveFilename) ); cfg.sync(); } void NativeAppConfigType::configureLaunchFromCmdLineArguments ( KConfigGroup cfg, const QStringList& args ) const { cfg.writeEntry( ExecutePlugin::isExecutableEntry, true ); Q_ASSERT(QFile::exists(args.first())); // TODO: we probably want to flexibilize, but at least we won't be accepting wrong values anymore cfg.writeEntry( ExecutePlugin::executableEntry, QUrl::fromLocalFile(args.first()) ); QStringList a(args); a.removeFirst(); cfg.writeEntry( ExecutePlugin::argumentsEntry, KShell::joinArgs(a) ); cfg.sync(); } QList targetsInFolder(KDevelop::ProjectFolderItem* folder) { QList ret; foreach(KDevelop::ProjectFolderItem* f, folder->folderList()) ret += targetsInFolder(f); ret += folder->targetList(); return ret; } bool actionLess(QAction* a, QAction* b) { return a->text() < b->text(); } bool menuLess(QMenu* a, QMenu* b) { return a->title() < b->title(); } QMenu* NativeAppConfigType::launcherSuggestions() { QMenu* ret = new QMenu(i18n("Project Executables")); KDevelop::ProjectModel* model = KDevelop::ICore::self()->projectController()->projectModel(); QList projects = KDevelop::ICore::self()->projectController()->projects(); foreach(KDevelop::IProject* project, projects) { if(project->projectFileManager()->features() & KDevelop::IProjectFileManager::Targets) { QList targets=targetsInFolder(project->projectItem()); QHash > targetsContainer; QMenu* projectMenu = ret->addMenu(QIcon::fromTheme(QStringLiteral("project-development")), project->name()); foreach(KDevelop::ProjectTargetItem* target, targets) { if(target->executable()) { QStringList path = model->pathFromIndex(target->index()); if(!path.isEmpty()){ QAction* act = new QAction(projectMenu); act->setData(KDevelop::joinWithEscaping(path, '/','\\')); act->setProperty("name", target->text()); path.removeFirst(); act->setText(path.join(QStringLiteral("/"))); act->setIcon(QIcon::fromTheme(QStringLiteral("system-run"))); connect(act, &QAction::triggered, this, &NativeAppConfigType::suggestionTriggered); targetsContainer[target->parent()].append(act); } } } QList separateActions; QList submenus; foreach(KDevelop::ProjectBaseItem* folder, targetsContainer.keys()) { QList actions = targetsContainer.value(folder); if(actions.size()==1 || !folder->parent()) { separateActions += actions.first(); } else { foreach(QAction* a, actions) { a->setText(a->property("name").toString()); } QStringList path = model->pathFromIndex(folder->index()); path.removeFirst(); QMenu* submenu = new QMenu(path.join(QStringLiteral("/"))); std::sort(actions.begin(), actions.end(), actionLess); submenu->addActions(actions); submenus += submenu; } } std::sort(separateActions.begin(), separateActions.end(), actionLess); std::sort(submenus.begin(), submenus.end(), menuLess); foreach(QMenu* m, submenus) projectMenu->addMenu(m); projectMenu->addActions(separateActions); projectMenu->setEnabled(!projectMenu->isEmpty()); } } return ret; } void NativeAppConfigType::suggestionTriggered() { QAction* action = qobject_cast(sender()); KDevelop::ProjectModel* model = KDevelop::ICore::self()->projectController()->projectModel(); KDevelop::ProjectTargetItem* pitem = dynamic_cast(itemForPath(KDevelop::splitWithEscaping(action->data().toString(),'/', '\\'), model)); if(pitem) { QPair launcher = qMakePair( launchers().at( 0 )->supportedModes().at(0), launchers().at( 0 )->id() ); KDevelop::IProject* p = pitem->project(); KDevelop::ILaunchConfiguration* config = KDevelop::ICore::self()->runController()->createLaunchConfiguration(this, launcher, p, pitem->text()); KConfigGroup cfg = config->config(); QStringList splitPath = model->pathFromIndex(pitem->index()); // QString path = KDevelop::joinWithEscaping(splitPath,'/','\\'); cfg.writeEntry( ExecutePlugin::projectTargetEntry, splitPath ); cfg.writeEntry( ExecutePlugin::dependencyEntry, KDevelop::qvariantToString( QVariantList() << splitPath ) ); cfg.writeEntry( ExecutePlugin::dependencyActionEntry, "Build" ); cfg.sync(); emit signalAddLaunchConfiguration(config); } } diff --git a/plugins/execute/nativeappjob.cpp b/plugins/execute/nativeappjob.cpp index d6a95562b..e97b92be5 100644 --- a/plugins/execute/nativeappjob.cpp +++ b/plugins/execute/nativeappjob.cpp @@ -1,145 +1,144 @@ /* This file is part of KDevelop Copyright 2009 Andreas Pakulat 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 "nativeappjob.h" -#include #include #include #include #include #include #include #include #include #include #include #include #include #include "iexecuteplugin.h" #include "debug.h" using namespace KDevelop; NativeAppJob::NativeAppJob(QObject* parent, KDevelop::ILaunchConfiguration* cfg) : KDevelop::OutputExecuteJob( parent ) , m_cfgname(cfg->name()) { setCapabilities(Killable); IExecutePlugin* iface = KDevelop::ICore::self()->pluginController()->pluginForExtension(QStringLiteral("org.kdevelop.IExecutePlugin"), QStringLiteral("kdevexecute"))->extension(); Q_ASSERT(iface); const KDevelop::EnvironmentProfileList environmentProfiles(KSharedConfig::openConfig()); QString envProfileName = iface->environmentProfileName(cfg); QString err; QUrl executable = iface->executable( cfg, err ); if( !err.isEmpty() ) { setError( -1 ); setErrorText( err ); return; } if (envProfileName.isEmpty()) { qWarning() << "Launch Configuration:" << cfg->name() << i18n("No environment profile specified, looks like a broken " "configuration, please check run configuration '%1'. " "Using default environment profile.", cfg->name() ); envProfileName = environmentProfiles.defaultProfileName(); } setEnvironmentProfile(envProfileName); QStringList arguments = iface->arguments( cfg, err ); if( !err.isEmpty() ) { setError( -2 ); setErrorText( err ); } if( error() != 0 ) { qWarning() << "Launch Configuration:" << cfg->name() << "oops, problem" << errorText(); return; } setStandardToolView(KDevelop::IOutputView::RunView); setBehaviours(KDevelop::IOutputView::AllowUserClose | KDevelop::IOutputView::AutoScroll); setFilteringStrategy(OutputModel::NativeAppErrorFilter); setProperties(DisplayStdout | DisplayStderr); // Now setup the process parameters QUrl wc = iface->workingDirectory( cfg ); if( !wc.isValid() || wc.isEmpty() ) { wc = QUrl::fromLocalFile( QFileInfo( executable.toLocalFile() ).absolutePath() ); } setWorkingDirectory( wc ); qCDebug(PLUGIN_EXECUTE) << "setting app:" << executable << arguments; if (iface->useTerminal(cfg)) { QStringList args = KShell::splitArgs(iface->terminal(cfg)); for (QStringList::iterator it = args.begin(); it != args.end(); ++it) { if (*it == QLatin1String("%exe")) { *it = KShell::quoteArg(executable.toLocalFile()); } else if (*it == QLatin1String("%workdir")) { *it = KShell::quoteArg(wc.toLocalFile()); } } args.append( arguments ); *this << args; } else { *this << executable.toLocalFile(); *this << arguments; } setJobName(cfg->name()); } NativeAppJob* findNativeJob(KJob* j) { NativeAppJob* job = qobject_cast(j); if (!job) { const QList jobs = j->findChildren(); if (!jobs.isEmpty()) job = jobs.first(); } return job; } void NativeAppJob::start() { // we kill any execution of the configuration foreach(KJob* j, ICore::self()->runController()->currentJobs()) { NativeAppJob* job = findNativeJob(j); if (job && job != this && job->m_cfgname == m_cfgname) { QMessageBox::StandardButton button = QMessageBox::question(nullptr, i18n("Job already running"), i18n("'%1' is already being executed. Should we kill the previous instance?", m_cfgname)); if (button != QMessageBox::No) j->kill(); } } OutputExecuteJob::start(); } diff --git a/plugins/executescript/CMakeLists.txt b/plugins/executescript/CMakeLists.txt index 1a27ead4d..8e8179205 100644 --- a/plugins/executescript/CMakeLists.txt +++ b/plugins/executescript/CMakeLists.txt @@ -1,18 +1,23 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kdevexecutescript\") set(kdevexecutescript_PART_SRCS executescriptplugin.cpp scriptappconfig.cpp scriptappjob.cpp ) +ecm_qt_declare_logging_category(kdevexecutescript_PART_SRCS + HEADER debug.h + IDENTIFIER PLUGIN_EXECUTESCRIPT + CATEGORY_NAME "kdevplatform.plugins.executescript" +) ki18n_wrap_ui(kdevexecutescript_PART_SRCS scriptappconfig.ui ) kdevplatform_add_plugin(kdevexecutescript JSON kdevexecutescript.json SOURCES ${kdevexecutescript_PART_SRCS}) target_link_libraries(kdevexecutescript KDev::Interfaces KDev::Util KDev::Project KDev::OutputView) install(FILES iexecutescriptplugin.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/kdevplatform/executescript COMPONENT Devel) diff --git a/plugins/executescript/debug.h b/plugins/executescript/debug.h deleted file mode 100644 index 598bc4ef1..000000000 --- a/plugins/executescript/debug.h +++ /dev/null @@ -1,26 +0,0 @@ -/* - * This file is part of KDevelop - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#ifndef KDEVPLATFORM_EXECUTESCRIPT_DEBUG_H -#define KDEVPLATFORM_EXECUTESCRIPT_DEBUG_H - -#include -Q_DECLARE_LOGGING_CATEGORY(PLUGIN_EXECUTESCRIPT) - -#endif diff --git a/plugins/executescript/executescriptplugin.cpp b/plugins/executescript/executescriptplugin.cpp index ec6813782..3b99fe411 100644 --- a/plugins/executescript/executescriptplugin.cpp +++ b/plugins/executescript/executescriptplugin.cpp @@ -1,259 +1,258 @@ /* * This file is part of KDevelop * * Copyright 2007 Hamish Rodda * Copyright 2009 Niko Sams * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "executescriptplugin.h" #include #include #include #include #include #include #include #include "scriptappconfig.h" #include "debug.h" #include #include QString ExecuteScriptPlugin::_scriptAppConfigTypeId = QStringLiteral("Script Application"); QString ExecuteScriptPlugin::interpreterEntry = QStringLiteral("Interpreter"); QString ExecuteScriptPlugin::workingDirEntry = QStringLiteral("Working Directory"); QString ExecuteScriptPlugin::executableEntry = QStringLiteral("Executable"); QString ExecuteScriptPlugin::executeOnRemoteHostEntry = QStringLiteral("Execute on Remote Host"); QString ExecuteScriptPlugin::runCurrentFileEntry = QStringLiteral("Run current file"); QString ExecuteScriptPlugin::remoteHostEntry = QStringLiteral("Remote Host"); QString ExecuteScriptPlugin::argumentsEntry = QStringLiteral("Arguments"); QString ExecuteScriptPlugin::isExecutableEntry = QStringLiteral("isExecutable"); // TODO: migrate to more consistent key term "EnvironmentProfile" QString ExecuteScriptPlugin::environmentProfileEntry = QStringLiteral("EnvironmentGroup"); //QString ExecuteScriptPlugin::useTerminalEntry = "Use External Terminal"; QString ExecuteScriptPlugin::userIdToRunEntry = QStringLiteral("User Id to Run"); QString ExecuteScriptPlugin::projectTargetEntry = QStringLiteral("Project Target"); QString ExecuteScriptPlugin::outputFilteringEntry = QStringLiteral("Output Filtering Mode"); using namespace KDevelop; -Q_LOGGING_CATEGORY(PLUGIN_EXECUTESCRIPT, "kdevplatform.plugins.executescript") K_PLUGIN_FACTORY_WITH_JSON(KDevExecuteFactory, "kdevexecutescript.json", registerPlugin();) ExecuteScriptPlugin::ExecuteScriptPlugin(QObject *parent, const QVariantList&) : KDevelop::IPlugin(QStringLiteral("kdevexecutescript"), parent) { m_configType = new ScriptAppConfigType(); m_configType->addLauncher( new ScriptAppLauncher( this ) ); qCDebug(PLUGIN_EXECUTESCRIPT) << "adding script launch config"; core()->runController()->addConfigurationType( m_configType ); } ExecuteScriptPlugin::~ExecuteScriptPlugin() { } void ExecuteScriptPlugin::unload() { core()->runController()->removeConfigurationType( m_configType ); delete m_configType; m_configType = nullptr; } QUrl ExecuteScriptPlugin::script( KDevelop::ILaunchConfiguration* cfg, QString& err_ ) const { QUrl script; if( !cfg ) { return script; } KConfigGroup grp = cfg->config(); script = grp.readEntry( ExecuteScriptPlugin::executableEntry, QUrl() ); if( !script.isLocalFile() || script.isEmpty() ) { err_ = i18n("No valid executable specified"); qWarning() << "Launch Configuration:" << cfg->name() << "no valid script set"; } else { KShell::Errors err; if( KShell::splitArgs( script.toLocalFile(), KShell::TildeExpand | KShell::AbortOnMeta, &err ).isEmpty() || err != KShell::NoError ) { script = QUrl(); if( err == KShell::BadQuoting ) { err_ = i18n("There is a quoting error in the script " "for the launch configuration '%1'. " "Aborting start.", cfg->name() ); } else { err_ = i18n("A shell meta character was included in the " "script for the launch configuration '%1', " "this is not supported currently. Aborting start.", cfg->name() ); } qWarning() << "Launch Configuration:" << cfg->name() << "script has meta characters"; } } return script; } QString ExecuteScriptPlugin::remoteHost(ILaunchConfiguration* cfg, QString& err) const { if (!cfg) return QString(); KConfigGroup grp = cfg->config(); if(grp.readEntry(ExecuteScriptPlugin::executeOnRemoteHostEntry, false)) { QString host = grp.readEntry(ExecuteScriptPlugin::remoteHostEntry, ""); if (host.isEmpty()) { err = i18n("No remote host set for launch configuration '%1'. " "Aborting start.", cfg->name() ); qWarning() << "Launch Configuration:" << cfg->name() << "no remote host set"; } return host; } return QString(); } QStringList ExecuteScriptPlugin::arguments( KDevelop::ILaunchConfiguration* cfg, QString& err_ ) const { if( !cfg ) { return QStringList(); } KShell::Errors err; QStringList args = KShell::splitArgs( cfg->config().readEntry( ExecuteScriptPlugin::argumentsEntry, "" ), KShell::TildeExpand | KShell::AbortOnMeta, &err ); if( err != KShell::NoError ) { if( err == KShell::BadQuoting ) { err_ = i18n("There is a quoting error in the arguments for " "the launch configuration '%1'. Aborting start.", cfg->name() ); } else { err_ = i18n("A shell meta character was included in the " "arguments for the launch configuration '%1', " "this is not supported currently. Aborting start.", cfg->name() ); } args = QStringList(); qWarning() << "Launch Configuration:" << cfg->name() << "arguments have meta characters"; } return args; } QString ExecuteScriptPlugin::environmentProfileName(KDevelop::ILaunchConfiguration* cfg) const { if( !cfg ) { return QString(); } return cfg->config().readEntry(ExecuteScriptPlugin::environmentProfileEntry, QString()); } int ExecuteScriptPlugin::outputFilterModeId( KDevelop::ILaunchConfiguration* cfg ) const { if( !cfg ) { return 0; } return cfg->config().readEntry( ExecuteScriptPlugin::outputFilteringEntry, 0 ); } bool ExecuteScriptPlugin::runCurrentFile(ILaunchConfiguration* cfg) const { if( !cfg ) { return false; } return cfg->config().readEntry( ExecuteScriptPlugin::runCurrentFileEntry, true ); } QString ExecuteScriptPlugin::interpreter( KDevelop::ILaunchConfiguration* cfg, QString& err ) const { QString interpreter; if( !cfg ) { return interpreter; } KConfigGroup grp = cfg->config(); interpreter = grp.readEntry( ExecuteScriptPlugin::interpreterEntry, QString() ); if( interpreter.isEmpty() ) { err = i18n("No valid interpreter specified"); qWarning() << "Launch Configuration:" << cfg->name() << "no valid interpreter set"; } else { KShell::Errors err_; if( KShell::splitArgs( interpreter, KShell::TildeExpand | KShell::AbortOnMeta, &err_ ).isEmpty() || err_ != KShell::NoError ) { interpreter.clear(); if( err_ == KShell::BadQuoting ) { err = i18n("There is a quoting error in the interpreter " "for the launch configuration '%1'. " "Aborting start.", cfg->name() ); } else { err = i18n("A shell meta character was included in the " "interpreter for the launch configuration '%1', " "this is not supported currently. Aborting start.", cfg->name() ); } qWarning() << "Launch Configuration:" << cfg->name() << "interpreter has meta characters"; } } return interpreter; } /* bool ExecuteScriptPlugin::useTerminal( KDevelop::ILaunchConfiguration* cfg ) const { if( !cfg ) { return false; } return cfg->config().readEntry( ExecuteScriptPlugin::useTerminalEntry, false ); } */ QUrl ExecuteScriptPlugin::workingDirectory( KDevelop::ILaunchConfiguration* cfg ) const { if( !cfg ) { return QUrl(); } return cfg->config().readEntry( ExecuteScriptPlugin::workingDirEntry, QUrl() ); } QString ExecuteScriptPlugin::scriptAppConfigTypeId() const { return _scriptAppConfigTypeId; } #include "executescriptplugin.moc" diff --git a/plugins/executescript/scriptappjob.cpp b/plugins/executescript/scriptappjob.cpp index daee5e2d8..5fd905da4 100644 --- a/plugins/executescript/scriptappjob.cpp +++ b/plugins/executescript/scriptappjob.cpp @@ -1,250 +1,249 @@ /* This file is part of KDevelop Copyright 2009 Andreas Pakulat Copyright 2009 Niko Sams 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 "scriptappjob.h" #include "executescriptplugin.h" -#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "iexecutescriptplugin.h" #include "debug.h" using namespace KDevelop; ScriptAppJob::ScriptAppJob(ExecuteScriptPlugin* parent, KDevelop::ILaunchConfiguration* cfg) : KDevelop::OutputJob( parent ), proc(new KProcess( this )), lineMaker(new KDevelop::ProcessLineMaker( proc, this )) { qCDebug(PLUGIN_EXECUTESCRIPT) << "creating script app job"; setCapabilities(Killable); IExecuteScriptPlugin* iface = KDevelop::ICore::self()->pluginController()->pluginForExtension(QStringLiteral("org.kdevelop.IExecuteScriptPlugin"))->extension(); Q_ASSERT(iface); const KDevelop::EnvironmentProfileList environmentProfiles(KSharedConfig::openConfig()); QString envProfileName = iface->environmentProfileName(cfg); QString err; QString interpreterString = iface->interpreter( cfg, err ); // check for errors happens in the executescript plugin already KShell::Errors err_; QStringList interpreter = KShell::splitArgs( interpreterString, KShell::TildeExpand | KShell::AbortOnMeta, &err_ ); if ( interpreter.isEmpty() ) { // This should not happen, because of the checks done in the executescript plugin qWarning() << "no interpreter specified"; return; } if( !err.isEmpty() ) { setError( -1 ); setErrorText( err ); return; } QUrl script; if( !iface->runCurrentFile( cfg ) ) { script = iface->script( cfg, err ); } else { KDevelop::IDocument* document = KDevelop::ICore::self()->documentController()->activeDocument(); if( !document ) { setError( -1 ); setErrorText( i18n( "There is no active document to launch." ) ); return; } script = document->url(); } if( !err.isEmpty() ) { setError( -3 ); setErrorText( err ); return; } QString remoteHost = iface->remoteHost( cfg, err ); if( !err.isEmpty() ) { setError( -4 ); setErrorText( err ); return; } if (envProfileName.isEmpty()) { qWarning() << "Launch Configuration:" << cfg->name() << i18n("No environment profile specified, looks like a broken " "configuration, please check run configuration '%1'. " "Using default environment profile.", cfg->name() ); envProfileName = environmentProfiles.defaultProfileName(); } QStringList arguments = iface->arguments( cfg, err ); if( !err.isEmpty() ) { setError( -2 ); setErrorText( err ); } if( error() != 0 ) { qWarning() << "Launch Configuration:" << cfg->name() << "oops, problem" << errorText(); return; } KDevelop::OutputModel::OutputFilterStrategy currentFilterMode = static_cast( iface->outputFilterModeId( cfg ) ); setStandardToolView(KDevelop::IOutputView::RunView); setBehaviours(KDevelop::IOutputView::AllowUserClose | KDevelop::IOutputView::AutoScroll); KDevelop::OutputModel* m = new KDevelop::OutputModel; m->setFilteringStrategy(currentFilterMode); setModel( m ); setDelegate( new KDevelop::OutputDelegate ); connect( lineMaker, &ProcessLineMaker::receivedStdoutLines, model(), &OutputModel::appendLines ); connect( proc, static_cast(&KProcess::error), this, &ScriptAppJob::processError ); connect( proc, static_cast(&KProcess::finished), this, &ScriptAppJob::processFinished ); // Now setup the process parameters proc->setEnvironment(environmentProfiles.createEnvironment(envProfileName, proc->systemEnvironment())); QUrl wc = iface->workingDirectory( cfg ); if( !wc.isValid() || wc.isEmpty() ) { wc = QUrl::fromLocalFile( QFileInfo( script.toLocalFile() ).absolutePath() ); } proc->setWorkingDirectory( wc.toLocalFile() ); proc->setProperty( "executable", interpreter.first() ); QStringList program; if (!remoteHost.isEmpty()) { program << QStringLiteral("ssh"); QStringList parts = remoteHost.split(QLatin1Char(':')); program << parts.first(); if (parts.length() > 1) { program << "-p "+parts.at(1); } } program << interpreter; program << script.toLocalFile(); program << arguments; qCDebug(PLUGIN_EXECUTESCRIPT) << "setting app:" << program; proc->setOutputChannelMode(KProcess::MergedChannels); proc->setProgram( program ); setTitle(cfg->name()); } void ScriptAppJob::start() { qCDebug(PLUGIN_EXECUTESCRIPT) << "launching?" << proc; if( proc ) { startOutput(); appendLine( i18n("Starting: %1", proc->program().join(QLatin1Char( ' ' ) ) ) ); proc->start(); } else { qWarning() << "No process, something went wrong when creating the job"; // No process means we've returned early on from the constructor, some bad error happened emitResult(); } } bool ScriptAppJob::doKill() { if( proc ) { proc->kill(); appendLine( i18n( "*** Killed Application ***" ) ); } return true; } void ScriptAppJob::processFinished( int exitCode , QProcess::ExitStatus status ) { lineMaker->flushBuffers(); if (exitCode == 0 && status == QProcess::NormalExit) { appendLine( i18n("*** Exited normally ***") ); } else if (status == QProcess::NormalExit) { appendLine( i18n("*** Exited with return code: %1 ***", QString::number(exitCode)) ); setError(OutputJob::FailedShownError); } else if (error() == KJob::KilledJobError) { appendLine( i18n("*** Process aborted ***") ); setError(KJob::KilledJobError); } else { appendLine( i18n("*** Crashed with return code: %1 ***", QString::number(exitCode)) ); setError(OutputJob::FailedShownError); } qCDebug(PLUGIN_EXECUTESCRIPT) << "Process done"; emitResult(); } void ScriptAppJob::processError( QProcess::ProcessError error ) { qCDebug(PLUGIN_EXECUTESCRIPT) << proc->readAllStandardError(); qCDebug(PLUGIN_EXECUTESCRIPT) << proc->readAllStandardOutput(); qCDebug(PLUGIN_EXECUTESCRIPT) << proc->errorString(); if( error == QProcess::FailedToStart ) { setError( FailedShownError ); QString errmsg = i18n("*** Could not start program '%1'. Make sure that the " "path is specified correctly ***", proc->program().join(QLatin1Char( ' ' ) ) ); appendLine( errmsg ); setErrorText( errmsg ); emitResult(); } qCDebug(PLUGIN_EXECUTESCRIPT) << "Process error"; } void ScriptAppJob::appendLine(const QString& l) { if (KDevelop::OutputModel* m = model()) { m->appendLine(l); } } KDevelop::OutputModel* ScriptAppJob::model() { return dynamic_cast( OutputJob::model() ); } diff --git a/plugins/externalscript/CMakeLists.txt b/plugins/externalscript/CMakeLists.txt index 099de0bb6..e1cc3d94d 100644 --- a/plugins/externalscript/CMakeLists.txt +++ b/plugins/externalscript/CMakeLists.txt @@ -1,27 +1,32 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kdevexternalscript\") ########### next target ############### set(kdevexternalscript_PART_SRCS externalscriptplugin.cpp externalscriptview.cpp externalscriptitem.cpp externalscriptjob.cpp editexternalscript.cpp - externalscriptdebug.cpp +) + +ecm_qt_declare_logging_category(kdevexternalscript_PART_SRCS + HEADER debug.h + IDENTIFIER PLUGIN_EXTERNALSCRIPT + CATEGORY_NAME "kdevplatform.plugins.externalscript" ) set(kdevexternalscript_PART_UI externalscriptview.ui editexternalscript.ui ) ki18n_wrap_ui(kdevexternalscript_PART_SRCS ${kdevexternalscript_PART_UI}) qt5_add_resources(kdevexternalscript_PART_SRCS kdevexternalscript.qrc) kdevplatform_add_plugin(kdevexternalscript JSON kdevexternalscript.json SOURCES ${kdevexternalscript_PART_SRCS}) target_link_libraries(kdevexternalscript KF5::TextEditor KF5::KIOWidgets KF5::Parts KF5::NewStuff KDev::Language KDev::Interfaces KDev::Project KDev::Util KDev::OutputView ) diff --git a/plugins/externalscript/externalscriptdebug.cpp b/plugins/externalscript/externalscriptdebug.cpp deleted file mode 100644 index 1f509544a..000000000 --- a/plugins/externalscript/externalscriptdebug.cpp +++ /dev/null @@ -1,24 +0,0 @@ -/* - This plugin is part of KDevelop. - - Copyright (C) 2011 Milian Wolff - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - -*/ - -#include "externalscriptdebug.h" - -Q_LOGGING_CATEGORY(PLUGIN_EXTERNALSCRIPT, "kdevplatform.plugins.externalscript") diff --git a/plugins/externalscript/externalscriptdebug.h b/plugins/externalscript/externalscriptdebug.h deleted file mode 100644 index 327e6f7b4..000000000 --- a/plugins/externalscript/externalscriptdebug.h +++ /dev/null @@ -1,28 +0,0 @@ -/* - This plugin is part of KDevelop. - - Copyright (C) 2011 Milian Wolff - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - -*/ - -#ifndef KDEVPLATFORM_PLUGIN_EXTERNALSCRIPTDEBUG_H -#define KDEVPLATFORM_PLUGIN_EXTERNALSCRIPTDEBUG_H - -#include -Q_DECLARE_LOGGING_CATEGORY(PLUGIN_EXTERNALSCRIPT) - -#endif // KDEVPLATFORM_PLUGIN_EXTERNALSCRIPTDEBUG_H diff --git a/plugins/externalscript/externalscriptjob.cpp b/plugins/externalscript/externalscriptjob.cpp index de47e09d9..b5f1aa03d 100644 --- a/plugins/externalscript/externalscriptjob.cpp +++ b/plugins/externalscript/externalscriptjob.cpp @@ -1,398 +1,398 @@ /* This file is part of KDevelop Copyright 2009 Andreas Pakulat Copyright 2010 Milian Wolff 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 "externalscriptjob.h" #include "externalscriptitem.h" -#include "externalscriptdebug.h" #include "externalscriptplugin.h" +#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; ExternalScriptJob::ExternalScriptJob( ExternalScriptItem* item, const QUrl& url, ExternalScriptPlugin* parent ) : KDevelop::OutputJob( parent ), m_proc( nullptr ), m_lineMaker( nullptr ), m_outputMode( item->outputMode() ), m_inputMode( item->inputMode() ), m_errorMode( item->errorMode() ), m_filterMode( item->filterMode() ), m_document( nullptr ), m_url( url ), m_selectionRange( KTextEditor::Range::invalid() ), m_showOutput( item->showOutput() ) { qCDebug(PLUGIN_EXTERNALSCRIPT) << "creating external script job"; setCapabilities( Killable ); setStandardToolView( KDevelop::IOutputView::RunView ); setBehaviours( KDevelop::IOutputView::AllowUserClose | KDevelop::IOutputView::AutoScroll ); KDevelop::OutputModel* model = new KDevelop::OutputModel; model->setFilteringStrategy(static_cast(m_filterMode)); setModel( model ); setDelegate( new KDevelop::OutputDelegate ); // also merge when error mode "equals" output mode if ( (m_outputMode == ExternalScriptItem::OutputInsertAtCursor && m_errorMode == ExternalScriptItem::ErrorInsertAtCursor) || (m_outputMode == ExternalScriptItem::OutputReplaceDocument && m_errorMode == ExternalScriptItem::ErrorReplaceDocument) || (m_outputMode == ExternalScriptItem::OutputReplaceSelectionOrDocument && m_errorMode == ExternalScriptItem::ErrorReplaceSelectionOrDocument) || (m_outputMode == ExternalScriptItem::OutputReplaceSelectionOrInsertAtCursor && m_errorMode == ExternalScriptItem::ErrorReplaceSelectionOrInsertAtCursor) || // also these two otherwise they clash... (m_outputMode == ExternalScriptItem::OutputReplaceSelectionOrInsertAtCursor && m_errorMode == ExternalScriptItem::ErrorReplaceSelectionOrDocument) || (m_outputMode == ExternalScriptItem::OutputReplaceSelectionOrDocument && m_errorMode == ExternalScriptItem::ErrorReplaceSelectionOrInsertAtCursor) ) { m_errorMode = ExternalScriptItem::ErrorMergeOutput; } KTextEditor::View* view = KDevelop::ICore::self()->documentController()->activeTextDocumentView(); if ( m_outputMode != ExternalScriptItem::OutputNone || m_inputMode != ExternalScriptItem::InputNone || m_errorMode != ExternalScriptItem::ErrorNone ) { if ( !view ) { KMessageBox::error( QApplication::activeWindow(), i18n( "Cannot run script '%1' since it tries to access " "the editor contents but no document is open.", item->text() ), i18n( "No Document Open" ) ); return; } m_document = view->document(); connect(m_document, &KTextEditor::Document::aboutToClose, this, [&] { kill(); }); m_selectionRange = view->selectionRange(); m_cursorPosition = view->cursorPosition(); } if ( item->saveMode() == ExternalScriptItem::SaveCurrentDocument && view ) { view->document()->save(); } else if ( item->saveMode() == ExternalScriptItem::SaveAllDocuments ) { foreach ( KDevelop::IDocument* doc, KDevelop::ICore::self()->documentController()->openDocuments() ) { doc->save(); } } QString command = item->command(); QString workingDir = item->workingDirectory(); if(item->performParameterReplacement()) command.replace( QLatin1String("%i"), QString::number( QCoreApplication::applicationPid() ) ); if ( !m_url.isEmpty() ) { const QUrl url = m_url; KDevelop::ProjectFolderItem* folder = nullptr; if ( KDevelop::ICore::self()->projectController()->findProjectForUrl( url ) ) { QList folders = KDevelop::ICore::self()->projectController()->findProjectForUrl(url)->foldersForPath(KDevelop::IndexedString(url)); if ( !folders.isEmpty() ) { folder = folders.first(); } } if ( folder ) { if ( folder->path().isLocalFile() && workingDir.isEmpty() ) { ///TODO: make configurable, use fallback to project dir workingDir = folder->path().toLocalFile(); } ///TODO: make those placeholders escapeable if(item->performParameterReplacement()) { command.replace( QLatin1String("%d"), KShell::quoteArg( m_url.toString(QUrl::PreferLocalFile) ) ); if ( KDevelop::IProject* project = KDevelop::ICore::self()->projectController()->findProjectForUrl( m_url ) ) { command.replace( QLatin1String("%p"), project->path().pathOrUrl() ); } } } else { if ( m_url.isLocalFile() && workingDir.isEmpty() ) { ///TODO: make configurable, use fallback to project dir workingDir = view->document()->url().adjusted(QUrl::RemoveFilename).toLocalFile(); } ///TODO: make those placeholders escapeable if(item->performParameterReplacement()) { command.replace( QLatin1String("%u"), KShell::quoteArg( m_url.toString() ) ); ///TODO: does that work with remote files? QFileInfo info( m_url.toString(QUrl::PreferLocalFile) ); command.replace( QLatin1String("%f"), KShell::quoteArg( info.filePath() ) ); command.replace( QLatin1String("%b"), KShell::quoteArg( info.baseName() ) ); command.replace( QLatin1String("%n"), KShell::quoteArg( info.fileName() ) ); command.replace( QLatin1String("%d"), KShell::quoteArg( info.path() ) ); if ( view->document() && view->selection() ) { command.replace( QLatin1String("%s"), KShell::quoteArg( view->selectionText() ) ); } if ( KDevelop::IProject* project = KDevelop::ICore::self()->projectController()->findProjectForUrl( m_url ) ) { command.replace( QLatin1String("%p"), project->path().pathOrUrl() ); } } } } m_proc = new KProcess( this ); if ( !workingDir.isEmpty() ) { m_proc->setWorkingDirectory( workingDir ); } m_lineMaker = new ProcessLineMaker( m_proc, this ); connect( m_lineMaker, &ProcessLineMaker::receivedStdoutLines, model, &OutputModel::appendLines ); connect( m_lineMaker, &ProcessLineMaker::receivedStdoutLines, this, &ExternalScriptJob::receivedStdoutLines ); connect( m_lineMaker, &ProcessLineMaker::receivedStderrLines, model, &OutputModel::appendLines ); connect( m_lineMaker, &ProcessLineMaker::receivedStderrLines, this, &ExternalScriptJob::receivedStderrLines ); connect( m_proc, static_cast(&KProcess::error), this, &ExternalScriptJob::processError ); connect( m_proc, static_cast(&KProcess::finished), this, &ExternalScriptJob::processFinished ); // Now setup the process parameters qCDebug(PLUGIN_EXTERNALSCRIPT) << "setting command:" << command; if ( m_errorMode == ExternalScriptItem::ErrorMergeOutput ) { m_proc->setOutputChannelMode( KProcess::MergedChannels ); } else { m_proc->setOutputChannelMode( KProcess::SeparateChannels ); } m_proc->setShellCommand( command ); setObjectName( command ); } void ExternalScriptJob::start() { qCDebug(PLUGIN_EXTERNALSCRIPT) << "launching?" << m_proc; if ( m_proc ) { if ( m_showOutput ) { startOutput(); } appendLine( i18n( "Running external script: %1", m_proc->program().join( QStringLiteral( " " ) ) ) ); m_proc->start(); if ( m_inputMode != ExternalScriptItem::InputNone ) { QString inputText; switch ( m_inputMode ) { case ExternalScriptItem::InputNone: // do nothing; break; case ExternalScriptItem::InputSelectionOrNone: if ( m_selectionRange.isValid() ) { inputText = m_document->text(m_selectionRange); } // else nothing break; case ExternalScriptItem::InputSelectionOrDocument: if ( m_selectionRange.isValid() ) { inputText = m_document->text(m_selectionRange); } else { inputText = m_document->text(); } break; case ExternalScriptItem::InputDocument: inputText = m_document->text(); break; } ///TODO: what to do with the encoding here? /// maybe ask Christoph for what kate returns... m_proc->write( inputText.toUtf8() ); m_proc->closeWriteChannel(); } } else { qWarning() << "No process, something went wrong when creating the job"; // No process means we've returned early on from the constructor, some bad error happened emitResult(); } } bool ExternalScriptJob::doKill() { if ( m_proc ) { m_proc->kill(); appendLine( i18n( "*** Killed Application ***" ) ); } return true; } void ExternalScriptJob::processFinished( int exitCode , QProcess::ExitStatus status ) { m_lineMaker->flushBuffers(); if ( exitCode == 0 && status == QProcess::NormalExit ) { if ( m_outputMode != ExternalScriptItem::OutputNone ) { if ( !m_stdout.isEmpty() ) { QString output = m_stdout.join( QStringLiteral("\n") ); switch ( m_outputMode ) { case ExternalScriptItem::OutputNone: // do nothing; break; case ExternalScriptItem::OutputCreateNewFile: KDevelop::ICore::self()->documentController()->openDocumentFromText( output ); break; case ExternalScriptItem::OutputInsertAtCursor: m_document->insertText( m_cursorPosition, output ); break; case ExternalScriptItem::OutputReplaceSelectionOrInsertAtCursor: if ( m_selectionRange.isValid() ) { m_document->replaceText( m_selectionRange, output ); } else { m_document->insertText( m_cursorPosition, output ); } break; case ExternalScriptItem::OutputReplaceSelectionOrDocument: if ( m_selectionRange.isValid() ) { m_document->replaceText( m_selectionRange, output ); } else { m_document->setText( output ); } break; case ExternalScriptItem::OutputReplaceDocument: m_document->setText( output ); break; } } } if ( m_errorMode != ExternalScriptItem::ErrorNone && m_errorMode != ExternalScriptItem::ErrorMergeOutput ) { QString output = m_stderr.join( QStringLiteral("\n") ); if ( !output.isEmpty() ) { switch ( m_errorMode ) { case ExternalScriptItem::ErrorNone: case ExternalScriptItem::ErrorMergeOutput: // do nothing; break; case ExternalScriptItem::ErrorCreateNewFile: KDevelop::ICore::self()->documentController()->openDocumentFromText( output ); break; case ExternalScriptItem::ErrorInsertAtCursor: m_document->insertText( m_cursorPosition, output ); break; case ExternalScriptItem::ErrorReplaceSelectionOrInsertAtCursor: if ( m_selectionRange.isValid() ) { m_document->replaceText( m_selectionRange, output ); } else { m_document->insertText( m_cursorPosition, output ); } break; case ExternalScriptItem::ErrorReplaceSelectionOrDocument: if ( m_selectionRange.isValid() ) { m_document->replaceText( m_selectionRange, output ); } else { m_document->setText( output ); } break; case ExternalScriptItem::ErrorReplaceDocument: m_document->setText( output ); break; } } } appendLine( i18n( "*** Exited normally ***" ) ); } else { if ( status == QProcess::NormalExit ) appendLine( i18n( "*** Exited with return code: %1 ***", QString::number( exitCode ) ) ); else if ( error() == KJob::KilledJobError ) appendLine( i18n( "*** Process aborted ***" ) ); else appendLine( i18n( "*** Crashed with return code: %1 ***", QString::number( exitCode ) ) ); } qCDebug(PLUGIN_EXTERNALSCRIPT) << "Process done"; emitResult(); } void ExternalScriptJob::processError( QProcess::ProcessError error ) { if ( error == QProcess::FailedToStart ) { setError( -1 ); QString errmsg = i18n("*** Could not start program '%1'. Make sure that the " "path is specified correctly ***", m_proc->program().join( QLatin1Char(' ') ) ); appendLine( errmsg ); setErrorText( errmsg ); emitResult(); } qCDebug(PLUGIN_EXTERNALSCRIPT) << "Process error"; } void ExternalScriptJob::appendLine( const QString& l ) { if ( KDevelop::OutputModel* m = model() ) { m->appendLine( l ); } } KDevelop::OutputModel* ExternalScriptJob::model() { return dynamic_cast( OutputJob::model() ); } void ExternalScriptJob::receivedStderrLines(const QStringList& lines) { m_stderr += lines; } void ExternalScriptJob::receivedStdoutLines(const QStringList& lines) { m_stdout += lines; } // kate: indent-mode cstyle; space-indent on; indent-width 2; replace-tabs on; diff --git a/plugins/externalscript/externalscriptplugin.cpp b/plugins/externalscript/externalscriptplugin.cpp index 05fd35382..a9dce202e 100644 --- a/plugins/externalscript/externalscriptplugin.cpp +++ b/plugins/externalscript/externalscriptplugin.cpp @@ -1,372 +1,372 @@ /* This plugin is part of KDevelop. Copyright (C) 2010 Milian Wolff This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "externalscriptplugin.h" #include "externalscriptview.h" #include "externalscriptitem.h" #include "externalscriptjob.h" -#include "externalscriptdebug.h" +#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include K_PLUGIN_FACTORY_WITH_JSON(ExternalScriptFactory, "kdevexternalscript.json", registerPlugin();) class ExternalScriptViewFactory: public KDevelop::IToolViewFactory { public: explicit ExternalScriptViewFactory( ExternalScriptPlugin *plugin ): m_plugin( plugin ) {} QWidget* create( QWidget *parent = nullptr ) override { return new ExternalScriptView( m_plugin, parent ); } Qt::DockWidgetArea defaultPosition() override { return Qt::RightDockWidgetArea; } QString id() const override { return QStringLiteral("org.kdevelop.ExternalScriptView"); } private: ExternalScriptPlugin *m_plugin; }; // We extend ExternalScriptJob so that it deletes the temporarily created item on destruction class ExternalScriptJobOwningItem : public ExternalScriptJob { Q_OBJECT public: ExternalScriptJobOwningItem( ExternalScriptItem* item, const QUrl &url, ExternalScriptPlugin* parent ) : ExternalScriptJob(item, url, parent), m_item(item) { } ~ExternalScriptJobOwningItem() override { delete m_item; } private: ExternalScriptItem* m_item; }; ExternalScriptPlugin* ExternalScriptPlugin::m_self = nullptr; ExternalScriptPlugin::ExternalScriptPlugin( QObject* parent, const QVariantList& /*args*/ ) : IPlugin( QStringLiteral("kdevexternalscript"), parent ), m_model( new QStandardItemModel( this ) ), m_factory( new ExternalScriptViewFactory( this ) ) { Q_ASSERT( !m_self ); m_self = this; QDBusConnection::sessionBus().registerObject( QStringLiteral("/org/kdevelop/ExternalScriptPlugin"), this, QDBusConnection::ExportScriptableSlots ); setXMLFile( QStringLiteral("kdevexternalscript.rc") ); //BEGIN load config KConfigGroup config = getConfig(); foreach( const QString& group, config.groupList() ) { KConfigGroup script = config.group( group ); if ( script.hasKey( "name" ) && script.hasKey( "command" ) ) { ExternalScriptItem* item = new ExternalScriptItem; item->setText( script.readEntry( "name" ) ); item->setCommand( script.readEntry( "command" )); item->setInputMode( static_cast( script.readEntry( "inputMode", 0u ) ) ); item->setOutputMode( static_cast( script.readEntry( "outputMode", 0u ) ) ); item->setErrorMode( static_cast( script.readEntry( "errorMode", 0u ) ) ); item->setSaveMode( static_cast( script.readEntry( "saveMode", 0u ) ) ); item->setFilterMode( script.readEntry( "filterMode", 0u )); item->action()->setShortcut( QKeySequence( script.readEntry( "shortcuts" ) ) ); item->setShowOutput( script.readEntry( "showOutput", true ) ); m_model->appendRow( item ); } } //END load config core()->uiController()->addToolView( i18n( "External Scripts" ), m_factory ); connect( m_model, &QStandardItemModel::rowsRemoved, this, &ExternalScriptPlugin::rowsRemoved ); connect( m_model, &QStandardItemModel::rowsInserted, this, &ExternalScriptPlugin::rowsInserted ); const bool firstUse = config.readEntry( "firstUse", true ); if ( firstUse ) { // some example scripts ExternalScriptItem* item = new ExternalScriptItem; item->setText( i18n("Quick Compile") ); item->setCommand( QStringLiteral("g++ -o %b %f && ./%b") ); m_model->appendRow( item ); item = new ExternalScriptItem; item->setText( i18n("Google Selection") ); item->setCommand( QStringLiteral("xdg-open \"https://www.google.com/search?q=%s\"") ); item->setShowOutput( false ); m_model->appendRow( item ); item = new ExternalScriptItem; item->setText( i18n("Sort Selection") ); item->setCommand( QStringLiteral("sort") ); item->setInputMode( ExternalScriptItem::InputSelectionOrDocument ); item->setOutputMode( ExternalScriptItem::OutputReplaceSelectionOrDocument ); item->setShowOutput( false ); m_model->appendRow( item ); config.writeEntry( "firstUse", false ); config.sync(); } } ExternalScriptPlugin* ExternalScriptPlugin::self() { return m_self; } ExternalScriptPlugin::~ExternalScriptPlugin() { m_self = nullptr; } KDevelop::ContextMenuExtension ExternalScriptPlugin::contextMenuExtension( KDevelop::Context* context ) { m_urls.clear(); int folderCount = 0; if ( context->type() == KDevelop::Context::FileContext ) { KDevelop::FileContext* filectx = dynamic_cast( context ); m_urls = filectx->urls(); } else if ( context->type() == KDevelop::Context::ProjectItemContext ) { KDevelop::ProjectItemContext* projctx = dynamic_cast( context ); foreach( KDevelop::ProjectBaseItem* item, projctx->items() ) { if ( item->file() ) { m_urls << item->file()->path().toUrl(); } else if ( item->folder() ) { m_urls << item->folder()->path().toUrl(); folderCount++; } } } else if ( context->type() == KDevelop::Context::EditorContext ) { KDevelop::EditorContext *econtext = dynamic_cast(context); m_urls << econtext->url(); } if ( !m_urls.isEmpty() ) { KDevelop::ContextMenuExtension ext; QMenu* menu = new QMenu(); menu->setTitle( i18n("External Scripts") ); for ( int row = 0; row < m_model->rowCount(); ++row ) { ExternalScriptItem* item = dynamic_cast( m_model->item( row ) ); Q_ASSERT( item ); if (context->type() != KDevelop::Context::EditorContext) { // filter scripts that depend on an opened document // if the context menu was not requested inside the editor if (item->performParameterReplacement() && item->command().contains(QStringLiteral("%s"))) { continue; } else if (item->inputMode() == ExternalScriptItem::InputSelectionOrNone) { continue; } } if ( folderCount == m_urls.count() ) { // when only folders filter items that don't have %d parameter (or another parameter) if (item->performParameterReplacement() && (!item->command().contains(QStringLiteral("%d")) || item->command().contains(QStringLiteral("%s")) || item->command().contains(QStringLiteral("%u")) || item->command().contains(QStringLiteral("%f")) || item->command().contains(QStringLiteral("%b")) || item->command().contains(QStringLiteral("%n")) ) ) { continue; } } QAction* scriptAction = new QAction( item->text(), this ); scriptAction->setData( QVariant::fromValue( item )); connect( scriptAction, &QAction::triggered, this, &ExternalScriptPlugin::executeScriptFromContextMenu ); menu->addAction( scriptAction ); } if (!menu->actions().isEmpty()) ext.addAction( KDevelop::ContextMenuExtension::ExtensionGroup, menu->menuAction() ); return ext; } return KDevelop::IPlugin::contextMenuExtension( context ); } void ExternalScriptPlugin::unload() { core()->uiController()->removeToolView( m_factory ); KDevelop::IPlugin::unload(); } KConfigGroup ExternalScriptPlugin::getConfig() const { return KSharedConfig::openConfig()->group("External Scripts"); } QStandardItemModel* ExternalScriptPlugin::model() const { return m_model; } void ExternalScriptPlugin::execute( ExternalScriptItem* item, const QUrl& url ) const { ExternalScriptJob* job = new ExternalScriptJob( item, url, const_cast(this) ); KDevelop::ICore::self()->runController()->registerJob( job ); } void ExternalScriptPlugin::execute(ExternalScriptItem* item) const { auto document = KDevelop::ICore::self()->documentController()->activeDocument(); execute( item, document ? document->url() : QUrl() ); } bool ExternalScriptPlugin::executeCommand ( QString command, QString workingDirectory ) const { ExternalScriptItem* item = new ExternalScriptItem; item->setCommand(command); item->setWorkingDirectory(workingDirectory); item->setPerformParameterReplacement(false); qCDebug(PLUGIN_EXTERNALSCRIPT) << "executing command " << command << " in dir " << workingDirectory << " as external script"; ExternalScriptJobOwningItem* job = new ExternalScriptJobOwningItem( item, QUrl(), const_cast(this) ); // When a command is executed, for example through the terminal, we don't want the command output to be risen job->setVerbosity(KDevelop::OutputJob::Silent); KDevelop::ICore::self()->runController()->registerJob( job ); return true; } QString ExternalScriptPlugin::executeCommandSync ( QString command, QString workingDirectory ) const { qCDebug(PLUGIN_EXTERNALSCRIPT) << "executing command " << command << " in working-dir " << workingDirectory; KProcess process; process.setWorkingDirectory( workingDirectory ); process.setShellCommand( command ); process.setOutputChannelMode( KProcess::OnlyStdoutChannel ); process.execute(); return QString::fromLocal8Bit(process.readAll()); } void ExternalScriptPlugin::executeScriptFromActionData() const { QAction* action = dynamic_cast( sender() ); Q_ASSERT( action ); ExternalScriptItem* item = action->data().value(); Q_ASSERT( item ); execute( item ); } void ExternalScriptPlugin::executeScriptFromContextMenu() const { QAction* action = dynamic_cast( sender() ); Q_ASSERT( action ); ExternalScriptItem* item = action->data().value(); Q_ASSERT( item ); foreach( const QUrl& url, m_urls) { KDevelop::ICore::self()->documentController()->openDocument( url ); execute( item, url ); } } void ExternalScriptPlugin::rowsInserted( const QModelIndex& /*parent*/, int start, int end ) { for ( int i = start; i <= end; ++i ) { saveItemForRow( i ); } } void ExternalScriptPlugin::rowsRemoved( const QModelIndex& /*parent*/, int start, int end ) { KConfigGroup config = getConfig(); for ( int i = start; i <= end; ++i ) { KConfigGroup child = config.group( QStringLiteral("script %1").arg(i) ); qCDebug(PLUGIN_EXTERNALSCRIPT) << "removing config group:" << child.name(); child.deleteGroup(); } config.sync(); } void ExternalScriptPlugin::saveItem( const ExternalScriptItem* item ) { const QModelIndex index = m_model->indexFromItem( item ); Q_ASSERT( index.isValid() ); saveItemForRow( index.row() ); } void ExternalScriptPlugin::saveItemForRow( int row ) { const QModelIndex idx = m_model->index( row, 0 ); Q_ASSERT( idx.isValid() ); ExternalScriptItem* item = dynamic_cast( m_model->item( row ) ); Q_ASSERT( item ); qCDebug(PLUGIN_EXTERNALSCRIPT) << "save extern script:" << item << idx; KConfigGroup config = getConfig().group( QStringLiteral("script %1").arg( row ) ); config.writeEntry( "name", item->text() ); config.writeEntry( "command", item->command() ); config.writeEntry( "inputMode", (uint) item->inputMode() ); config.writeEntry( "outputMode", (uint) item->outputMode() ); config.writeEntry( "errorMode", (uint) item->errorMode() ); config.writeEntry( "saveMode", (uint) item->saveMode() ); config.writeEntry( "shortcuts", item->action()->shortcut().toString() ); config.writeEntry( "showOutput", item->showOutput() ); config.writeEntry( "filterMode", item->filterMode()); config.sync(); } #include "externalscriptplugin.moc" // kate: indent-mode cstyle; space-indent on; indent-width 2; replace-tabs on; diff --git a/plugins/filemanager/CMakeLists.txt b/plugins/filemanager/CMakeLists.txt index 3a5b211a8..a49f99fac 100644 --- a/plugins/filemanager/CMakeLists.txt +++ b/plugins/filemanager/CMakeLists.txt @@ -1,10 +1,15 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kdevfilemanager\") set(kdevfilemanager_PART_SRCS kdevfilemanagerplugin.cpp filemanager.cpp bookmarkhandler.cpp ) +ecm_qt_declare_logging_category(kdevfilemanager_PART_SRCS + HEADER debug.h + IDENTIFIER PLUGIN_FILEMANAGER + CATEGORY_NAME "kdevplatform.plugins.filemanager" +) qt5_add_resources(kdevfilemanager_PART_SRCS kdevfilemanager.qrc) kdevplatform_add_plugin(kdevfilemanager JSON kdevfilemanager.json SOURCES ${kdevfilemanager_PART_SRCS}) target_link_libraries(kdevfilemanager KF5::Bookmarks KF5::KIOCore KF5::KIOFileWidgets KF5::KIOWidgets KF5::TextEditor KDev::Interfaces) diff --git a/plugins/filemanager/debug.h b/plugins/filemanager/debug.h deleted file mode 100644 index d56e363bc..000000000 --- a/plugins/filemanager/debug.h +++ /dev/null @@ -1,26 +0,0 @@ -/* - * This file is part of KDevelop - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#ifndef KDEVPLATFORM_FILEMANAGER_DEBUG_H -#define KDEVPLATFORM_FILEMANAGER_DEBUG_H - -#include -Q_DECLARE_LOGGING_CATEGORY(PLUGIN_FILEMANAGER) - -#endif diff --git a/plugins/filemanager/filemanager.cpp b/plugins/filemanager/filemanager.cpp index 21ca0d55e..539aecdf5 100644 --- a/plugins/filemanager/filemanager.cpp +++ b/plugins/filemanager/filemanager.cpp @@ -1,214 +1,212 @@ /*************************************************************************** * Copyright 2006-2007 Alexander Dymo * * Copyright 2006 Andreas Pakulat * * Copyright 2016 Imran Tatriev * * * * 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 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 "filemanager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../openwith/iopenwith.h" #include "kdevfilemanagerplugin.h" #include "bookmarkhandler.h" #include "debug.h" -Q_LOGGING_CATEGORY(PLUGIN_FILEMANAGER, "kdevplatform.plugins.filemanager") - FileManager::FileManager(KDevFileManagerPlugin *plugin, QWidget* parent) : QWidget(parent), m_plugin(plugin) { setObjectName(QStringLiteral("FileManager")); setWindowIcon(QIcon::fromTheme(QStringLiteral("folder-sync"), windowIcon())); setWindowTitle(i18n("File System")); KConfigGroup cg = KDevelop::ICore::self()->activeSession()->config()->group( "Filesystem" ); QVBoxLayout *l = new QVBoxLayout(this); l->setMargin(0); l->setSpacing(0); KFilePlacesModel* model = new KFilePlacesModel( this ); urlnav = new KUrlNavigator(model, QUrl(cg.readEntry( "LastLocation", QUrl::fromLocalFile( QDir::homePath() ) )), this ); connect(urlnav, &KUrlNavigator::urlChanged, this, &FileManager::gotoUrl); l->addWidget(urlnav); dirop = new KDirOperator( urlnav->locationUrl(), this); dirop->setView( KFile::Tree ); dirop->setupMenu( KDirOperator::SortActions | KDirOperator::FileActions | KDirOperator::NavActions | KDirOperator::ViewActions ); connect(dirop, &KDirOperator::urlEntered, this, &FileManager::updateNav); connect(dirop, &KDirOperator::contextMenuAboutToShow, this, &FileManager::fillContextMenu); l->addWidget(dirop); connect( dirop, &KDirOperator::fileSelected, this, &FileManager::openFile ); setFocusProxy(dirop); // includes some actions, but not hooked into the shortcut dialog atm m_actionCollection = new KActionCollection(this); m_actionCollection->addAssociatedWidget(this); setupActions(); // Connect the bookmark handler connect(m_bookmarkHandler, &BookmarkHandler::openUrl, this, &FileManager::gotoUrl); connect(m_bookmarkHandler, &BookmarkHandler::openUrl, this, &FileManager::updateNav); } FileManager::~FileManager() { KConfigGroup cg = KDevelop::ICore::self()->activeSession()->config()->group( "Filesystem" ); cg.writeEntry( "LastLocation", urlnav->locationUrl() ); cg.sync(); } void FileManager::fillContextMenu(KFileItem item, QMenu* menu) { foreach(QAction* a, contextActions){ if(menu->actions().contains(a)){ menu->removeAction(a); } } contextActions.clear(); contextActions.append(menu->addSeparator()); menu->addAction(newFileAction); contextActions.append(newFileAction); KDevelop::FileContext context(QList() << item.url()); QList extensions = KDevelop::ICore::self()->pluginController()->queryPluginsForContextMenuExtensions( &context ); KDevelop::ContextMenuExtension::populateMenu(menu, extensions); QMenu* tmpMenu = new QMenu(); KDevelop::ContextMenuExtension::populateMenu(tmpMenu, extensions); contextActions.append(tmpMenu->actions()); delete tmpMenu; } void FileManager::openFile(const KFileItem& file) { KDevelop::IOpenWith::openFiles(QList() << file.url()); } void FileManager::gotoUrl( const QUrl& url ) { dirop->setUrl( url, true ); } void FileManager::updateNav( const QUrl& url ) { urlnav->setLocationUrl( url ); } void FileManager::setupActions() { KActionMenu *acmBookmarks = new KActionMenu(QIcon::fromTheme(QStringLiteral("bookmarks")), i18n("Bookmarks"), this); acmBookmarks->setDelayed(false); m_bookmarkHandler = new BookmarkHandler(this, acmBookmarks->menu()); acmBookmarks->setShortcutContext(Qt::WidgetWithChildrenShortcut); QAction* action = new QAction(this); action->setShortcutContext(Qt::WidgetWithChildrenShortcut); action->setText(i18n("Current Document Directory")); action->setIcon(QIcon::fromTheme(QStringLiteral("dirsync"))); connect(action, &QAction::triggered, this, &FileManager::syncCurrentDocumentDirectory); tbActions << (dirop->actionCollection()->action(QStringLiteral("back"))); tbActions << (dirop->actionCollection()->action(QStringLiteral("up"))); tbActions << (dirop->actionCollection()->action(QStringLiteral("home"))); tbActions << (dirop->actionCollection()->action(QStringLiteral("forward"))); tbActions << (dirop->actionCollection()->action(QStringLiteral("reload"))); tbActions << acmBookmarks; tbActions << action; tbActions << (dirop->actionCollection()->action(QStringLiteral("sorting menu"))); tbActions << (dirop->actionCollection()->action(QStringLiteral("show hidden"))); newFileAction = new QAction(this); newFileAction->setText(i18n("New File...")); newFileAction->setIcon(QIcon::fromTheme(QStringLiteral("document-new"))); connect(newFileAction, &QAction::triggered, this, &FileManager::createNewFile); } void FileManager::createNewFile() { QUrl destUrl = QFileDialog::getSaveFileUrl(KDevelop::ICore::self()->uiController()->activeMainWindow(), i18n("Create New File")); if (destUrl.isEmpty()) { return; } KJob* job = KIO::storedPut(QByteArray(), destUrl, -1); KJobWidgets::setWindow(job, this); connect(job, &KJob::result, this, &FileManager::fileCreated); } void FileManager::fileCreated(KJob* job) { auto transferJob = qobject_cast(job); Q_ASSERT(transferJob); if (!transferJob->error()) { KDevelop::ICore::self()->documentController()->openDocument( transferJob->url() ); } else { KMessageBox::error(KDevelop::ICore::self()->uiController()->activeMainWindow(), i18n("Unable to create file '%1'", transferJob->url().toDisplayString(QUrl::PreferLocalFile))); } } void FileManager::syncCurrentDocumentDirectory() { if( KDevelop::IDocument* activeDoc = KDevelop::ICore::self()->documentController()->activeDocument() ) updateNav( activeDoc->url().adjusted(QUrl::RemoveFilename) ); } QList FileManager::toolBarActions() const { return tbActions; } KActionCollection* FileManager::actionCollection() const { return m_actionCollection; } KDirOperator* FileManager::dirOperator() const { return dirop; } KDevFileManagerPlugin* FileManager::plugin() const { return m_plugin; } #include "moc_filemanager.cpp" diff --git a/plugins/filetemplates/CMakeLists.txt b/plugins/filetemplates/CMakeLists.txt index 607a64f95..419ae5aea 100644 --- a/plugins/filetemplates/CMakeLists.txt +++ b/plugins/filetemplates/CMakeLists.txt @@ -1,94 +1,99 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kdevfiletemplates\") set(kdevfiletemplates_PART_SRCS templatepreview.cpp templatepreviewtoolview.cpp filetemplatesplugin.cpp ipagefocus.cpp classidentifierpage.cpp classmemberspage.cpp defaultcreateclasshelper.cpp licensepage.cpp outputpage.cpp overridespage.cpp templateclassassistant.cpp templateoptionspage.cpp templateselectionpage.cpp testcasespage.cpp - debug.cpp +) + +ecm_qt_declare_logging_category(kdevfiletemplates_PART_SRCS + HEADER debug.h + IDENTIFIER PLUGIN_FILETEMPLATES + CATEGORY_NAME "kdevplatform.plugins.filetemplates" ) ki18n_wrap_ui(kdevfiletemplates_PART_SRCS templatepreviewtoolview.ui ui/licensechooser.ui ui/newclass.ui ui/outputlocation.ui ui/overridevirtuals.ui ui/templateselection.ui ui/testcases.ui ) qt5_add_resources(kdevfiletemplates_PART_SRCS kdevfiletemplates.qrc) kdevplatform_add_plugin(kdevfiletemplates JSON kdevfiletemplates.json SOURCES ${kdevfiletemplates_PART_SRCS}) target_link_libraries(kdevfiletemplates KDev::Interfaces KDev::Language KDev::Project KDev::Util KF5::NewStuff KF5::TextEditor KF5::ItemModels ) ########### install files ############### install(FILES licenses/GPL\ v2 licenses/GPL\ v3 licenses/LGPL\ v2 licenses/LGPL\ v3 licenses/Apache\ v2 licenses/BSD licenses/Boost licenses/MIT\ X11 licenses/Mozilla\ v1.1 "licenses/LGPL v2+ (KDE)" "licenses/GPL v2+ (KDE)" DESTINATION ${KDE_INSTALL_DATADIR}/kdevcodegen/licenses ) ################ set(test_srcs main.cpp templatepreview.cpp ipagefocus.cpp classidentifierpage.cpp classmemberspage.cpp defaultcreateclasshelper.cpp licensepage.cpp outputpage.cpp overridespage.cpp templateclassassistant.cpp templateoptionspage.cpp templateselectionpage.cpp testcasespage.cpp debug.cpp ) add_executable(testfiletemplates ${test_srcs}) add_dependencies(testfiletemplates kdevfiletemplates) target_link_libraries(testfiletemplates KDev::Interfaces KDev::Language KDev::Project KDev::Util KDev::Tests KF5::NewStuff ) add_subdirectory(tests) diff --git a/plugins/filetemplates/debug.cpp b/plugins/filetemplates/debug.cpp deleted file mode 100644 index 2d39808fc..000000000 --- a/plugins/filetemplates/debug.cpp +++ /dev/null @@ -1,22 +0,0 @@ -/* - * This file is part of KDevelop - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#include "debug.h" - -Q_LOGGING_CATEGORY(PLUGIN_FILETEMPLATES, "kdevplatform.plugins.filetemplates") diff --git a/plugins/filetemplates/debug.h b/plugins/filetemplates/debug.h deleted file mode 100644 index 3d75feee8..000000000 --- a/plugins/filetemplates/debug.h +++ /dev/null @@ -1,26 +0,0 @@ -/* - * This file is part of KDevelop - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#ifndef KDEVPLATFORM_FILETEMPLATES_DEBUG_H -#define KDEVPLATFORM_FILETEMPLATES_DEBUG_H - -#include -Q_DECLARE_LOGGING_CATEGORY(PLUGIN_FILETEMPLATES) - -#endif diff --git a/plugins/git/CMakeLists.txt b/plugins/git/CMakeLists.txt index 8b395ef8d..c1d8fdf6d 100644 --- a/plugins/git/CMakeLists.txt +++ b/plugins/git/CMakeLists.txt @@ -1,26 +1,32 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kdevgit\") -add_subdirectory(tests) -add_subdirectory(icons) - +ecm_qt_declare_logging_category(kdevgit_LOG_PART_SRCS + HEADER debug.h + IDENTIFIER PLUGIN_GIT + CATEGORY_NAME "kdevplatform.plugins.git" +) set(kdevgit_PART_SRCS stashmanagerdialog.cpp stashpatchsource.cpp gitmessagehighlighter.cpp gitclonejob.cpp gitplugin.cpp gitpluginmetadata.cpp gitjob.cpp gitplugincheckinrepositoryjob.cpp gitnameemaildialog.cpp + ${kdevgit_LOG_PART_SRCS} ) ki18n_wrap_ui(kdevgit_PART_SRCS stashmanagerdialog.ui) ki18n_wrap_ui(kdevgit_PART_SRCS gitnameemaildialog.ui) kdevplatform_add_plugin(kdevgit JSON kdevgit.json SOURCES ${kdevgit_PART_SRCS}) target_link_libraries(kdevgit KDev::Util KDev::Interfaces KDev::Vcs KDev::Project KF5::SonnetUi ) + +add_subdirectory(tests) +add_subdirectory(icons) diff --git a/plugins/git/debug.h b/plugins/git/debug.h deleted file mode 100644 index df860614d..000000000 --- a/plugins/git/debug.h +++ /dev/null @@ -1,26 +0,0 @@ -/* - * This file is part of KDevelop - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#ifndef KDEVPLATFORM_GIT_DEBUG_H -#define KDEVPLATFORM_GIT_DEBUG_H - -#include -Q_DECLARE_LOGGING_CATEGORY(PLUGIN_GIT) - -#endif diff --git a/plugins/git/gitplugin.cpp b/plugins/git/gitplugin.cpp index b8f9d384a..bc926bf51 100644 --- a/plugins/git/gitplugin.cpp +++ b/plugins/git/gitplugin.cpp @@ -1,1561 +1,1558 @@ /*************************************************************************** * Copyright 2008 Evgeniy Ivanov * * Copyright 2009 Hugo Parente Lima * * Copyright 2010 Aleix Pol Gonzalez * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "gitplugin.h" #include -#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gitclonejob.h" #include #include #include "stashmanagerdialog.h" #include #include #include #include #include #include #include "gitjob.h" #include "gitmessagehighlighter.h" #include "gitplugincheckinrepositoryjob.h" #include "gitnameemaildialog.h" #include "debug.h" -Q_LOGGING_CATEGORY(PLUGIN_GIT, "kdevplatform.plugins.git") - using namespace KDevelop; QVariant runSynchronously(KDevelop::VcsJob* job) { QVariant ret; if(job->exec() && job->status()==KDevelop::VcsJob::JobSucceeded) { ret = job->fetchResults(); } delete job; return ret; } namespace { QDir dotGitDirectory(const QUrl& dirPath) { const QFileInfo finfo(dirPath.toLocalFile()); QDir dir = finfo.isDir() ? QDir(finfo.filePath()): finfo.absoluteDir(); static const QString gitDir = QStringLiteral(".git"); while (!dir.exists(gitDir) && dir.cdUp()) {} // cdUp, until there is a sub-directory called .git if (dir.isRoot()) { qCWarning(PLUGIN_GIT) << "couldn't find the git root for" << dirPath; } return dir; } /** * Whenever a directory is provided, change it for all the files in it but not inner directories, * that way we make sure we won't get into recursion, */ static QList preventRecursion(const QList& urls) { QList ret; foreach(const QUrl& url, urls) { QDir d(url.toLocalFile()); if(d.exists()) { QStringList entries = d.entryList(QDir::Files | QDir::NoDotAndDotDot); foreach(const QString& entry, entries) { QUrl entryUrl = QUrl::fromLocalFile(d.absoluteFilePath(entry)); ret += entryUrl; } } else ret += url; } return ret; } QString toRevisionName(const KDevelop::VcsRevision& rev, QString currentRevision=QString()) { switch(rev.revisionType()) { case VcsRevision::Special: switch(rev.revisionValue().value()) { case VcsRevision::Head: return QStringLiteral("^HEAD"); case VcsRevision::Base: return QString(); case VcsRevision::Working: return QString(); case VcsRevision::Previous: Q_ASSERT(!currentRevision.isEmpty()); return currentRevision + "^1"; case VcsRevision::Start: return QString(); case VcsRevision::UserSpecialType: //Not used Q_ASSERT(false && "i don't know how to do that"); } break; case VcsRevision::GlobalNumber: return rev.revisionValue().toString(); case VcsRevision::Date: case VcsRevision::FileNumber: case VcsRevision::Invalid: case VcsRevision::UserSpecialType: Q_ASSERT(false); } return QString(); } QString revisionInterval(const KDevelop::VcsRevision& rev, const KDevelop::VcsRevision& limit) { QString ret; if(rev.revisionType()==VcsRevision::Special && rev.revisionValue().value()==VcsRevision::Start) //if we want it to the beginning just put the revisionInterval ret = toRevisionName(limit, QString()); else { QString dst = toRevisionName(limit); if(dst.isEmpty()) ret = dst; else { QString src = toRevisionName(rev, dst); if(src.isEmpty()) ret = src; else ret = src+".."+dst; } } return ret; } QDir urlDir(const QUrl& url) { QFileInfo f(url.toLocalFile()); if(f.isDir()) return QDir(url.toLocalFile()); else return f.absoluteDir(); } QDir urlDir(const QList& urls) { return urlDir(urls.first()); } //TODO: could be improved } GitPlugin::GitPlugin( QObject *parent, const QVariantList & ) : DistributedVersionControlPlugin(parent, QStringLiteral("kdevgit")), m_oldVersion(false), m_usePrefix(true) { if (QStandardPaths::findExecutable(QStringLiteral("git")).isEmpty()) { setErrorDescription(i18n("Unable to find git executable. Is it installed on the system?")); return; } setObjectName(QStringLiteral("Git")); DVcsJob* versionJob = new DVcsJob(QDir::tempPath(), this, KDevelop::OutputJob::Silent); *versionJob << "git" << "--version"; connect(versionJob, &DVcsJob::readyForParsing, this, &GitPlugin::parseGitVersionOutput); ICore::self()->runController()->registerJob(versionJob); m_watcher = new KDirWatch(this); connect(m_watcher, &KDirWatch::dirty, this, &GitPlugin::fileChanged); connect(m_watcher, &KDirWatch::created, this, &GitPlugin::fileChanged); } GitPlugin::~GitPlugin() {} bool emptyOutput(DVcsJob* job) { QScopedPointer _job(job); if(job->exec() && job->status()==VcsJob::JobSucceeded) return job->rawOutput().trimmed().isEmpty(); return false; } bool GitPlugin::hasStashes(const QDir& repository) { return !emptyOutput(gitStash(repository, QStringList(QStringLiteral("list")), KDevelop::OutputJob::Silent)); } bool GitPlugin::hasModifications(const QDir& d) { return !emptyOutput(lsFiles(d, QStringList(QStringLiteral("-m")), OutputJob::Silent)); } bool GitPlugin::hasModifications(const QDir& repo, const QUrl& file) { return !emptyOutput(lsFiles(repo, QStringList() << QStringLiteral("-m") << file.path(), OutputJob::Silent)); } void GitPlugin::additionalMenuEntries(QMenu* menu, const QList& urls) { m_urls = urls; QDir dir=urlDir(urls); bool hasSt = hasStashes(dir); menu->addSeparator()->setText(i18n("Git Stashes")); menu->addAction(i18n("Stash Manager"), this, SLOT(ctxStashManager()))->setEnabled(hasSt); menu->addAction(i18n("Push Stash"), this, SLOT(ctxPushStash())); menu->addAction(i18n("Pop Stash"), this, SLOT(ctxPopStash()))->setEnabled(hasSt); } void GitPlugin::ctxPushStash() { VcsJob* job = gitStash(urlDir(m_urls), QStringList(), KDevelop::OutputJob::Verbose); ICore::self()->runController()->registerJob(job); } void GitPlugin::ctxPopStash() { VcsJob* job = gitStash(urlDir(m_urls), QStringList(QStringLiteral("pop")), KDevelop::OutputJob::Verbose); ICore::self()->runController()->registerJob(job); } void GitPlugin::ctxStashManager() { QPointer d = new StashManagerDialog(urlDir(m_urls), this, nullptr); d->exec(); delete d; } DVcsJob* GitPlugin::errorsFound(const QString& error, KDevelop::OutputJob::OutputJobVerbosity verbosity=OutputJob::Verbose) { DVcsJob* j = new DVcsJob(QDir::temp(), this, verbosity); *j << "echo" << i18n("error: %1", error) << "-n"; return j; } QString GitPlugin::name() const { return QStringLiteral("Git"); } QUrl GitPlugin::repositoryRoot(const QUrl& path) { return QUrl::fromLocalFile(dotGitDirectory(path).absolutePath()); } bool GitPlugin::isValidDirectory(const QUrl & dirPath) { QDir dir=dotGitDirectory(dirPath); QFile dotGitPotentialFile(dir.filePath(QStringLiteral(".git"))); // if .git is a file, we may be in a git worktree QFileInfo dotGitPotentialFileInfo(dotGitPotentialFile); if (!dotGitPotentialFileInfo.isDir() && dotGitPotentialFile.exists()) { QString gitWorktreeFileContent; if (dotGitPotentialFile.open(QFile::ReadOnly)) { // the content should be gitdir: /path/to/the/.git/worktree gitWorktreeFileContent = QString::fromUtf8(dotGitPotentialFile.readAll()); dotGitPotentialFile.close(); } else { return false; } const auto items = gitWorktreeFileContent.split(' '); if (items.size() == 2 && items.at(0) == QLatin1String("gitdir:")) { qCDebug(PLUGIN_GIT) << "we are in a git worktree" << items.at(1); return true; } } return dir.exists(QStringLiteral(".git/HEAD")); } bool GitPlugin::isValidRemoteRepositoryUrl(const QUrl& remoteLocation) { if (remoteLocation.isLocalFile()) { QFileInfo fileInfo(remoteLocation.toLocalFile()); if (fileInfo.isDir()) { QDir dir(fileInfo.filePath()); if (dir.exists(QStringLiteral(".git/HEAD"))) { return true; } // TODO: check also for bare repo } } else { const QString scheme = remoteLocation.scheme(); if (scheme == QLatin1String("git")) { return true; } // heuristic check, anything better we can do here without talking to server? if ((scheme == QLatin1String("http") || scheme == QLatin1String("https")) && remoteLocation.path().endsWith(QLatin1String(".git"))) { return true; } } return false; } bool GitPlugin::isVersionControlled(const QUrl &path) { QFileInfo fsObject(path.toLocalFile()); if (!fsObject.exists()) { return false; } if (fsObject.isDir()) { return isValidDirectory(path); } QString filename = fsObject.fileName(); QStringList otherFiles = getLsFiles(fsObject.dir(), QStringList(QStringLiteral("--")) << filename, KDevelop::OutputJob::Silent); return !otherFiles.empty(); } VcsJob* GitPlugin::init(const QUrl &directory) { DVcsJob* job = new DVcsJob(urlDir(directory), this); job->setType(VcsJob::Import); *job << "git" << "init"; return job; } VcsJob* GitPlugin::createWorkingCopy(const KDevelop::VcsLocation & source, const QUrl& dest, KDevelop::IBasicVersionControl::RecursionMode) { DVcsJob* job = new GitCloneJob(urlDir(dest), this); job->setType(VcsJob::Import); *job << "git" << "clone" << "--progress" << "--" << source.localUrl().url() << dest; return job; } VcsJob* GitPlugin::add(const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion) { if (localLocations.empty()) return errorsFound(i18n("Did not specify the list of files"), OutputJob::Verbose); DVcsJob* job = new GitJob(dotGitDirectory(localLocations.front()), this); job->setType(VcsJob::Add); *job << "git" << "add" << "--" << (recursion == IBasicVersionControl::Recursive ? localLocations : preventRecursion(localLocations)); return job; } KDevelop::VcsJob* GitPlugin::status(const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion) { if (localLocations.empty()) return errorsFound(i18n("Did not specify the list of files"), OutputJob::Verbose); DVcsJob* job = new GitJob(urlDir(localLocations), this, OutputJob::Silent); job->setType(VcsJob::Status); if(m_oldVersion) { *job << "git" << "ls-files" << "-t" << "-m" << "-c" << "-o" << "-d" << "-k" << "--directory"; connect(job, &DVcsJob::readyForParsing, this, &GitPlugin::parseGitStatusOutput_old); } else { *job << "git" << "status" << "--porcelain"; job->setIgnoreError(true); connect(job, &DVcsJob::readyForParsing, this, &GitPlugin::parseGitStatusOutput); } *job << "--" << (recursion == IBasicVersionControl::Recursive ? localLocations : preventRecursion(localLocations)); return job; } VcsJob* GitPlugin::diff(const QUrl& fileOrDirectory, const KDevelop::VcsRevision& srcRevision, const KDevelop::VcsRevision& dstRevision, VcsDiff::Type /*type*/, IBasicVersionControl::RecursionMode recursion) { //TODO: control different types DVcsJob* job = new GitJob(dotGitDirectory(fileOrDirectory), this, KDevelop::OutputJob::Silent); job->setType(VcsJob::Diff); *job << "git" << "diff" << "--no-color" << "--no-ext-diff"; if (!usePrefix()) { // KDE's ReviewBoard now requires p1 patchfiles, so `git diff --no-prefix` to generate p0 patches // has become optional. *job << "--no-prefix"; } if (dstRevision.revisionType() == VcsRevision::Special && dstRevision.specialType() == VcsRevision::Working) { if (srcRevision.revisionType() == VcsRevision::Special && srcRevision.specialType() == VcsRevision::Base) { *job << "HEAD"; } else { *job << "--cached" << srcRevision.revisionValue().toString(); } } else { QString revstr = revisionInterval(srcRevision, dstRevision); if(!revstr.isEmpty()) *job << revstr; } *job << "--"; if (recursion == IBasicVersionControl::Recursive) { *job << fileOrDirectory; } else { *job << preventRecursion(QList() << fileOrDirectory); } connect(job, &DVcsJob::readyForParsing, this, &GitPlugin::parseGitDiffOutput); return job; } VcsJob* GitPlugin::revert(const QList& localLocations, IBasicVersionControl::RecursionMode recursion) { if(localLocations.isEmpty() ) return errorsFound(i18n("Could not revert changes"), OutputJob::Verbose); QDir repo = urlDir(repositoryRoot(localLocations.first())); QString modified; for (const auto& file: localLocations) { if (hasModifications(repo, file)) { modified.append(file.toDisplayString(QUrl::PreferLocalFile) + "
"); } } if (!modified.isEmpty()) { auto res = KMessageBox::questionYesNo(nullptr, i18n("The following files have uncommited changes, " "which will be lost. Continue?") + "

" + modified); if (res != KMessageBox::Yes) { return errorsFound(QString(), OutputJob::Silent); } } DVcsJob* job = new GitJob(dotGitDirectory(localLocations.front()), this); job->setType(VcsJob::Revert); *job << "git" << "checkout" << "--"; *job << (recursion == IBasicVersionControl::Recursive ? localLocations : preventRecursion(localLocations)); return job; } //TODO: git doesn't like empty messages, but "KDevelop didn't provide any message, it may be a bug" looks ugly... //If no files specified then commit already added files VcsJob* GitPlugin::commit(const QString& message, const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion) { if (localLocations.empty() || message.isEmpty()) return errorsFound(i18n("No files or message specified")); const QDir dir = dotGitDirectory(localLocations.front()); if (!ensureValidGitIdentity(dir)) { return errorsFound(i18n("Email or name for Git not specified")); } DVcsJob* job = new DVcsJob(dir, this); job->setType(VcsJob::Commit); QList files = (recursion == IBasicVersionControl::Recursive ? localLocations : preventRecursion(localLocations)); addNotVersionedFiles(dir, files); *job << "git" << "commit" << "-m" << message; *job << "--" << files; return job; } bool GitPlugin::ensureValidGitIdentity(const QDir& dir) { const QUrl url = QUrl::fromLocalFile(dir.absolutePath()); const QString name = readConfigOption(url, QStringLiteral("user.name")); const QString email = readConfigOption(url, QStringLiteral("user.email")); if (!email.isEmpty() && !name.isEmpty()) { return true; // already okay } GitNameEmailDialog dialog; dialog.setName(name); dialog.setEmail(email); if (!dialog.exec()) { return false; } runSynchronously(setConfigOption(url, QStringLiteral("user.name"), dialog.name(), dialog.isGlobal())); runSynchronously(setConfigOption(url, QStringLiteral("user.email"), dialog.email(), dialog.isGlobal())); return true; } void GitPlugin::addNotVersionedFiles(const QDir& dir, const QList& files) { QStringList otherStr = getLsFiles(dir, QStringList() << QStringLiteral("--others"), KDevelop::OutputJob::Silent); QList toadd, otherFiles; foreach(const QString& file, otherStr) { QUrl v = QUrl::fromLocalFile(dir.absoluteFilePath(file)); otherFiles += v; } //We add the files that are not versioned foreach(const QUrl& file, files) { if(otherFiles.contains(file) && QFileInfo(file.toLocalFile()).isFile()) toadd += file; } if(!toadd.isEmpty()) { VcsJob* job = add(toadd); job->exec(); } } bool isEmptyDirStructure(const QDir &dir) { foreach (const QFileInfo &i, dir.entryInfoList(QDir::AllEntries | QDir::NoDotAndDotDot)) { if (i.isDir()) { if (!isEmptyDirStructure(QDir(i.filePath()))) return false; } else if (i.isFile()) { return false; } } return true; } VcsJob* GitPlugin::remove(const QList& files) { if (files.isEmpty()) return errorsFound(i18n("No files to remove")); QDir dotGitDir = dotGitDirectory(files.front()); QList files_(files); QMutableListIterator i(files_); while (i.hasNext()) { QUrl file = i.next(); QFileInfo fileInfo(file.toLocalFile()); QStringList otherStr = getLsFiles(dotGitDir, QStringList() << QStringLiteral("--others") << QStringLiteral("--") << file.toLocalFile(), KDevelop::OutputJob::Silent); if(!otherStr.isEmpty()) { //remove files not under version control QList otherFiles; foreach(const QString &f, otherStr) { otherFiles << QUrl::fromLocalFile(dotGitDir.path()+'/'+f); } if (fileInfo.isFile()) { //if it's an unversioned file we are done, don't use git rm on it i.remove(); } auto trashJob = KIO::trash(otherFiles); trashJob->exec(); qCDebug(PLUGIN_GIT) << "other files" << otherFiles; } if (fileInfo.isDir()) { if (isEmptyDirStructure(QDir(file.toLocalFile()))) { //remove empty folders, git doesn't do that auto trashJob = KIO::trash(file); trashJob->exec(); qCDebug(PLUGIN_GIT) << "empty folder, removing" << file; //we already deleted it, don't use git rm on it i.remove(); } } } if (files_.isEmpty()) return nullptr; DVcsJob* job = new GitJob(dotGitDir, this); job->setType(VcsJob::Remove); // git refuses to delete files with local modifications // use --force to overcome this *job << "git" << "rm" << "-r" << "--force"; *job << "--" << files_; return job; } VcsJob* GitPlugin::log(const QUrl& localLocation, const KDevelop::VcsRevision& src, const KDevelop::VcsRevision& dst) { DVcsJob* job = new GitJob(dotGitDirectory(localLocation), this, KDevelop::OutputJob::Silent); job->setType(VcsJob::Log); *job << "git" << "log" << "--date=raw" << "--name-status" << "-M80%" << "--follow"; QString rev = revisionInterval(dst, src); if(!rev.isEmpty()) *job << rev; *job << "--" << localLocation; connect(job, &DVcsJob::readyForParsing, this, &GitPlugin::parseGitLogOutput); return job; } VcsJob* GitPlugin::log(const QUrl& localLocation, const KDevelop::VcsRevision& rev, unsigned long int limit) { DVcsJob* job = new GitJob(dotGitDirectory(localLocation), this, KDevelop::OutputJob::Silent); job->setType(VcsJob::Log); *job << "git" << "log" << "--date=raw" << "--name-status" << "-M80%" << "--follow"; QString revStr = toRevisionName(rev, QString()); if(!revStr.isEmpty()) *job << revStr; if(limit>0) *job << QStringLiteral("-%1").arg(limit); *job << "--" << localLocation; connect(job, &DVcsJob::readyForParsing, this, &GitPlugin::parseGitLogOutput); return job; } KDevelop::VcsJob* GitPlugin::annotate(const QUrl &localLocation, const KDevelop::VcsRevision&) { DVcsJob* job = new GitJob(dotGitDirectory(localLocation), this, KDevelop::OutputJob::Silent); job->setType(VcsJob::Annotate); *job << "git" << "blame" << "--porcelain" << "-w"; *job << "--" << localLocation; connect(job, &DVcsJob::readyForParsing, this, &GitPlugin::parseGitBlameOutput); return job; } void GitPlugin::parseGitBlameOutput(DVcsJob *job) { QVariantList results; VcsAnnotationLine* annotation = nullptr; const auto output = job->output(); const auto lines = output.splitRef('\n'); bool skipNext=false; QMap definedRevisions; for(QVector::const_iterator it=lines.constBegin(), itEnd=lines.constEnd(); it!=itEnd; ++it) { if(skipNext) { skipNext=false; results += qVariantFromValue(*annotation); continue; } if(it->isEmpty()) continue; QStringRef name = it->left(it->indexOf(' ')); QStringRef value = it->right(it->size()-name.size()-1); if(name==QLatin1String("author")) annotation->setAuthor(value.toString()); else if(name==QLatin1String("author-mail")) {} //TODO: do smth with the e-mail? else if(name==QLatin1String("author-tz")) {} //TODO: does it really matter? else if(name==QLatin1String("author-time")) annotation->setDate(QDateTime::fromTime_t(value.toUInt())); else if(name==QLatin1String("summary")) annotation->setCommitMessage(value.toString()); else if(name.startsWith(QStringLiteral("committer"))) {} //We will just store the authors else if(name==QLatin1String("previous")) {} //We don't need that either else if(name==QLatin1String("filename")) { skipNext=true; } else if(name==QLatin1String("boundary")) { definedRevisions.insert(QStringLiteral("boundary"), VcsAnnotationLine()); } else { const auto values = value.split(' '); VcsRevision rev; rev.setRevisionValue(name.left(8).toString(), KDevelop::VcsRevision::GlobalNumber); skipNext = definedRevisions.contains(name.toString()); if(!skipNext) definedRevisions.insert(name.toString(), VcsAnnotationLine()); annotation = &definedRevisions[name.toString()]; annotation->setLineNumber(values[1].toInt() - 1); annotation->setRevision(rev); } } job->setResults(results); } DVcsJob* GitPlugin::lsFiles(const QDir &repository, const QStringList &args, OutputJob::OutputJobVerbosity verbosity) { DVcsJob* job = new DVcsJob(repository, this, verbosity); *job << "git" << "ls-files" << args; return job; } DVcsJob* GitPlugin::gitStash(const QDir& repository, const QStringList& args, OutputJob::OutputJobVerbosity verbosity) { DVcsJob* job = new DVcsJob(repository, this, verbosity); *job << "git" << "stash" << args; return job; } VcsJob* GitPlugin::tag(const QUrl& repository, const QString& commitMessage, const VcsRevision& rev, const QString& tagName) { DVcsJob* job = new DVcsJob(urlDir(repository), this); *job << "git" << "tag" << "-m" << commitMessage << tagName; if(rev.revisionValue().isValid()) *job << rev.revisionValue().toString(); return job; } VcsJob* GitPlugin::switchBranch(const QUrl &repository, const QString &branch) { QDir d=urlDir(repository); if(hasModifications(d) && KMessageBox::questionYesNo(nullptr, i18n("There are pending changes, do you want to stash them first?"))==KMessageBox::Yes) { QScopedPointer stash(gitStash(d, QStringList(), KDevelop::OutputJob::Verbose)); stash->exec(); } DVcsJob* job = new DVcsJob(d, this); *job << "git" << "checkout" << branch; return job; } VcsJob* GitPlugin::branch(const QUrl& repository, const KDevelop::VcsRevision& rev, const QString& branchName) { Q_ASSERT(!branchName.isEmpty()); DVcsJob* job = new DVcsJob(urlDir(repository), this); *job << "git" << "branch" << "--" << branchName; if(!rev.prettyValue().isEmpty()) *job << rev.revisionValue().toString(); return job; } VcsJob* GitPlugin::deleteBranch(const QUrl& repository, const QString& branchName) { DVcsJob* job = new DVcsJob(urlDir(repository), this, OutputJob::Silent); *job << "git" << "branch" << "-D" << branchName; connect(job, &DVcsJob::readyForParsing, this, &GitPlugin::parseGitCurrentBranch); return job; } VcsJob* GitPlugin::renameBranch(const QUrl& repository, const QString& oldBranchName, const QString& newBranchName) { DVcsJob* job = new DVcsJob(urlDir(repository), this, OutputJob::Silent); *job << "git" << "branch" << "-m" << newBranchName << oldBranchName; connect(job, &DVcsJob::readyForParsing, this, &GitPlugin::parseGitCurrentBranch); return job; } VcsJob* GitPlugin::mergeBranch(const QUrl& repository, const QString& branchName) { Q_ASSERT(!branchName.isEmpty()); DVcsJob* job = new DVcsJob(urlDir(repository), this); *job << "git" << "merge" << branchName; return job; } VcsJob* GitPlugin::currentBranch(const QUrl& repository) { DVcsJob* job = new DVcsJob(urlDir(repository), this, OutputJob::Silent); job->setIgnoreError(true); *job << "git" << "symbolic-ref" << "-q" << "--short" << "HEAD"; connect(job, &DVcsJob::readyForParsing, this, &GitPlugin::parseGitCurrentBranch); return job; } void GitPlugin::parseGitCurrentBranch(DVcsJob* job) { QString out = job->output().trimmed(); job->setResults(out); } VcsJob* GitPlugin::branches(const QUrl &repository) { DVcsJob* job=new DVcsJob(urlDir(repository)); *job << "git" << "branch" << "-a"; connect(job, &DVcsJob::readyForParsing, this, &GitPlugin::parseGitBranchOutput); return job; } void GitPlugin::parseGitBranchOutput(DVcsJob* job) { const auto output = job->output(); const auto branchListDirty = output.splitRef('\n', QString::SkipEmptyParts); QStringList branchList; foreach(const auto & branch, branchListDirty) { // Skip pointers to another branches (one example of this is "origin/HEAD -> origin/master"); // "git rev-list" chokes on these entries and we do not need duplicate branches altogether. if (branch.contains(QStringLiteral("->"))) continue; // Skip entries such as '(no branch)' if (branch.contains(QStringLiteral("(no branch)"))) continue; QStringRef name = branch; if (name.startsWith('*')) name = branch.right(branch.size()-2); branchList << name.trimmed().toString(); } job->setResults(branchList); } /* Few words about how this hardcore works: 1. get all commits (with --paretns) 2. select master (root) branch and get all unicial commits for branches (git-rev-list br2 ^master ^br3) 3. parse allCommits. While parsing set mask (columns state for every row) for BRANCH, INITIAL, CROSS, MERGE and INITIAL are also set in DVCScommit::setParents (depending on parents count) another setType(INITIAL) is used for "bottom/root/first" commits of branches 4. find and set merges, HEADS. It's an ittaration through all commits. - first we check if parent is from the same branch, if no then we go through all commits searching parent's index and set CROSS/HCROSS for rows (in 3 rows are set EMPTY after commit with parent from another tree met) - then we check branchesShas[i][0] to mark heads 4 can be a seporate function. TODO: All this porn require refactoring (rewriting is better)! It's a very dirty implementation. FIXME: 1. HEAD which is head has extra line to connect it with further commit 2. If you menrge branch2 to master, only new commits of branch2 will be visible (it's fine, but there will be extra merge rectangle in master. If there are no extra commits in branch2, but there are another branches, then the place for branch2 will be empty (instead of be used for branch3). 3. Commits that have additional commit-data (not only history merging, but changes to fix conflicts) are shown incorrectly */ QList GitPlugin::getAllCommits(const QString &repo) { initBranchHash(repo); QStringList args; args << QStringLiteral("--all") << QStringLiteral("--pretty") << QStringLiteral("--parents"); QScopedPointer job(gitRevList(repo, args)); bool ret = job->exec(); Q_ASSERT(ret && job->status()==VcsJob::JobSucceeded && "TODO: provide a fall back in case of failing"); Q_UNUSED(ret); QStringList commits = job->output().split('\n', QString::SkipEmptyParts); static QRegExp rx_com("commit \\w{40,40}"); QListcommitList; DVcsEvent item; //used to keep where we have empty/cross/branch entry //true if it's an active branch (then cross or branch) and false if not QVector additionalFlags(branchesShas.count()); additionalFlags.fill(false); //parse output for(int i = 0; i < commits.count(); ++i) { if (commits[i].contains(rx_com)) { qCDebug(PLUGIN_GIT) << "commit found in " << commits[i]; item.setCommit(commits[i].section(' ', 1, 1).trimmed()); // qCDebug(PLUGIN_GIT) << "commit is: " << commits[i].section(' ', 1); QStringList parents; QString parent = commits[i].section(' ', 2); int section = 2; while (!parent.isEmpty()) { /* qCDebug(PLUGIN_GIT) << "Parent is: " << parent;*/ parents.append(parent.trimmed()); section++; parent = commits[i].section(' ', section); } item.setParents(parents); //Avoid Merge string while (!commits[i].contains(QStringLiteral("Author: "))) ++i; item.setAuthor(commits[i].section(QStringLiteral("Author: "), 1).trimmed()); // qCDebug(PLUGIN_GIT) << "author is: " << commits[i].section("Author: ", 1); item.setDate(commits[++i].section(QStringLiteral("Date: "), 1).trimmed()); // qCDebug(PLUGIN_GIT) << "date is: " << commits[i].section("Date: ", 1); QString log; i++; //next line! while (i < commits.count() && !commits[i].contains(rx_com)) log += commits[i++]; --i; //while took commit line item.setLog(log.trimmed()); // qCDebug(PLUGIN_GIT) << "log is: " << log; //mask is used in CommitViewDelegate to understand what we should draw for each branch QList mask; //set mask (properties for each graph column in row) for(int i = 0; i < branchesShas.count(); ++i) { qCDebug(PLUGIN_GIT)<<"commit: " << item.getCommit(); if (branchesShas[i].contains(item.getCommit())) { mask.append(item.getType()); //we set type in setParents //check if parent from the same branch, if not then we have found a root of the branch //and will use empty column for all futher (from top to bottom) revisions //FIXME: we should set CROSS between parent and child (and do it when find merge point) additionalFlags[i] = false; foreach(const QString &sha, item.getParents()) { if (branchesShas[i].contains(sha)) additionalFlags[i] = true; } if (additionalFlags[i] == false) item.setType(DVcsEvent::INITIAL); //hasn't parents from the same branch, used in drawing } else { if (additionalFlags[i] == false) mask.append(DVcsEvent::EMPTY); else mask.append(DVcsEvent::CROSS); } qCDebug(PLUGIN_GIT) << "mask " << i << "is " << mask[i]; } item.setProperties(mask); commitList.append(item); } } //find and set merges, HEADS, require refactoring! for(QList::iterator iter = commitList.begin(); iter != commitList.end(); ++iter) { QStringList parents = iter->getParents(); //we need only only child branches if (parents.count() != 1) break; QString parent = parents[0]; QString commit = iter->getCommit(); bool parent_checked = false; int heads_checked = 0; for(int i = 0; i < branchesShas.count(); ++i) { //check parent if (branchesShas[i].contains(commit)) { if (!branchesShas[i].contains(parent)) { //parent and child are not in same branch //since it is list, than parent has i+1 index //set CROSS and HCROSS for(QList::iterator f_iter = iter; f_iter != commitList.end(); ++f_iter) { if (parent == f_iter->getCommit()) { for(int j = 0; j < i; ++j) { if(branchesShas[j].contains(parent)) f_iter->setPropetry(j, DVcsEvent::MERGE); else f_iter->setPropetry(j, DVcsEvent::HCROSS); } f_iter->setType(DVcsEvent::MERGE); f_iter->setPropetry(i, DVcsEvent::MERGE_RIGHT); qCDebug(PLUGIN_GIT) << parent << " is parent of " << commit; qCDebug(PLUGIN_GIT) << f_iter->getCommit() << " is merge"; parent_checked = true; break; } else f_iter->setPropetry(i, DVcsEvent::CROSS); } } } //mark HEADs if (!branchesShas[i].empty() && commit == branchesShas[i][0]) { iter->setType(DVcsEvent::HEAD); iter->setPropetry(i, DVcsEvent::HEAD); heads_checked++; qCDebug(PLUGIN_GIT) << "HEAD found"; } //some optimization if (heads_checked == branchesShas.count() && parent_checked) break; } } return commitList; } void GitPlugin::initBranchHash(const QString &repo) { const QUrl repoUrl = QUrl::fromLocalFile(repo); QStringList gitBranches = runSynchronously(branches(repoUrl)).toStringList(); qCDebug(PLUGIN_GIT) << "BRANCHES: " << gitBranches; //Now root branch is the current branch. In future it should be the longest branch //other commitLists are got with git-rev-lits branch ^br1 ^ br2 QString root = runSynchronously(currentBranch(repoUrl)).toString(); QScopedPointer job(gitRevList(repo, QStringList(root))); bool ret = job->exec(); Q_ASSERT(ret && job->status()==VcsJob::JobSucceeded && "TODO: provide a fall back in case of failing"); Q_UNUSED(ret); QStringList commits = job->output().split('\n', QString::SkipEmptyParts); // qCDebug(PLUGIN_GIT) << "\n\n\n commits" << commits << "\n\n\n"; branchesShas.append(commits); foreach(const QString &branch, gitBranches) { if (branch == root) continue; QStringList args(branch); foreach(const QString &branch_arg, gitBranches) { if (branch_arg != branch) //man gitRevList for '^' args<<'^' + branch_arg; } QScopedPointer job(gitRevList(repo, args)); bool ret = job->exec(); Q_ASSERT(ret && job->status()==VcsJob::JobSucceeded && "TODO: provide a fall back in case of failing"); Q_UNUSED(ret); QStringList commits = job->output().split('\n', QString::SkipEmptyParts); // qCDebug(PLUGIN_GIT) << "\n\n\n commits" << commits << "\n\n\n"; branchesShas.append(commits); } } //Actually we can just copy the output without parsing. So it's a kind of draft for future void GitPlugin::parseLogOutput(const DVcsJob * job, QList& commits) const { // static QRegExp rx_sep( "[-=]+" ); // static QRegExp rx_date( "date:\\s+([^;]*);\\s+author:\\s+([^;]*).*" ); static QRegularExpression rx_com( QStringLiteral("commit \\w{1,40}") ); const auto output = job->output(); const auto lines = output.splitRef('\n', QString::SkipEmptyParts); DVcsEvent item; QString commitLog; for (int i=0; i= 0x050500 if (rx_com.match(lines[i]).hasMatch()) { #else if (rx_com.match(lines[i].toString()).hasMatch()) { #endif // qCDebug(PLUGIN_GIT) << "MATCH COMMIT"; item.setCommit(lines[++i].toString()); item.setAuthor(lines[++i].toString()); item.setDate(lines[++i].toString()); item.setLog(commitLog); commits.append(item); } else { //FIXME: add this in a loop to the if, like in getAllCommits() commitLog += lines[i].toString() +'\n'; } } } VcsItemEvent::Actions actionsFromString(char c) { switch(c) { case 'A': return VcsItemEvent::Added; case 'D': return VcsItemEvent::Deleted; case 'R': return VcsItemEvent::Replaced; case 'M': return VcsItemEvent::Modified; } return VcsItemEvent::Modified; } void GitPlugin::parseGitLogOutput(DVcsJob * job) { static QRegExp commitRegex( "^commit (\\w{8})\\w{32}" ); static QRegExp infoRegex( "^(\\w+):(.*)" ); static QRegExp modificationsRegex("^([A-Z])[0-9]*\t([^\t]+)\t?(.*)", Qt::CaseSensitive, QRegExp::RegExp2); //R099 plugins/git/kdevgit.desktop plugins/git/kdevgit.desktop.cmake //M plugins/grepview/CMakeLists.txt QList commits; QString contents = job->output(); // check if git-log returned anything if (contents.isEmpty()) { job->setResults(commits); // empty list return; } // start parsing the output QTextStream s(&contents); VcsEvent item; QString message; bool pushCommit = false; while (!s.atEnd()) { QString line = s.readLine(); if (commitRegex.exactMatch(line)) { if (pushCommit) { item.setMessage(message.trimmed()); commits.append(QVariant::fromValue(item)); item.setItems(QList()); } else { pushCommit = true; } VcsRevision rev; rev.setRevisionValue(commitRegex.cap(1), KDevelop::VcsRevision::GlobalNumber); item.setRevision(rev); message.clear(); } else if (infoRegex.exactMatch(line)) { QString cap1 = infoRegex.cap(1); if (cap1 == QLatin1String("Author")) { item.setAuthor(infoRegex.cap(2).trimmed()); } else if (cap1 == QLatin1String("Date")) { item.setDate(QDateTime::fromTime_t(infoRegex.cap(2).trimmed().split(' ')[0].toUInt())); } } else if (modificationsRegex.exactMatch(line)) { VcsItemEvent::Actions a = actionsFromString(modificationsRegex.cap(1).at(0).toLatin1()); QString filenameA = modificationsRegex.cap(2); VcsItemEvent itemEvent; itemEvent.setActions(a); itemEvent.setRepositoryLocation(filenameA); if(a==VcsItemEvent::Replaced) { QString filenameB = modificationsRegex.cap(3); itemEvent.setRepositoryCopySourceLocation(filenameB); } item.addItem(itemEvent); } else if (line.startsWith(QLatin1String(" "))) { message += line.remove(0, 4); message += '\n'; } } item.setMessage(message.trimmed()); commits.append(QVariant::fromValue(item)); job->setResults(commits); } void GitPlugin::parseGitDiffOutput(DVcsJob* job) { VcsDiff diff; diff.setDiff(job->output()); diff.setBaseDiff(repositoryRoot(QUrl::fromLocalFile(job->directory().absolutePath()))); diff.setDepth(usePrefix()? 1 : 0); job->setResults(qVariantFromValue(diff)); } static VcsStatusInfo::State lsfilesToState(char id) { switch(id) { case 'H': return VcsStatusInfo::ItemUpToDate; //Cached case 'S': return VcsStatusInfo::ItemUpToDate; //Skip work tree case 'M': return VcsStatusInfo::ItemHasConflicts; //unmerged case 'R': return VcsStatusInfo::ItemDeleted; //removed/deleted case 'C': return VcsStatusInfo::ItemModified; //modified/changed case 'K': return VcsStatusInfo::ItemDeleted; //to be killed case '?': return VcsStatusInfo::ItemUnknown; //other } Q_ASSERT(false); return VcsStatusInfo::ItemUnknown; } void GitPlugin::parseGitStatusOutput_old(DVcsJob* job) { QStringList outputLines = job->output().split('\n', QString::SkipEmptyParts); QDir dir = job->directory(); QMap allStatus; foreach(const QString& line, outputLines) { VcsStatusInfo::State status = lsfilesToState(line[0].toLatin1()); QUrl url = QUrl::fromLocalFile(dir.absoluteFilePath(line.right(line.size()-2))); allStatus[url] = status; } QVariantList statuses; QMap< QUrl, VcsStatusInfo::State >::const_iterator it = allStatus.constBegin(), itEnd=allStatus.constEnd(); for(; it!=itEnd; ++it) { VcsStatusInfo status; status.setUrl(it.key()); status.setState(it.value()); statuses.append(qVariantFromValue(status)); } job->setResults(statuses); } void GitPlugin::parseGitStatusOutput(DVcsJob* job) { const auto output = job->output(); const auto outputLines = output.splitRef('\n', QString::SkipEmptyParts); QDir workingDir = job->directory(); QDir dotGit = dotGitDirectory(QUrl::fromLocalFile(workingDir.absolutePath())); QVariantList statuses; QList processedFiles; foreach(const QStringRef& line, outputLines) { //every line is 2 chars for the status, 1 space then the file desc QStringRef curr=line.right(line.size()-3); QStringRef state = line.left(2); int arrow = curr.indexOf(QStringLiteral(" -> ")); if(arrow>=0) { VcsStatusInfo status; status.setUrl(QUrl::fromLocalFile(dotGit.absoluteFilePath(curr.toString().left(arrow)))); status.setState(VcsStatusInfo::ItemDeleted); statuses.append(qVariantFromValue(status)); processedFiles += status.url(); curr = curr.mid(arrow+4); } if(curr.startsWith('\"') && curr.endsWith('\"')) { //if the path is quoted, unquote curr = curr.mid(1, curr.size()-2); } VcsStatusInfo status; status.setUrl(QUrl::fromLocalFile(dotGit.absoluteFilePath(curr.toString()))); status.setState(messageToState(state)); processedFiles.append(status.url()); qCDebug(PLUGIN_GIT) << "Checking git status for " << line << curr << status.state(); statuses.append(qVariantFromValue(status)); } QStringList paths; QStringList oldcmd=job->dvcsCommand(); QStringList::const_iterator it=oldcmd.constBegin()+oldcmd.indexOf(QStringLiteral("--"))+1, itEnd=oldcmd.constEnd(); for(; it!=itEnd; ++it) paths += *it; //here we add the already up to date files QStringList files = getLsFiles(job->directory(), QStringList() << QStringLiteral("-c") << QStringLiteral("--") << paths, OutputJob::Silent); foreach(const QString& file, files) { QUrl fileUrl = QUrl::fromLocalFile(workingDir.absoluteFilePath(file)); if(!processedFiles.contains(fileUrl)) { VcsStatusInfo status; status.setUrl(fileUrl); status.setState(VcsStatusInfo::ItemUpToDate); statuses.append(qVariantFromValue(status)); } } job->setResults(statuses); } void GitPlugin::parseGitVersionOutput(DVcsJob* job) { const auto output = job->output().trimmed(); auto versionString = output.midRef(output.lastIndexOf(' ')).split('.'); static const QList minimumVersion = QList() << 1 << 7; qCDebug(PLUGIN_GIT) << "checking git version" << versionString << "against" << minimumVersion; m_oldVersion = false; if (versionString.size() < minimumVersion.size()) { m_oldVersion = true; qCWarning(PLUGIN_GIT) << "invalid git version string:" << job->output().trimmed(); return; } foreach(int num, minimumVersion) { QStringRef curr = versionString.takeFirst(); int valcurr = curr.toInt(); if (valcurr < num) { m_oldVersion = true; break; } if (valcurr > num) { m_oldVersion = false; break; } } qCDebug(PLUGIN_GIT) << "the current git version is old: " << m_oldVersion; } QStringList GitPlugin::getLsFiles(const QDir &directory, const QStringList &args, KDevelop::OutputJob::OutputJobVerbosity verbosity) { QScopedPointer job(lsFiles(directory, args, verbosity)); if (job->exec() && job->status() == KDevelop::VcsJob::JobSucceeded) return job->output().split('\n', QString::SkipEmptyParts); return QStringList(); } DVcsJob* GitPlugin::gitRevParse(const QString &repository, const QStringList &args, KDevelop::OutputJob::OutputJobVerbosity verbosity) { DVcsJob* job = new DVcsJob(QDir(repository), this, verbosity); *job << "git" << "rev-parse" << args; return job; } DVcsJob* GitPlugin::gitRevList(const QString& directory, const QStringList& args) { DVcsJob* job = new DVcsJob(urlDir(QUrl::fromLocalFile(directory)), this, KDevelop::OutputJob::Silent); { *job << "git" << "rev-list" << args; return job; } } VcsStatusInfo::State GitPlugin::messageToState(const QStringRef& msg) { Q_ASSERT(msg.size()==1 || msg.size()==2); VcsStatusInfo::State ret = VcsStatusInfo::ItemUnknown; if(msg.contains('U') || msg == QLatin1String("AA") || msg == QLatin1String("DD")) ret = VcsStatusInfo::ItemHasConflicts; else switch(msg.at(0).toLatin1()) { case 'M': ret = VcsStatusInfo::ItemModified; break; case 'A': ret = VcsStatusInfo::ItemAdded; break; case 'R': ret = VcsStatusInfo::ItemModified; break; case 'C': ret = VcsStatusInfo::ItemHasConflicts; break; case ' ': ret = msg.at(1) == 'M' ? VcsStatusInfo::ItemModified : VcsStatusInfo::ItemDeleted; break; case 'D': ret = VcsStatusInfo::ItemDeleted; break; case '?': ret = VcsStatusInfo::ItemUnknown; break; default: qCDebug(PLUGIN_GIT) << "Git status not identified:" << msg; break; } return ret; } StandardJob::StandardJob(IPlugin* parent, KJob* job, OutputJob::OutputJobVerbosity verbosity) : VcsJob(parent, verbosity) , m_job(job) , m_plugin(parent) , m_status(JobNotStarted) {} void StandardJob::start() { connect(m_job, &KJob::result, this, &StandardJob::result); m_job->start(); m_status=JobRunning; } void StandardJob::result(KJob* job) { if (job->error() == 0) { m_status = JobSucceeded; setError(NoError); } else { m_status = JobFailed; setError(UserDefinedError); } emitResult(); } VcsJob* GitPlugin::copy(const QUrl& localLocationSrc, const QUrl& localLocationDstn) { //TODO: Probably we should "git add" after return new StandardJob(this, KIO::copy(localLocationSrc, localLocationDstn), KDevelop::OutputJob::Silent); } VcsJob* GitPlugin::move(const QUrl& source, const QUrl& destination) { QDir dir = urlDir(source); QFileInfo fileInfo(source.toLocalFile()); if (fileInfo.isDir()) { if (isEmptyDirStructure(QDir(source.toLocalFile()))) { //move empty folder, git doesn't do that qCDebug(PLUGIN_GIT) << "empty folder" << source; return new StandardJob(this, KIO::move(source, destination), KDevelop::OutputJob::Silent); } } QStringList otherStr = getLsFiles(dir, QStringList() << QStringLiteral("--others") << QStringLiteral("--") << source.toLocalFile(), KDevelop::OutputJob::Silent); if(otherStr.isEmpty()) { DVcsJob* job = new DVcsJob(dir, this, KDevelop::OutputJob::Verbose); *job << "git" << "mv" << source.toLocalFile() << destination.toLocalFile(); return job; } else { return new StandardJob(this, KIO::move(source, destination), KDevelop::OutputJob::Silent); } } void GitPlugin::parseGitRepoLocationOutput(DVcsJob* job) { job->setResults(QVariant::fromValue(QUrl::fromLocalFile(job->output()))); } VcsJob* GitPlugin::repositoryLocation(const QUrl& localLocation) { DVcsJob* job = new DVcsJob(urlDir(localLocation), this); //Probably we should check first if origin is the proper remote we have to use but as a first attempt it works *job << "git" << "config" << "remote.origin.url"; connect(job, &DVcsJob::readyForParsing, this, &GitPlugin::parseGitRepoLocationOutput); return job; } VcsJob* GitPlugin::pull(const KDevelop::VcsLocation& localOrRepoLocationSrc, const QUrl& localRepositoryLocation) { DVcsJob* job = new DVcsJob(urlDir(localRepositoryLocation), this); job->setCommunicationMode(KProcess::MergedChannels); *job << "git" << "pull"; if(!localOrRepoLocationSrc.localUrl().isEmpty()) *job << localOrRepoLocationSrc.localUrl().url(); return job; } VcsJob* GitPlugin::push(const QUrl& localRepositoryLocation, const KDevelop::VcsLocation& localOrRepoLocationDst) { DVcsJob* job = new DVcsJob(urlDir(localRepositoryLocation), this); job->setCommunicationMode(KProcess::MergedChannels); *job << "git" << "push"; if(!localOrRepoLocationDst.localUrl().isEmpty()) *job << localOrRepoLocationDst.localUrl().url(); return job; } VcsJob* GitPlugin::resolve(const QList& localLocations, IBasicVersionControl::RecursionMode recursion) { return add(localLocations, recursion); } VcsJob* GitPlugin::update(const QList& localLocations, const KDevelop::VcsRevision& rev, IBasicVersionControl::RecursionMode recursion) { if(rev.revisionType()==VcsRevision::Special && rev.revisionValue().value()==VcsRevision::Head) { return pull(VcsLocation(), localLocations.first()); } else { DVcsJob* job = new DVcsJob(urlDir(localLocations.first()), this); { //Probably we should check first if origin is the proper remote we have to use but as a first attempt it works *job << "git" << "checkout" << rev.revisionValue().toString() << "--"; *job << (recursion == IBasicVersionControl::Recursive ? localLocations : preventRecursion(localLocations)); return job; } } } void GitPlugin::setupCommitMessageEditor(const QUrl& localLocation, KTextEdit* editor) const { new GitMessageHighlighter(editor); QFile mergeMsgFile(dotGitDirectory(localLocation).filePath(QStringLiteral(".git/MERGE_MSG"))); // Some limit on the file size should be set since whole content is going to be read into // the memory. 1Mb seems to be good value since it's rather strange to have so huge commit // message. static const qint64 maxMergeMsgFileSize = 1024*1024; if (mergeMsgFile.size() > maxMergeMsgFileSize || !mergeMsgFile.open(QIODevice::ReadOnly)) return; QString mergeMsg = QString::fromLocal8Bit(mergeMsgFile.read(maxMergeMsgFileSize)); editor->setPlainText(mergeMsg); } class GitVcsLocationWidget : public KDevelop::StandardVcsLocationWidget { Q_OBJECT public: explicit GitVcsLocationWidget(QWidget* parent = nullptr) : StandardVcsLocationWidget(parent) {} bool isCorrect() const override { return !url().isEmpty(); } }; KDevelop::VcsLocationWidget* GitPlugin::vcsLocation(QWidget* parent) const { return new GitVcsLocationWidget(parent); } void GitPlugin::registerRepositoryForCurrentBranchChanges(const QUrl& repository) { QDir dir = dotGitDirectory(repository); QString headFile = dir.absoluteFilePath(QStringLiteral(".git/HEAD")); m_watcher->addFile(headFile); } void GitPlugin::fileChanged(const QString& file) { Q_ASSERT(file.endsWith(QStringLiteral("HEAD"))); //SMTH/.git/HEAD -> SMTH/ const QUrl fileUrl = Path(file).parent().parent().toUrl(); //We need to delay the emitted signal, otherwise the branch hasn't change yet //and the repository is not functional m_branchesChange.append(fileUrl); QTimer::singleShot(1000, this, &GitPlugin::delayedBranchChanged); } void GitPlugin::delayedBranchChanged() { emit repositoryBranchChanged(m_branchesChange.takeFirst()); } CheckInRepositoryJob* GitPlugin::isInRepository(KTextEditor::Document* document) { CheckInRepositoryJob* job = new GitPluginCheckInRepositoryJob(document, repositoryRoot(document->url()).path()); job->start(); return job; } DVcsJob* GitPlugin::setConfigOption(const QUrl& repository, const QString& key, const QString& value, bool global) { auto job = new DVcsJob(urlDir(repository), this); QStringList args; args << QStringLiteral("git") << QStringLiteral("config"); if(global) args << QStringLiteral("--global"); args << key << value; *job << args; return job; } QString GitPlugin::readConfigOption(const QUrl& repository, const QString& key) { QProcess exec; exec.setWorkingDirectory(urlDir(repository).absolutePath()); exec.start(QStringLiteral("git"), QStringList() << QStringLiteral("config") << QStringLiteral("--get") << key); exec.waitForFinished(); return exec.readAllStandardOutput().trimmed(); } #include "gitplugin.moc" diff --git a/plugins/git/tests/CMakeLists.txt b/plugins/git/tests/CMakeLists.txt index 9fe8022c9..07ab6121e 100644 --- a/plugins/git/tests/CMakeLists.txt +++ b/plugins/git/tests/CMakeLists.txt @@ -1,24 +1,28 @@ # Running the test only makes sense if the git command line client # is present. So check for it before adding the test... find_program(GIT_FOUND NAMES git) if (GIT_FOUND) - + include_directories( + .. + ${CMAKE_CURRENT_BINARY_DIR}/.. + ) set(gittest_SRCS test_git.cpp ../gitplugin.cpp ../gitclonejob.cpp ../stashmanagerdialog.cpp ../stashpatchsource.cpp ../gitjob.cpp ../gitmessagehighlighter.cpp ../gitplugincheckinrepositoryjob.cpp ../gitnameemaildialog.cpp + ${kdevgit_LOG_PART_SRCS} ) ki18n_wrap_ui(gittest_SRCS ../stashmanagerdialog.ui) ki18n_wrap_ui(gittest_SRCS ../gitnameemaildialog.ui) ecm_add_test(${gittest_SRCS} TEST_NAME test_kdevgit LINK_LIBRARIES Qt5::Test KDev::Vcs KDev::Util KDev::Tests GUI) endif () diff --git a/plugins/grepview/CMakeLists.txt b/plugins/grepview/CMakeLists.txt index b1906897b..f5906207c 100644 --- a/plugins/grepview/CMakeLists.txt +++ b/plugins/grepview/CMakeLists.txt @@ -1,42 +1,49 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kdevgrepview\") project(grepview) ########### next target ############### +ecm_qt_declare_logging_category(kdevgrepview_LOG_PART_SRCS + HEADER debug.h + IDENTIFIER PLUGIN_GREPVIEW + CATEGORY_NAME "kdevplatform.plugins.grepview" +) + set(kdevgrepview_PART_SRCS grepviewplugin.cpp grepviewpluginmetadata.cpp grepdialog.cpp grepoutputmodel.cpp grepoutputdelegate.cpp grepjob.cpp grepfindthread.cpp grepoutputview.cpp greputil.cpp + ${kdevgrepview_LOG_PART_SRCS} ) set(kdevgrepview_PART_UI grepwidget.ui grepoutputview.ui ) ki18n_wrap_ui(kdevgrepview_PART_SRCS ${kdevgrepview_PART_UI}) qt5_add_resources(kdevgrepview_PART_SRCS kdevgrepview.qrc) kdevplatform_add_plugin(kdevgrepview JSON kdevgrepview.json SOURCES ${kdevgrepview_PART_SRCS}) target_link_libraries(kdevgrepview KF5::Parts KF5::TextEditor KF5::Completion KF5::TextEditor KDev::Interfaces KDev::OutputView KDev::Project KDev::Util KDev::Language ) ########### install files ############### add_subdirectory(tests) diff --git a/plugins/grepview/debug.h b/plugins/grepview/debug.h deleted file mode 100644 index feab2f029..000000000 --- a/plugins/grepview/debug.h +++ /dev/null @@ -1,26 +0,0 @@ -/* - * This file is part of KDevelop - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#ifndef KDEVPLATFORM_GREPVIEW_DEBUG_H -#define KDEVPLATFORM_GREPVIEW_DEBUG_H - -#include -Q_DECLARE_LOGGING_CATEGORY(PLUGIN_GREPVIEW) - -#endif diff --git a/plugins/grepview/grepviewplugin.cpp b/plugins/grepview/grepviewplugin.cpp index 7d92a8b23..96fe2543b 100644 --- a/plugins/grepview/grepviewplugin.cpp +++ b/plugins/grepview/grepviewplugin.cpp @@ -1,240 +1,238 @@ /*************************************************************************** * Copyright 1999-2001 by Bernd Gehrmann * * bernd@kdevelop.org * * Copyright 2007 Dukju Ahn * * Copyright 2010 Benjamin Port * * Copyright 2010 Julien Desgats * * 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 "grepviewplugin.h" #include "grepdialog.h" #include "grepoutputmodel.h" #include "grepoutputdelegate.h" #include "grepjob.h" #include "grepoutputview.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include -Q_LOGGING_CATEGORY(PLUGIN_GREPVIEW, "kdevplatform.plugins.grepview") - static QString patternFromSelection(const KDevelop::IDocument* doc) { if (!doc) return QString(); QString pattern; KTextEditor::Range range = doc->textSelection(); if( range.isValid() ) { pattern = doc->textDocument()->text( range ); } if( pattern.isEmpty() ) { pattern = doc->textWord(); } // Before anything, this removes line feeds from the // beginning and the end. int len = pattern.length(); if (len > 0 && pattern[0] == '\n') { pattern.remove(0, 1); len--; } if (len > 0 && pattern[len-1] == '\n') pattern.truncate(len-1); return pattern; } GrepViewPlugin::GrepViewPlugin( QObject *parent, const QVariantList & ) : KDevelop::IPlugin( QStringLiteral("kdevgrepview"), parent ), m_currentJob(nullptr) { setXMLFile(QStringLiteral("kdevgrepview.rc")); QDBusConnection::sessionBus().registerObject( QStringLiteral("/org/kdevelop/GrepViewPlugin"), this, QDBusConnection::ExportScriptableSlots ); QAction*action = actionCollection()->addAction(QStringLiteral("edit_grep")); action->setText(i18n("Find/Replace in Fi&les...")); actionCollection()->setDefaultShortcut( action, QKeySequence(QStringLiteral("Ctrl+Alt+F")) ); connect(action, &QAction::triggered, this, &GrepViewPlugin::showDialogFromMenu); action->setToolTip( i18n("Search for expressions over several files") ); action->setWhatsThis( i18n("Opens the 'Find/Replace in files' dialog. There you " "can enter a regular expression which is then " "searched for within all files in the directories " "you specify. Matches will be displayed, you " "can switch to a match directly. You can also do replacement.") ); action->setIcon(QIcon::fromTheme(QStringLiteral("edit-find"))); // instantiate delegate, it's supposed to be deleted via QObject inheritance new GrepOutputDelegate(this); m_factory = new GrepOutputViewFactory(this); core()->uiController()->addToolView(i18n("Find/Replace in Files"), m_factory); } GrepOutputViewFactory* GrepViewPlugin::toolViewFactory() const { return m_factory; } GrepViewPlugin::~GrepViewPlugin() { } void GrepViewPlugin::unload() { if (m_currentDialog) { m_currentDialog->reject(); m_currentDialog->deleteLater(); } core()->uiController()->removeToolView(m_factory); } void GrepViewPlugin::startSearch(QString pattern, QString directory, bool show) { m_directory = directory; showDialog(false, pattern, show); } KDevelop::ContextMenuExtension GrepViewPlugin::contextMenuExtension(KDevelop::Context* context) { KDevelop::ContextMenuExtension extension = KDevelop::IPlugin::contextMenuExtension(context); if( context->type() == KDevelop::Context::ProjectItemContext ) { KDevelop::ProjectItemContext* ctx = dynamic_cast( context ); QList items = ctx->items(); // verify if there is only one folder selected if ((items.count() == 1) && (items.first()->folder())) { QAction* action = new QAction( i18n( "Find/Replace in This Folder..." ), this ); action->setIcon(QIcon::fromTheme(QStringLiteral("edit-find"))); m_contextMenuDirectory = items.at(0)->folder()->path().toLocalFile(); connect( action, &QAction::triggered, this, &GrepViewPlugin::showDialogFromProject); extension.addAction( KDevelop::ContextMenuExtension::ExtensionGroup, action ); } } if ( context->type() == KDevelop::Context::EditorContext ) { KDevelop::EditorContext *econtext = dynamic_cast(context); if ( econtext->view()->selection() ) { QAction* action = new QAction(QIcon::fromTheme(QStringLiteral("edit-find")), i18n("&Find/Replace in Files..."), this); connect(action, &QAction::triggered, this, &GrepViewPlugin::showDialogFromMenu); extension.addAction(KDevelop::ContextMenuExtension::ExtensionGroup, action); } } if(context->type() == KDevelop::Context::FileContext) { KDevelop::FileContext *fcontext = dynamic_cast(context); // TODO: just stat() or QFileInfo().isDir() for local files? should be faster than mime type checking QMimeType mimetype = QMimeDatabase().mimeTypeForUrl(fcontext->urls().at(0)); static const QMimeType directoryMime = QMimeDatabase().mimeTypeForName(QStringLiteral("inode/directory")); if (mimetype == directoryMime) { QAction* action = new QAction( i18n( "Find/Replace in This Folder..." ), this ); action->setIcon(QIcon::fromTheme(QStringLiteral("edit-find"))); m_contextMenuDirectory = fcontext->urls().at(0).toLocalFile(); connect( action, &QAction::triggered, this, &GrepViewPlugin::showDialogFromProject); extension.addAction( KDevelop::ContextMenuExtension::ExtensionGroup, action ); } } return extension; } void GrepViewPlugin::showDialog(bool setLastUsed, QString pattern, bool show) { GrepDialog* dlg = new GrepDialog( this, core()->uiController()->activeMainWindow() ); m_currentDialog = dlg; GrepJobSettings dlgSettings = dlg->settings(); KDevelop::IDocument* doc = core()->documentController()->activeDocument(); if(!pattern.isEmpty()) { dlgSettings.pattern = pattern; dlg->setSettings(dlgSettings); } else if(!setLastUsed) { QString pattern = patternFromSelection(doc); if (!pattern.isEmpty()) { dlgSettings.pattern = pattern; dlg->setSettings(dlgSettings); } } //if directory is empty then use a default value from the config file. if (!m_directory.isEmpty()) { dlg->setSearchLocations(m_directory); } if(show) dlg->show(); else{ dlg->startSearch(); dlg->deleteLater(); } } void GrepViewPlugin::showDialogFromMenu() { showDialog(); } void GrepViewPlugin::showDialogFromProject() { rememberSearchDirectory(m_contextMenuDirectory); showDialog(); } void GrepViewPlugin::rememberSearchDirectory(QString const & directory) { m_directory = directory; } GrepJob* GrepViewPlugin::newGrepJob() { if(m_currentJob != nullptr) { m_currentJob->kill(); } m_currentJob = new GrepJob(); connect(m_currentJob, &GrepJob::finished, this, &GrepViewPlugin::jobFinished); return m_currentJob; } GrepJob* GrepViewPlugin::grepJob() { return m_currentJob; } void GrepViewPlugin::jobFinished(KJob* job) { if(job == m_currentJob) { emit grepJobFinished(); m_currentJob = nullptr; } } diff --git a/plugins/grepview/tests/CMakeLists.txt b/plugins/grepview/tests/CMakeLists.txt index c232f5a77..eca7411a3 100644 --- a/plugins/grepview/tests/CMakeLists.txt +++ b/plugins/grepview/tests/CMakeLists.txt @@ -1,21 +1,26 @@ +include_directories( + .. + ${CMAKE_CURRENT_BINARY_DIR}/.. +) set(findReplaceTest_SRCS test_findreplace.cpp ../grepviewplugin.cpp ../grepdialog.cpp ../grepoutputmodel.cpp ../grepoutputdelegate.cpp ../grepjob.cpp ../grepfindthread.cpp ../grepoutputview.cpp ../greputil.cpp + ${kdevgrepview_LOG_PART_SRCS} ) set(kdevgrepview_PART_UI ../grepwidget.ui ../grepoutputview.ui ) ki18n_wrap_ui(findReplaceTest_SRCS ${kdevgrepview_PART_UI}) ecm_add_test(${findReplaceTest_SRCS} TEST_NAME test_findreplace LINK_LIBRARIES Qt5::Test KDev::Language KDev::Project KDev::Util KDev::Tests GUI) diff --git a/plugins/konsole/CMakeLists.txt b/plugins/konsole/CMakeLists.txt index 257c6822f..900cd20a1 100644 --- a/plugins/konsole/CMakeLists.txt +++ b/plugins/konsole/CMakeLists.txt @@ -1,10 +1,15 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kdevkonsole\") ########### next target ############### set(kdevkonsoleview_PART_SRCS kdevkonsoleviewplugin.cpp kdevkonsoleview.cpp ) +ecm_qt_declare_logging_category(kdevkonsoleview_PART_SRCS + HEADER debug.h + IDENTIFIER PLUGIN_KONSOLE + CATEGORY_NAME "kdevplatform.plugins.konsole" +) kdevplatform_add_plugin(kdevkonsoleview JSON kdevkonsoleview.json SOURCES ${kdevkonsoleview_PART_SRCS}) target_link_libraries(kdevkonsoleview KF5::Parts KDev::Interfaces) diff --git a/plugins/konsole/debug.h b/plugins/konsole/debug.h deleted file mode 100644 index 7ce79526c..000000000 --- a/plugins/konsole/debug.h +++ /dev/null @@ -1,26 +0,0 @@ -/* - * This file is part of KDevelop - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#ifndef KDEVPLATFORM_KONSOLE_DEBUG_H -#define KDEVPLATFORM_KONSOLE_DEBUG_H - -#include -Q_DECLARE_LOGGING_CATEGORY(PLUGIN_KONSOLE) - -#endif diff --git a/plugins/konsole/kdevkonsoleviewplugin.cpp b/plugins/konsole/kdevkonsoleviewplugin.cpp index ffe68b818..974863e61 100644 --- a/plugins/konsole/kdevkonsoleviewplugin.cpp +++ b/plugins/konsole/kdevkonsoleviewplugin.cpp @@ -1,89 +1,87 @@ /*************************************************************************** * Copyright 2003, 2006 Adam Treat * * Copyright 2007 Andreas Pakulat * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "kdevkonsoleviewplugin.h" #include #include #include #include #include "kdevkonsoleview.h" #include "debug.h" -Q_LOGGING_CATEGORY(PLUGIN_KONSOLE, "kdevplatform.plugins.konsole") - QObject* createKonsoleView( QWidget*, QObject* op, const QVariantList& args) { KService::Ptr service = KService::serviceByDesktopName(QStringLiteral("konsolepart")); KPluginFactory* factory = nullptr; if (service) { factory = KPluginLoader(*service.data()).factory(); } if (!factory) { qWarning() << "Failed to load 'konsolepart' plugin"; } return new KDevKonsoleViewPlugin(factory, op, args); } K_PLUGIN_FACTORY_WITH_JSON(KonsoleViewFactory, "kdevkonsoleview.json", registerPlugin( QString(), &createKonsoleView );) class KDevKonsoleViewFactory: public KDevelop::IToolViewFactory{ public: explicit KDevKonsoleViewFactory(KDevKonsoleViewPlugin *plugin): mplugin(plugin) {} QWidget* create(QWidget *parent = nullptr) override { return new KDevKonsoleView(mplugin, parent); } Qt::DockWidgetArea defaultPosition() override { return Qt::BottomDockWidgetArea; } QString id() const override { return QStringLiteral("org.kdevelop.KonsoleView"); } private: KDevKonsoleViewPlugin *mplugin; }; KDevKonsoleViewPlugin::KDevKonsoleViewPlugin( KPluginFactory* konsoleFactory, QObject *parent, const QVariantList & ) : KDevelop::IPlugin( QStringLiteral("kdevkonsoleview"), parent ) , m_konsoleFactory(konsoleFactory) , m_viewFactory(konsoleFactory ? new KDevKonsoleViewFactory(this) : nullptr) { if(!m_viewFactory) { setErrorDescription(i18n("Failed to load 'konsolepart' plugin")); } else { core()->uiController()->addToolView(QStringLiteral("Konsole"), m_viewFactory); } } void KDevKonsoleViewPlugin::unload() { if (m_viewFactory) { core()->uiController()->removeToolView(m_viewFactory); } } KPluginFactory* KDevKonsoleViewPlugin::konsoleFactory() const { return m_konsoleFactory; } KDevKonsoleViewPlugin::~KDevKonsoleViewPlugin() { } #include "kdevkonsoleviewplugin.moc" diff --git a/plugins/outlineview/CMakeLists.txt b/plugins/outlineview/CMakeLists.txt index 1bcd1bb1b..b83d426da 100644 --- a/plugins/outlineview/CMakeLists.txt +++ b/plugins/outlineview/CMakeLists.txt @@ -1,17 +1,22 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kdevoutlineview\") set(kdevoutlineview_SRCS outlineviewplugin.cpp outlinenode.cpp outlinemodel.cpp outlinewidget.cpp ) +ecm_qt_declare_logging_category(kdevoutlineview_SRCS + HEADER debug.h + IDENTIFIER PLUGIN_OUTLINE + CATEGORY_NAME "kdevplatform.plugins.outline" +) kdevplatform_add_plugin(kdevoutlineview JSON kdevoutlineview.json SOURCES ${kdevoutlineview_SRCS}) target_link_libraries(kdevoutlineview KDev::Interfaces KDev::Language KF5::CoreAddons KF5::I18n KF5::ItemModels KF5::TextEditor ) diff --git a/plugins/outlineview/debug_outline.h b/plugins/outlineview/debug_outline.h deleted file mode 100644 index 1343bda09..000000000 --- a/plugins/outlineview/debug_outline.h +++ /dev/null @@ -1,23 +0,0 @@ -/* - * KDevelop outline view - * Copyright 2015 Alex Richardson - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ -#pragma once - -#include - -Q_DECLARE_LOGGING_CATEGORY(PLUGIN_OUTLINE) diff --git a/plugins/outlineview/outlinemodel.cpp b/plugins/outlineview/outlinemodel.cpp index b29cab06f..6543d43e1 100644 --- a/plugins/outlineview/outlinemodel.cpp +++ b/plugins/outlineview/outlinemodel.cpp @@ -1,220 +1,220 @@ /* * KDevelop outline view * Copyright 2010, 2015 Alex Richardson * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "outlinemodel.h" #include #include #include #include #include #include #include #include #include -#include "debug_outline.h" +#include #include "outlinenode.h" using namespace KDevelop; OutlineModel::OutlineModel(QObject* parent) : QAbstractItemModel(parent) , m_lastDoc(nullptr) { auto docController = ICore::self()->documentController(); // build the initial outline now rebuildOutline(docController->activeDocument()); // we must always have a valid root node Q_ASSERT(m_rootNode); // we want to rebuild the outline whenever the current document has been reparsed connect(DUChain::self(), &DUChain::updateReady, this, [this] (const IndexedString& document, const ReferencedTopDUContext& /*topContext*/) { if (document == m_lastUrl) { rebuildOutline(m_lastDoc); } }); // and also when we switch the current document connect(docController, &IDocumentController::documentActivated, this, &OutlineModel::rebuildOutline); connect(docController, &IDocumentController::documentClosed, this, [this](IDocument* doc) { if (doc == m_lastDoc) { m_lastDoc = nullptr; m_lastUrl = IndexedString(); rebuildOutline(nullptr); } }); connect(docController, &IDocumentController::documentUrlChanged, this, [this](IDocument* doc) { if (doc == m_lastDoc) { m_lastUrl = IndexedString(doc->url()); } }); } OutlineModel::~OutlineModel() { } Qt::ItemFlags OutlineModel::flags(const QModelIndex& index) const { if (!index.isValid()) { return Qt::NoItemFlags; } else { return Qt::ItemIsEnabled | Qt::ItemIsSelectable; } } int OutlineModel::columnCount(const QModelIndex& parent) const { Q_UNUSED(parent) return 1; } QVariant OutlineModel::data(const QModelIndex& index, int role) const { if (!index.isValid()) { return QVariant(); } if (index.column() != 0) { return QVariant(); } OutlineNode* node = static_cast(index.internalPointer()); Q_ASSERT(node); if (role == Qt::DecorationRole) { return node->icon(); } if (role == Qt::DisplayRole) { return node->text(); } return QVariant(); } bool OutlineModel::hasChildren(const QModelIndex& parent) const { return rowCount(parent) > 0; } int OutlineModel::rowCount(const QModelIndex& parent) const { if (!parent.isValid()) { Q_ASSERT(m_rootNode); return m_rootNode->childCount(); } else if (parent.column() != 0) { return 0; } else { const OutlineNode* node = static_cast(parent.internalPointer()); return node->childCount(); } } QModelIndex OutlineModel::index(int row, int column, const QModelIndex &parent) const { if (!hasIndex(row, column, parent)) { return QModelIndex(); } if (!parent.isValid()) { // topLevelItem if (row < m_rootNode->childCount()) { return createIndex(row, column, const_cast(m_rootNode->childAt(row))); } return QModelIndex(); } else { if (parent.column() != 0) { return QModelIndex(); //only column 0 should have children } OutlineNode* node = static_cast(parent.internalPointer()); if (row < node->childCount()) { return createIndex(row, column, const_cast(node->childAt(row))); } return QModelIndex(); // out of range } return QModelIndex(); } QModelIndex OutlineModel::parent(const QModelIndex& index) const { if (!index.isValid()) { return QModelIndex(); } const OutlineNode* node = static_cast(index.internalPointer()); const OutlineNode* parentNode = node->parent(); Q_ASSERT(parentNode); if (parentNode == m_rootNode.get()) { return QModelIndex(); //node is a top level item } // parent node was not m_rootNode -> parent() must be valid const OutlineNode* parentParentNode = parentNode->parent(); Q_ASSERT(parentNode); const int row = parentParentNode->indexOf(parentNode); return createIndex(row, 0, const_cast(parentNode)); } void OutlineModel::rebuildOutline(IDocument* doc) { beginResetModel(); if (!doc) { m_rootNode = OutlineNode::dummyNode(); } else { // TODO: do this in a separate thread? Might take a while for large documents // and we really shouldn't be blocking the GUI thread! DUChainReadLocker lock; TopDUContext* topContext = DUChainUtils::standardContextForUrl(doc->url()); if (topContext) { m_rootNode = OutlineNode::fromTopContext(topContext); } else { m_rootNode = OutlineNode::dummyNode(); } } if (doc != m_lastDoc) { m_lastUrl = doc ? IndexedString(doc->url()) : IndexedString(); m_lastDoc = doc; } endResetModel(); } void OutlineModel::activate(const QModelIndex& realIndex) { if (!realIndex.isValid()) { qCWarning(PLUGIN_OUTLINE) << "attempting to activate invalid item!"; return; } OutlineNode* node = static_cast(realIndex.internalPointer()); KTextEditor::Range range; { DUChainReadLocker lock; const DUChainBase* dcb = node->duChainObject(); if (!dcb) { qCDebug(PLUGIN_OUTLINE) << "No declaration exists for node:" << node->text(); return; } //foreground thread == GUI thread? if so then we are fine range = dcb->rangeInCurrentRevision(); //outline view should ALWAYS correspond to currently active document Q_ASSERT(dcb->url().toUrl() == ICore::self()->documentController()->activeDocument()->url()); // lock should be released before activating the document } ICore::self()->documentController()->activateDocument(m_lastDoc, range); } diff --git a/plugins/outlineview/outlinenode.cpp b/plugins/outlineview/outlinenode.cpp index 474518772..c1f8339f6 100644 --- a/plugins/outlineview/outlinenode.cpp +++ b/plugins/outlineview/outlinenode.cpp @@ -1,299 +1,299 @@ /* * KDevelop outline view * Copyright 2010, 2015 Alex Richardson * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "outlinenode.h" #include #include #include #include #include #include #include #include #include #include #include #include -#include "debug_outline.h" +#include using namespace KDevelop; OutlineNode::OutlineNode(const QString& text, OutlineNode* parent) : m_cachedText(text) , m_parent(parent) { } OutlineNode::OutlineNode(DUContext* ctx, const QString& name, OutlineNode* parent) : m_cachedText(name) , m_declOrContext(ctx) , m_parent(parent) { KTextEditor::CodeCompletionModel::CompletionProperties prop; switch (ctx->type()) { case KDevelop::DUContext::Class: prop |= KTextEditor::CodeCompletionModel::Class; break; case KDevelop::DUContext::Enum: prop |= KTextEditor::CodeCompletionModel::Enum; break; case KDevelop::DUContext::Function: prop |= KTextEditor::CodeCompletionModel::Function; break; case KDevelop::DUContext::Namespace: prop |= KTextEditor::CodeCompletionModel::Namespace; break; case KDevelop::DUContext::Template: prop |= KTextEditor::CodeCompletionModel::Template; break; default: break; } m_cachedIcon = DUChainUtils::iconForProperties(prop); appendContext(ctx, ctx->topContext()); } OutlineNode::OutlineNode(Declaration* decl, OutlineNode* parent) : m_declOrContext(decl) , m_parent(parent) { // qCDebug(PLUGIN_OUTLINE) << "Adding:" << decl->qualifiedIdentifier().toString() << ": " <identifier().toString(); m_cachedIcon = DUChainUtils::iconForDeclaration(decl); if (NamespaceAliasDeclaration* alias = dynamic_cast(decl)) { //e.g. C++ using namespace statement m_cachedText = alias->importIdentifier().toString(); } else if (ClassMemberDeclaration* member = dynamic_cast(decl)) { if (member->isFriend()) { m_cachedText = "friend " + m_cachedText; } } if (AbstractType::Ptr type = decl->abstractType()) { //add the (function return) type at the end (after a colon - like UML) //so that the first thing seen is the name of the function/variable //and not the (function return) type AbstractType::WhichType typeEnum = type->whichType(); switch (typeEnum) { case AbstractType::TypeFunction: { FunctionType::Ptr func = type.cast(); // func->partToString() does not add the argument names -> do it manually if (DUContext* fCtx = DUChainUtils::getFunctionContext(decl)) { m_cachedText += '('; bool first = true; foreach (Declaration* childDecl, fCtx->localDeclarations(decl->topContext())) { if (first) { first = false; } else { m_cachedText += QStringLiteral(", "); } if (childDecl->abstractType()) { m_cachedText += childDecl->abstractType()->toString(); } auto ident = childDecl->identifier(); if (!ident.isEmpty()) { m_cachedText += ' ' + ident.toString(); } } m_cachedText += ')'; } else { qCWarning(PLUGIN_OUTLINE) << "Missing function context:" << decl->qualifiedIdentifier().toString(); m_cachedText += func->partToString(FunctionType::SignatureArguments); } //constructors/destructors have no return type, a trailing semicolon would look stupid if (func->returnType()) { m_cachedText += " : " + func->partToString(FunctionType::SignatureReturn); } return; // don't append any children here! } case AbstractType::TypeEnumeration: //no need to append the fully qualified type break; case AbstractType::TypeEnumerator: //no need to append the fully qualified type Q_ASSERT(decl->type()); m_cachedText += " = " + decl->type()->valueAsString(); break; case AbstractType::TypeStructure: { //this seems to be the way it has to be done (after grepping through source code) //TODO shouldn't there be some kind of isFriend() functionality? static IndexedIdentifier friendIdentifier(Identifier(QStringLiteral("friend"))); const bool isFriend = decl->indexedIdentifier() == friendIdentifier; if (isFriend) { //FIXME There seems to be no way of finding out whether the friend is class/struct/etc m_cachedText += ' ' + type->toString(); } break; } case AbstractType::TypeAlias: { //append the type it aliases TypeAliasType::Ptr alias = type.cast(); if (AbstractType::Ptr targetType = alias->type()) { m_cachedText += " : " + targetType->toString(); } } break; default: QString typeStr = type->toString(); if (!typeStr.isEmpty()) { m_cachedText += " : " + typeStr; } } } //these two don't seem to be hit if (decl->isAutoDeclaration()) { m_cachedText = "Implicit: " + m_cachedText; } if (decl->isAnonymous()) { m_cachedText = "" + m_cachedText; } if (DUContext* ctx = decl->internalContext()) { appendContext(ctx, decl->topContext()); } if (m_cachedText.isEmpty()) { m_cachedText = i18nc("An anonymous declaration (class, function, etc.)", ""); } } std::unique_ptr OutlineNode::dummyNode() { return std::unique_ptr(new OutlineNode(QStringLiteral(""), nullptr)); } std::unique_ptr OutlineNode::fromTopContext(TopDUContext* ctx) { auto result = dummyNode(); result->appendContext(ctx, ctx); return result; } void OutlineNode::appendContext(DUContext* ctx, TopDUContext* top) { // qDebug() << ctx->scopeIdentifier().toString() << "context type=" << ctx->type(); foreach (Declaration* childDecl, ctx->localDeclarations(top)) { if (childDecl) { m_children.emplace_back(childDecl, this); } } bool certainlyRequiresSorting = false; foreach (DUContext* childContext, ctx->childContexts()) { if (childContext->owner()) { // if there is a onwner, this will already have been handled by the loop above // TODO: is this always true? With my testing so far it seems to be // qDebug() << childContext->scopeIdentifier(true).toString() // << " has an owner declaration: " << childContext->owner()->toString() << "-> skip"; continue; } QVector decls = childContext->localDeclarations(top); if (decls.isEmpty()) { continue; } // we now know that we will have o sort since we appended a node in the wrong order certainlyRequiresSorting = true; QString ctxName = childContext->scopeIdentifier(true).toString(); // if child context is a template context or if name is empty append to current list, // otherwise create a new context node if (childContext->type() == DUContext::Template || ctxName.isEmpty()) { //append all subcontexts to this node appendContext(childContext, top); } else { // context without matching declaration, for example the definition of // "class Foo::Bar if it was forward declared in a namespace before: // namespace Foo { class Bar; } // class Foo::Bar { ... }; // TODO: icon and location for the namespace if (childContext->type() == DUContext::ContextType::Helper) { // This context could be for a definition of an existing class method. // If we don't merge all those context end up with a tree like this: // +-+- FooClass // | \-- method1() // +-+- FooClass // | \-- method2() // \ OtherStuff auto it = std::find_if(m_children.begin(), m_children.end(), [childContext](const OutlineNode& node) { if (DUContext* ctx = dynamic_cast(node.duChainObject())) { return ctx->equalScopeIdentifier(childContext); } return false; }); if (it != m_children.end()) { it->appendContext(childContext, top); } else { // TODO: get the correct icon for the context m_children.emplace_back(childContext, ctxName, this); } } else { // just add the context m_children.emplace_back(childContext, ctxName, this); } } } // we now need to sort since sometimes the elements from ctx->localDeclarations(top) // are not in the order they appear in the source. Additionally, if we had any child // contexts that were added, they will be at the end of the list // and need to be moved to the correct location. In that case certainlyRequiresSorting // will be true and we can pass it to sortByLocation() to skip the std::is_sorted() call sortByLocation(certainlyRequiresSorting); } void OutlineNode::sortByLocation(bool requiresSorting) { if (m_children.size() <= 1) { return; } // TODO: does it make sense to cache m_declOrContext->range().start? // adds 8 bytes to each node, but save a lot of pointer lookups when sorting // qDebug("sorting children of %s (%p) by location", qPrintable(m_cachedText), this); auto compare = [](const OutlineNode& n1, const OutlineNode& n2) -> bool { // nodes without decl always go at the end if (!n1.m_declOrContext) { return false; } else if (!n2.m_declOrContext) { return true; } return n1.m_declOrContext->range().start < n2.m_declOrContext->range().start; }; // since most nodes will be correctly sorted we check that before calling std::sort(). // This saves a lot of move ctor/assingnment calls in the common case. // If we appended a context without a Declaration* we know that it will be unsorted // so we can pass requiresSorting = true to skip the useless std::is_sorted() call. // uncomment the following qDebug() lines to see whether this optimization really makes sense if (requiresSorting || !std::is_sorted(m_children.begin(), m_children.end(), compare)) { // qDebug("Need to sort node %s(%p)", qPrintable(m_cachedText), this); std::sort(m_children.begin(), m_children.end(), compare); } else { // qDebug("Node %s(%p) was sorted!", qPrintable(m_cachedText), this); } } OutlineNode::~OutlineNode() { } diff --git a/plugins/outlineview/outlineviewplugin.cpp b/plugins/outlineview/outlineviewplugin.cpp index e81e1b37c..0f005cf90 100644 --- a/plugins/outlineview/outlineviewplugin.cpp +++ b/plugins/outlineview/outlineviewplugin.cpp @@ -1,75 +1,73 @@ /* * KDevelop outline view * Copyright 2010, 2015 Alex Richardson * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "outlineviewplugin.h" #include #include #include #include -#include "debug_outline.h" +#include #include "outlinewidget.h" K_PLUGIN_FACTORY_WITH_JSON(KDevOutlineViewFactory, "kdevoutlineview.json", registerPlugin();) -Q_LOGGING_CATEGORY(PLUGIN_OUTLINE, "kdevplatform.plugins.outline") - using namespace KDevelop; class OutlineViewFactory: public KDevelop::IToolViewFactory { public: explicit OutlineViewFactory(OutlineViewPlugin *plugin) : m_plugin(plugin) {} QWidget* create(QWidget *parent = nullptr) override { return new OutlineWidget(parent, m_plugin); } Qt::DockWidgetArea defaultPosition() override { return Qt::RightDockWidgetArea; } QString id() const override { return QStringLiteral("org.kdevelop.OutlineView"); } private: OutlineViewPlugin *m_plugin; }; OutlineViewPlugin::OutlineViewPlugin(QObject *parent, const QVariantList&) : KDevelop::IPlugin(QStringLiteral("kdevoutlineview"), parent) , m_factory(new OutlineViewFactory(this)) { core()->uiController()->addToolView(i18n("Outline"), m_factory); } OutlineViewPlugin::~OutlineViewPlugin() { } void OutlineViewPlugin::unload() { core()->uiController()->removeToolView(m_factory); } #include "outlineviewplugin.moc" diff --git a/plugins/patchreview/CMakeLists.txt b/plugins/patchreview/CMakeLists.txt index c4d2c36c1..8b69b786a 100644 --- a/plugins/patchreview/CMakeLists.txt +++ b/plugins/patchreview/CMakeLists.txt @@ -1,45 +1,50 @@ find_package(LibKompareDiff2 5.0 REQUIRED) find_package(KDEExperimentalPurpose QUIET) set_package_properties(KDEExperimentalPurpose PROPERTIES DESCRIPTION "EXPERIMENTAL. Support for patch sharing" URL "https://projects.kde.org/projects/playground/libs/purpose" TYPE OPTIONAL ) add_definitions(-DTRANSLATION_DOMAIN=\"kdevpatchreview\") kde_enable_exceptions() if(LibKompareDiff2_VERSION VERSION_LESS 5.1) remove_definitions( -DQT_NO_SIGNALS_SLOTS_KEYWORDS ) endif() set(patchreview_PART_SRCS patchreview.cpp patchhighlighter.cpp patchreviewtoolview.cpp localpatchsource.cpp ) +ecm_qt_declare_logging_category(patchreview_PART_SRCS + HEADER debug.h + IDENTIFIER PLUGIN_PATCHREVIEW + CATEGORY_NAME "kdevplatform.plugins.patchreview" +) ki18n_wrap_ui(patchreview_PART_SRCS patchreview.ui localpatchwidget.ui) qt5_add_resources(patchreview_PART_SRCS kdevpatchreview.qrc) kdevplatform_add_plugin(kdevpatchreview JSON kdevpatchreview.json SOURCES ${patchreview_PART_SRCS}) target_link_libraries(kdevpatchreview KDev::Project KDev::Interfaces KDev::Util KDev::Language KDev::Vcs KDev::Sublime ${LIBKOMPAREDIFF2_LIBRARIES} # from cmake config file, has matching target name, which changed for 5.1 KF5::IconThemes KF5::TextEditor KF5::Parts ) if (KDEExperimentalPurpose_FOUND) target_compile_definitions(kdevpatchreview PRIVATE WITH_PURPOSE) target_link_libraries(kdevpatchreview KDEExperimental::PurposeWidgets) endif() diff --git a/plugins/patchreview/debug.h b/plugins/patchreview/debug.h deleted file mode 100644 index c2bf1b11c..000000000 --- a/plugins/patchreview/debug.h +++ /dev/null @@ -1,26 +0,0 @@ -/* - * This file is part of KDevelop - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#ifndef KDEVPLATFORM_PATCHREVIEW_DEBUG_H -#define KDEVPLATFORM_PATCHREVIEW_DEBUG_H - -#include -Q_DECLARE_LOGGING_CATEGORY(PLUGIN_PATCHREVIEW) - -#endif diff --git a/plugins/patchreview/patchreview.cpp b/plugins/patchreview/patchreview.cpp index 27350766a..9dd6be969 100644 --- a/plugins/patchreview/patchreview.cpp +++ b/plugins/patchreview/patchreview.cpp @@ -1,626 +1,624 @@ /*************************************************************************** Copyright 2006-2009 David Nolden ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "patchreview.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 ///Whether arbitrary exceptions that occurred while diff-parsing within the library should be caught #define CATCHLIBDIFF /* Exclude this file from doublequote_chars check as krazy doesn't understand std::string*/ //krazy:excludeall=doublequote_chars #include #include #include #include #include #include "patchhighlighter.h" #include "patchreviewtoolview.h" #include "localpatchsource.h" #include "debug.h" -Q_LOGGING_CATEGORY(PLUGIN_PATCHREVIEW, "kdevplatform.plugins.patchreview") - using namespace KDevelop; namespace { // Maximum number of files to open directly within a tab when the review is started const int maximumFilesToOpenDirectly = 15; } Q_DECLARE_METATYPE( const Diff2::DiffModel* ) void PatchReviewPlugin::seekHunk( bool forwards, const QUrl& fileName ) { try { qCDebug(PLUGIN_PATCHREVIEW) << forwards << fileName << fileName.isEmpty(); if ( !m_modelList ) throw "no model"; for ( int a = 0; a < m_modelList->modelCount(); ++a ) { const Diff2::DiffModel* model = m_modelList->modelAt( a ); if ( !model || !model->differences() ) continue; QUrl file = urlForFileModel( model ); if ( !fileName.isEmpty() && fileName != file ) continue; IDocument* doc = ICore::self()->documentController()->documentForUrl( file ); if ( doc && m_highlighters.contains( doc->url() ) && m_highlighters[doc->url()] ) { if ( doc->textDocument() ) { const QList ranges = m_highlighters[doc->url()]->ranges(); KTextEditor::View * v = doc->activeTextView(); int bestLine = -1; if ( v ) { KTextEditor::Cursor c = v->cursorPosition(); for ( QList::const_iterator it = ranges.begin(); it != ranges.end(); ++it ) { int line = ( *it )->start().line(); if ( forwards ) { if ( line > c.line() && ( bestLine == -1 || line < bestLine ) ) bestLine = line; } else { if ( line < c.line() && ( bestLine == -1 || line > bestLine ) ) bestLine = line; } } if ( bestLine != -1 ) { v->setCursorPosition( KTextEditor::Cursor( bestLine, 0 ) ); return; } else if(fileName.isEmpty()) { int next = qBound(0, forwards ? a+1 : a-1, m_modelList->modelCount()-1); if (next < maximumFilesToOpenDirectly) { ICore::self()->documentController()->openDocument(urlForFileModel(m_modelList->modelAt(next))); } } } } } } } catch ( const QString & str ) { qCDebug(PLUGIN_PATCHREVIEW) << "seekHunk():" << str; } catch ( const char * str ) { qCDebug(PLUGIN_PATCHREVIEW) << "seekHunk():" << str; } qCDebug(PLUGIN_PATCHREVIEW) << "no matching hunk found"; } void PatchReviewPlugin::addHighlighting( const QUrl& highlightFile, IDocument* document ) { try { if ( !modelList() ) throw "no model"; for ( int a = 0; a < modelList()->modelCount(); ++a ) { Diff2::DiffModel* model = modelList()->modelAt( a ); if ( !model ) continue; QUrl file = urlForFileModel( model ); if ( file != highlightFile ) continue; qCDebug(PLUGIN_PATCHREVIEW) << "highlighting" << file.toDisplayString(); IDocument* doc = document; if( !doc ) doc = ICore::self()->documentController()->documentForUrl( file ); qCDebug(PLUGIN_PATCHREVIEW) << "highlighting file" << file << "with doc" << doc; if ( !doc || !doc->textDocument() ) continue; removeHighlighting( file ); m_highlighters[file] = new PatchHighlighter( model, doc, this, dynamic_cast(m_patch.data()) == nullptr ); } } catch ( const QString & str ) { qCDebug(PLUGIN_PATCHREVIEW) << "highlightFile():" << str; } catch ( const char * str ) { qCDebug(PLUGIN_PATCHREVIEW) << "highlightFile():" << str; } } void PatchReviewPlugin::highlightPatch() { try { if ( !modelList() ) throw "no model"; for ( int a = 0; a < modelList()->modelCount(); ++a ) { const Diff2::DiffModel* model = modelList()->modelAt( a ); if ( !model ) continue; QUrl file = urlForFileModel( model ); addHighlighting( file ); } } catch ( const QString & str ) { qCDebug(PLUGIN_PATCHREVIEW) << "highlightFile():" << str; } catch ( const char * str ) { qCDebug(PLUGIN_PATCHREVIEW) << "highlightFile():" << str; } } void PatchReviewPlugin::removeHighlighting( const QUrl& file ) { if ( file.isEmpty() ) { ///Remove all highlighting qDeleteAll( m_highlighters ); m_highlighters.clear(); } else { HighlightMap::iterator it = m_highlighters.find( file ); if ( it != m_highlighters.end() ) { delete *it; m_highlighters.erase( it ); } } } void PatchReviewPlugin::notifyPatchChanged() { if (m_patch) { qCDebug(PLUGIN_PATCHREVIEW) << "notifying patch change: " << m_patch->file(); m_updateKompareTimer->start( 500 ); } else { m_updateKompareTimer->stop(); } } void PatchReviewPlugin::forceUpdate() { if( m_patch ) { m_patch->update(); notifyPatchChanged(); } } void PatchReviewPlugin::updateKompareModel() { if ( !m_patch ) { ///TODO: this method should be cleaned up, it can be called by the timer and /// e.g. https://bugs.kde.org/show_bug.cgi?id=267187 shows how it could /// lead to asserts before... return; } qCDebug(PLUGIN_PATCHREVIEW) << "updating model"; removeHighlighting(); m_modelList.reset( nullptr ); m_depth = 0; delete m_diffSettings; { IDocument* patchDoc = ICore::self()->documentController()->documentForUrl( m_patch->file() ); if( patchDoc ) patchDoc->reload(); } QString patchFile; if( m_patch->file().isLocalFile() ) patchFile = m_patch->file().toLocalFile(); else if( m_patch->file().isValid() && !m_patch->file().isEmpty() ) { patchFile = QStandardPaths::writableLocation(QStandardPaths::TempLocation); bool ret = KIO::copy(m_patch->file(), QUrl::fromLocalFile(patchFile), KIO::HideProgressInfo)->exec(); if( !ret ) { qCWarning(PLUGIN_PATCHREVIEW) << "Problem while downloading: " << m_patch->file() << "to" << patchFile; patchFile.clear(); } } if (!patchFile.isEmpty()) //only try to construct the model if we have a patch to load try { m_diffSettings = new DiffSettings( nullptr ); m_kompareInfo.reset( new Kompare::Info() ); m_kompareInfo->localDestination = patchFile; m_kompareInfo->localSource = m_patch->baseDir().toLocalFile(); m_kompareInfo->depth = m_patch->depth(); m_kompareInfo->applied = m_patch->isAlreadyApplied(); m_modelList.reset( new Diff2::KompareModelList( m_diffSettings.data(), new QWidget, this ) ); m_modelList->slotKompareInfo( m_kompareInfo.data() ); try { m_modelList->openDirAndDiff(); } catch ( const QString & str ) { throw; } catch ( ... ) { throw QStringLiteral( "lib/libdiff2 crashed, memory may be corrupted. Please restart kdevelop." ); } for (m_depth = 0; m_depth < 10; ++m_depth) { bool allFound = true; for( int i = 0; i < m_modelList->modelCount(); i++ ) { if (!QFile::exists(urlForFileModel(m_modelList->modelAt(i)).toLocalFile())) { allFound = false; } } if (allFound) { break; // found depth } } emit patchChanged(); for( int i = 0; i < m_modelList->modelCount(); i++ ) { const Diff2::DiffModel* model = m_modelList->modelAt( i ); for( int j = 0; j < model->differences()->count(); j++ ) { model->differences()->at( j )->apply( m_patch->isAlreadyApplied() ); } } highlightPatch(); return; } catch ( const QString & str ) { KMessageBox::error( nullptr, str, i18n( "Kompare Model Update" ) ); } catch ( const char * str ) { KMessageBox::error( nullptr, str, i18n( "Kompare Model Update" ) ); } removeHighlighting(); m_modelList.reset( nullptr ); m_depth = 0; m_kompareInfo.reset( nullptr ); delete m_diffSettings; emit patchChanged(); } K_PLUGIN_FACTORY_WITH_JSON(KDevProblemReporterFactory, "kdevpatchreview.json", registerPlugin();) class PatchReviewToolViewFactory : public KDevelop::IToolViewFactory { public: explicit PatchReviewToolViewFactory( PatchReviewPlugin *plugin ) : m_plugin( plugin ) {} QWidget* create( QWidget *parent = nullptr ) override { return new PatchReviewToolView( parent, m_plugin ); } Qt::DockWidgetArea defaultPosition() override { return Qt::BottomDockWidgetArea; } QString id() const override { return QStringLiteral("org.kdevelop.PatchReview"); } private: PatchReviewPlugin *m_plugin; }; PatchReviewPlugin::~PatchReviewPlugin() { removeHighlighting(); // Tweak to work around a crash on OS X; see https://bugs.kde.org/show_bug.cgi?id=338829 // and http://qt-project.org/forums/viewthread/38406/#162801 // modified tweak: use setPatch() and deleteLater in that method. setPatch(nullptr); } void PatchReviewPlugin::clearPatch( QObject* _patch ) { qCDebug(PLUGIN_PATCHREVIEW) << "clearing patch" << _patch << "current:" << ( QObject* )m_patch; IPatchSource::Ptr patch( ( IPatchSource* )_patch ); if( patch == m_patch ) { qCDebug(PLUGIN_PATCHREVIEW) << "is current patch"; setPatch( IPatchSource::Ptr( new LocalPatchSource ) ); } } void PatchReviewPlugin::closeReview() { if( m_patch ) { IDocument* patchDocument = ICore::self()->documentController()->documentForUrl( m_patch->file() ); if (patchDocument) { // Revert modifications to the text document which we've done in updateReview patchDocument->setPrettyName( QString() ); patchDocument->textDocument()->setReadWrite( true ); KTextEditor::ModificationInterface* modif = dynamic_cast( patchDocument->textDocument() ); modif->setModifiedOnDiskWarning( true ); } removeHighlighting(); m_modelList.reset( nullptr ); m_depth = 0; if( !dynamic_cast( m_patch.data() ) ) { // make sure "show" button still openes the file dialog to open a custom patch file setPatch( new LocalPatchSource ); } else emit patchChanged(); Sublime::Area* area = ICore::self()->uiController()->activeArea(); if( area->objectName() == QLatin1String("review") ) { if( ICore::self()->documentController()->saveAllDocuments() ) ICore::self()->uiController()->switchToArea( QStringLiteral("code"), KDevelop::IUiController::ThisWindow ); } } } void PatchReviewPlugin::cancelReview() { if( m_patch ) { m_patch->cancelReview(); closeReview(); } } void PatchReviewPlugin::finishReview( QList selection ) { if( m_patch && m_patch->finishReview( selection ) ) { closeReview(); } } void PatchReviewPlugin::startReview( IPatchSource* patch, IPatchReview::ReviewMode mode ) { Q_UNUSED( mode ); emit startingNewReview(); setPatch( patch ); QMetaObject::invokeMethod( this, "updateReview", Qt::QueuedConnection ); } void PatchReviewPlugin::switchToEmptyReviewArea() { foreach(Sublime::Area* area, ICore::self()->uiController()->allAreas()) { if (area->objectName() == QLatin1String("review")) { area->clearDocuments(); } } if ( ICore::self()->uiController()->activeArea()->objectName() != QLatin1String("review") ) ICore::self()->uiController()->switchToArea( QStringLiteral("review"), KDevelop::IUiController::ThisWindow ); } QUrl PatchReviewPlugin::urlForFileModel( const Diff2::DiffModel* model ) { KDevelop::Path path(QDir::cleanPath(m_patch->baseDir().toLocalFile())); QVector destPath = KDevelop::Path("/"+model->destinationPath()).segments(); if (destPath.size() >= (int)m_depth) { destPath = destPath.mid(m_depth); } foreach(const QString& segment, destPath) { path.addPath(segment); } path.addPath(model->destinationFile()); return path.toUrl(); } void PatchReviewPlugin::updateReview() { if( !m_patch ) return; m_updateKompareTimer->stop(); switchToEmptyReviewArea(); KDevelop::IDocumentController *docController = ICore::self()->documentController(); // don't add documents opened automatically to the Files/Open Recent list IDocument* futureActiveDoc = docController->openDocument( m_patch->file(), KTextEditor::Range::invalid(), IDocumentController::DoNotAddToRecentOpen ); updateKompareModel(); if ( !m_modelList || !futureActiveDoc || !futureActiveDoc->textDocument() ) { // might happen if e.g. openDocument dialog was cancelled by user // or under the theoretic possibility of a non-text document getting opened return; } futureActiveDoc->textDocument()->setReadWrite( false ); futureActiveDoc->setPrettyName( i18n( "Overview" ) ); KTextEditor::ModificationInterface* modif = dynamic_cast( futureActiveDoc->textDocument() ); modif->setModifiedOnDiskWarning( false ); docController->activateDocument( futureActiveDoc ); PatchReviewToolView* toolView = qobject_cast(ICore::self()->uiController()->findToolView( i18n( "Patch Review" ), m_factory )); Q_ASSERT( toolView ); //Open all relates files for( int a = 0; a < m_modelList->modelCount() && a < maximumFilesToOpenDirectly; ++a ) { QUrl absoluteUrl = urlForFileModel( m_modelList->modelAt( a ) ); if (absoluteUrl.isRelative()) { KMessageBox::error( nullptr, i18n("The base directory of the patch must be an absolute directory"), i18n( "Patch Review" ) ); break; } if( QFileInfo::exists( absoluteUrl.toLocalFile() ) && absoluteUrl.toLocalFile() != QLatin1String("/dev/null") ) { toolView->open( absoluteUrl, false ); }else{ // Maybe the file was deleted qCDebug(PLUGIN_PATCHREVIEW) << "could not open" << absoluteUrl << "because it doesn't exist"; } } } void PatchReviewPlugin::setPatch( IPatchSource* patch ) { if ( patch == m_patch ) { return; } if( m_patch ) { disconnect( m_patch.data(), &IPatchSource::patchChanged, this, &PatchReviewPlugin::notifyPatchChanged ); if ( qobject_cast( m_patch ) ) { // make sure we don't leak this // TODO: what about other patch sources? m_patch->deleteLater(); } } m_patch = patch; if( m_patch ) { qCDebug(PLUGIN_PATCHREVIEW) << "setting new patch" << patch->name() << "with file" << patch->file() << "basedir" << patch->baseDir(); connect( m_patch.data(), &IPatchSource::patchChanged, this, &PatchReviewPlugin::notifyPatchChanged ); } QString finishText = i18n( "Finish Review" ); if( m_patch && !m_patch->finishReviewCustomText().isEmpty() ) finishText = m_patch->finishReviewCustomText(); m_finishReview->setText( finishText ); m_finishReview->setEnabled( patch ); notifyPatchChanged(); } PatchReviewPlugin::PatchReviewPlugin( QObject *parent, const QVariantList & ) : KDevelop::IPlugin( QStringLiteral("kdevpatchreview"), parent ), m_patch( nullptr ), m_factory( new PatchReviewToolViewFactory( this ) ) { qRegisterMetaType( "const Diff2::DiffModel*" ); setXMLFile( QStringLiteral("kdevpatchreview.rc") ); connect( ICore::self()->documentController(), &IDocumentController::documentClosed, this, &PatchReviewPlugin::documentClosed ); connect( ICore::self()->documentController(), &IDocumentController::textDocumentCreated, this, &PatchReviewPlugin::textDocumentCreated ); connect( ICore::self()->documentController(), &IDocumentController::documentSaved, this, &PatchReviewPlugin::documentSaved ); m_updateKompareTimer = new QTimer( this ); m_updateKompareTimer->setSingleShot( true ); connect( m_updateKompareTimer, &QTimer::timeout, this, &PatchReviewPlugin::updateKompareModel ); m_finishReview = new QAction(i18n("Finish Review"), this); m_finishReview->setIcon( QIcon::fromTheme( QStringLiteral("dialog-ok") ) ); actionCollection()->setDefaultShortcut( m_finishReview, Qt::CTRL|Qt::Key_Return ); actionCollection()->addAction(QStringLiteral("commit_or_finish_review"), m_finishReview); foreach(Sublime::Area* area, ICore::self()->uiController()->allAreas()) { if (area->objectName() == QLatin1String("review")) area->addAction(m_finishReview); } core()->uiController()->addToolView( i18n( "Patch Review" ), m_factory, IUiController::None ); areaChanged(ICore::self()->uiController()->activeArea()); } void PatchReviewPlugin::documentClosed( IDocument* doc ) { removeHighlighting( doc->url() ); } void PatchReviewPlugin::documentSaved( IDocument* doc ) { // Only update if the url is not the patch-file, because our call to // the reload() KTextEditor function also causes this signal, // which would lead to an endless update loop. // Also, don't automatically update local patch sources, because // they may correspond to static files which don't match any more // after an edit was done. if( m_patch && doc->url() != m_patch->file() && !dynamic_cast(m_patch.data()) ) forceUpdate(); } void PatchReviewPlugin::textDocumentCreated( IDocument* doc ) { if (m_patch) { addHighlighting( doc->url(), doc ); } } void PatchReviewPlugin::unload() { core()->uiController()->removeToolView( m_factory ); KDevelop::IPlugin::unload(); } void PatchReviewPlugin::areaChanged(Sublime::Area* area) { bool reviewing = area->objectName() == QLatin1String("review"); m_finishReview->setEnabled(reviewing); if(!reviewing) { closeReview(); } } KDevelop::ContextMenuExtension PatchReviewPlugin::contextMenuExtension( KDevelop::Context* context ) { QList urls; if ( context->type() == KDevelop::Context::FileContext ) { KDevelop::FileContext* filectx = dynamic_cast( context ); urls = filectx->urls(); } else if ( context->type() == KDevelop::Context::ProjectItemContext ) { KDevelop::ProjectItemContext* projctx = dynamic_cast( context ); foreach( KDevelop::ProjectBaseItem* item, projctx->items() ) { if ( item->file() ) { urls << item->file()->path().toUrl(); } } } else if ( context->type() == KDevelop::Context::EditorContext ) { KDevelop::EditorContext *econtext = dynamic_cast( context ); urls << econtext->url(); } if (urls.size() == 1) { QAction* reviewAction = new QAction( QIcon::fromTheme(QStringLiteral("text-x-patch")), i18n( "Review Patch" ), this ); reviewAction->setData(QVariant(urls[0])); connect( reviewAction, &QAction::triggered, this, &PatchReviewPlugin::executeFileReviewAction ); ContextMenuExtension cm; cm.addAction( KDevelop::ContextMenuExtension::VcsGroup, reviewAction ); return cm; } return KDevelop::IPlugin::contextMenuExtension( context ); } void PatchReviewPlugin::executeFileReviewAction() { QAction* reviewAction = qobject_cast(sender()); KDevelop::Path path(reviewAction->data().toUrl()); LocalPatchSource* ps = new LocalPatchSource(); ps->setFilename(path.toUrl()); ps->setBaseDir(path.parent().toUrl()); ps->setAlreadyApplied(true); ps->createWidget(); startReview(ps, OpenAndRaise); } #include "patchreview.moc" // kate: space-indent on; indent-width 4; tab-width 4; replace-tabs on diff --git a/plugins/perforce/CMakeLists.txt b/plugins/perforce/CMakeLists.txt index 0381bce2a..f750d5df7 100644 --- a/plugins/perforce/CMakeLists.txt +++ b/plugins/perforce/CMakeLists.txt @@ -1,14 +1,21 @@ add_subdirectory(p4clientstub) -add_subdirectory(test) +ecm_qt_declare_logging_category(kdevperforce_LOG_PART_SRCS + HEADER debug.h + IDENTIFIER PLUGIN_PERFORCE + CATEGORY_NAME "kdevplatform.plugins.perforce" +) set(kdevperforce_PART_SRCS perforceplugin.cpp perforcepluginmetadata.cpp + ${kdevperforce_LOG_PART_SRCS} ) kdevplatform_add_plugin(kdevperforce JSON kdevperforce.json SOURCES ${kdevperforce_PART_SRCS}) target_link_libraries(kdevperforce KDev::Interfaces KDev::Vcs ) + +add_subdirectory(test) diff --git a/plugins/perforce/debug.h b/plugins/perforce/debug.h deleted file mode 100644 index 27340e501..000000000 --- a/plugins/perforce/debug.h +++ /dev/null @@ -1,23 +0,0 @@ -/*************************************************************************** - * Copyright 2010 Morten Danielsen Volden * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - ***************************************************************************/ -#ifndef PERFORCE_PLUGIN_DEBUG_H -#define PERFORCE_PLUGIN_DEBUG_H - -#include -Q_DECLARE_LOGGING_CATEGORY(PLUGIN_PERFORCE) - -#endif diff --git a/plugins/perforce/perforceplugin.cpp b/plugins/perforce/perforceplugin.cpp index fc4db624f..0d8cb4f84 100644 --- a/plugins/perforce/perforceplugin.cpp +++ b/plugins/perforce/perforceplugin.cpp @@ -1,697 +1,694 @@ /*************************************************************************** * Copyright 2010 Morten Danielsen Volden * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "perforceplugin.h" #include "debug.h" #include "qtcompat_p.h" #include #include #include #include #include #include -#include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; namespace { QString toRevisionName(const KDevelop::VcsRevision& rev, QString currentRevision=QString()) { bool ok; int previous = currentRevision.toInt(&ok); previous--; QString tmp; switch(rev.revisionType()) { case VcsRevision::Special: switch(rev.revisionValue().value()) { case VcsRevision::Head: return QStringLiteral("#head"); case VcsRevision::Base: return QStringLiteral("#have"); case VcsRevision::Working: return QStringLiteral("#have"); case VcsRevision::Previous: Q_ASSERT(!currentRevision.isEmpty()); tmp.setNum(previous); tmp.prepend("#"); return tmp; case VcsRevision::Start: return QString(); case VcsRevision::UserSpecialType: //Not used Q_ASSERT(false && "i don't know how to do that"); } break; case VcsRevision::GlobalNumber: tmp.append("#"); tmp.append(rev.revisionValue().toString()); return tmp; case VcsRevision::Date: case VcsRevision::FileNumber: case VcsRevision::Invalid: case VcsRevision::UserSpecialType: Q_ASSERT(false); } return QString(); } VcsItemEvent::Actions actionsFromString(QString const& changeDescription) { if(changeDescription == QLatin1String("add")) return VcsItemEvent::Added; if(changeDescription == QLatin1String("delete")) return VcsItemEvent::Deleted; return VcsItemEvent::Modified; } QDir urlDir(const QUrl& url) { QFileInfo f(url.toLocalFile()); if(f.isDir()) return QDir(url.toLocalFile()); else return f.absoluteDir(); } } -Q_LOGGING_CATEGORY(PLUGIN_PERFORCE, "kdevplatform.plugins.perforce") - PerforcePlugin::PerforcePlugin(QObject* parent, const QVariantList&): KDevelop::IPlugin(QStringLiteral("kdevperforce"), parent) , m_common(new KDevelop::VcsPluginHelper(this, this)) , m_perforcemenu(nullptr) , m_perforceConfigName(QStringLiteral("p4config.txt")) , m_perforceExecutable(QStringLiteral("p4")) , m_edit_action(nullptr) { QProcessEnvironment currentEviron(QProcessEnvironment::systemEnvironment()); QString tmp(currentEviron.value(QStringLiteral("P4CONFIG"))); if (tmp.isEmpty()) { // We require the P4CONFIG variable to be set because the perforce command line client will need it setErrorDescription(i18n("The variable P4CONFIG is not set. Is perforce installed on the system?")); return; } else { m_perforceConfigName = tmp; } qCDebug(PLUGIN_PERFORCE) << "The value of P4CONFIG is : " << tmp; } PerforcePlugin::~PerforcePlugin() { } QString PerforcePlugin::name() const { return i18n("Perforce"); } KDevelop::VcsImportMetadataWidget* PerforcePlugin::createImportMetadataWidget(QWidget* /*parent*/) { return nullptr; } bool PerforcePlugin::isValidRemoteRepositoryUrl(const QUrl& remoteLocation) { Q_UNUSED(remoteLocation); // TODO return false; } bool PerforcePlugin::isValidDirectory(const QUrl & dirPath) { const QFileInfo finfo(dirPath.toLocalFile()); QDir dir = finfo.isDir() ? QDir(dirPath.toLocalFile()) : finfo.absoluteDir(); do { if (dir.exists(m_perforceConfigName)) { return true; } } while (dir.cdUp()); return false; } bool PerforcePlugin::isVersionControlled(const QUrl& localLocation) { QFileInfo fsObject(localLocation.toLocalFile()); if (fsObject.isDir()) { return isValidDirectory(localLocation); } return parseP4fstat(fsObject, KDevelop::OutputJob::Silent); } DVcsJob* PerforcePlugin::p4fstatJob(const QFileInfo& curFile, OutputJob::OutputJobVerbosity verbosity) { DVcsJob* job = new DVcsJob(curFile.absolutePath(), this, verbosity); setEnvironmentForJob(job, curFile); *job << m_perforceExecutable << "fstat" << curFile.fileName(); return job; } bool PerforcePlugin::parseP4fstat(const QFileInfo& curFile, OutputJob::OutputJobVerbosity verbosity) { QScopedPointer job(p4fstatJob(curFile, verbosity)); if (job->exec() && job->status() == KDevelop::VcsJob::JobSucceeded) { qCDebug(PLUGIN_PERFORCE) << "Perforce returned: " << job->output(); if (!job->output().isEmpty()) return true; } return false; } QString PerforcePlugin::getRepositoryName(const QFileInfo& curFile) { static const QString DEPOT_FILE_STR(QStringLiteral("... depotFile ")); QString ret; QScopedPointer job(p4fstatJob(curFile, KDevelop::OutputJob::Silent)); if (job->exec() && job->status() == KDevelop::VcsJob::JobSucceeded) { if (!job->output().isEmpty()) { QStringList outputLines = job->output().split('\n', QString::SkipEmptyParts); foreach(const QString & line, outputLines) { int idx(line.indexOf(DEPOT_FILE_STR)); if (idx != -1) { ret = line.right(line.size() - DEPOT_FILE_STR.size()); return ret; } } } } return ret; } KDevelop::VcsJob* PerforcePlugin::repositoryLocation(const QUrl& /*localLocation*/) { return nullptr; } KDevelop::VcsJob* PerforcePlugin::add(const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/) { QFileInfo curFile(localLocations.front().toLocalFile()); DVcsJob* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose); setEnvironmentForJob(job, curFile); *job << m_perforceExecutable << "add" << localLocations; return job; } KDevelop::VcsJob* PerforcePlugin::remove(const QList& /*localLocations*/) { return nullptr; } KDevelop::VcsJob* PerforcePlugin::copy(const QUrl& /*localLocationSrc*/, const QUrl& /*localLocationDstn*/) { return nullptr; } KDevelop::VcsJob* PerforcePlugin::move(const QUrl& /*localLocationSrc*/, const QUrl& /*localLocationDst*/) { return nullptr; } KDevelop::VcsJob* PerforcePlugin::status(const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/) { if (localLocations.count() != 1) { KMessageBox::error(nullptr, i18n("Please select only one item for this operation")); return nullptr; } QFileInfo curFile(localLocations.front().toLocalFile()); DVcsJob* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose); setEnvironmentForJob(job, curFile); *job << m_perforceExecutable << "fstat" << curFile.fileName(); connect(job, &DVcsJob::readyForParsing, this, &PerforcePlugin::parseP4StatusOutput); return job; } KDevelop::VcsJob* PerforcePlugin::revert(const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/) { if (localLocations.count() != 1) { KMessageBox::error(nullptr, i18n("Please select only one item for this operation")); return nullptr; } QFileInfo curFile(localLocations.front().toLocalFile()); DVcsJob* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose); setEnvironmentForJob(job, curFile); *job << m_perforceExecutable << "revert" << curFile.fileName(); return job; } KDevelop::VcsJob* PerforcePlugin::update(const QList& localLocations, const KDevelop::VcsRevision& /*rev*/, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/) { QFileInfo curFile(localLocations.front().toLocalFile()); DVcsJob* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose); setEnvironmentForJob(job, curFile); //*job << m_perforceExecutable << "-p" << "127.0.0.1:1666" << "info"; - Let's keep this for now it's very handy for debugging QString fileOrDirectory; if (curFile.isDir()) fileOrDirectory = curFile.absolutePath() + "/..."; else fileOrDirectory = curFile.fileName(); *job << m_perforceExecutable << "sync" << fileOrDirectory; return job; } KDevelop::VcsJob* PerforcePlugin::commit(const QString& message, const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/) { if (localLocations.empty() || message.isEmpty()) return errorsFound(i18n("No files or message specified")); QFileInfo curFile(localLocations.front().toLocalFile()); DVcsJob* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose); setEnvironmentForJob(job, curFile); *job << m_perforceExecutable << "submit" << "-d" << message << localLocations; return job; } KDevelop::VcsJob* PerforcePlugin::diff(const QUrl& fileOrDirectory, const KDevelop::VcsRevision& srcRevision, const KDevelop::VcsRevision& dstRevision, KDevelop::VcsDiff::Type , KDevelop::IBasicVersionControl::RecursionMode /*recursion*/) { QFileInfo curFile(fileOrDirectory.toLocalFile()); QString depotSrcFileName = getRepositoryName(curFile); QString depotDstFileName = depotSrcFileName; depotSrcFileName.append(toRevisionName(srcRevision, dstRevision.prettyValue())); // dstRevision acutally contains the number that we want to take previous of DVcsJob* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose); setEnvironmentForJob(job, curFile); switch (dstRevision.revisionType()) { case VcsRevision::FileNumber: case VcsRevision::GlobalNumber: depotDstFileName.append("#"); depotDstFileName.append(dstRevision.prettyValue()); *job << m_perforceExecutable << "diff2" << "-u" << depotSrcFileName << depotDstFileName; break; case VcsRevision::Special: switch (dstRevision.revisionValue().value()) { case VcsRevision::Working: *job << m_perforceExecutable << "diff" << "-du" << depotSrcFileName; break; case VcsRevision::Start: case VcsRevision::UserSpecialType: default: break; } default: break; } connect(job, &DVcsJob::readyForParsing, this, &PerforcePlugin::parseP4DiffOutput); return job; } KDevelop::VcsJob* PerforcePlugin::log(const QUrl& localLocation, const KDevelop::VcsRevision& rev, long unsigned int limit) { static QString lastSeenChangeList; QFileInfo curFile(localLocation.toLocalFile()); QString localLocationAndRevStr = localLocation.toLocalFile(); DVcsJob* job = new DVcsJob(urlDir(localLocation), this, KDevelop::OutputJob::Verbose); setEnvironmentForJob(job, curFile); *job << m_perforceExecutable << "filelog" << "-lit"; if(limit > 0) *job << QStringLiteral("-m %1").arg(limit); if (curFile.isDir()) { localLocationAndRevStr.append("/..."); } QString revStr = toRevisionName(rev, QString()); if(!revStr.isEmpty()) { // This is not too nice, but perforce argument for restricting output from filelog does not Work :-( // So putting this in so we do not end up in infinite loop calling log, if(revStr == lastSeenChangeList) { localLocationAndRevStr.append("#none"); lastSeenChangeList.clear(); } else { localLocationAndRevStr.append(revStr); lastSeenChangeList = revStr; } } *job << localLocationAndRevStr; qCDebug(PLUGIN_PERFORCE) << "Issuing the following command to p4: " << job->dvcsCommand(); connect(job, &DVcsJob::readyForParsing, this, &PerforcePlugin::parseP4LogOutput); return job; } KDevelop::VcsJob* PerforcePlugin::log(const QUrl& localLocation, const KDevelop::VcsRevision& /*rev*/, const KDevelop::VcsRevision& /*limit*/) { QFileInfo curFile(localLocation.toLocalFile()); if (curFile.isDir()) { KMessageBox::error(nullptr, i18n("Please select a file for this operation")); return errorsFound(i18n("Directory not supported for this operation")); } DVcsJob* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose); setEnvironmentForJob(job, curFile); *job << m_perforceExecutable << "filelog" << "-lit" << localLocation; connect(job, &DVcsJob::readyForParsing, this , &PerforcePlugin::parseP4LogOutput); return job; } KDevelop::VcsJob* PerforcePlugin::annotate(const QUrl& localLocation, const KDevelop::VcsRevision& /*rev*/) { QFileInfo curFile(localLocation.toLocalFile()); if (curFile.isDir()) { KMessageBox::error(nullptr, i18n("Please select a file for this operation")); return errorsFound(i18n("Directory not supported for this operation")); } DVcsJob* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose); setEnvironmentForJob(job, curFile); *job << m_perforceExecutable << "annotate" << "-qi" << localLocation; connect(job, &DVcsJob::readyForParsing, this , &PerforcePlugin::parseP4AnnotateOutput); return job; } KDevelop::VcsJob* PerforcePlugin::resolve(const QList& /*localLocations*/, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/) { return nullptr; } KDevelop::VcsJob* PerforcePlugin::createWorkingCopy(const KDevelop::VcsLocation& /*sourceRepository*/, const QUrl& /*destinationDirectory*/, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/) { return nullptr; } KDevelop::VcsLocationWidget* PerforcePlugin::vcsLocation(QWidget* parent) const { return new StandardVcsLocationWidget(parent); } KDevelop::VcsJob* PerforcePlugin::edit(const QList& localLocations) { QFileInfo curFile(localLocations.front().toLocalFile()); DVcsJob* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose); setEnvironmentForJob(job, curFile); *job << m_perforceExecutable << "edit" << localLocations; return job; } KDevelop::VcsJob* PerforcePlugin::edit(const QUrl& /*localLocation*/) { return nullptr; } KDevelop::VcsJob* PerforcePlugin::unedit(const QUrl& /*localLocation*/) { return nullptr; } KDevelop::VcsJob* PerforcePlugin::localRevision(const QUrl& /*localLocation*/, KDevelop::VcsRevision::RevisionType) { return nullptr; } KDevelop::VcsJob* PerforcePlugin::import(const QString& /*commitMessage*/, const QUrl& /*sourceDirectory*/, const KDevelop::VcsLocation& /*destinationRepository*/) { return nullptr; } KDevelop::ContextMenuExtension PerforcePlugin::contextMenuExtension(KDevelop::Context* context) { m_common->setupFromContext(context); const QList & ctxUrlList = m_common->contextUrlList(); bool hasVersionControlledEntries = false; for( const QUrl& url : ctxUrlList) { if (isValidDirectory(url)) { hasVersionControlledEntries = true; break; } } if (!hasVersionControlledEntries) return IPlugin::contextMenuExtension(context); QMenu * perforceMenu = m_common->commonActions(); perforceMenu->addSeparator(); perforceMenu->addSeparator(); if (!m_edit_action) { m_edit_action = new QAction(i18n("Edit"), this); connect(m_edit_action, &QAction::triggered, this, & PerforcePlugin::ctxEdit); } perforceMenu->addAction(m_edit_action); ContextMenuExtension menuExt; menuExt.addAction(ContextMenuExtension::VcsGroup, perforceMenu->menuAction()); return menuExt; } void PerforcePlugin::ctxEdit() { QList const & ctxUrlList = m_common->contextUrlList(); KDevelop::ICore::self()->runController()->registerJob(edit(ctxUrlList)); } void PerforcePlugin::setEnvironmentForJob(DVcsJob* job, const QFileInfo& curFile) { KProcess* jobproc = job->process(); jobproc->setEnv(QStringLiteral("P4CONFIG"), m_perforceConfigName); if (curFile.isDir()) { jobproc->setEnv(QStringLiteral("PWD"), curFile.filePath()); } else { jobproc->setEnv(QStringLiteral("PWD"), curFile.absolutePath()); } } QList PerforcePlugin::getQvariantFromLogOutput(QStringList const& outputLines) { static const QString LOGENTRY_START(QStringLiteral("... #")); static const QString DEPOTMESSAGE_START(QStringLiteral("... .")); QMap changes; QList commits; QString currentFileName; QString changeNumberStr, author,changeDescription, commitMessage; VcsEvent currentVcsEvent; VcsItemEvent currentRepoFile; VcsRevision rev; int indexofAt; int changeNumber = 0; foreach(const QString & line, outputLines) { if (!line.startsWith(LOGENTRY_START) && !line.startsWith(DEPOTMESSAGE_START) && !line.startsWith('\t')) { currentFileName = line; } if(line.indexOf(LOGENTRY_START) != -1) { // expecting the Logentry line to be of the form: //... #5 change 10 edit on 2010/12/06 12:07:31 by mvo@testbed (text) changeNumberStr = line.section(' ', 3, 3 ); // We use global change number changeNumber = changeNumberStr.toInt(); author = line.section(' ', 9, 9); changeDescription = line.section(' ' , 4, 4 ); indexofAt = author.indexOf('@'); author.remove(indexofAt, author.size()); // Only keep the username itself rev.setRevisionValue(changeNumberStr, KDevelop::VcsRevision::GlobalNumber); changes[changeNumber].setRevision(rev); changes[changeNumber].setAuthor(author); changes[changeNumber].setDate(QDateTime::fromString(line.section(' ', 6, 7), QStringLiteral("yyyy/MM/dd hh:mm:ss"))); currentRepoFile.setRepositoryLocation(currentFileName); currentRepoFile.setActions( actionsFromString(changeDescription) ); changes[changeNumber].addItem(currentRepoFile); commitMessage.clear(); // We have a new entry, clear message } if (line.startsWith('\t') || line.startsWith(DEPOTMESSAGE_START)) { commitMessage += line.trimmed() + '\n'; changes[changeNumber].setMessage(commitMessage); } } for(const auto& item : qAsConst(changes)) { commits.prepend(QVariant::fromValue(item)); } return commits; } void PerforcePlugin::parseP4StatusOutput(DVcsJob* job) { QStringList outputLines = job->output().split('\n', QString::SkipEmptyParts); QVariantList statuses; QList processedFiles; static const QString ACTION_STR(QStringLiteral("... action ")); static const QString CLIENT_FILE_STR(QStringLiteral("... clientFile ")); VcsStatusInfo status; status.setState(VcsStatusInfo::ItemUserState); foreach(const QString & line, outputLines) { int idx(line.indexOf(ACTION_STR)); if (idx != -1) { QString curr = line.right(line.size() - ACTION_STR.size()); if (curr == QLatin1String("edit")) { status.setState(VcsStatusInfo::ItemModified); } else if (curr == QLatin1String("add")) { status.setState(VcsStatusInfo::ItemAdded); } else { status.setState(VcsStatusInfo::ItemUserState); } continue; } idx = line.indexOf(CLIENT_FILE_STR); if (idx != -1) { QUrl fileUrl = QUrl::fromLocalFile(line.right(line.size() - CLIENT_FILE_STR.size())); status.setUrl(fileUrl); } } statuses.append(qVariantFromValue(status)); job->setResults(statuses); } void PerforcePlugin::parseP4LogOutput(KDevelop::DVcsJob* job) { QList commits(getQvariantFromLogOutput(job->output().split('\n', QString::SkipEmptyParts))); job->setResults(commits); } void PerforcePlugin::parseP4DiffOutput(DVcsJob* job) { VcsDiff diff; diff.setDiff(job->output()); QDir dir(job->directory()); do { if (dir.exists(m_perforceConfigName)) { break; } } while (dir.cdUp()); diff.setBaseDiff(QUrl::fromLocalFile(dir.absolutePath())); job->setResults(qVariantFromValue(diff)); } void PerforcePlugin::parseP4AnnotateOutput(DVcsJob *job) { QVariantList results; /// First get the changelists for this file QStringList strList(job->dvcsCommand()); QString localLocation(strList.last()); /// ASSUMPTION WARNING - localLocation is the last in the annotate command KDevelop::VcsRevision dummyRev; QScopedPointer logJob(new DVcsJob(job->directory(), this, OutputJob::Silent)); QFileInfo curFile(localLocation); setEnvironmentForJob(logJob.data(), curFile); *logJob << m_perforceExecutable << "filelog" << "-lit" << localLocation; QList commits; if (logJob->exec() && logJob->status() == KDevelop::VcsJob::JobSucceeded) { if (!job->output().isEmpty()) { commits = getQvariantFromLogOutput(logJob->output().split('\n', QString::SkipEmptyParts)); } } VcsEvent item; QMap globalCommits; /// Move the VcsEvents to a more suitable data strucure for (QList::const_iterator commitsIt = commits.constBegin(), commitsEnd = commits.constEnd(); commitsIt != commitsEnd; ++commitsIt) { if(commitsIt->canConvert()) { item = commitsIt->value(); } globalCommits.insert(item.revision().revisionValue().toLongLong(), item); } VcsAnnotationLine* annotation; QStringList lines = job->output().split('\n'); size_t lineNumber(0); QMap definedRevisions; QMap::iterator currentEvent; bool convertToIntOk(false); int globalRevisionInt(0); QString globalRevision; for (QStringList::const_iterator it = lines.constBegin(), itEnd = lines.constEnd(); it != itEnd; ++it) { if (it->isEmpty()) { continue; } globalRevision = it->left(it->indexOf(':')); annotation = new VcsAnnotationLine; annotation->setLineNumber(lineNumber); VcsRevision rev; rev.setRevisionValue(globalRevision, KDevelop::VcsRevision::GlobalNumber); annotation->setRevision(rev); // Find the other info in the commits list globalRevisionInt = globalRevision.toLongLong(&convertToIntOk); if(convertToIntOk) { currentEvent = globalCommits.find(globalRevisionInt); annotation->setAuthor(currentEvent->author()); annotation->setCommitMessage(currentEvent->message()); annotation->setDate(currentEvent->date()); } results += qVariantFromValue(*annotation); ++lineNumber; } job->setResults(results); } KDevelop::VcsJob* PerforcePlugin::errorsFound(const QString& error, KDevelop::OutputJob::OutputJobVerbosity verbosity) { DVcsJob* j = new DVcsJob(QDir::temp(), this, verbosity); *j << "echo" << i18n("error: %1", error) << "-n"; return j; } diff --git a/plugins/perforce/test/CMakeLists.txt b/plugins/perforce/test/CMakeLists.txt index 2bcc77a12..5fb5869c8 100644 --- a/plugins/perforce/test/CMakeLists.txt +++ b/plugins/perforce/test/CMakeLists.txt @@ -1,12 +1,16 @@ +include_directories( + .. + ${CMAKE_CURRENT_BINARY_DIR}/.. +) set(perforceplugintest_SRCS test_perforce.cpp ../perforceplugin.cpp + ${kdevperforce_LOG_PART_SRCS} ) -include_directories(../) add_definitions( "-DP4_CLIENT_STUB_EXE=\"${CMAKE_CURRENT_BINARY_DIR}/../p4clientstub/p4clientstub\"" ) ecm_add_test(${perforceplugintest_SRCS} TEST_NAME test_kdevperforce LINK_LIBRARIES Qt5::Test KDev::Vcs KDev::Util KDev::Tests ) diff --git a/plugins/problemreporter/CMakeLists.txt b/plugins/problemreporter/CMakeLists.txt index 3c52a993e..8822cd906 100644 --- a/plugins/problemreporter/CMakeLists.txt +++ b/plugins/problemreporter/CMakeLists.txt @@ -1,18 +1,23 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kdevproblemreporter\") ########### next target ############### set(kdevproblemreporter_PART_SRCS problemreporterplugin.cpp problemtreeview.cpp problemhighlighter.cpp problemsview.cpp #problemnavigationcontext.cpp problemreportermodel.cpp ) +ecm_qt_declare_logging_category(kdevproblemreporter_PART_SRCS + HEADER debug.h + IDENTIFIER PLUGIN_PROBLEMREPORTER + CATEGORY_NAME "kdevplatform.plugins.problemreporter" +) qt5_add_resources(kdevproblemreporter_PART_SRCS kdevproblemreporter.qrc) kdevplatform_add_plugin(kdevproblemreporter JSON kdevproblemreporter.json SOURCES ${kdevproblemreporter_PART_SRCS}) target_link_libraries(kdevproblemreporter KF5::TextEditor KF5::Parts KDev::Language KDev::Interfaces KDev::Util KDev::Project KDev::Shell) add_subdirectory(tests) diff --git a/plugins/problemreporter/problemreporterplugin.cpp b/plugins/problemreporter/problemreporterplugin.cpp index b52c8056c..e4b3e1a07 100644 --- a/plugins/problemreporter/problemreporterplugin.cpp +++ b/plugins/problemreporter/problemreporterplugin.cpp @@ -1,251 +1,251 @@ /* * KDevelop Problem Reporter * * Copyright 2006 Adam Treat * Copyright 2006-2007 Hamish Rodda * Copyright 2007-2008 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "problemreporterplugin.h" #include #include #include #include #include #include #include #include #include #include #include #include "problemhighlighter.h" #include "problemreportermodel.h" #include "language/assistant/staticassistantsmanager.h" #include #include #include #include #include #include #include "shell/problemmodelset.h" #include "problemsview.h" +#include #include -Q_LOGGING_CATEGORY(PLUGIN_PROBLEMREPORTER, "kdevplatform.plugins.problemreporter") K_PLUGIN_FACTORY_WITH_JSON(KDevProblemReporterFactory, "kdevproblemreporter.json", registerPlugin();) using namespace KDevelop; class ProblemReporterFactory : public KDevelop::IToolViewFactory { public: QWidget* create(QWidget* parent = nullptr) override { Q_UNUSED(parent); ProblemsView* v = new ProblemsView(); v->load(); return v; } Qt::DockWidgetArea defaultPosition() override { return Qt::BottomDockWidgetArea; } QString id() const override { return QStringLiteral("org.kdevelop.ProblemReporterView"); } }; ProblemReporterPlugin::ProblemReporterPlugin(QObject* parent, const QVariantList&) : KDevelop::IPlugin(QStringLiteral("kdevproblemreporter"), parent) , m_factory(new ProblemReporterFactory) , m_model(new ProblemReporterModel(this)) { KDevelop::ProblemModelSet* pms = core()->languageController()->problemModelSet(); pms->addModel(QStringLiteral("Parser"), i18n("Parser"), m_model); core()->uiController()->addToolView(i18n("Problems"), m_factory); setXMLFile(QStringLiteral("kdevproblemreporter.rc")); connect(ICore::self()->documentController(), &IDocumentController::documentClosed, this, &ProblemReporterPlugin::documentClosed); connect(ICore::self()->documentController(), &IDocumentController::textDocumentCreated, this, &ProblemReporterPlugin::textDocumentCreated); connect(ICore::self()->documentController(), &IDocumentController::documentActivated, this, &ProblemReporterPlugin::documentActivated); connect(DUChain::self(), &DUChain::updateReady, this, &ProblemReporterPlugin::updateReady); connect(ICore::self()->languageController()->staticAssistantsManager(), &StaticAssistantsManager::problemsChanged, this, &ProblemReporterPlugin::updateHighlight); connect(pms, &ProblemModelSet::showRequested, this, &ProblemReporterPlugin::showModel); connect(pms, &ProblemModelSet::problemsChanged, this, &ProblemReporterPlugin::updateOpenedDocumentsHighlight); } ProblemReporterPlugin::~ProblemReporterPlugin() { qDeleteAll(m_highlighters); } ProblemReporterModel* ProblemReporterPlugin::model() const { return m_model; } void ProblemReporterPlugin::unload() { KDevelop::ProblemModelSet* pms = KDevelop::ICore::self()->languageController()->problemModelSet(); pms->removeModel(QStringLiteral("Parser")); core()->uiController()->removeToolView(m_factory); } void ProblemReporterPlugin::documentClosed(IDocument* doc) { if (!doc->textDocument()) return; IndexedString url(doc->url()); delete m_highlighters.take(url); m_reHighlightNeeded.remove(url); } void ProblemReporterPlugin::textDocumentCreated(KDevelop::IDocument* document) { Q_ASSERT(document->textDocument()); m_highlighters.insert(IndexedString(document->url()), new ProblemHighlighter(document->textDocument())); DUChain::self()->updateContextForUrl(IndexedString(document->url()), KDevelop::TopDUContext::AllDeclarationsContextsAndUses, this); } void ProblemReporterPlugin::documentActivated(KDevelop::IDocument* document) { IndexedString documentUrl(document->url()); if (m_reHighlightNeeded.contains(documentUrl)) { m_reHighlightNeeded.remove(documentUrl); updateHighlight(documentUrl); } } void ProblemReporterPlugin::updateReady(const IndexedString& url, const KDevelop::ReferencedTopDUContext&) { m_model->problemsUpdated(url); updateHighlight(url); } void ProblemReporterPlugin::updateHighlight(const KDevelop::IndexedString& url) { ProblemHighlighter* ph = m_highlighters.value(url); if (!ph) return; KDevelop::ProblemModelSet* pms(core()->languageController()->problemModelSet()); QVector documentProblems; foreach (const ModelData& modelData, pms->models()) { documentProblems += modelData.model->problems({url}); } ph->setProblems(documentProblems); } void ProblemReporterPlugin::showModel(const QString& id) { auto w = dynamic_cast(core()->uiController()->findToolView(i18n("Problems"), m_factory)); if (w) w->showModel(id); } KDevelop::ContextMenuExtension ProblemReporterPlugin::contextMenuExtension(KDevelop::Context* context) { KDevelop::ContextMenuExtension extension; KDevelop::EditorContext* editorContext = dynamic_cast(context); if (editorContext) { DUChainReadLocker lock(DUChain::lock(), 1000); if (!lock.locked()) { qCDebug(PLUGIN_PROBLEMREPORTER) << "failed to lock duchain in time"; return extension; } QString title; QList actions; TopDUContext* top = DUChainUtils::standardContextForUrl(editorContext->url()); if (top) { foreach (KDevelop::ProblemPointer problem, top->problems()) { if (problem->range().contains( top->transformToLocalRevision(KTextEditor::Cursor(editorContext->position())))) { KDevelop::IAssistant::Ptr solution = problem->solutionAssistant(); if (solution) { title = solution->title(); foreach (KDevelop::IAssistantAction::Ptr action, solution->actions()) actions << action->toKAction(); } } } } if (!actions.isEmpty()) { QString text; if (title.isEmpty()) text = i18n("Solve Problem"); else { text = i18n("Solve: %1", KDevelop::htmlToPlainText(title)); } QAction* menuAction = new QAction(text, nullptr); QMenu* menu(new QMenu(text, nullptr)); menuAction->setMenu(menu); foreach (QAction* action, actions) menu->addAction(action); extension.addAction(ContextMenuExtension::ExtensionGroup, menuAction); } } return extension; } void ProblemReporterPlugin::updateOpenedDocumentsHighlight() { foreach(auto document, core()->documentController()->openDocuments()) { // Skip non-text documents. // This also fixes crash caused by calling updateOpenedDocumentsHighlight() method without // any opened documents. In this case documentController()->openDocuments() returns single // (non-text) document with url like file:///tmp/kdevelop_QW2530.patch which has fatal bug: // if we call isActive() method from this document the crash will happens. if (!document->isTextDocument()) continue; IndexedString documentUrl(document->url()); if (document->isActive()) updateHighlight(documentUrl); else m_reHighlightNeeded.insert(documentUrl); } } #include "problemreporterplugin.moc" // kate: space-indent on; indent-width 2; tab-width 4; replace-tabs on; auto-insert-doxygen on diff --git a/plugins/problemreporter/problemreporterplugin.h b/plugins/problemreporter/problemreporterplugin.h index 4b9c00552..e1989b859 100644 --- a/plugins/problemreporter/problemreporterplugin.h +++ b/plugins/problemreporter/problemreporterplugin.h @@ -1,83 +1,80 @@ /* * KDevelop Problem Reporter * * Copyright 2006 Adam Treat * Copyright 2006-2007 Hamish Rodda * Copyright 2007-2008 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_PLUGIN_PROBLEMREPORTERPLUGIN_H #define KDEVPLATFORM_PLUGIN_PROBLEMREPORTERPLUGIN_H #include #include #include #include -#include -Q_DECLARE_LOGGING_CATEGORY(PLUGIN_PROBLEMREPORTER) - namespace KTextEditor { class Document; } namespace KDevelop { class IDocument; } class ProblemHighlighter; class ProblemReporterModel; class ProblemReporterPlugin : public KDevelop::IPlugin { Q_OBJECT public: explicit ProblemReporterPlugin(QObject* parent, const QVariantList& = QVariantList()); ~ProblemReporterPlugin() override; KDevelop::ContextMenuExtension contextMenuExtension(KDevelop::Context* context) override; // KDevelop::Plugin methods void unload() override; ProblemReporterModel* model() const; private Q_SLOTS: void updateReady(const KDevelop::IndexedString& url, const KDevelop::ReferencedTopDUContext&); void updateHighlight(const KDevelop::IndexedString& url); void textDocumentCreated(KDevelop::IDocument* document); void documentActivated(KDevelop::IDocument* document); void showModel(const QString& id); private: void updateOpenedDocumentsHighlight(); class ProblemReporterFactory* m_factory; ProblemReporterModel* m_model; QHash m_highlighters; QSet m_reHighlightNeeded; public Q_SLOTS: void documentClosed(KDevelop::IDocument*); }; #endif // KDEVPLATFORM_PLUGIN_PROBLEMREPORTERPLUGIN_H // kate: space-indent on; indent-width 2; tab-width 4; replace-tabs on; auto-insert-doxygen on diff --git a/plugins/projectfilter/CMakeLists.txt b/plugins/projectfilter/CMakeLists.txt index 4cc37682f..fb7e5256e 100644 --- a/plugins/projectfilter/CMakeLists.txt +++ b/plugins/projectfilter/CMakeLists.txt @@ -1,29 +1,32 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kdevprojectfilter\") ############################## # PLUGIN ##################### ############################## set( projectfilter_SRCS projectfilterprovider.cpp projectfilter.cpp - projectfilterdebug.cpp filter.cpp projectfilterconfigpage.cpp - projectfilterdebug.cpp filter.cpp filtermodel.cpp comboboxdelegate.cpp ) +ecm_qt_declare_logging_category(projectfilter_SRCS + HEADER debug.h + IDENTIFIER PLUGIN_PROJECTFILTER + CATEGORY_NAME "kdevplatform.plugins.projectfilter" +) ki18n_wrap_ui(projectfilter_SRCS projectfiltersettings.ui) kconfig_add_kcfg_files(projectfilter_SRCS projectfiltersettings.kcfgc) kdevplatform_add_plugin(kdevprojectfilter JSON kdevprojectfilter.json SOURCES ${projectfilter_SRCS}) target_link_libraries(kdevprojectfilter KDev::Project KDev::Util KDev::Interfaces ) add_subdirectory(tests) diff --git a/plugins/projectfilter/projectfilterconfigpage.cpp b/plugins/projectfilter/projectfilterconfigpage.cpp index 31b9597a7..346e3e52e 100644 --- a/plugins/projectfilter/projectfilterconfigpage.cpp +++ b/plugins/projectfilter/projectfilterconfigpage.cpp @@ -1,215 +1,215 @@ /* This file is part of KDevelop Copyright 2008 Alexander Dymo 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 "projectfilterconfigpage.h" #include #include #include #include #include #include "ui_projectfiltersettings.h" -#include "projectfilterdebug.h" +#include #include "filtermodel.h" #include "comboboxdelegate.h" #include "projectfilterprovider.h" using namespace KDevelop; ProjectFilterConfigPage::ProjectFilterConfigPage(ProjectFilterProvider* provider, const ProjectConfigOptions& options, QWidget* parent) : ProjectConfigPage(provider, options, parent) , m_model(new FilterModel(this)) , m_projectFilterProvider(provider) , m_ui(new Ui::ProjectFilterSettings) { m_ui->setupUi(this); m_ui->messageWidget->hide(); m_ui->filters->setSelectionMode(QAbstractItemView::SingleSelection); m_ui->filters->setModel(m_model); m_ui->filters->setRootIsDecorated(false); m_ui->filters->header()->setSectionResizeMode(FilterModel::Pattern, QHeaderView::Stretch); m_ui->filters->header()->setSectionResizeMode(FilterModel::Targets, QHeaderView::ResizeToContents); m_ui->filters->header()->setSectionResizeMode(FilterModel::Inclusive, QHeaderView::ResizeToContents); m_ui->filters->setItemDelegateForColumn(FilterModel::Targets, new ComboBoxDelegate(QVector() << ComboBoxDelegate::Item(i18n("Files"), static_cast(Filter::Files)) << ComboBoxDelegate::Item(i18n("Folders"), static_cast(Filter::Folders)) << ComboBoxDelegate::Item(i18n("Files and Folders"), static_cast(Filter::Folders | Filter::Files)) , this)); m_ui->filters->setItemDelegateForColumn(FilterModel::Inclusive, new ComboBoxDelegate(QVector() << ComboBoxDelegate::Item(i18n("Exclude"), false) << ComboBoxDelegate::Item(i18n("Include"), true) , this)); m_ui->filters->installEventFilter(this); m_ui->filters->setDragEnabled(true); m_ui->filters->setDragDropMode(QAbstractItemView::InternalMove); m_ui->filters->setAutoScroll(true); reset(); selectionChanged(); connect(m_ui->filters->selectionModel(), &QItemSelectionModel::currentChanged, this, &ProjectFilterConfigPage::selectionChanged); connect(this, &ProjectFilterConfigPage::changed, this, &ProjectFilterConfigPage::selectionChanged); connect(m_model, &FilterModel::dataChanged, this, &ProjectFilterConfigPage::emitChanged); connect(m_model, &FilterModel::rowsInserted, this, &ProjectFilterConfigPage::emitChanged); connect(m_model, &FilterModel::rowsRemoved, this, &ProjectFilterConfigPage::emitChanged); connect(m_model, &FilterModel::modelReset, this, &ProjectFilterConfigPage::emitChanged); connect(m_model, &FilterModel::rowsMoved, this, &ProjectFilterConfigPage::emitChanged); connect(m_ui->add, &QPushButton::clicked, this, &ProjectFilterConfigPage::add); connect(m_ui->remove, &QPushButton::clicked, this, &ProjectFilterConfigPage::remove); connect(m_ui->moveUp, &QPushButton::clicked, this, &ProjectFilterConfigPage::moveUp); connect(m_ui->moveDown, &QPushButton::clicked, this, &ProjectFilterConfigPage::moveDown); } ProjectFilterConfigPage::~ProjectFilterConfigPage() { } void ProjectFilterConfigPage::apply() { ProjectConfigPage::apply(); writeFilters(m_model->filters(), project()->projectConfiguration()); m_projectFilterProvider->updateProjectFilters(project()); } void ProjectFilterConfigPage::reset() { ProjectConfigPage::reset(); m_model->setFilters(readFilters(project()->projectConfiguration())); } void ProjectFilterConfigPage::defaults() { ProjectConfigPage::defaults(); m_model->setFilters(defaultFilters()); } bool ProjectFilterConfigPage::eventFilter(QObject* object, QEvent* event) { Q_ASSERT(object == m_ui->filters); Q_UNUSED(object); if (event->type() == QEvent::KeyRelease) { QKeyEvent* key = static_cast(event); if (key->key() == Qt::Key_Delete && key->modifiers() == Qt::NoModifier && m_ui->filters->currentIndex().isValid()) { // workaround https://bugs.kde.org/show_bug.cgi?id=324451 // there is no other way I see to figure out whether an editor is showing... QWidget* editor = m_ui->filters->viewport()->findChild(); if (editor && editor->isVisible()) { // editor is showing return false; } remove(); return true; } } return false; } void ProjectFilterConfigPage::selectionChanged() { bool hasSelection = m_ui->filters->currentIndex().isValid(); int row = -1; if (hasSelection) { row = m_ui->filters->currentIndex().row(); } m_ui->remove->setEnabled(hasSelection); m_ui->moveDown->setEnabled(hasSelection && row != m_model->rowCount() - 1); m_ui->moveUp->setEnabled(hasSelection && row != 0); } void ProjectFilterConfigPage::add() { m_model->insertRows(m_model->rowCount(), 1); const QModelIndex index = m_model->index(m_model->rowCount() - 1, FilterModel::Pattern, QModelIndex()); m_ui->filters->setCurrentIndex(index); m_ui->filters->edit(index); } void ProjectFilterConfigPage::remove() { Q_ASSERT(m_ui->filters->currentIndex().isValid()); m_model->removeRows(m_ui->filters->currentIndex().row(), 1); } void ProjectFilterConfigPage::moveUp() { Q_ASSERT(m_ui->filters->currentIndex().isValid()); m_model->moveFilterUp(m_ui->filters->currentIndex().row()); } void ProjectFilterConfigPage::moveDown() { Q_ASSERT(m_ui->filters->currentIndex().isValid()); m_model->moveFilterDown(m_ui->filters->currentIndex().row()); } void ProjectFilterConfigPage::checkFilters() { // check for errors, only show one error at once QString errorText; foreach(const Filter& filter, m_model->filters()) { const QString &pattern = filter.pattern.pattern(); if (pattern.isEmpty()) { errorText = i18n("A filter with an empty pattern will match all items. Use \"*\" to make this explicit."); break; } else if (pattern.endsWith('/') && filter.targets == Filter::Files) { errorText = i18n("A filter ending on \"/\" can never match a file."); break; } } if (!errorText.isEmpty()) { m_ui->messageWidget->setMessageType(KMessageWidget::Error); m_ui->messageWidget->setText(errorText); m_ui->messageWidget->animatedShow(); } else { m_ui->messageWidget->animatedHide(); } } void ProjectFilterConfigPage::emitChanged() { checkFilters(); emit changed(); } QString ProjectFilterConfigPage::fullName() const { return i18n("Configure Project Filter"); } QIcon ProjectFilterConfigPage::icon() const { return QIcon::fromTheme(QStringLiteral("view-filter")); } QString ProjectFilterConfigPage::name() const { return i18n("Project Filter"); } diff --git a/plugins/projectfilter/projectfilterdebug.cpp b/plugins/projectfilter/projectfilterdebug.cpp deleted file mode 100644 index af7c1912b..000000000 --- a/plugins/projectfilter/projectfilterdebug.cpp +++ /dev/null @@ -1,28 +0,0 @@ -/* - * This file is part of KDevelop - * - * Copyright 2013 Milian Wolff - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#include "projectfilterdebug.h" - -Q_LOGGING_CATEGORY(PLUGIN_PROJECTFILTER, "kdevplatform.plugins.projectfilter") - -namespace KDevelop { - -} diff --git a/plugins/projectfilter/projectfilterdebug.h b/plugins/projectfilter/projectfilterdebug.h deleted file mode 100644 index e2be89fa0..000000000 --- a/plugins/projectfilter/projectfilterdebug.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - * This file is part of KDevelop - * - * Copyright 2013 Milian Wolff - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#ifndef PROJECTFILTERDEBUG_H -#define PROJECTFILTERDEBUG_H - -#include -Q_DECLARE_LOGGING_CATEGORY(PLUGIN_PROJECTFILTER) - -namespace KDevelop { - -#define projectFilterDebug() qCDebug(PLUGIN_PROJECTFILTER) - -} - -#endif // PROJECTFILTERDEBUG_H diff --git a/plugins/projectfilter/projectfilterprovider.cpp b/plugins/projectfilter/projectfilterprovider.cpp index f871ec9b5..147c44777 100644 --- a/plugins/projectfilter/projectfilterprovider.cpp +++ b/plugins/projectfilter/projectfilterprovider.cpp @@ -1,164 +1,164 @@ /* This file is part of KDevelop Copyright 2013 Milian Wolff This library is free software; you can redistribute it and/or modify it under the terms of the GNU 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 "projectfilterprovider.h" #include #include #include #include #include #include #include #include #include #include #include #include -#include "projectfilterdebug.h" +#include #include "projectfilterconfigpage.h" #include using namespace KDevelop; K_PLUGIN_FACTORY_WITH_JSON(ProjectFilterProviderFactory, "kdevprojectfilter.json", registerPlugin();) ProjectFilterProvider::ProjectFilterProvider( QObject* parent, const QVariantList& /*args*/ ) : IPlugin( QStringLiteral( "kdevprojectfilter" ), parent ) { connect(core()->projectController(), &IProjectController::projectClosing, this, &ProjectFilterProvider::projectClosing); connect(core()->projectController(), &IProjectController::projectAboutToBeOpened, this, &ProjectFilterProvider::projectAboutToBeOpened); // initialize the filters for each project foreach(IProject* project, core()->projectController()->projects()) { updateProjectFilters(project); } } QSharedPointer ProjectFilterProvider::createFilter(IProject* project) const { return QSharedPointer(new ProjectFilter(project, m_filters[project])); } ContextMenuExtension ProjectFilterProvider::contextMenuExtension(Context* context) { ContextMenuExtension ret; if (!context->hasType(Context::ProjectItemContext)) { return ret; } ProjectItemContext* ctx = static_cast( context ); QList items = ctx->items(); // filter out project roots and items in targets QList< ProjectBaseItem* >::iterator it = items.begin(); while (it != items.end()) { if ((*it)->isProjectRoot() || !(*it)->parent()->folder()) { it = items.erase(it); } else { ++it; } } if (items.isEmpty()) { return ret; } QAction* action = new QAction(QIcon::fromTheme(QStringLiteral("view-filter")), i18np("Exclude Item From Project", "Exclude Items From Project", items.size()), this); action->setData(QVariant::fromValue(items)); connect(action, &QAction::triggered, this, &ProjectFilterProvider::addFilterFromContextMenu); ret.addAction(ContextMenuExtension::FileGroup, action); return ret; } void ProjectFilterProvider::addFilterFromContextMenu() { QAction* action = qobject_cast(sender()); Q_ASSERT(action); QList items = action->data().value >(); QHash changedProjectFilters; foreach(ProjectBaseItem* item, items) { if (!changedProjectFilters.contains(item->project())) { changedProjectFilters[item->project()] = readFilters(item->project()->projectConfiguration()); } SerializedFilters& filters = changedProjectFilters[item->project()]; Path path; if (item->target()) { path = Path(item->parent()->path(), item->text()); } else { path = item->path(); } filters << SerializedFilter('/' + item->project()->path().relativePath(path), item->folder() ? Filter::Folders : Filter::Files); } QHash< IProject*, SerializedFilters >::const_iterator it = changedProjectFilters.constBegin(); while (it != changedProjectFilters.constEnd()) { writeFilters(it.value(), it.key()->projectConfiguration()); m_filters[it.key()] = deserialize(it.value()); emit filterChanged(this, it.key()); ++it; } KMessageBox::information(ICore::self()->uiController()->activeMainWindow(), i18np("A filter for the item was added. To undo, use the project filter settings.", "A filter for the items was added. To undo, use the project filter settings.", items.size()), i18n("Project Filter Added"), QStringLiteral("projectfilter-addfromctxmenu")); } void ProjectFilterProvider::updateProjectFilters(IProject* project) { Filters newFilters = deserialize(readFilters(project->projectConfiguration())); Filters& filters = m_filters[project]; if (filters != newFilters) { - projectFilterDebug() << "project filter changed:" << project->name(); + qCDebug(PLUGIN_PROJECTFILTER) << "project filter changed:" << project->name(); filters = newFilters; emit filterChanged(this, project); } } void ProjectFilterProvider::projectAboutToBeOpened(IProject* project) { m_filters[project] = deserialize(readFilters(project->projectConfiguration())); } void ProjectFilterProvider::projectClosing(IProject* project) { m_filters.remove(project); } int ProjectFilterProvider::perProjectConfigPages() const { return 1; } ConfigPage* ProjectFilterProvider::perProjectConfigPage(int i, const ProjectConfigOptions& options, QWidget* parent) { return i == 0 ? new ProjectFilterConfigPage(this, options, parent) : nullptr; } #include "projectfilterprovider.moc" diff --git a/plugins/projectmanagerview/CMakeLists.txt b/plugins/projectmanagerview/CMakeLists.txt index 1b955b18e..820e9d288 100644 --- a/plugins/projectmanagerview/CMakeLists.txt +++ b/plugins/projectmanagerview/CMakeLists.txt @@ -1,25 +1,30 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kdevprojectmanagerview\") ########### next target ############### set(kdevprojectmanagerview_PLUGIN_SRCS projectmanagerviewplugin.cpp projectmanagerview.cpp projectmodelsaver.cpp projecttreeview.cpp projectbuildsetwidget.cpp vcsoverlayproxymodel.cpp projectmodelitemdelegate.cpp ) +ecm_qt_declare_logging_category(kdevprojectmanagerview_PLUGIN_SRCS + HEADER debug.h + IDENTIFIER PLUGIN_PROJECTMANAGERVIEW + CATEGORY_NAME "kdevplatform.plugins.projectmanagerview" +) ki18n_wrap_ui( kdevprojectmanagerview_PLUGIN_SRCS projectbuildsetwidget.ui projectmanagerview.ui ) qt5_add_resources(kdevprojectmanagerview_PLUGIN_SRCS kdevprojectmanagerview.qrc) kdevplatform_add_plugin(kdevprojectmanagerview JSON kdevprojectmanagerview.json SOURCES ${kdevprojectmanagerview_PLUGIN_SRCS}) target_link_libraries(kdevprojectmanagerview KF5::TextEditor KF5::ThreadWeaver KDev::Interfaces KDev::Project KDev::Language KDev::Util KDev::Vcs KDev::Sublime ) diff --git a/plugins/projectmanagerview/debug.h b/plugins/projectmanagerview/debug.h deleted file mode 100644 index 361355328..000000000 --- a/plugins/projectmanagerview/debug.h +++ /dev/null @@ -1,26 +0,0 @@ -/* - * This file is part of KDevelop - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#ifndef KDEVPLATFORM_PROJECTMANAGERVIEW_DEBUG_H -#define KDEVPLATFORM_PROJECTMANAGERVIEW_DEBUG_H - -#include -Q_DECLARE_LOGGING_CATEGORY(PLUGIN_PROJECTMANAGERVIEW) - -#endif diff --git a/plugins/projectmanagerview/projectmanagerviewplugin.cpp b/plugins/projectmanagerview/projectmanagerviewplugin.cpp index a6b66c47e..29b43a0f9 100644 --- a/plugins/projectmanagerview/projectmanagerviewplugin.cpp +++ b/plugins/projectmanagerview/projectmanagerviewplugin.cpp @@ -1,714 +1,713 @@ /* This file is part of KDevelop Copyright 2004 Roberto Raggi Copyright 2007 Andreas Pakulat 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 "projectmanagerviewplugin.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 #include #include "projectmanagerview.h" #include "debug.h" using namespace KDevelop; -Q_LOGGING_CATEGORY(PLUGIN_PROJECTMANAGERVIEW, "kdevplatform.plugins.projectmanagerview") K_PLUGIN_FACTORY_WITH_JSON(ProjectManagerFactory, "kdevprojectmanagerview.json", registerPlugin();) class KDevProjectManagerViewFactory: public KDevelop::IToolViewFactory { public: explicit KDevProjectManagerViewFactory( ProjectManagerViewPlugin *plugin ): mplugin( plugin ) {} QWidget* create( QWidget *parent = nullptr ) override { return new ProjectManagerView( mplugin, parent ); } Qt::DockWidgetArea defaultPosition() override { return Qt::LeftDockWidgetArea; } QString id() const override { return QStringLiteral("org.kdevelop.ProjectsView"); } private: ProjectManagerViewPlugin *mplugin; }; class ProjectManagerViewPluginPrivate { public: ProjectManagerViewPluginPrivate() {} KDevProjectManagerViewFactory *factory; QList ctxProjectItemList; QAction* m_buildAll; QAction* m_build; QAction* m_install; QAction* m_clean; QAction* m_configure; QAction* m_prune; }; static QList itemsFromIndexes(const QList& indexes) { QList items; ProjectModel* model = ICore::self()->projectController()->projectModel(); foreach(const QModelIndex& index, indexes) { items += model->itemFromIndex(index); } return items; } ProjectManagerViewPlugin::ProjectManagerViewPlugin( QObject *parent, const QVariantList& ) : IPlugin( QStringLiteral("kdevprojectmanagerview"), parent ), d(new ProjectManagerViewPluginPrivate) { d->m_buildAll = new QAction( i18n("Build all Projects"), this ); d->m_buildAll->setIcon(QIcon::fromTheme(QStringLiteral("run-build"))); connect( d->m_buildAll, &QAction::triggered, this, &ProjectManagerViewPlugin::buildAllProjects ); actionCollection()->addAction( QStringLiteral("project_buildall"), d->m_buildAll ); d->m_build = new QAction( i18n("Build Selection"), this ); d->m_build->setIconText( i18n("Build") ); actionCollection()->setDefaultShortcut( d->m_build, Qt::Key_F8 ); d->m_build->setIcon(QIcon::fromTheme(QStringLiteral("run-build"))); d->m_build->setEnabled( false ); connect( d->m_build, &QAction::triggered, this, &ProjectManagerViewPlugin::buildProjectItems ); actionCollection()->addAction( QStringLiteral("project_build"), d->m_build ); d->m_install = new QAction( i18n("Install Selection"), this ); d->m_install->setIconText( i18n("Install") ); d->m_install->setIcon(QIcon::fromTheme(QStringLiteral("run-build-install"))); actionCollection()->setDefaultShortcut( d->m_install, Qt::SHIFT + Qt::Key_F8 ); d->m_install->setEnabled( false ); connect( d->m_install, &QAction::triggered, this, &ProjectManagerViewPlugin::installProjectItems ); actionCollection()->addAction( QStringLiteral("project_install"), d->m_install ); d->m_clean = new QAction( i18n("Clean Selection"), this ); d->m_clean->setIconText( i18n("Clean") ); d->m_clean->setIcon(QIcon::fromTheme(QStringLiteral("run-build-clean"))); d->m_clean->setEnabled( false ); connect( d->m_clean, &QAction::triggered, this, &ProjectManagerViewPlugin::cleanProjectItems ); actionCollection()->addAction( QStringLiteral("project_clean"), d->m_clean ); d->m_configure = new QAction( i18n("Configure Selection"), this ); d->m_configure->setMenuRole( QAction::NoRole ); // OSX: Be explicit about role, prevent hiding due to conflict with "Preferences..." menu item d->m_configure->setIconText( i18n("Configure") ); d->m_configure->setIcon(QIcon::fromTheme(QStringLiteral("run-build-configure"))); d->m_configure->setEnabled( false ); connect( d->m_configure, &QAction::triggered, this, &ProjectManagerViewPlugin::configureProjectItems ); actionCollection()->addAction( QStringLiteral("project_configure"), d->m_configure ); d->m_prune = new QAction( i18n("Prune Selection"), this ); d->m_prune->setIconText( i18n("Prune") ); d->m_prune->setIcon(QIcon::fromTheme(QStringLiteral("run-build-prune"))); d->m_prune->setEnabled( false ); connect( d->m_prune, &QAction::triggered, this, &ProjectManagerViewPlugin::pruneProjectItems ); actionCollection()->addAction( QStringLiteral("project_prune"), d->m_prune ); // only add the action so that its known in the actionCollection // and so that it's shortcut etc. pp. is restored // apparently that is not possible to be done in the view itself *sigh* actionCollection()->addAction( QStringLiteral("locate_document") ); setXMLFile( QStringLiteral("kdevprojectmanagerview.rc") ); d->factory = new KDevProjectManagerViewFactory( this ); core()->uiController()->addToolView( i18n("Projects"), d->factory ); connect(core()->selectionController(), &ISelectionController::selectionChanged, this, &ProjectManagerViewPlugin::updateActionState); connect(ICore::self()->projectController()->buildSetModel(), &KDevelop::ProjectBuildSetModel::rowsInserted, this, &ProjectManagerViewPlugin::updateFromBuildSetChange); connect(ICore::self()->projectController()->buildSetModel(), &KDevelop::ProjectBuildSetModel::rowsRemoved, this, &ProjectManagerViewPlugin::updateFromBuildSetChange); connect(ICore::self()->projectController()->buildSetModel(), &KDevelop::ProjectBuildSetModel::modelReset, this, &ProjectManagerViewPlugin::updateFromBuildSetChange); } void ProjectManagerViewPlugin::updateFromBuildSetChange() { updateActionState( core()->selectionController()->currentSelection() ); } void ProjectManagerViewPlugin::updateActionState( KDevelop::Context* ctx ) { bool isEmpty = ICore::self()->projectController()->buildSetModel()->items().isEmpty(); if( isEmpty ) { isEmpty = !ctx || ctx->type() != Context::ProjectItemContext || dynamic_cast( ctx )->items().isEmpty(); } d->m_build->setEnabled( !isEmpty ); d->m_install->setEnabled( !isEmpty ); d->m_clean->setEnabled( !isEmpty ); d->m_configure->setEnabled( !isEmpty ); d->m_prune->setEnabled( !isEmpty ); } ProjectManagerViewPlugin::~ProjectManagerViewPlugin() { delete d; } void ProjectManagerViewPlugin::unload() { qCDebug(PLUGIN_PROJECTMANAGERVIEW) << "unloading manager view"; core()->uiController()->removeToolView(d->factory); } ContextMenuExtension ProjectManagerViewPlugin::contextMenuExtension( KDevelop::Context* context ) { if( context->type() != KDevelop::Context::ProjectItemContext ) return IPlugin::contextMenuExtension( context ); KDevelop::ProjectItemContext* ctx = dynamic_cast( context ); QList items = ctx->items(); d->ctxProjectItemList.clear(); if( items.isEmpty() ) return IPlugin::contextMenuExtension( context ); //TODO: also needs: removeTarget, removeFileFromTarget, runTargetsFromContextMenu ContextMenuExtension menuExt; bool needsCreateFile = true; bool needsCreateFolder = true; bool needsCloseProjects = true; bool needsBuildItems = true; bool needsFolderItems = true; bool needsRemoveAndRename = true; bool needsRemoveTargetFiles = true; bool needsPaste = true; //needsCreateFile if there is one item and it's a folder or target needsCreateFile &= (items.count() == 1) && (items.first()->folder() || items.first()->target()); //needsCreateFolder if there is one item and it's a folder needsCreateFolder &= (items.count() == 1) && (items.first()->folder()); needsPaste = needsCreateFolder; foreach( ProjectBaseItem* item, items ) { d->ctxProjectItemList << item->index(); //needsBuildItems if items are limited to targets and buildfolders needsBuildItems &= item->target() || item->type() == ProjectBaseItem::BuildFolder; //needsCloseProjects if items are limited to top level folders (Project Folders) needsCloseProjects &= item->folder() && !item->folder()->parent(); //needsFolderItems if items are limited to folders needsFolderItems &= (bool)item->folder(); //needsRemove if items are limited to non-top-level folders or files that don't belong to targets needsRemoveAndRename &= (item->folder() && item->parent()) || (item->file() && !item->parent()->target()); //needsRemoveTargets if items are limited to file items with target parents needsRemoveTargetFiles &= (item->file() && item->parent()->target()); } if ( needsCreateFile ) { QAction* action = new QAction( i18n( "Create &File..." ), this ); action->setIcon(QIcon::fromTheme(QStringLiteral("document-new"))); connect( action, &QAction::triggered, this, &ProjectManagerViewPlugin::createFileFromContextMenu ); menuExt.addAction( ContextMenuExtension::FileGroup, action ); } if ( needsCreateFolder ) { QAction* action = new QAction( i18n( "Create F&older..." ), this ); action->setIcon(QIcon::fromTheme(QStringLiteral("folder-new"))); connect( action, &QAction::triggered, this, &ProjectManagerViewPlugin::createFolderFromContextMenu ); menuExt.addAction( ContextMenuExtension::FileGroup, action ); } if ( needsBuildItems ) { QAction* action = new QAction( i18nc( "@action", "&Build" ), this ); action->setIcon(QIcon::fromTheme(QStringLiteral("run-build"))); connect( action, &QAction::triggered, this, &ProjectManagerViewPlugin::buildItemsFromContextMenu ); menuExt.addAction( ContextMenuExtension::BuildGroup, action ); action = new QAction( i18nc( "@action", "&Install" ), this ); action->setIcon(QIcon::fromTheme(QStringLiteral("run-build-install"))); connect( action, &QAction::triggered, this, &ProjectManagerViewPlugin::installItemsFromContextMenu ); menuExt.addAction( ContextMenuExtension::BuildGroup, action ); action = new QAction( i18nc( "@action", "&Clean" ), this ); action->setIcon(QIcon::fromTheme(QStringLiteral("run-build-clean"))); connect( action, &QAction::triggered, this, &ProjectManagerViewPlugin::cleanItemsFromContextMenu ); menuExt.addAction( ContextMenuExtension::BuildGroup, action ); action = new QAction( i18n( "&Add to Build Set" ), this ); action->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); connect( action, &QAction::triggered, this, &ProjectManagerViewPlugin::addItemsFromContextMenuToBuildset ); menuExt.addAction( ContextMenuExtension::BuildGroup, action ); } if ( needsCloseProjects ) { QAction* close = new QAction( i18np( "C&lose Project", "Close Projects", items.count() ), this ); close->setIcon(QIcon::fromTheme(QStringLiteral("project-development-close"))); connect( close, &QAction::triggered, this, &ProjectManagerViewPlugin::closeProjects ); menuExt.addAction( ContextMenuExtension::ProjectGroup, close ); } if ( needsFolderItems ) { QAction* action = new QAction( i18n( "&Reload" ), this ); action->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); connect( action, &QAction::triggered, this, &ProjectManagerViewPlugin::reloadFromContextMenu ); menuExt.addAction( ContextMenuExtension::FileGroup, action ); } if ( needsRemoveAndRename ) { QAction* remove = new QAction( i18n( "Remo&ve" ), this ); remove->setIcon(QIcon::fromTheme(QStringLiteral("user-trash"))); connect( remove, &QAction::triggered, this, &ProjectManagerViewPlugin::removeFromContextMenu ); menuExt.addAction( ContextMenuExtension::FileGroup, remove ); QAction* rename = new QAction( i18n( "Re&name..." ), this ); rename->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename"))); connect( rename, &QAction::triggered, this, &ProjectManagerViewPlugin::renameItemFromContextMenu ); menuExt.addAction( ContextMenuExtension::FileGroup, rename ); } if ( needsRemoveTargetFiles ) { QAction* remove = new QAction( i18n( "Remove From &Target" ), this ); remove->setIcon(QIcon::fromTheme(QStringLiteral("user-trash"))); connect( remove, &QAction::triggered, this, &ProjectManagerViewPlugin::removeTargetFilesFromContextMenu ); menuExt.addAction( ContextMenuExtension::FileGroup, remove ); } { QAction* copy = KStandardAction::copy(this, SLOT(copyFromContextMenu()), this); copy->setShortcutContext(Qt::WidgetShortcut); menuExt.addAction( ContextMenuExtension::FileGroup, copy ); } if (needsPaste) { QAction* paste = KStandardAction::paste(this, SLOT(pasteFromContextMenu()), this); paste->setShortcutContext(Qt::WidgetShortcut); menuExt.addAction( ContextMenuExtension::FileGroup, paste ); } return menuExt; } void ProjectManagerViewPlugin::closeProjects() { QList projectsToClose; ProjectModel* model = ICore::self()->projectController()->projectModel(); foreach( const QModelIndex& index, d->ctxProjectItemList ) { KDevelop::ProjectBaseItem* item = model->itemFromIndex(index); if( !projectsToClose.contains( item->project() ) ) { projectsToClose << item->project(); } } d->ctxProjectItemList.clear(); foreach( KDevelop::IProject* proj, projectsToClose ) { core()->projectController()->closeProject( proj ); } } void ProjectManagerViewPlugin::installItemsFromContextMenu() { runBuilderJob( BuilderJob::Install, itemsFromIndexes(d->ctxProjectItemList) ); d->ctxProjectItemList.clear(); } void ProjectManagerViewPlugin::cleanItemsFromContextMenu() { runBuilderJob( BuilderJob::Clean, itemsFromIndexes( d->ctxProjectItemList ) ); d->ctxProjectItemList.clear(); } void ProjectManagerViewPlugin::buildItemsFromContextMenu() { runBuilderJob( BuilderJob::Build, itemsFromIndexes( d->ctxProjectItemList ) ); d->ctxProjectItemList.clear(); } QList ProjectManagerViewPlugin::collectAllProjects() { QList items; foreach( KDevelop::IProject* project, core()->projectController()->projects() ) { items << project->projectItem(); } return items; } void ProjectManagerViewPlugin::buildAllProjects() { runBuilderJob( BuilderJob::Build, collectAllProjects() ); } QList ProjectManagerViewPlugin::collectItems() { QList items; QList buildItems = ICore::self()->projectController()->buildSetModel()->items(); if( !buildItems.isEmpty() ) { foreach( const BuildItem& buildItem, buildItems ) { if( ProjectBaseItem* item = buildItem.findItem() ) { items << item; } } } else { KDevelop::ProjectItemContext* ctx = dynamic_cast(ICore::self()->selectionController()->currentSelection()); items = ctx->items(); } return items; } void ProjectManagerViewPlugin::runBuilderJob( BuilderJob::BuildType type, QList items ) { BuilderJob* builder = new BuilderJob; builder->addItems( type, items ); builder->updateJobName(); ICore::self()->uiController()->registerStatus(new JobStatus(builder)); ICore::self()->runController()->registerJob( builder ); } void ProjectManagerViewPlugin::installProjectItems() { runBuilderJob( KDevelop::BuilderJob::Install, collectItems() ); } void ProjectManagerViewPlugin::pruneProjectItems() { runBuilderJob( KDevelop::BuilderJob::Prune, collectItems() ); } void ProjectManagerViewPlugin::configureProjectItems() { runBuilderJob( KDevelop::BuilderJob::Configure, collectItems() ); } void ProjectManagerViewPlugin::cleanProjectItems() { runBuilderJob( KDevelop::BuilderJob::Clean, collectItems() ); } void ProjectManagerViewPlugin::buildProjectItems() { runBuilderJob( KDevelop::BuilderJob::Build, collectItems() ); } void ProjectManagerViewPlugin::addItemsFromContextMenuToBuildset( ) { foreach( KDevelop::ProjectBaseItem* item, itemsFromIndexes( d->ctxProjectItemList )) { ICore::self()->projectController()->buildSetModel()->addProjectItem( item ); } } void ProjectManagerViewPlugin::runTargetsFromContextMenu( ) { foreach( KDevelop::ProjectBaseItem* item, itemsFromIndexes( d->ctxProjectItemList )) { KDevelop::ProjectExecutableTargetItem* t=item->executable(); if(t) { qCDebug(PLUGIN_PROJECTMANAGERVIEW) << "Running target: " << t->text() << t->builtUrl(); } } } void ProjectManagerViewPlugin::projectConfiguration( ) { if( !d->ctxProjectItemList.isEmpty() ) { ProjectModel* model = ICore::self()->projectController()->projectModel(); core()->projectController()->configureProject( model->itemFromIndex(d->ctxProjectItemList.at( 0 ))->project() ); } } void ProjectManagerViewPlugin::reloadFromContextMenu( ) { QList< KDevelop::ProjectFolderItem* > folders; foreach( KDevelop::ProjectBaseItem* item, itemsFromIndexes( d->ctxProjectItemList ) ) { if ( item->folder() ) { // since reloading should be recursive, only pass the upper-most items bool found = false; foreach ( KDevelop::ProjectFolderItem* existing, folders ) { if ( existing->path().isParentOf(item->folder()->path()) ) { // simply skip this child found = true; break; } else if ( item->folder()->path().isParentOf(existing->path()) ) { // remove the child in the list and add the current item instead folders.removeOne(existing); // continue since there could be more than one existing child } } if ( !found ) { folders << item->folder(); } } } foreach( KDevelop::ProjectFolderItem* folder, folders ) { folder->project()->projectFileManager()->reload(folder); } } void ProjectManagerViewPlugin::createFolderFromContextMenu( ) { foreach( KDevelop::ProjectBaseItem* item, itemsFromIndexes( d->ctxProjectItemList )) { if ( item->folder() ) { QWidget* window(ICore::self()->uiController()->activeMainWindow()->window()); QString name = QInputDialog::getText ( window, i18n ( "Create Folder in %1", item->folder()->path().pathOrUrl() ), i18n ( "Folder Name" ) ); if (!name.isEmpty()) { item->project()->projectFileManager()->addFolder( Path(item->path(), name), item->folder() ); } } } } void ProjectManagerViewPlugin::removeFromContextMenu() { removeItems(itemsFromIndexes( d->ctxProjectItemList )); } void ProjectManagerViewPlugin::removeItems(const QList< ProjectBaseItem* >& items) { if (items.isEmpty()) { return; } //copy the list of selected items and sort it to guarantee parents will come before children QList sortedItems = items; std::sort(sortedItems.begin(), sortedItems.end(), ProjectBaseItem::pathLessThan); Path lastFolder; QHash< IProjectFileManager*, QList > filteredItems; QStringList itemPaths; foreach( KDevelop::ProjectBaseItem* item, sortedItems ) { if (item->isProjectRoot()) { continue; } else if (item->folder() || item->file()) { //make sure no children of folders that will be deleted are listed if (lastFolder.isParentOf(item->path())) { continue; } else if (item->folder()) { lastFolder = item->path(); } IProjectFileManager* manager = item->project()->projectFileManager(); if (manager) { filteredItems[manager] << item; itemPaths << item->path().pathOrUrl(); } } } if (filteredItems.isEmpty()) { return; } if (KMessageBox::warningYesNoList( QApplication::activeWindow(), i18np("Do you really want to delete this item?", "Do you really want to delete these %1 items?", itemPaths.size()), itemPaths, i18n("Delete Files"), KStandardGuiItem::del(), KStandardGuiItem::cancel() ) == KMessageBox::No) { return; } //Go though projectmanagers, have them remove the files and folders that they own QHash< IProjectFileManager*, QList >::iterator it; for (it = filteredItems.begin(); it != filteredItems.end(); ++it) { Q_ASSERT(it.key()); it.key()->removeFilesAndFolders(it.value()); } } void ProjectManagerViewPlugin::removeTargetFilesFromContextMenu() { QList items = itemsFromIndexes( d->ctxProjectItemList ); QHash< IBuildSystemManager*, QList > itemsByBuildSystem; foreach(ProjectBaseItem *item, items) itemsByBuildSystem[item->project()->buildSystemManager()].append(item->file()); QHash< IBuildSystemManager*, QList >::iterator it; for (it = itemsByBuildSystem.begin(); it != itemsByBuildSystem.end(); ++it) it.key()->removeFilesFromTargets(it.value()); } void ProjectManagerViewPlugin::renameItemFromContextMenu() { renameItems(itemsFromIndexes( d->ctxProjectItemList )); } void ProjectManagerViewPlugin::renameItems(const QList< ProjectBaseItem* >& items) { if (items.isEmpty()) { return; } QWidget* window = ICore::self()->uiController()->activeMainWindow()->window(); foreach( KDevelop::ProjectBaseItem* item, items ) { if ((item->type()!=ProjectBaseItem::BuildFolder && item->type()!=ProjectBaseItem::Folder && item->type()!=ProjectBaseItem::File) || !item->parent()) { continue; } const QString src = item->text(); //Change QInputDialog->KFileSaveDialog? QString name = QInputDialog::getText( window, i18n("Rename..."), i18n("New name for '%1':", item->text()), QLineEdit::Normal, item->text() ); if (!name.isEmpty() && name != src) { ProjectBaseItem::RenameStatus status = item->rename( name ); switch(status) { case ProjectBaseItem::RenameOk: break; case ProjectBaseItem::ExistingItemSameName: KMessageBox::error(window, i18n("There is already a file named '%1'", name)); break; case ProjectBaseItem::ProjectManagerRenameFailed: KMessageBox::error(window, i18n("Could not rename '%1'", name)); break; case ProjectBaseItem::InvalidNewName: KMessageBox::error(window, i18n("'%1' is not a valid file name", name)); break; } } } } ProjectFileItem* createFile(const ProjectFolderItem* item) { QWidget* window = ICore::self()->uiController()->activeMainWindow()->window(); QString name = QInputDialog::getText(window, i18n("Create File in %1", item->path().pathOrUrl()), i18n("File name:")); if(name.isEmpty()) return nullptr; ProjectFileItem* ret = item->project()->projectFileManager()->addFile( Path(item->path(), name), item->folder() ); if (ret) { ICore::self()->documentController()->openDocument( ret->path().toUrl() ); } return ret; } void ProjectManagerViewPlugin::createFileFromContextMenu( ) { foreach( KDevelop::ProjectBaseItem* item, itemsFromIndexes( d->ctxProjectItemList ) ) { if ( item->folder() ) { createFile(item->folder()); } else if ( item->target() ) { ProjectFolderItem* folder=dynamic_cast(item->parent()); if(folder) { ProjectFileItem* f=createFile(folder); if(f) item->project()->buildSystemManager()->addFilesToTarget(QList() << f, item->target()); } } } } void ProjectManagerViewPlugin::copyFromContextMenu() { KDevelop::ProjectItemContext* ctx = dynamic_cast(ICore::self()->selectionController()->currentSelection()); QList urls; foreach (ProjectBaseItem* item, ctx->items()) { if (item->folder() || item->file()) { urls << item->path().toUrl(); } } qCDebug(PLUGIN_PROJECTMANAGERVIEW) << urls; if (!urls.isEmpty()) { QMimeData* data = new QMimeData; data->setUrls(urls); qApp->clipboard()->setMimeData(data); } } void ProjectManagerViewPlugin::pasteFromContextMenu() { KDevelop::ProjectItemContext* ctx = dynamic_cast(ICore::self()->selectionController()->currentSelection()); if (ctx->items().count() != 1) return; //do nothing if multiple or none items are selected ProjectBaseItem* destItem = ctx->items().at(0); if (!destItem->folder()) return; //do nothing if the target is not a directory const QMimeData* data = qApp->clipboard()->mimeData(); qCDebug(PLUGIN_PROJECTMANAGERVIEW) << data->urls(); const Path::List paths = toPathList(data->urls()); bool success = destItem->project()->projectFileManager()->copyFilesAndFolders(paths, destItem->folder()); if (success) { ProjectManagerViewItemContext* viewCtx = dynamic_cast(ICore::self()->selectionController()->currentSelection()); if (viewCtx) { //expand target folder viewCtx->view()->expandItem(destItem); //and select new items QList newItems; foreach (const Path &path, paths) { const Path targetPath(destItem->path(), path.lastPathSegment()); foreach (ProjectBaseItem *item, destItem->children()) { if (item->path() == targetPath) { newItems << item; } } } viewCtx->view()->selectItems(newItems); } } } #include "projectmanagerviewplugin.moc" diff --git a/plugins/quickopen/CMakeLists.txt b/plugins/quickopen/CMakeLists.txt index 2867a4b75..c7d2bdfaf 100644 --- a/plugins/quickopen/CMakeLists.txt +++ b/plugins/quickopen/CMakeLists.txt @@ -1,23 +1,28 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kdevquickopen\") add_subdirectory( tests ) set(kdevquickopen_PART_SRCS quickopenplugin.cpp quickopenmodel.cpp quickopenwidget.cpp projectfilequickopen.cpp duchainitemquickopen.cpp declarationlistquickopen.cpp projectitemquickopen.cpp documentationquickopenprovider.cpp actionsquickopenprovider.cpp expandingtree/expandingdelegate.cpp expandingtree/expandingtree.cpp expandingtree/expandingwidgetmodel.cpp ) +ecm_qt_declare_logging_category(kdevquickopen_PART_SRCS + HEADER debug.h + IDENTIFIER PLUGIN_QUICKOPEN + CATEGORY_NAME "kdevplatform.plugins.quickopen" +) ki18n_wrap_ui(kdevquickopen_PART_SRCS quickopenwidget.ui ) qt5_add_resources(kdevquickopen_PART_SRCS kdevquickopen.qrc) kdevplatform_add_plugin(kdevquickopen JSON kdevquickopen.json SOURCES ${kdevquickopen_PART_SRCS}) target_link_libraries(kdevquickopen KF5::IconThemes KF5::GuiAddons KF5::TextEditor KDev::Language KDev::Interfaces KDev::Project KDev::Util) diff --git a/plugins/quickopen/actionsquickopenprovider.cpp b/plugins/quickopen/actionsquickopenprovider.cpp index 248848959..a89678679 100644 --- a/plugins/quickopen/actionsquickopenprovider.cpp +++ b/plugins/quickopen/actionsquickopenprovider.cpp @@ -1,137 +1,136 @@ /* * This file is part of KDevelop * * Copyright 2015 Aleix Pol Gonzalez * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "actionsquickopenprovider.h" #include #include #include #include -#include #include using namespace KDevelop; class ActionsQuickOpenItem : public QuickOpenDataBase { public: ActionsQuickOpenItem(const QString& display, QAction* action) : QuickOpenDataBase() , m_action(action) , m_display(display) {} QString text() const override { return m_display; } QString htmlDescription() const override { QString desc = m_action->toolTip(); const QKeySequence shortcut = m_action->shortcut(); if (!shortcut.isEmpty()) { desc = i18nc("description (shortcut)", "%1 (%2)", desc, shortcut.toString()); } return desc; } bool execute(QString&) override { m_action->trigger(); return true; } QIcon icon() const override { // note: not the best icon, but can't find anything better static const QIcon fallbackIcon = QIcon::fromTheme("system-run"); const QIcon icon = m_action->icon(); if (icon.isNull()) { return fallbackIcon; } return icon; } private: QAction* m_action; ///needed because it won't have the "E&xit" ampersand QString m_display; }; ActionsQuickOpenProvider::ActionsQuickOpenProvider() { } void ActionsQuickOpenProvider::setFilterText(const QString& text) { if (text.size() < 2) { return; } m_results.clear(); const QList collections = KActionCollection::allCollections(); QRegularExpression mnemonicRx(QStringLiteral("^(.*)&(.+)$")); for (KActionCollection* c : collections) { QList actions = c->actions(); foreach (QAction* action, actions) { if (!action->isEnabled()) { continue; } QString display = action->text(); QRegularExpressionMatch match = mnemonicRx.match(display); if (match.hasMatch()) { display = match.captured(1) + match.captured(2); } if (display.contains(text, Qt::CaseInsensitive)) { m_results += QuickOpenDataPointer(new ActionsQuickOpenItem(display, action)); } } } } uint ActionsQuickOpenProvider::unfilteredItemCount() const { uint ret = 0; QList collections = KActionCollection::allCollections(); foreach (KActionCollection* c, collections) { ret += c->count(); } return ret; } QuickOpenDataPointer ActionsQuickOpenProvider::data(uint row) const { return m_results.at(row); } uint ActionsQuickOpenProvider::itemCount() const { return m_results.count(); } void ActionsQuickOpenProvider::reset() { m_results.clear(); } diff --git a/plugins/quickopen/debug.h b/plugins/quickopen/debug.h deleted file mode 100644 index be1352146..000000000 --- a/plugins/quickopen/debug.h +++ /dev/null @@ -1,26 +0,0 @@ -/* - * This file is part of KDevelop - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#ifndef KDEVPLATFORM_QUICKOPEN_DEBUG_H -#define KDEVPLATFORM_QUICKOPEN_DEBUG_H - -#include -Q_DECLARE_LOGGING_CATEGORY(PLUGIN_QUICKOPEN) - -#endif diff --git a/plugins/quickopen/expandingtree/expandingdelegate.cpp b/plugins/quickopen/expandingtree/expandingdelegate.cpp index 9a95d1635..3fd620c41 100644 --- a/plugins/quickopen/expandingtree/expandingdelegate.cpp +++ b/plugins/quickopen/expandingtree/expandingdelegate.cpp @@ -1,369 +1,369 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2006 Hamish Rodda * Copyright (C) 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 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 "expandingdelegate.h" #include #include #include #include #include #include "expandingwidgetmodel.h" -#include "../debug.h" +#include ExpandingDelegate::ExpandingDelegate(ExpandingWidgetModel* model, QObject* parent) : QItemDelegate(parent) , m_model(model) { } //Gets the background-color in the way QItemDelegate does it static QColor getUsedBackgroundColor(const QStyleOptionViewItem& option, const QModelIndex& index) { if (option.showDecorationSelected && (option.state & QStyle::State_Selected)) { QPalette::ColorGroup cg = option.state & QStyle::State_Enabled ? QPalette::Normal : QPalette::Disabled; if (cg == QPalette::Normal && !(option.state & QStyle::State_Active)) { cg = QPalette::Inactive; } return option.palette.brush(cg, QPalette::Highlight).color(); } else { QVariant value = index.data(Qt::BackgroundRole); if ((value).canConvert()) { return qvariant_cast(value).color(); } } return QApplication::palette().base().color(); } static void dampColors(QColor& col) { //Reduce the colors that are less visible to the eye, because they are closer to black when it comes to contrast //The most significant color to the eye is green. Then comes red, and then blue, with blue _much_ less significant. col.setBlue(0); col.setRed(col.red() / 2); } //A hack to compute more eye-focused contrast values static double readabilityContrast(QColor foreground, QColor background) { dampColors(foreground); dampColors(background); return abs(foreground.green() - background.green()) + abs(foreground.red() - background.red()) + abs(foreground.blue() - background.blue()); } void ExpandingDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optionOld, const QModelIndex& index) const { QStyleOptionViewItem option(optionOld); m_currentIndex = index; adjustStyle(index, option); const QModelIndex sourceIndex = model()->mapToSource(index); if (index.column() == 0) { model()->placeExpandingWidget(sourceIndex); } //Make sure the decorations are painted at the top, because the center of expanded items will be filled with the embedded widget. if (model()->isPartiallyExpanded(sourceIndex) == ExpandingWidgetModel::ExpandUpwards) { m_cachedAlignment = Qt::AlignBottom; } else { m_cachedAlignment = Qt::AlignTop; } option.decorationAlignment = m_cachedAlignment; option.displayAlignment = m_cachedAlignment; //qCDebug( PLUGIN_QUICKOPEN ) << "Painting row " << index.row() << ", column " << index.column() << ", internal " << index.internalPointer() << ", drawselected " << option.showDecorationSelected << ", selected " << (option.state & QStyle::State_Selected); m_cachedHighlights.clear(); m_backgroundColor = getUsedBackgroundColor(option, index); if (model()->indexIsItem(sourceIndex)) { m_currentColumnStart = 0; m_cachedHighlights = createHighlighting(index, option); } /*qCDebug( PLUGIN_QUICKOPEN ) << "Highlights for line:"; foreach (const QTextLayout::FormatRange& fr, m_cachedHighlights) qCDebug( PLUGIN_QUICKOPEN ) << fr.start << " len " << fr.length << " format ";*/ QItemDelegate::paint(painter, option, index); ///This is a bug workaround for the Qt raster paint engine: It paints over widgets embedded into the viewport when updating due to mouse events ///@todo report to Qt Software if (model()->isExpanded(sourceIndex) && model()->expandingWidget(sourceIndex)) { model()->expandingWidget(sourceIndex)->update(); } } QList ExpandingDelegate::createHighlighting(const QModelIndex& index, QStyleOptionViewItem& option) const { Q_UNUSED(index); Q_UNUSED(option); return QList(); } QSize ExpandingDelegate::basicSizeHint(const QModelIndex& index) const { return QItemDelegate::sizeHint(QStyleOptionViewItem(), index); } QSize ExpandingDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const { const QModelIndex sourceIndex = model()->mapToSource(index); QSize s = QItemDelegate::sizeHint(option, index); if (model()->isExpanded(sourceIndex) && model()->expandingWidget(sourceIndex)) { QWidget* widget = model()->expandingWidget(sourceIndex); QSize widgetSize = widget->size(); s.setHeight(widgetSize.height() + s.height() + 10); //10 is the sum that must match exactly the offsets used in ExpandingWidgetModel::placeExpandingWidgets } else if (model()->isPartiallyExpanded(sourceIndex) != ExpandingWidgetModel::ExpansionType::NotExpanded) { s.setHeight(s.height() + 30 + 10); } return s; } void ExpandingDelegate::adjustStyle(const QModelIndex& index, QStyleOptionViewItem& option) const { Q_UNUSED(index) Q_UNUSED(option) } void ExpandingDelegate::adjustRect(QRect& rect) const { const QModelIndex sourceIndex = model()->mapToSource(m_currentIndex); if (!model()->indexIsItem(sourceIndex) /*&& m_currentIndex.column() == 0*/) { rect.setLeft(model()->treeView()->columnViewportPosition(0)); int columnCount = model()->columnCount(sourceIndex.parent()); if (!columnCount) { return; } rect.setRight(model()->treeView()->columnViewportPosition(columnCount - 1) + model()->treeView()->columnWidth(columnCount - 1)); } } void ExpandingDelegate::drawDisplay(QPainter* painter, const QStyleOptionViewItem& option, const QRect& _rect, const QString& text) const { QRect rect(_rect); adjustRect(rect); QTextLayout layout(text, option.font, painter->device()); #if QT_VERSION < 0x050600 QList additionalFormats; #else QVector additionalFormats; #endif int missingFormats = text.length(); for (int i = 0; i < m_cachedHighlights.count(); ++i) { if (m_cachedHighlights[i].start + m_cachedHighlights[i].length <= m_currentColumnStart) { continue; } if (additionalFormats.isEmpty()) { if (i != 0 && m_cachedHighlights[i - 1].start + m_cachedHighlights[i - 1].length > m_currentColumnStart) { QTextLayout::FormatRange before; before.start = 0; before.length = m_cachedHighlights[i - 1].start + m_cachedHighlights[i - 1].length - m_currentColumnStart; before.format = m_cachedHighlights[i - 1].format; additionalFormats.append(before); } } QTextLayout::FormatRange format; format.start = m_cachedHighlights[i].start - m_currentColumnStart; format.length = m_cachedHighlights[i].length; format.format = m_cachedHighlights[i].format; additionalFormats.append(format); } if (!additionalFormats.isEmpty()) { missingFormats = text.length() - (additionalFormats.back().length + additionalFormats.back().start); } if (missingFormats > 0) { QTextLayout::FormatRange format; format.start = text.length() - missingFormats; format.length = missingFormats; QTextCharFormat fm; fm.setForeground(option.palette.text()); format.format = fm; additionalFormats.append(format); } if (m_backgroundColor.isValid()) { QColor background = m_backgroundColor; // qCDebug(PLUGIN_QUICKOPEN) << text << "background:" << background.name(); //Now go through the formats, and make sure the contrast background/foreground is readable for (int a = 0; a < additionalFormats.size(); ++a) { QColor currentBackground = background; if (additionalFormats[a].format.hasProperty(QTextFormat::BackgroundBrush)) { currentBackground = additionalFormats[a].format.background().color(); } QColor currentColor = additionalFormats[a].format.foreground().color(); double currentContrast = readabilityContrast(currentColor, currentBackground); QColor invertedColor(0xffffffff - additionalFormats[a].format.foreground().color().rgb()); double invertedContrast = readabilityContrast(invertedColor, currentBackground); // qCDebug(PLUGIN_QUICKOPEN) << "values:" << invertedContrast << currentContrast << invertedColor.name() << currentColor.name(); if (invertedContrast > currentContrast) { // qCDebug(PLUGIN_QUICKOPEN) << text << additionalFormats[a].length << "switching from" << currentColor.name() << "to" << invertedColor.name(); QBrush b(additionalFormats[a].format.foreground()); b.setColor(invertedColor); additionalFormats[a].format.setForeground(b); } } } for (int a = additionalFormats.size() - 1; a >= 0; --a) { if (additionalFormats[a].length == 0) { additionalFormats.removeAt(a); } else { ///For some reason the text-formats seem to be invalid in some way, sometimes ///@todo Fix this properly, it sucks not copying everything over QTextCharFormat fm; fm.setForeground(QBrush(additionalFormats[a].format.foreground().color())); fm.setBackground(additionalFormats[a].format.background()); fm.setUnderlineStyle(additionalFormats[a].format.underlineStyle()); fm.setUnderlineColor(additionalFormats[a].format.underlineColor()); fm.setFontWeight(additionalFormats[a].format.fontWeight()); additionalFormats[a].format = fm; } } // qCDebug( PLUGIN_QUICKOPEN ) << "Highlights for text [" << text << "] col start " << m_currentColumnStart << ":"; // foreach (const QTextLayout::FormatRange& fr, additionalFormats) // qCDebug( PLUGIN_QUICKOPEN ) << fr.start << " len " << fr.length << "foreground" << fr.format.foreground() << "background" << fr.format.background(); #if QT_VERSION < 0x050600 layout.setAdditionalFormats(additionalFormats); #else layout.setFormats(additionalFormats); #endif QTextOption to; to.setAlignment(m_cachedAlignment); to.setWrapMode(QTextOption::WrapAnywhere); layout.setTextOption(to); layout.beginLayout(); QTextLine line = layout.createLine(); line.setLineWidth(rect.width()); layout.endLayout(); //We need to do some hand layouting here if (to.alignment() & Qt::AlignBottom) { layout.draw(painter, QPoint(rect.left(), rect.bottom() - ( int )line.height())); } else { layout.draw(painter, rect.topLeft()); } return; //if (painter->fontMetrics().width(text) > textRect.width() && !text.contains(QLatin1Char('\n'))) //str = elidedText(option.fontMetrics, textRect.width(), option.textElideMode, text); //qt_format_text(option.font, textRect, option.displayAlignment, str, 0, 0, 0, 0, painter); } void ExpandingDelegate::drawDecoration(QPainter* painter, const QStyleOptionViewItem& option, const QRect& rect, const QPixmap& pixmap) const { const QModelIndex sourceIndex = model()->mapToSource(m_currentIndex); if (model()->indexIsItem(sourceIndex)) { QItemDelegate::drawDecoration(painter, option, rect, pixmap); } } void ExpandingDelegate::drawBackground(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { Q_UNUSED(index) QStyleOptionViewItem opt = option; //initStyleOption(&opt, index); //Problem: This isn't called at all, because drawBrackground is not virtual :-/ QStyle* style = model()->treeView()->style() ? model()->treeView()->style() : QApplication::style(); style->drawControl(QStyle::CE_ItemViewItem, &opt, painter); } ExpandingWidgetModel* ExpandingDelegate::model() const { return m_model; } void ExpandingDelegate::heightChanged() const { } bool ExpandingDelegate::editorEvent(QEvent* event, QAbstractItemModel* /*model*/, const QStyleOptionViewItem& /*option*/, const QModelIndex& index) { if (event->type() == QEvent::MouseButtonRelease) { const QModelIndex sourceIndex = model()->mapToSource(index); event->accept(); model()->setExpanded(sourceIndex, !model()->isExpanded(sourceIndex)); heightChanged(); return true; } else { event->ignore(); } return false; } QList ExpandingDelegate::highlightingFromVariantList(const QList& customHighlights) const { QList ret; for (int i = 0; i + 2 < customHighlights.count(); i += 3) { if (!customHighlights[i].canConvert(QVariant::Int) || !customHighlights[i + 1].canConvert(QVariant::Int) || !customHighlights[i + 2].canConvert()) { qCWarning(PLUGIN_QUICKOPEN) << "Unable to convert triple to custom formatting."; continue; } QTextLayout::FormatRange format; format.start = customHighlights[i].toInt(); format.length = customHighlights[i + 1].toInt(); format.format = customHighlights[i + 2].value().toCharFormat(); if (!format.format.isValid()) { qCWarning(PLUGIN_QUICKOPEN) << "Format is not valid"; } ret << format; } return ret; } diff --git a/plugins/quickopen/expandingtree/expandingwidgetmodel.cpp b/plugins/quickopen/expandingtree/expandingwidgetmodel.cpp index d13cbd0e7..4d644fbd2 100644 --- a/plugins/quickopen/expandingtree/expandingwidgetmodel.cpp +++ b/plugins/quickopen/expandingtree/expandingwidgetmodel.cpp @@ -1,592 +1,592 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 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 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 "expandingwidgetmodel.h" #include #include #include #include +#include #include #include #include #include "expandingdelegate.h" -#include -#include "../debug.h" +#include QIcon ExpandingWidgetModel::m_expandedIcon; QIcon ExpandingWidgetModel::m_collapsedIcon; using namespace KTextEditor; inline QModelIndex firstColumn(const QModelIndex& index) { return index.sibling(index.row(), 0); } ExpandingWidgetModel::ExpandingWidgetModel(QWidget* parent) : QAbstractTableModel(parent) { } ExpandingWidgetModel::~ExpandingWidgetModel() { clearExpanding(); } static QColor doAlternate(QColor color) { QColor background = QApplication::palette().background().color(); return KColorUtils::mix(color, background, 0.15); } uint ExpandingWidgetModel::matchColor(const QModelIndex& index) const { int matchQuality = contextMatchQuality(index.sibling(index.row(), 0)); if (matchQuality > 0) { bool alternate = index.row() & 1; QColor badMatchColor(0xff00aa44); //Blue-ish green QColor goodMatchColor(0xff00ff00); //Green QColor background = treeView()->palette().light().color(); QColor totalColor = KColorUtils::mix(badMatchColor, goodMatchColor, (( float )matchQuality) / 10.0); if (alternate) { totalColor = doAlternate(totalColor); } const float dynamicTint = 0.2f; const float minimumTint = 0.2f; double tintStrength = (dynamicTint * matchQuality) / 10; if (tintStrength) { tintStrength += minimumTint; //Some minimum tinting strength, else it's not visible any more } return KColorUtils::tint(background, totalColor, tintStrength).rgb(); } else { return 0; } } QVariant ExpandingWidgetModel::data(const QModelIndex& index, int role) const { switch (role) { case Qt::BackgroundRole: { if (index.column() == 0) { //Highlight by match-quality uint color = matchColor(index); if (color) { return QBrush(color); } } //Use a special background-color for expanded items if (isExpanded(index)) { if (index.row() & 1) { return doAlternate(treeView()->palette().toolTipBase().color()); } else { return treeView()->palette().toolTipBase(); } } } } return QVariant(); } QModelIndex ExpandingWidgetModel::mapFromSource(const QModelIndex& index) const { const auto proxyModel = qobject_cast(treeView()->model()); Q_ASSERT(proxyModel); Q_ASSERT(!index.isValid() || index.model() == this); return proxyModel->mapFromSource(index); } QModelIndex ExpandingWidgetModel::mapToSource(const QModelIndex& index) const { const auto proxyModel = qobject_cast(treeView()->model()); Q_ASSERT(proxyModel); Q_ASSERT(!index.isValid() || index.model() == proxyModel); return proxyModel->mapToSource(index); } void ExpandingWidgetModel::clearMatchQualities() { m_contextMatchQualities.clear(); } QModelIndex ExpandingWidgetModel::partiallyExpandedRow() const { if (m_partiallyExpanded.isEmpty()) { return QModelIndex(); } else { return m_partiallyExpanded.constBegin().key(); } } void ExpandingWidgetModel::clearExpanding() { clearMatchQualities(); QMap oldExpandState = m_expandState; foreach (QPointer widget, m_expandingWidgets) { delete widget; } m_expandingWidgets.clear(); m_expandState.clear(); m_partiallyExpanded.clear(); for (QMap::const_iterator it = oldExpandState.constBegin(); it != oldExpandState.constEnd(); ++it) { if (it.value() == Expanded) { emit dataChanged(it.key(), it.key()); } } } ExpandingWidgetModel::ExpansionType ExpandingWidgetModel::isPartiallyExpanded(const QModelIndex& index) const { if (m_partiallyExpanded.contains(firstColumn(index))) { return m_partiallyExpanded[firstColumn(index)]; } else { return NotExpanded; } } void ExpandingWidgetModel::partiallyUnExpand(const QModelIndex& idx_) { QModelIndex index(firstColumn(idx_)); m_partiallyExpanded.remove(index); m_partiallyExpanded.remove(idx_); } int ExpandingWidgetModel::partiallyExpandWidgetHeight() const { return 60; ///@todo use font-metrics text-height*2 for 2 lines } void ExpandingWidgetModel::rowSelected(const QModelIndex& idx_) { Q_ASSERT(idx_.model() == this); QModelIndex idx(firstColumn(idx_)); if (!m_partiallyExpanded.contains(idx)) { QModelIndex oldIndex = partiallyExpandedRow(); //Unexpand the previous partially expanded row if (!m_partiallyExpanded.isEmpty()) { ///@todo allow multiple partially expanded rows while (!m_partiallyExpanded.isEmpty()) m_partiallyExpanded.erase(m_partiallyExpanded.begin()); //partiallyUnExpand( m_partiallyExpanded.begin().key() ); } //Notify the underlying models that the item was selected, and eventually get back the text for the expanding widget. if (!idx.isValid()) { //All items have been unselected if (oldIndex.isValid()) { emit dataChanged(oldIndex, oldIndex); } } else { QVariant variant = data(idx, CodeCompletionModel::ItemSelected); if (!isExpanded(idx) && variant.type() == QVariant::String) { //Either expand upwards or downwards, choose in a way that //the visible fields of the new selected entry are not moved. if (oldIndex.isValid() && (oldIndex < idx || (!(oldIndex < idx) && oldIndex.parent() < idx.parent()))) { m_partiallyExpanded.insert(idx, ExpandUpwards); } else { m_partiallyExpanded.insert(idx, ExpandDownwards); } //Say that one row above until one row below has changed, so no items will need to be moved(the space that is taken from one item is given to the other) if (oldIndex.isValid() && oldIndex < idx) { emit dataChanged(oldIndex, idx); if (treeView()->verticalScrollMode() == QAbstractItemView::ScrollPerItem) { const QModelIndex viewIndex = mapFromSource(idx); //Qt fails to correctly scroll in ScrollPerItem mode, so the selected index is completely visible, //so we do the scrolling by hand. QRect selectedRect = treeView()->visualRect(viewIndex); QRect frameRect = treeView()->frameRect(); if (selectedRect.bottom() > frameRect.bottom()) { int diff = selectedRect.bottom() - frameRect.bottom(); //We need to scroll down QModelIndex newTopIndex = viewIndex; QModelIndex nextTopIndex = viewIndex; QRect nextRect = treeView()->visualRect(nextTopIndex); while (nextTopIndex.isValid() && nextRect.isValid() && nextRect.top() >= diff) { newTopIndex = nextTopIndex; nextTopIndex = treeView()->indexAbove(nextTopIndex); if (nextTopIndex.isValid()) { nextRect = treeView()->visualRect(nextTopIndex); } } treeView()->scrollTo(newTopIndex, QAbstractItemView::PositionAtTop); } } //This is needed to keep the item we are expanding completely visible. Qt does not scroll the view to keep the item visible. //But we must make sure that it isn't too expensive. //We need to make sure that scrolling is efficient, and the whole content is not repainted. //Since we are scrolling anyway, we can keep the next line visible, which might be a cool feature. //Since this also doesn't work smoothly, leave it for now //treeView()->scrollTo( nextLine, QAbstractItemView::EnsureVisible ); } else if (oldIndex.isValid() && idx < oldIndex) { emit dataChanged(idx, oldIndex); //For consistency with the down-scrolling, we keep one additional line visible above the current visible. //Since this also doesn't work smoothly, leave it for now /* QModelIndex prevLine = idx.sibling(idx.row()-1, idx.column()); if( prevLine.isValid() ) treeView()->scrollTo( prevLine );*/ } else { emit dataChanged(idx, idx); } } else if (oldIndex.isValid()) { //We are not partially expanding a new row, but we previously had a partially expanded row. So signalize that it has been unexpanded. emit dataChanged(oldIndex, oldIndex); } } } else { qCDebug(PLUGIN_QUICKOPEN) << "ExpandingWidgetModel::rowSelected: Row is already partially expanded"; } } QString ExpandingWidgetModel::partialExpandText(const QModelIndex& idx) const { Q_ASSERT(idx.model() == this); if (!idx.isValid()) { return QString(); } return data(firstColumn(idx), CodeCompletionModel::ItemSelected).toString(); } QRect ExpandingWidgetModel::partialExpandRect(const QModelIndex& idx_) const { Q_ASSERT(idx_.model() == this); QModelIndex idx(firstColumn(idx_)); if (!idx.isValid()) { return QRect(); } ExpansionType expansion = ExpandDownwards; if (m_partiallyExpanded.find(idx) != m_partiallyExpanded.constEnd()) { expansion = m_partiallyExpanded[idx]; } //Get the whole rectangle of the row: const QModelIndex viewIndex = mapFromSource(idx); QModelIndex rightMostIndex = viewIndex; QModelIndex tempIndex = viewIndex; while ((tempIndex = rightMostIndex.sibling(rightMostIndex.row(), rightMostIndex.column() + 1)).isValid()) rightMostIndex = tempIndex; QRect rect = treeView()->visualRect(viewIndex); QRect rightMostRect = treeView()->visualRect(rightMostIndex); rect.setLeft(rect.left() + 20); rect.setRight(rightMostRect.right() - 5); //These offsets must match exactly those used in ExpandingDelegate::sizeHint() int top = rect.top() + 5; int bottom = rightMostRect.bottom() - 5; if (expansion == ExpandDownwards) { top += basicRowHeight(viewIndex); } else { bottom -= basicRowHeight(viewIndex); } rect.setTop(top); rect.setBottom(bottom); return rect; } bool ExpandingWidgetModel::isExpandable(const QModelIndex& idx_) const { Q_ASSERT(idx_.model() == this); QModelIndex idx(firstColumn(idx_)); if (!m_expandState.contains(idx)) { m_expandState.insert(idx, NotExpandable); QVariant v = data(idx, CodeCompletionModel::IsExpandable); if (v.canConvert() && v.toBool()) { m_expandState[idx] = Expandable; } } return m_expandState[idx] != NotExpandable; } bool ExpandingWidgetModel::isExpanded(const QModelIndex& idx_) const { Q_ASSERT(idx_.model() == this); QModelIndex idx(firstColumn(idx_)); return m_expandState.contains(idx) && m_expandState[idx] == Expanded; } void ExpandingWidgetModel::setExpanded(const QModelIndex& idx_, bool expanded) { Q_ASSERT(idx_.model() == this); QModelIndex idx(firstColumn(idx_)); qCDebug(PLUGIN_QUICKOPEN) << "Setting expand-state of row " << idx.row() << " to " << expanded; if (!idx.isValid()) { return; } if (isExpandable(idx)) { if (!expanded && m_expandingWidgets.contains(idx) && m_expandingWidgets[idx]) { m_expandingWidgets[idx]->hide(); } m_expandState[idx] = expanded ? Expanded : Expandable; if (expanded) { partiallyUnExpand(idx); } if (expanded && !m_expandingWidgets.contains(idx)) { QVariant v = data(idx, CodeCompletionModel::ExpandingWidget); if (v.canConvert()) { m_expandingWidgets[idx] = v.value(); } else if (v.canConvert()) { //Create a html widget that shows the given string KTextEdit* edit = new KTextEdit(v.toString()); edit->setReadOnly(true); edit->resize(200, 50); //Make the widget small so it embeds nicely. m_expandingWidgets[idx] = edit; } else { m_expandingWidgets[idx] = nullptr; } } //Eventually partially expand the row if (!expanded && firstColumn(mapToSource(treeView()->currentIndex())) == idx && (isPartiallyExpanded(idx) == ExpandingWidgetModel::ExpansionType::NotExpanded)) { rowSelected(idx); //Partially expand the row. } emit dataChanged(idx, idx); if (treeView()) { treeView()->scrollTo(mapFromSource(idx)); } } } int ExpandingWidgetModel::basicRowHeight(const QModelIndex& idx_) const { Q_ASSERT(idx_.model() == treeView()->model()); QModelIndex idx(firstColumn(idx_)); ExpandingDelegate* delegate = dynamic_cast(treeView()->itemDelegate(idx)); if (!delegate || !idx.isValid()) { qCDebug(PLUGIN_QUICKOPEN) << "ExpandingWidgetModel::basicRowHeight: Could not get delegate"; return 15; } return delegate->basicSizeHint(idx).height(); } void ExpandingWidgetModel::placeExpandingWidget(const QModelIndex& idx_) { Q_ASSERT(idx_.model() == this); QModelIndex idx(firstColumn(idx_)); QWidget* w = nullptr; if (m_expandingWidgets.contains(idx)) { w = m_expandingWidgets[idx]; } if (w && isExpanded(idx)) { if (!idx.isValid()) { return; } const QModelIndex viewIndex = mapFromSource(idx_); QRect rect = treeView()->visualRect(viewIndex); if (!rect.isValid() || rect.bottom() < 0 || rect.top() >= treeView()->height()) { //The item is currently not visible w->hide(); return; } QModelIndex rightMostIndex = viewIndex; QModelIndex tempIndex = viewIndex; while ((tempIndex = rightMostIndex.sibling(rightMostIndex.row(), rightMostIndex.column() + 1)).isValid()) rightMostIndex = tempIndex; QRect rightMostRect = treeView()->visualRect(rightMostIndex); //Find out the basic height of the row rect.setLeft(rect.left() + 20); rect.setRight(rightMostRect.right() - 5); //These offsets must match exactly those used in KateCompletionDeleage::sizeHint() rect.setTop(rect.top() + basicRowHeight(viewIndex) + 5); rect.setHeight(w->height()); if (w->parent() != treeView()->viewport() || w->geometry() != rect || !w->isVisible()) { w->setParent(treeView()->viewport()); w->setGeometry(rect); w->show(); } } } void ExpandingWidgetModel::placeExpandingWidgets() { for (QMap >::const_iterator it = m_expandingWidgets.constBegin(); it != m_expandingWidgets.constEnd(); ++it) { placeExpandingWidget(it.key()); } } int ExpandingWidgetModel::expandingWidgetsHeight() const { int sum = 0; for (QMap >::const_iterator it = m_expandingWidgets.constBegin(); it != m_expandingWidgets.constEnd(); ++it) { if (isExpanded(it.key()) && (*it)) { sum += (*it)->height(); } } return sum; } QWidget* ExpandingWidgetModel::expandingWidget(const QModelIndex& idx_) const { QModelIndex idx(firstColumn(idx_)); if (m_expandingWidgets.contains(idx)) { return m_expandingWidgets[idx]; } else { return nullptr; } } void ExpandingWidgetModel::cacheIcons() const { if (m_expandedIcon.isNull()) { m_expandedIcon = QIcon::fromTheme(QStringLiteral("arrow-down")); } if (m_collapsedIcon.isNull()) { m_collapsedIcon = QIcon::fromTheme(QStringLiteral("arrow-right")); } } QList mergeCustomHighlighting(int leftSize, const QList& left, int rightSize, const QList& right) { QList ret = left; if (left.isEmpty()) { ret << QVariant(0); ret << QVariant(leftSize); ret << QTextFormat(QTextFormat::CharFormat); } if (right.isEmpty()) { ret << QVariant(leftSize); ret << QVariant(rightSize); ret << QTextFormat(QTextFormat::CharFormat); } else { QList::const_iterator it = right.constBegin(); while (it != right.constEnd()) { { QList::const_iterator testIt = it; for (int a = 0; a < 2; a++) { ++testIt; if (testIt == right.constEnd()) { qCWarning(PLUGIN_QUICKOPEN) << "Length of input is not multiple of 3"; break; } } } ret << QVariant((*it).toInt() + leftSize); ++it; ret << QVariant((*it).toInt()); ++it; ret << *it; if (!(*it).value().isValid()) { qCDebug(PLUGIN_QUICKOPEN) << "Text-format is invalid"; } ++it; } } return ret; } //It is assumed that between each two strings, one space is inserted QList mergeCustomHighlighting(QStringList strings, QList highlights, int grapBetweenStrings) { if (strings.isEmpty()) { qCWarning(PLUGIN_QUICKOPEN) << "List of strings is empty"; return QList(); } if (highlights.isEmpty()) { qCWarning(PLUGIN_QUICKOPEN) << "List of highlightings is empty"; return QList(); } if (strings.count() != highlights.count()) { qCWarning(PLUGIN_QUICKOPEN) << "Length of string-list is " << strings.count() << " while count of highlightings is " << highlights.count() << ", should be same"; return QList(); } //Merge them together QString totalString = strings[0]; QVariantList totalHighlighting = highlights[0]; strings.pop_front(); highlights.pop_front(); while (!strings.isEmpty()) { totalHighlighting = mergeCustomHighlighting(totalString.length(), totalHighlighting, strings[0].length(), highlights[0]); totalString += strings[0]; for (int a = 0; a < grapBetweenStrings; a++) { totalString += ' '; } strings.pop_front(); highlights.pop_front(); } //Combine the custom-highlightings return totalHighlighting; } diff --git a/plugins/quickopen/quickopenmodel.cpp b/plugins/quickopen/quickopenmodel.cpp index 9f49c2647..f3c1c085a 100644 --- a/plugins/quickopen/quickopenmodel.cpp +++ b/plugins/quickopen/quickopenmodel.cpp @@ -1,492 +1,491 @@ /* This file is part of the KDE libraries Copyright (C) 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 "quickopenmodel.h" #include "debug.h" #include #include -#include #include #include #include "expandingtree/expandingtree.h" #include "projectfilequickopen.h" #include "duchainitemquickopen.h" #define QUICKOPEN_USE_ITEM_CACHING using namespace KDevelop; QuickOpenModel::QuickOpenModel(QWidget* parent) : ExpandingWidgetModel(parent) , m_treeView(nullptr) , m_expandingWidgetHeightIncrease(0) , m_resetBehindRow(0) { m_resetTimer = new QTimer(this); m_resetTimer->setSingleShot(true); connect(m_resetTimer, &QTimer::timeout, this, &QuickOpenModel::resetTimer); } void QuickOpenModel::setExpandingWidgetHeightIncrease(int pixels) { m_expandingWidgetHeightIncrease = pixels; } QStringList QuickOpenModel::allScopes() const { QStringList scopes; foreach (const ProviderEntry& provider, m_providers) { foreach (const QString& scope, provider.scopes) { if (!scopes.contains(scope)) { scopes << scope; } } } return scopes; } QStringList QuickOpenModel::allTypes() const { QSet types; foreach (const ProviderEntry& provider, m_providers) { types += provider.types; } return types.toList(); } void QuickOpenModel::registerProvider(const QStringList& scopes, const QStringList& types, KDevelop::QuickOpenDataProviderBase* provider) { ProviderEntry e; e.scopes = QSet::fromList(scopes); e.types = QSet::fromList(types); e.provider = provider; m_providers << e; //.insert( types, e ); connect(provider, &QuickOpenDataProviderBase::destroyed, this, &QuickOpenModel::destroyed); restart(true); } bool QuickOpenModel::removeProvider(KDevelop::QuickOpenDataProviderBase* provider) { bool ret = false; for (ProviderList::iterator it = m_providers.begin(); it != m_providers.end(); ++it) { if ((*it).provider == provider) { m_providers.erase(it); disconnect(provider, &QuickOpenDataProviderBase::destroyed, this, &QuickOpenModel::destroyed); ret = true; break; } } restart(true); return ret; } void QuickOpenModel::enableProviders(const QStringList& _items, const QStringList& _scopes) { QSet items = QSet::fromList(_items); QSet scopes = QSet::fromList(_scopes); if (m_enabledItems == items && m_enabledScopes == scopes && !items.isEmpty() && !scopes.isEmpty()) { return; } m_enabledItems = items; m_enabledScopes = scopes; qCDebug(PLUGIN_QUICKOPEN) << "params " << items << " " << scopes; //We use 2 iterations here: In the first iteration, all providers that implement QuickOpenFileSetInterface are initialized, then the other ones. //The reason is that the second group can refer to the first one. for (ProviderList::iterator it = m_providers.begin(); it != m_providers.end(); ++it) { if (!dynamic_cast((*it).provider)) { continue; } qCDebug(PLUGIN_QUICKOPEN) << "comparing" << (*it).scopes << (*it).types; if ((scopes.isEmpty() || !(scopes & (*it).scopes).isEmpty()) && (!(items & (*it).types).isEmpty() || items.isEmpty())) { qCDebug(PLUGIN_QUICKOPEN) << "enabling " << (*it).types << " " << (*it).scopes; (*it).enabled = true; (*it).provider->enableData(_items, _scopes); } else { qCDebug(PLUGIN_QUICKOPEN) << "disabling " << (*it).types << " " << (*it).scopes; (*it).enabled = false; if ((scopes.isEmpty() || !(scopes & (*it).scopes).isEmpty())) { (*it).provider->enableData(_items, _scopes); //The provider may still provide files } } } for (ProviderList::iterator it = m_providers.begin(); it != m_providers.end(); ++it) { if (dynamic_cast((*it).provider)) { continue; } qCDebug(PLUGIN_QUICKOPEN) << "comparing" << (*it).scopes << (*it).types; if ((scopes.isEmpty() || !(scopes & (*it).scopes).isEmpty()) && (!(items & (*it).types).isEmpty() || items.isEmpty())) { qCDebug(PLUGIN_QUICKOPEN) << "enabling " << (*it).types << " " << (*it).scopes; (*it).enabled = true; (*it).provider->enableData(_items, _scopes); } else { qCDebug(PLUGIN_QUICKOPEN) << "disabling " << (*it).types << " " << (*it).scopes; (*it).enabled = false; } } restart(true); } void QuickOpenModel::textChanged(const QString& str) { if (m_filterText == str) { return; } beginResetModel(); m_filterText = str; foreach (const ProviderEntry& provider, m_providers) { if (provider.enabled) { provider.provider->setFilterText(str); } } m_cachedData.clear(); clearExpanding(); //Get the 50 first items, so the data-providers notice changes without ui-glitches due to resetting for (int a = 0; a < 50 && a < rowCount(QModelIndex()); ++a) { getItem(a, true); } endResetModel(); } void QuickOpenModel::restart(bool keepFilterText) { // make sure we do not restart recursivly which could lead to // recursive loading of provider plugins e.g. (happened for the cpp plugin) QMetaObject::invokeMethod(this, "restart_internal", Qt::QueuedConnection, Q_ARG(bool, keepFilterText)); } void QuickOpenModel::restart_internal(bool keepFilterText) { if (!keepFilterText) { m_filterText.clear(); } bool anyEnabled = false; foreach (const ProviderEntry& e, m_providers) { anyEnabled |= e.enabled; } if (!anyEnabled) { return; } foreach (const ProviderEntry& provider, m_providers) { if (!dynamic_cast(provider.provider)) { continue; } ///Always reset providers that implement QuickOpenFileSetInterface and have some matchign scopes, because they may be needed by other providers. if (m_enabledScopes.isEmpty() || !(m_enabledScopes & provider.scopes).isEmpty()) { provider.provider->reset(); } } foreach (const ProviderEntry& provider, m_providers) { if (dynamic_cast(provider.provider)) { continue; } if (provider.enabled && provider.provider) { provider.provider->reset(); } } if (keepFilterText) { textChanged(m_filterText); } else { beginResetModel(); m_cachedData.clear(); clearExpanding(); endResetModel(); } } void QuickOpenModel::destroyed(QObject* obj) { removeProvider(static_cast(obj)); } QModelIndex QuickOpenModel::index(int row, int column, const QModelIndex& /*parent*/) const { if (column >= columnCount() || row >= rowCount(QModelIndex())) { return QModelIndex(); } if (row < 0 || column < 0) { return QModelIndex(); } return createIndex(row, column); } QModelIndex QuickOpenModel::parent(const QModelIndex&) const { return QModelIndex(); } int QuickOpenModel::rowCount(const QModelIndex& i) const { if (i.isValid()) { return 0; } int count = 0; foreach (const ProviderEntry& provider, m_providers) { if (provider.enabled) { count += provider.provider->itemCount(); } } return count; } int QuickOpenModel::unfilteredRowCount() const { int count = 0; foreach (const ProviderEntry& provider, m_providers) { if (provider.enabled) { count += provider.provider->unfilteredItemCount(); } } return count; } int QuickOpenModel::columnCount() const { return 2; } int QuickOpenModel::columnCount(const QModelIndex& index) const { if (index.parent().isValid()) { return 0; } else { return columnCount(); } } QVariant QuickOpenModel::data(const QModelIndex& index, int role) const { QuickOpenDataPointer d = getItem(index.row()); if (!d) { return QVariant(); } switch (role) { case KTextEditor::CodeCompletionModel::ItemSelected: { QString desc = d->htmlDescription(); if (desc.isEmpty()) { return QVariant(); } else { return desc; } } case KTextEditor::CodeCompletionModel::IsExpandable: return d->isExpandable(); case KTextEditor::CodeCompletionModel::ExpandingWidget: { QVariant v; QWidget* w = d->expandingWidget(); if (w && m_expandingWidgetHeightIncrease) { w->resize(w->width(), w->height() + m_expandingWidgetHeightIncrease); } v.setValue(w); return v; } case ExpandingTree::ProjectPathRole: // TODO: put this into the QuickOpenDataBase API // we cannot do this in 5.0, cannot change ABI if (auto projectFile = dynamic_cast(d.constData())) { return QVariant::fromValue(projectFile->projectPath()); } else if (auto duchainItem = dynamic_cast(d.constData())) { return QVariant::fromValue(duchainItem->projectPath()); } } if (index.column() == 1) { //This column contains the actual content switch (role) { case Qt::DecorationRole: return d->icon(); case Qt::DisplayRole: return d->text(); case KTextEditor::CodeCompletionModel::HighlightingMethod: return KTextEditor::CodeCompletionModel::CustomHighlighting; case KTextEditor::CodeCompletionModel::CustomHighlight: return d->highlighting(); } } else if (index.column() == 0) { //This column only contains the expanded/not expanded icon switch (role) { case Qt::DecorationRole: { if (isExpandable(index)) { //Show the expanded/unexpanded handles cacheIcons(); if (isExpanded(index)) { return m_expandedIcon; } else { return m_collapsedIcon; } } } } } return ExpandingWidgetModel::data(index, role); } void QuickOpenModel::resetTimer() { int currentRow = treeView() ? mapToSource(treeView()->currentIndex()).row() : -1; beginResetModel(); //Remove all cached data behind row m_resetBehindRow for (DataList::iterator it = m_cachedData.begin(); it != m_cachedData.end(); ) { if (it.key() > m_resetBehindRow) { it = m_cachedData.erase(it); } else { ++it; } } endResetModel(); if (currentRow != -1) { treeView()->setCurrentIndex(mapFromSource(index(currentRow, 0, QModelIndex()))); //Preserve the current index } m_resetBehindRow = 0; } QuickOpenDataPointer QuickOpenModel::getItem(int row, bool noReset) const { ///@todo mix all the models alphabetically here. For now, they are simply ordered. ///@todo Deal with unexpected item-counts, like for example in the case of overloaded function-declarations #ifdef QUICKOPEN_USE_ITEM_CACHING if (m_cachedData.contains(row)) { return m_cachedData[row]; } #endif int rowOffset = 0; Q_ASSERT(row < rowCount(QModelIndex())); foreach (const ProviderEntry& provider, m_providers) { if (!provider.enabled) { continue; } uint itemCount = provider.provider->itemCount(); if (( uint )row < itemCount) { QuickOpenDataPointer item = provider.provider->data(row); if (!noReset && provider.provider->itemCount() != itemCount) { qCDebug(PLUGIN_QUICKOPEN) << "item-count in provider has changed, resetting model"; m_resetTimer->start(0); m_resetBehindRow = rowOffset + row; //Don't reset everything, only everything behind this position } #ifdef QUICKOPEN_USE_ITEM_CACHING m_cachedData[row + rowOffset] = item; #endif return item; } else { row -= provider.provider->itemCount(); rowOffset += provider.provider->itemCount(); } } // qWarning() << "No item for row " << row; return QuickOpenDataPointer(); } QSet QuickOpenModel::fileSet() const { QSet merged; foreach (const ProviderEntry& provider, m_providers) { if (m_enabledScopes.isEmpty() || !(m_enabledScopes & provider.scopes).isEmpty()) { if (QuickOpenFileSetInterface* iface = dynamic_cast(provider.provider)) { QSet ifiles = iface->files(); //qCDebug(PLUGIN_QUICKOPEN) << "got file-list with" << ifiles.count() << "entries from data-provider" << typeid(*iface).name(); merged += ifiles; } } } return merged; } QTreeView* QuickOpenModel::treeView() const { return m_treeView; } bool QuickOpenModel::indexIsItem(const QModelIndex& index) const { Q_ASSERT(index.model() == this); Q_UNUSED(index); return true; } void QuickOpenModel::setTreeView(QTreeView* view) { m_treeView = view; } int QuickOpenModel::contextMatchQuality(const QModelIndex& /*index*/) const { return -1; } bool QuickOpenModel::execute(const QModelIndex& index, QString& filterText) { qCDebug(PLUGIN_QUICKOPEN) << "executing model"; if (!index.isValid()) { qCWarning(PLUGIN_QUICKOPEN) << "Invalid index executed"; return false; } QuickOpenDataPointer item = getItem(index.row()); if (item) { return item->execute(filterText); } else { qCWarning(PLUGIN_QUICKOPEN) << "Got no item for row " << index.row() << " "; } return false; } diff --git a/plugins/quickopen/quickopenplugin.cpp b/plugins/quickopen/quickopenplugin.cpp index d86989a5e..8fe52fca8 100644 --- a/plugins/quickopen/quickopenplugin.cpp +++ b/plugins/quickopen/quickopenplugin.cpp @@ -1,1169 +1,1167 @@ /* * This file is part of KDevelop * * Copyright 2007 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "quickopenplugin.h" #include "quickopenwidget.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 #include "quickopenmodel.h" #include "projectfilequickopen.h" #include "projectitemquickopen.h" #include "declarationlistquickopen.h" #include "documentationquickopenprovider.h" #include "actionsquickopenprovider.h" #include "debug.h" #include #include #include #include #include -Q_LOGGING_CATEGORY(PLUGIN_QUICKOPEN, "kdevplatform.plugins.quickopen") - using namespace KDevelop; const bool noHtmlDestriptionInOutline = true; class QuickOpenWidgetCreator { public: virtual ~QuickOpenWidgetCreator() { } virtual QuickOpenWidget* createWidget() = 0; virtual QString objectNameForLine() = 0; virtual void widgetShown() { } }; class StandardQuickOpenWidgetCreator : public QuickOpenWidgetCreator { public: StandardQuickOpenWidgetCreator(const QStringList& items, const QStringList& scopes) : m_items(items) , m_scopes(scopes) { } QString objectNameForLine() override { return QStringLiteral("Quickopen"); } void setItems(const QStringList& scopes, const QStringList& items) { m_scopes = scopes; m_items = items; } QuickOpenWidget* createWidget() override { QStringList useItems = m_items; if (useItems.isEmpty()) { useItems = QuickOpenPlugin::self()->lastUsedItems; } QStringList useScopes = m_scopes; if (useScopes.isEmpty()) { useScopes = QuickOpenPlugin::self()->lastUsedScopes; } return new QuickOpenWidget(i18n("Quick Open"), QuickOpenPlugin::self()->m_model, QuickOpenPlugin::self()->lastUsedItems, useScopes, false, true); } QStringList m_items; QStringList m_scopes; }; class OutlineFilter : public DUChainUtils::DUChainItemFilter { public: enum OutlineMode { Functions, FunctionsAndClasses }; explicit OutlineFilter(QList& _items, OutlineMode _mode = FunctionsAndClasses) : items(_items) , mode(_mode) { } bool accept(Declaration* decl) override { if (decl->range().isEmpty()) { return false; } bool collectable = mode == Functions ? decl->isFunctionDeclaration() : (decl->isFunctionDeclaration() || (decl->internalContext() && decl->internalContext()->type() == DUContext::Class)); if (collectable) { DUChainItem item; item.m_item = IndexedDeclaration(decl); item.m_text = decl->toString(); items << item; return true; } else { return false; } } bool accept(DUContext* ctx) override { if (ctx->type() == DUContext::Class || ctx->type() == DUContext::Namespace || ctx->type() == DUContext::Global || ctx->type() == DUContext::Other || ctx->type() == DUContext::Helper) { return true; } else { return false; } } QList& items; OutlineMode mode; }; K_PLUGIN_FACTORY_WITH_JSON(KDevQuickOpenFactory, "kdevquickopen.json", registerPlugin(); ) Declaration * cursorDeclaration() { KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView(); if (!view) { return nullptr; } KDevelop::DUChainReadLocker lock(DUChain::lock()); return DUChainUtils::declarationForDefinition(DUChainUtils::itemUnderCursor(view->document()->url(), KTextEditor::Cursor(view->cursorPosition())).declaration); } ///The first definition that belongs to a context that surrounds the current cursor Declaration* cursorContextDeclaration() { KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView(); if (!view) { return nullptr; } KDevelop::DUChainReadLocker lock(DUChain::lock()); TopDUContext* ctx = DUChainUtils::standardContextForUrl(view->document()->url()); if (!ctx) { return nullptr; } KTextEditor::Cursor cursor(view->cursorPosition()); DUContext* subCtx = ctx->findContext(ctx->transformToLocalRevision(cursor)); while (subCtx && !subCtx->owner()) subCtx = subCtx->parentContext(); Declaration* definition = nullptr; if (!subCtx || !subCtx->owner()) { definition = DUChainUtils::declarationInLine(cursor, ctx); } else { definition = subCtx->owner(); } if (!definition) { return nullptr; } return definition; } //Returns only the name, no template-parameters or scope QString cursorItemText() { KDevelop::DUChainReadLocker lock(DUChain::lock()); Declaration* decl = cursorDeclaration(); if (!decl) { return QString(); } IDocument* doc = ICore::self()->documentController()->activeDocument(); if (!doc) { return QString(); } TopDUContext* context = DUChainUtils::standardContextForUrl(doc->url()); if (!context) { qCDebug(PLUGIN_QUICKOPEN) << "Got no standard context"; return QString(); } AbstractType::Ptr t = decl->abstractType(); IdentifiedType* idType = dynamic_cast(t.data()); if (idType && idType->declaration(context)) { decl = idType->declaration(context); } if (!decl->qualifiedIdentifier().isEmpty()) { return decl->qualifiedIdentifier().last().identifier().str(); } return QString(); } QuickOpenLineEdit* QuickOpenPlugin::createQuickOpenLineWidget() { return new QuickOpenLineEdit(new StandardQuickOpenWidgetCreator(QStringList(), QStringList())); } QuickOpenLineEdit* QuickOpenPlugin::quickOpenLine(QString name) { QList lines = ICore::self()->uiController()->activeMainWindow()->findChildren(name); foreach (QuickOpenLineEdit* line, lines) { if (line->isVisible()) { return line; } } return nullptr; } static QuickOpenPlugin* staticQuickOpenPlugin = nullptr; QuickOpenPlugin* QuickOpenPlugin::self() { return staticQuickOpenPlugin; } void QuickOpenPlugin::createActionsForMainWindow(Sublime::MainWindow* /*window*/, QString& xmlFile, KActionCollection& actions) { xmlFile = QStringLiteral("kdevquickopen.rc"); QAction* quickOpen = actions.addAction(QStringLiteral("quick_open")); quickOpen->setText(i18n("&Quick Open")); quickOpen->setIcon(QIcon::fromTheme(QStringLiteral("quickopen"))); actions.setDefaultShortcut(quickOpen, Qt::CTRL | Qt::ALT | Qt::Key_Q); connect(quickOpen, &QAction::triggered, this, &QuickOpenPlugin::quickOpen); QAction* quickOpenFile = actions.addAction(QStringLiteral("quick_open_file")); quickOpenFile->setText(i18n("Quick Open &File")); quickOpenFile->setIcon(QIcon::fromTheme(QStringLiteral("quickopen-file"))); actions.setDefaultShortcut(quickOpenFile, Qt::CTRL | Qt::ALT | Qt::Key_O); connect(quickOpenFile, &QAction::triggered, this, &QuickOpenPlugin::quickOpenFile); QAction* quickOpenClass = actions.addAction(QStringLiteral("quick_open_class")); quickOpenClass->setText(i18n("Quick Open &Class")); quickOpenClass->setIcon(QIcon::fromTheme(QStringLiteral("quickopen-class"))); actions.setDefaultShortcut(quickOpenClass, Qt::CTRL | Qt::ALT | Qt::Key_C); connect(quickOpenClass, &QAction::triggered, this, &QuickOpenPlugin::quickOpenClass); QAction* quickOpenFunction = actions.addAction(QStringLiteral("quick_open_function")); quickOpenFunction->setText(i18n("Quick Open &Function")); quickOpenFunction->setIcon(QIcon::fromTheme(QStringLiteral("quickopen-function"))); actions.setDefaultShortcut(quickOpenFunction, Qt::CTRL | Qt::ALT | Qt::Key_M); connect(quickOpenFunction, &QAction::triggered, this, &QuickOpenPlugin::quickOpenFunction); QAction* quickOpenAlreadyOpen = actions.addAction(QStringLiteral("quick_open_already_open")); quickOpenAlreadyOpen->setText(i18n("Quick Open &Already Open File")); quickOpenAlreadyOpen->setIcon(QIcon::fromTheme(QStringLiteral("quickopen-file"))); connect(quickOpenAlreadyOpen, &QAction::triggered, this, &QuickOpenPlugin::quickOpenOpenFile); QAction* quickOpenDocumentation = actions.addAction(QStringLiteral("quick_open_documentation")); quickOpenDocumentation->setText(i18n("Quick Open &Documentation")); quickOpenDocumentation->setIcon(QIcon::fromTheme(QStringLiteral("quickopen-documentation"))); actions.setDefaultShortcut(quickOpenDocumentation, Qt::CTRL | Qt::ALT | Qt::Key_D); connect(quickOpenDocumentation, &QAction::triggered, this, &QuickOpenPlugin::quickOpenDocumentation); QAction* quickOpenActions = actions.addAction(QStringLiteral("quick_open_actions")); quickOpenActions->setText(i18n("Quick Open &Actions")); actions.setDefaultShortcut(quickOpenActions, Qt::CTRL | Qt::ALT | Qt::Key_A); connect(quickOpenActions, &QAction::triggered, this, &QuickOpenPlugin::quickOpenActions); m_quickOpenDeclaration = actions.addAction(QStringLiteral("quick_open_jump_declaration")); m_quickOpenDeclaration->setText(i18n("Jump to Declaration")); m_quickOpenDeclaration->setIcon(QIcon::fromTheme(QStringLiteral("go-jump-declaration"))); actions.setDefaultShortcut(m_quickOpenDeclaration, Qt::CTRL | Qt::Key_Period); connect(m_quickOpenDeclaration, &QAction::triggered, this, &QuickOpenPlugin::quickOpenDeclaration, Qt::QueuedConnection); m_quickOpenDefinition = actions.addAction(QStringLiteral("quick_open_jump_definition")); m_quickOpenDefinition->setText(i18n("Jump to Definition")); m_quickOpenDefinition->setIcon(QIcon::fromTheme(QStringLiteral("go-jump-definition"))); actions.setDefaultShortcut(m_quickOpenDefinition, Qt::CTRL | Qt::Key_Comma); connect(m_quickOpenDefinition, &QAction::triggered, this, &QuickOpenPlugin::quickOpenDefinition, Qt::QueuedConnection); QWidgetAction* quickOpenLine = new QWidgetAction(this); quickOpenLine->setText(i18n("Embedded Quick Open")); // actions.setDefaultShortcut( quickOpenLine, Qt::CTRL | Qt::ALT | Qt::Key_E ); // connect(quickOpenLine, SIGNAL(triggered(bool)), this, SLOT(quickOpenLine(bool))); quickOpenLine->setDefaultWidget(createQuickOpenLineWidget()); actions.addAction(QStringLiteral("quick_open_line"), quickOpenLine); QAction* quickOpenNextFunction = actions.addAction(QStringLiteral("quick_open_next_function")); quickOpenNextFunction->setText(i18n("Next Function")); actions.setDefaultShortcut(quickOpenNextFunction, Qt::CTRL | Qt::ALT | Qt::Key_PageDown); connect(quickOpenNextFunction, &QAction::triggered, this, &QuickOpenPlugin::nextFunction); QAction* quickOpenPrevFunction = actions.addAction(QStringLiteral("quick_open_prev_function")); quickOpenPrevFunction->setText(i18n("Previous Function")); actions.setDefaultShortcut(quickOpenPrevFunction, Qt::CTRL | Qt::ALT | Qt::Key_PageUp); connect(quickOpenPrevFunction, &QAction::triggered, this, &QuickOpenPlugin::previousFunction); QAction* quickOpenNavigateFunctions = actions.addAction(QStringLiteral("quick_open_outline")); quickOpenNavigateFunctions->setText(i18n("Outline")); actions.setDefaultShortcut(quickOpenNavigateFunctions, Qt::CTRL | Qt::ALT | Qt::Key_N); connect(quickOpenNavigateFunctions, &QAction::triggered, this, &QuickOpenPlugin::quickOpenNavigateFunctions); } QuickOpenPlugin::QuickOpenPlugin(QObject* parent, const QVariantList&) : KDevelop::IPlugin(QStringLiteral("kdevquickopen"), parent) { staticQuickOpenPlugin = this; m_model = new QuickOpenModel(nullptr); KConfigGroup quickopengrp = KSharedConfig::openConfig()->group("QuickOpen"); lastUsedScopes = quickopengrp.readEntry("SelectedScopes", QStringList() << i18n("Project") << i18n("Includes") << i18n("Includers") << i18n("Currently Open")); lastUsedItems = quickopengrp.readEntry("SelectedItems", QStringList()); { m_openFilesData = new OpenFilesDataProvider(); QStringList scopes, items; scopes << i18n("Currently Open"); items << i18n("Files"); m_model->registerProvider(scopes, items, m_openFilesData); } { m_projectFileData = new ProjectFileDataProvider(); QStringList scopes, items; scopes << i18n("Project"); items << i18n("Files"); m_model->registerProvider(scopes, items, m_projectFileData); } { m_projectItemData = new ProjectItemDataProvider(this); QStringList scopes, items; scopes << i18n("Project"); items << ProjectItemDataProvider::supportedItemTypes(); m_model->registerProvider(scopes, items, m_projectItemData); } { m_documentationItemData = new DocumentationQuickOpenProvider; QStringList scopes, items; scopes << i18n("Includes"); items << i18n("Documentation"); m_model->registerProvider(scopes, items, m_documentationItemData); } { m_actionsItemData = new ActionsQuickOpenProvider; QStringList scopes, items; scopes << i18n("Includes"); items << i18n("Actions"); m_model->registerProvider(scopes, items, m_actionsItemData); } } QuickOpenPlugin::~QuickOpenPlugin() { freeModel(); delete m_model; delete m_projectFileData; delete m_projectItemData; delete m_openFilesData; delete m_documentationItemData; delete m_actionsItemData; } void QuickOpenPlugin::unload() { } ContextMenuExtension QuickOpenPlugin::contextMenuExtension(Context* context) { KDevelop::ContextMenuExtension menuExt = KDevelop::IPlugin::contextMenuExtension(context); KDevelop::DeclarationContext* codeContext = dynamic_cast(context); if (!codeContext) { return menuExt; } DUChainReadLocker readLock; Declaration* decl(codeContext->declaration().data()); if (decl) { const bool isDef = FunctionDefinition::definition(decl); if (codeContext->use().isValid() || !isDef) { menuExt.addAction(KDevelop::ContextMenuExtension::NavigationGroup, m_quickOpenDeclaration); } if (isDef) { menuExt.addAction(KDevelop::ContextMenuExtension::NavigationGroup, m_quickOpenDefinition); } } return menuExt; } void QuickOpenPlugin::showQuickOpen(const QStringList& items) { if (!freeModel()) { return; } QStringList initialItems = items; QStringList useScopes = lastUsedScopes; if (!useScopes.contains(i18n("Currently Open"))) { useScopes << i18n("Currently Open"); } showQuickOpenWidget(initialItems, useScopes, false); } void QuickOpenPlugin::showQuickOpen(ModelTypes modes) { if (!freeModel()) { return; } QStringList initialItems; if (modes & Files || modes & OpenFiles) { initialItems << i18n("Files"); } if (modes & Functions) { initialItems << i18n("Functions"); } if (modes & Classes) { initialItems << i18n("Classes"); } QStringList useScopes; if (modes != OpenFiles) { useScopes = lastUsedScopes; } if ((modes & OpenFiles) && !useScopes.contains(i18n("Currently Open"))) { useScopes << i18n("Currently Open"); } bool preselectText = (!(modes & Files) || modes == QuickOpenPlugin::All); showQuickOpenWidget(initialItems, useScopes, preselectText); } void QuickOpenPlugin::showQuickOpenWidget(const QStringList& items, const QStringList& scopes, bool preselectText) { QuickOpenWidgetDialog* dialog = new QuickOpenWidgetDialog(i18n("Quick Open"), m_model, items, scopes); m_currentWidgetHandler = dialog; if (preselectText) { KDevelop::IDocument* currentDoc = core()->documentController()->activeDocument(); if (currentDoc && currentDoc->isTextDocument()) { QString preselected = currentDoc->textSelection().isEmpty() ? currentDoc->textWord() : currentDoc->textDocument()->text(currentDoc->textSelection()); dialog->widget()->setPreselectedText(preselected); } } connect(dialog->widget(), &QuickOpenWidget::scopesChanged, this, &QuickOpenPlugin::storeScopes); //Not connecting itemsChanged to storeItems, as showQuickOpen doesn't use lastUsedItems and so shouldn't store item changes //connect( dialog->widget(), SIGNAL(itemsChanged(QStringList)), this, SLOT(storeItems(QStringList)) ); dialog->widget()->ui.itemsButton->setEnabled(false); if (quickOpenLine()) { quickOpenLine()->showWithWidget(dialog->widget()); dialog->deleteLater(); } else { dialog->run(); } } void QuickOpenPlugin::storeScopes(const QStringList& scopes) { lastUsedScopes = scopes; KConfigGroup grp = KSharedConfig::openConfig()->group("QuickOpen"); grp.writeEntry("SelectedScopes", scopes); } void QuickOpenPlugin::storeItems(const QStringList& items) { lastUsedItems = items; KConfigGroup grp = KSharedConfig::openConfig()->group("QuickOpen"); grp.writeEntry("SelectedItems", items); } void QuickOpenPlugin::quickOpen() { if (quickOpenLine()) { //Same as clicking on Quick Open quickOpenLine()->setFocus(); } else { showQuickOpen(All); } } void QuickOpenPlugin::quickOpenFile() { showQuickOpen(( ModelTypes )(Files | OpenFiles)); } void QuickOpenPlugin::quickOpenFunction() { showQuickOpen(Functions); } void QuickOpenPlugin::quickOpenClass() { showQuickOpen(Classes); } void QuickOpenPlugin::quickOpenOpenFile() { showQuickOpen(OpenFiles); } void QuickOpenPlugin::quickOpenDocumentation() { showQuickOpenWidget(QStringList(i18n("Documentation")), QStringList(i18n("Includes")), true); } void QuickOpenPlugin::quickOpenActions() { showQuickOpenWidget(QStringList(i18n("Actions")), QStringList(i18n("Includes")), true); } QSet QuickOpenPlugin::fileSet() const { return m_model->fileSet(); } void QuickOpenPlugin::registerProvider(const QStringList& scope, const QStringList& type, KDevelop::QuickOpenDataProviderBase* provider) { m_model->registerProvider(scope, type, provider); } bool QuickOpenPlugin::removeProvider(KDevelop::QuickOpenDataProviderBase* provider) { m_model->removeProvider(provider); return true; } void QuickOpenPlugin::quickOpenDeclaration() { if (jumpToSpecialObject()) { return; } KDevelop::DUChainReadLocker lock(DUChain::lock()); Declaration* decl = cursorDeclaration(); if (!decl) { qCDebug(PLUGIN_QUICKOPEN) << "Found no declaration for cursor, cannot jump"; return; } decl->activateSpecialization(); IndexedString u = decl->url(); KTextEditor::Cursor c = decl->rangeInCurrentRevision().start(); if (u.isEmpty()) { qCDebug(PLUGIN_QUICKOPEN) << "Got empty url for declaration" << decl->toString(); return; } lock.unlock(); core()->documentController()->openDocument(u.toUrl(), c); } QWidget* QuickOpenPlugin::specialObjectNavigationWidget() const { KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView(); if (!view) { return nullptr; } QUrl url = ICore::self()->documentController()->activeDocument()->url(); const auto languages = ICore::self()->languageController()->languagesForUrl(url); foreach (const auto language, languages) { QWidget* w = language->specialLanguageObjectNavigationWidget(url, KTextEditor::Cursor(view->cursorPosition())); if (w) { return w; } } return nullptr; } QPair QuickOpenPlugin::specialObjectJumpPosition() const { KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView(); if (!view) { return qMakePair(QUrl(), KTextEditor::Cursor()); } QUrl url = ICore::self()->documentController()->activeDocument()->url(); const auto languages = ICore::self()->languageController()->languagesForUrl(url); foreach (const auto language, languages) { QPair pos = language->specialLanguageObjectJumpCursor(url, KTextEditor::Cursor(view->cursorPosition())); if (pos.second.isValid()) { return pos; } } return qMakePair(QUrl(), KTextEditor::Cursor::invalid()); } bool QuickOpenPlugin::jumpToSpecialObject() { QPair pos = specialObjectJumpPosition(); if (pos.second.isValid()) { if (pos.first.isEmpty()) { qCDebug(PLUGIN_QUICKOPEN) << "Got empty url for special language object"; return false; } ICore::self()->documentController()->openDocument(pos.first, pos.second); return true; } return false; } void QuickOpenPlugin::quickOpenDefinition() { if (jumpToSpecialObject()) { return; } KDevelop::DUChainReadLocker lock(DUChain::lock()); Declaration* decl = cursorDeclaration(); if (!decl) { qCDebug(PLUGIN_QUICKOPEN) << "Found no declaration for cursor, cannot jump"; return; } IndexedString u = decl->url(); KTextEditor::Cursor c = decl->rangeInCurrentRevision().start(); if (FunctionDefinition* def = FunctionDefinition::definition(decl)) { def->activateSpecialization(); u = def->url(); c = def->rangeInCurrentRevision().start(); } else { qCDebug(PLUGIN_QUICKOPEN) << "Found no definition for declaration"; decl->activateSpecialization(); } if (u.isEmpty()) { qCDebug(PLUGIN_QUICKOPEN) << "Got empty url for declaration" << decl->toString(); return; } lock.unlock(); core()->documentController()->openDocument(u.toUrl(), c); } bool QuickOpenPlugin::freeModel() { if (m_currentWidgetHandler) { delete m_currentWidgetHandler; } m_currentWidgetHandler = nullptr; return true; } void QuickOpenPlugin::nextFunction() { jumpToNearestFunction(NextFunction); } void QuickOpenPlugin::previousFunction() { jumpToNearestFunction(PreviousFunction); } void QuickOpenPlugin::jumpToNearestFunction(QuickOpenPlugin::FunctionJumpDirection direction) { IDocument* doc = ICore::self()->documentController()->activeDocument(); if (!doc) { qCDebug(PLUGIN_QUICKOPEN) << "No active document"; return; } KDevelop::DUChainReadLocker lock(DUChain::lock()); TopDUContext* context = DUChainUtils::standardContextForUrl(doc->url()); if (!context) { qCDebug(PLUGIN_QUICKOPEN) << "Got no standard context"; return; } QList items; OutlineFilter filter(items, OutlineFilter::Functions); DUChainUtils::collectItems(context, filter); CursorInRevision cursor = context->transformToLocalRevision(KTextEditor::Cursor(doc->cursorPosition())); if (!cursor.isValid()) { return; } Declaration* nearestDeclBefore = nullptr; int distanceBefore = INT_MIN; Declaration* nearestDeclAfter = nullptr; int distanceAfter = INT_MAX; for (int i = 0; i < items.count(); ++i) { Declaration* decl = items[i].m_item.data(); int distance = decl->range().start.line - cursor.line; if (distance < 0 && distance >= distanceBefore) { distanceBefore = distance; nearestDeclBefore = decl; } else if (distance > 0 && distance <= distanceAfter) { distanceAfter = distance; nearestDeclAfter = decl; } } CursorInRevision c = CursorInRevision::invalid(); if (direction == QuickOpenPlugin::NextFunction && nearestDeclAfter) { c = nearestDeclAfter->range().start; } else if (direction == QuickOpenPlugin::PreviousFunction && nearestDeclBefore) { c = nearestDeclBefore->range().start; } KTextEditor::Cursor textCursor = KTextEditor::Cursor::invalid(); if (c.isValid()) { textCursor = context->transformFromLocalRevision(c); } lock.unlock(); if (textCursor.isValid()) { core()->documentController()->openDocument(doc->url(), textCursor); } else { qCDebug(PLUGIN_QUICKOPEN) << "No declaration to jump to"; } } struct CreateOutlineDialog { CreateOutlineDialog() : dialog(nullptr) , cursorDecl(nullptr) , model(nullptr) { } void start() { if (!QuickOpenPlugin::self()->freeModel()) { return; } IDocument* doc = ICore::self()->documentController()->activeDocument(); if (!doc) { qCDebug(PLUGIN_QUICKOPEN) << "No active document"; return; } KDevelop::DUChainReadLocker lock(DUChain::lock()); TopDUContext* context = DUChainUtils::standardContextForUrl(doc->url()); if (!context) { qCDebug(PLUGIN_QUICKOPEN) << "Got no standard context"; return; } model = new QuickOpenModel(nullptr); OutlineFilter filter(items); DUChainUtils::collectItems(context, filter); if (noHtmlDestriptionInOutline) { for (int a = 0; a < items.size(); ++a) { items[a].m_noHtmlDestription = true; } } cursorDecl = cursorContextDeclaration(); model->registerProvider(QStringList(), QStringList(), new DeclarationListDataProvider(QuickOpenPlugin::self(), items, true)); dialog = new QuickOpenWidgetDialog(i18n("Outline"), model, QStringList(), QStringList(), true); dialog->widget()->setSortingEnabled(true); model->setParent(dialog->widget()); } void finish() { //Select the declaration that contains the cursor if (cursorDecl && dialog) { int num = 0; foreach (const DUChainItem& item, items) { if (item.m_item.data() == cursorDecl) { QModelIndex index(model->index(num, 0, QModelIndex())); // Need to invoke the scrolling later. If we did it now, then it wouldn't have any effect, // apparently because the widget internals aren't initialized yet properly (although we've // already called 'widget->show()'. auto list = dialog->widget()->ui.list; QMetaObject::invokeMethod(list, "setCurrentIndex", Qt::QueuedConnection, Q_ARG(QModelIndex, index)); QMetaObject::invokeMethod(list, "scrollTo", Qt::QueuedConnection, Q_ARG(QModelIndex, index), Q_ARG(QAbstractItemView::ScrollHint, QAbstractItemView::PositionAtCenter)); } ++num; } } } QPointer dialog; Declaration* cursorDecl; QList items; QuickOpenModel* model; }; class OutlineQuickopenWidgetCreator : public QuickOpenWidgetCreator { public: OutlineQuickopenWidgetCreator(QStringList /*scopes*/, QStringList /*items*/) : m_creator(nullptr) { } ~OutlineQuickopenWidgetCreator() override { delete m_creator; } QuickOpenWidget* createWidget() override { delete m_creator; m_creator = new CreateOutlineDialog; m_creator->start(); if (!m_creator->dialog) { return nullptr; } m_creator->dialog->deleteLater(); return m_creator->dialog->widget(); } void widgetShown() override { if (m_creator) { m_creator->finish(); delete m_creator; m_creator = nullptr; } } QString objectNameForLine() override { return QStringLiteral("Outline"); } CreateOutlineDialog* m_creator; }; void QuickOpenPlugin::quickOpenNavigateFunctions() { CreateOutlineDialog create; create.start(); if (!create.dialog) { return; } m_currentWidgetHandler = create.dialog; QuickOpenLineEdit* line = quickOpenLine(QStringLiteral("Outline")); if (!line) { line = quickOpenLine(); } if (line) { line->showWithWidget(create.dialog->widget()); create.dialog->deleteLater(); } else { create.dialog->run(); } create.finish(); } QuickOpenLineEdit::QuickOpenLineEdit(QuickOpenWidgetCreator* creator) : m_widget(nullptr) , m_forceUpdate(false) , m_widgetCreator(creator) { setMinimumWidth(200); setMaximumWidth(400); deactivate(); setDefaultText(i18n("Quick Open...")); setToolTip(i18n("Search for files, classes, functions and more," " allowing you to quickly navigate in your source code.")); setObjectName(m_widgetCreator->objectNameForLine()); setFocusPolicy(Qt::ClickFocus); } QuickOpenLineEdit::~QuickOpenLineEdit() { delete m_widget; delete m_widgetCreator; } bool QuickOpenLineEdit::insideThis(QObject* object) { while (object) { qCDebug(PLUGIN_QUICKOPEN) << object; if (object == this || object == m_widget) { return true; } object = object->parent(); } return false; } void QuickOpenLineEdit::widgetDestroyed(QObject* obj) { Q_UNUSED(obj); // need to use a queued connection here, because this function is called in ~QWidget! // => QuickOpenWidget instance is half-destructed => connections are not yet cleared // => clear() will trigger signals which will operate on the invalid QuickOpenWidget // So, just wait until properly destructed QMetaObject::invokeMethod(this, "deactivate", Qt::QueuedConnection); } void QuickOpenLineEdit::showWithWidget(QuickOpenWidget* widget) { connect(widget, &QuickOpenWidget::destroyed, this, &QuickOpenLineEdit::widgetDestroyed); qCDebug(PLUGIN_QUICKOPEN) << "storing widget" << widget; deactivate(); if (m_widget) { qCDebug(PLUGIN_QUICKOPEN) << "deleting" << m_widget; delete m_widget; } m_widget = widget; m_forceUpdate = true; setFocus(); } void QuickOpenLineEdit::focusInEvent(QFocusEvent* ev) { QLineEdit::focusInEvent(ev); // delete m_widget; qCDebug(PLUGIN_QUICKOPEN) << "got focus"; qCDebug(PLUGIN_QUICKOPEN) << "old widget" << m_widget << "force update:" << m_forceUpdate; if (m_widget && !m_forceUpdate) { return; } if (!m_forceUpdate && !QuickOpenPlugin::self()->freeModel()) { deactivate(); return; } m_forceUpdate = false; if (!m_widget) { m_widget = m_widgetCreator->createWidget(); if (!m_widget) { deactivate(); return; } } activate(); m_widget->showStandardButtons(false); m_widget->showSearchField(false); m_widget->setParent(nullptr, Qt::ToolTip); m_widget->setFocusPolicy(Qt::NoFocus); m_widget->setAlternativeSearchField(this); QuickOpenPlugin::self()->m_currentWidgetHandler = m_widget; connect(m_widget.data(), &QuickOpenWidget::ready, this, &QuickOpenLineEdit::deactivate); connect(m_widget.data(), &QuickOpenWidget::scopesChanged, QuickOpenPlugin::self(), &QuickOpenPlugin::storeScopes); connect(m_widget.data(), &QuickOpenWidget::itemsChanged, QuickOpenPlugin::self(), &QuickOpenPlugin::storeItems); Q_ASSERT(m_widget->ui.searchLine == this); m_widget->prepareShow(); QRect widgetGeometry = QRect(mapToGlobal(QPoint(0, height())), mapToGlobal(QPoint(width(), height() + 400))); widgetGeometry.setWidth(700); ///@todo Waste less space QRect screenGeom = QApplication::desktop()->screenGeometry(this); if (widgetGeometry.right() > screenGeom.right()) { widgetGeometry.moveRight(screenGeom.right()); } if (widgetGeometry.bottom() > screenGeom.bottom()) { widgetGeometry.moveBottom(mapToGlobal(QPoint(0, 0)).y()); } m_widget->setGeometry(widgetGeometry); m_widget->show(); m_widgetCreator->widgetShown(); } void QuickOpenLineEdit::hideEvent(QHideEvent* ev) { QWidget::hideEvent(ev); if (m_widget) { QMetaObject::invokeMethod(this, "checkFocus", Qt::QueuedConnection); } // deactivate(); } bool QuickOpenLineEdit::eventFilter(QObject* obj, QEvent* e) { if (!m_widget) { return false; } switch (e->type()) { case QEvent::KeyPress: case QEvent::ShortcutOverride: if (static_cast(e)->key() == Qt::Key_Escape) { deactivate(); e->accept(); return true; } break; case QEvent::WindowActivate: case QEvent::WindowDeactivate: QMetaObject::invokeMethod(this, "checkFocus", Qt::QueuedConnection); break; // handle bug 260657 - "Outline menu doesn't follow main window on its move" case QEvent::Move: { if (QWidget* widget = qobject_cast(obj)) { // close the outline menu in case a parent widget moved if (widget->isAncestorOf(this)) { qCDebug(PLUGIN_QUICKOPEN) << "closing because of parent widget move"; deactivate(); } break; } } case QEvent::FocusIn: if (dynamic_cast(obj)) { QFocusEvent* focusEvent = dynamic_cast(e); Q_ASSERT(focusEvent); //Eat the focus event, keep the focus qCDebug(PLUGIN_QUICKOPEN) << "focus change" << "inside this: " << insideThis(obj) << "this" << this << "obj" << obj; if (obj == this) { return false; } qCDebug(PLUGIN_QUICKOPEN) << "reason" << focusEvent->reason(); if (focusEvent->reason() != Qt::MouseFocusReason && focusEvent->reason() != Qt::ActiveWindowFocusReason) { QMetaObject::invokeMethod(this, "checkFocus", Qt::QueuedConnection); return false; } if (!insideThis(obj)) { deactivate(); } } else if (obj != this) { QMetaObject::invokeMethod(this, "checkFocus", Qt::QueuedConnection); } break; default: break; } return false; } void QuickOpenLineEdit::activate() { qCDebug(PLUGIN_QUICKOPEN) << "activating"; setText(QString()); setStyleSheet(QString()); qApp->installEventFilter(this); } void QuickOpenLineEdit::deactivate() { qCDebug(PLUGIN_QUICKOPEN) << "deactivating"; clear(); if (m_widget || hasFocus()) { QMetaObject::invokeMethod(this, "checkFocus", Qt::QueuedConnection); } if (m_widget) { m_widget->deleteLater(); } m_widget = nullptr; qApp->removeEventFilter(this); } void QuickOpenLineEdit::checkFocus() { qCDebug(PLUGIN_QUICKOPEN) << "checking focus" << m_widget; if (m_widget) { QWidget* focusWidget = QApplication::focusWidget(); bool focusWidgetInsideThis = focusWidget ? insideThis(focusWidget) : false; if (QApplication::focusWindow() && isVisible() && !isHidden() && (!focusWidget || (focusWidget && focusWidgetInsideThis))) { qCDebug(PLUGIN_QUICKOPEN) << "setting focus to line edit"; activateWindow(); setFocus(); } else { qCDebug(PLUGIN_QUICKOPEN) << "deactivating because check failed, focusWidget" << focusWidget << "insideThis" << focusWidgetInsideThis; deactivate(); } } else { if (ICore::self()->documentController()->activeDocument()) { ICore::self()->documentController()->activateDocument(ICore::self()->documentController()->activeDocument()); } //Make sure the focus is somewehre else, even if there is no active document setEnabled(false); setEnabled(true); } } IQuickOpenLine* QuickOpenPlugin::createQuickOpenLine(const QStringList& scopes, const QStringList& type, IQuickOpen::QuickOpenType kind) { if (kind == Outline) { return new QuickOpenLineEdit(new OutlineQuickopenWidgetCreator(scopes, type)); } else { return new QuickOpenLineEdit(new StandardQuickOpenWidgetCreator(scopes, type)); } } #include "quickopenplugin.moc" diff --git a/plugins/standardoutputview/CMakeLists.txt b/plugins/standardoutputview/CMakeLists.txt index 485788c60..813c4db1e 100644 --- a/plugins/standardoutputview/CMakeLists.txt +++ b/plugins/standardoutputview/CMakeLists.txt @@ -1,22 +1,29 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kdevstandardoutputview\") -add_subdirectory(tests) ########### next target ############### +ecm_qt_declare_logging_category(standardoutputview_LOG_PART_SRCS + HEADER debug.h + IDENTIFIER PLUGIN_STANDARDOUTPUTVIEW + CATEGORY_NAME "kdevplatform.plugins.standardoutputview" +) + set(standardoutputview_LIB_SRCS standardoutputview.cpp outputwidget.cpp toolviewdata.cpp standardoutputviewmetadata.cpp + ${standardoutputview_LOG_PART_SRCS} ) qt5_add_resources(standardoutputview_LIB_SRCS kdevstandardoutputview.qrc) kdevplatform_add_plugin(kdevstandardoutputview JSON kdevstandardoutputview.json SOURCES ${standardoutputview_LIB_SRCS}) target_link_libraries(kdevstandardoutputview KDev::Interfaces KDev::Sublime KDev::Util KDev::OutputView ) +add_subdirectory(tests) diff --git a/plugins/standardoutputview/debug.h b/plugins/standardoutputview/debug.h deleted file mode 100644 index 57e62d9d0..000000000 --- a/plugins/standardoutputview/debug.h +++ /dev/null @@ -1,26 +0,0 @@ -/* - * This file is part of KDevelop - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#ifndef KDEVPLATFORM_STANDARDOUTPUTVIEW_DEBUG_H -#define KDEVPLATFORM_STANDARDOUTPUTVIEW_DEBUG_H - -#include -Q_DECLARE_LOGGING_CATEGORY(PLUGIN_STANDARDOUTPUTVIEW) - -#endif diff --git a/plugins/standardoutputview/outputwidget.cpp b/plugins/standardoutputview/outputwidget.cpp index b3b40840e..e027507c2 100644 --- a/plugins/standardoutputview/outputwidget.cpp +++ b/plugins/standardoutputview/outputwidget.cpp @@ -1,680 +1,679 @@ /* This file is part of KDevelop * * Copyright 2007 Andreas Pakulat * Copyright 2007 Dukju Ahn * * 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 "outputwidget.h" #include "standardoutputview.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "outputmodel.h" #include "toolviewdata.h" -#include "debug.h" +#include -Q_LOGGING_CATEGORY(PLUGIN_STANDARDOUTPUTVIEW, "kdevplatform.plugins.standardoutputview") Q_DECLARE_METATYPE(QTreeView*) OutputWidget::OutputWidget(QWidget* parent, const ToolViewData* tvdata) : QWidget( parent ) , m_tabwidget(nullptr) , m_stackwidget(nullptr) , data(tvdata) , m_closeButton(nullptr) , m_closeOthersAction(nullptr) , m_nextAction(nullptr) , m_previousAction(nullptr) , m_activateOnSelect(nullptr) , m_focusOnSelect(nullptr) , m_filterInput(nullptr) , m_filterAction(nullptr) { setWindowTitle(i18n("Output View")); setWindowIcon(tvdata->icon); QVBoxLayout* layout = new QVBoxLayout(this); layout->setMargin(0); if( data->type & KDevelop::IOutputView::MultipleView ) { m_tabwidget = new QTabWidget(this); layout->addWidget( m_tabwidget ); m_closeButton = new QToolButton( this ); connect( m_closeButton, &QToolButton::clicked, this, &OutputWidget::closeActiveView ); m_closeButton->setIcon( QIcon::fromTheme( QStringLiteral( "tab-close") ) ); m_closeButton->setToolTip( i18n( "Close the currently active output view") ); m_closeButton->setAutoRaise(true); m_closeOthersAction = new QAction( this ); connect(m_closeOthersAction, &QAction::triggered, this, &OutputWidget::closeOtherViews); m_closeOthersAction->setIcon(QIcon::fromTheme(QStringLiteral("tab-close-other"))); m_closeOthersAction->setToolTip( i18n( "Close all other output views" ) ); m_closeOthersAction->setText( m_closeOthersAction->toolTip() ); addAction(m_closeOthersAction); m_tabwidget->setCornerWidget(m_closeButton, Qt::TopRightCorner); } else if ( data->type == KDevelop::IOutputView::HistoryView ) { m_stackwidget = new QStackedWidget( this ); layout->addWidget( m_stackwidget ); m_previousAction = new QAction( QIcon::fromTheme( QStringLiteral( "arrow-left" ) ), i18n("Previous Output"), this ); connect(m_previousAction, &QAction::triggered, this, &OutputWidget::previousOutput); addAction(m_previousAction); m_nextAction = new QAction( QIcon::fromTheme( QStringLiteral( "arrow-right" ) ), i18n("Next Output"), this ); connect(m_nextAction, &QAction::triggered, this, &OutputWidget::nextOutput); addAction(m_nextAction); } addAction(dynamic_cast(data->plugin->actionCollection()->action(QStringLiteral("prev_error")))); addAction(dynamic_cast(data->plugin->actionCollection()->action(QStringLiteral("next_error")))); m_activateOnSelect = new KToggleAction( QIcon(), i18n("Select activated Item"), this ); m_activateOnSelect->setChecked( true ); m_focusOnSelect = new KToggleAction( QIcon(), i18n("Focus when selecting Item"), this ); m_focusOnSelect->setChecked( false ); if( data->option & KDevelop::IOutputView::ShowItemsButton ) { addAction(m_activateOnSelect); addAction(m_focusOnSelect); } QAction *separator = new QAction(this); separator->setSeparator(true); addAction(separator); QAction* action; action = new QAction(QIcon::fromTheme(QStringLiteral("go-first")), i18n("First Item"), this); connect(action, &QAction::triggered, this, &OutputWidget::selectFirstItem); addAction(action); action = new QAction(QIcon::fromTheme(QStringLiteral("go-previous")), i18n("Previous Item"), this); connect(action, &QAction::triggered, this, &OutputWidget::selectPreviousItem); addAction(action); action = new QAction(QIcon::fromTheme(QStringLiteral("go-next")), i18n("Next Item"), this); connect(action, &QAction::triggered, this, &OutputWidget::selectNextItem); addAction(action); action = new QAction(QIcon::fromTheme(QStringLiteral("go-last")), i18n("Last Item"), this); connect(action, &QAction::triggered, this, &OutputWidget::selectLastItem); addAction(action); QAction* selectAllAction = KStandardAction::selectAll(this, SLOT(selectAll()), this); selectAllAction->setShortcut(QKeySequence()); //FIXME: why does CTRL-A conflict with Katepart (while CTRL-Cbelow doesn't) ? selectAllAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); addAction(selectAllAction); QAction* copyAction = KStandardAction::copy(this, SLOT(copySelection()), this); copyAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); addAction(copyAction); QAction *clearAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-clear-list")), i18n("Clear"), this); connect(clearAction, &QAction::triggered, this, &OutputWidget::clearModel); addAction(clearAction); if( data->option & KDevelop::IOutputView::AddFilterAction ) { QAction *separator = new QAction(this); separator->setSeparator(true); addAction(separator); m_filterInput = new QLineEdit(); m_filterInput->setMaximumWidth(150); m_filterInput->setMinimumWidth(100); m_filterInput->setPlaceholderText(i18n("Search...")); m_filterInput->setClearButtonEnabled(true); m_filterInput->setToolTip(i18n("Enter a wild card string to filter the output view")); m_filterAction = new QWidgetAction(this); m_filterAction->setDefaultWidget(m_filterInput); addAction(m_filterAction); connect(m_filterInput, &QLineEdit::textEdited, this, &OutputWidget::outputFilter ); if( data->type & KDevelop::IOutputView::MultipleView ) { connect(m_tabwidget, &QTabWidget::currentChanged, this, &OutputWidget::updateFilter); } else if ( data->type == KDevelop::IOutputView::HistoryView ) { connect(m_stackwidget, &QStackedWidget::currentChanged, this, &OutputWidget::updateFilter); } } addActions(data->actionList); connect( data, &ToolViewData::outputAdded, this, &OutputWidget::addOutput ); connect( this, &OutputWidget::outputRemoved, data->plugin, &StandardOutputView::outputRemoved ); foreach( int id, data->outputdata.keys() ) { changeModel( id ); changeDelegate( id ); } enableActions(); } void OutputWidget::clearModel() { auto view = qobject_cast(currentWidget()); if( !view || !view->isVisible()) return; KDevelop::OutputModel *outputModel = nullptr; if (auto proxy = qobject_cast(view->model())) { outputModel = qobject_cast(proxy->sourceModel()); } else { outputModel = qobject_cast(view->model()); } outputModel->clear(); } void OutputWidget::addOutput( int id ) { QTreeView* listview = createListView(id); setCurrentWidget( listview ); connect( data->outputdata.value(id), &OutputData::modelChanged, this, &OutputWidget::changeModel); connect( data->outputdata.value(id), &OutputData::delegateChanged, this, &OutputWidget::changeDelegate); enableActions(); } void OutputWidget::setCurrentWidget( QTreeView* view ) { if( data->type & KDevelop::IOutputView::MultipleView ) { m_tabwidget->setCurrentWidget( view ); } else if( data->type & KDevelop::IOutputView::HistoryView ) { m_stackwidget->setCurrentWidget( view ); } } void OutputWidget::changeDelegate( int id ) { if( data->outputdata.contains( id ) && m_views.contains( id ) ) { m_views.value(id)->setItemDelegate(data->outputdata.value(id)->delegate); } else { addOutput(id); } } void OutputWidget::changeModel( int id ) { if( data->outputdata.contains( id ) && m_views.contains( id ) ) { OutputData* od = data->outputdata.value(id); m_views.value( id )->setModel(od->model); } else { addOutput( id ); } } void OutputWidget::removeOutput( int id ) { if( data->outputdata.contains( id ) && m_views.contains( id ) ) { QTreeView* view = m_views.value(id); if( data->type & KDevelop::IOutputView::MultipleView || data->type & KDevelop::IOutputView::HistoryView ) { if( data->type & KDevelop::IOutputView::MultipleView ) { int idx = m_tabwidget->indexOf( view ); if( idx != -1 ) { m_tabwidget->removeTab( idx ); if( m_proxyModels.contains( idx ) ) { delete m_proxyModels.take( idx ); m_filters.remove( idx ); } } } else { int idx = m_stackwidget->indexOf( view ); if( idx != -1 && m_proxyModels.contains( idx ) ) { delete m_proxyModels.take( idx ); m_filters.remove( idx ); } m_stackwidget->removeWidget( view ); } delete view; } else { m_views.value( id )->setModel( nullptr ); m_views.value( id )->setItemDelegate( nullptr ); if( m_proxyModels.contains( 0 ) ) { delete m_proxyModels.take( 0 ); m_filters.remove( 0 ); } } m_views.remove( id ); emit outputRemoved( data->toolViewId, id ); } enableActions(); } void OutputWidget::closeActiveView() { QWidget* widget = m_tabwidget->currentWidget(); if( !widget ) return; foreach( int id, m_views.keys() ) { if( m_views.value(id) == widget ) { OutputData* od = data->outputdata.value(id); if( od->behaviour & KDevelop::IOutputView::AllowUserClose ) { data->plugin->removeOutput( id ); } } } enableActions(); } void OutputWidget::closeOtherViews() { QWidget* widget = m_tabwidget->currentWidget(); if (!widget) return; foreach (int id, m_views.keys()) { if (m_views.value(id) == widget) { continue; // leave the active view open } OutputData* od = data->outputdata.value(id); if (od->behaviour & KDevelop::IOutputView::AllowUserClose) { data->plugin->removeOutput( id ); } } enableActions(); } QWidget* OutputWidget::currentWidget() const { QWidget* widget; if( data->type & KDevelop::IOutputView::MultipleView ) { widget = m_tabwidget->currentWidget(); } else if( data->type & KDevelop::IOutputView::HistoryView ) { widget = m_stackwidget->currentWidget(); } else { widget = m_views.begin().value(); } return widget; } KDevelop::IOutputViewModel *OutputWidget::outputViewModel() const { auto view = qobject_cast(currentWidget()); if( !view || !view->isVisible()) return nullptr; QAbstractItemModel *absmodel = view->model(); KDevelop::IOutputViewModel *iface = dynamic_cast(absmodel); if ( ! iface ) { // try if it's a proxy model? if ( QAbstractProxyModel* proxy = qobject_cast(absmodel) ) { iface = dynamic_cast(proxy->sourceModel()); } } return iface; } void OutputWidget::eventuallyDoFocus() { QWidget* widget = currentWidget(); if( m_focusOnSelect->isChecked() && !widget->hasFocus() ) { widget->setFocus( Qt::OtherFocusReason ); } } QAbstractItemView *OutputWidget::outputView() const { return qobject_cast(currentWidget()); } void OutputWidget::activateIndex(const QModelIndex &index, QAbstractItemView *view, KDevelop::IOutputViewModel *iface) { if( ! index.isValid() ) return; int tabIndex = currentOutputIndex(); QModelIndex sourceIndex = index; QModelIndex viewIndex = index; if( QAbstractProxyModel* proxy = m_proxyModels.value(tabIndex) ) { if ( index.model() == proxy ) { // index is from the proxy, map it to the source sourceIndex = proxy->mapToSource(index); } else if (proxy == view->model()) { // index is from the source, map it to the proxy viewIndex = proxy->mapFromSource(index); } } view->setCurrentIndex( viewIndex ); view->scrollTo( viewIndex ); if( m_activateOnSelect->isChecked() ) { iface->activate( sourceIndex ); } } void OutputWidget::selectFirstItem() { selectItem(First); } void OutputWidget::selectNextItem() { selectItem(Next); } void OutputWidget::selectPreviousItem() { selectItem(Previous); } void OutputWidget::selectLastItem() { selectItem(Last); } void OutputWidget::selectItem(SelectionMode selectionMode) { auto view = outputView(); auto iface = outputViewModel(); if ( ! view || ! iface ) return; eventuallyDoFocus(); auto index = view->currentIndex(); if (QAbstractProxyModel* proxy = m_proxyModels.value(currentOutputIndex())) { if ( index.model() == proxy ) { // index is from the proxy, map it to the source index = proxy->mapToSource(index); } } QModelIndex newIndex; switch (selectionMode) { case First: newIndex = iface->firstHighlightIndex(); break; case Next: newIndex = iface->nextHighlightIndex( index ); break; case Previous: newIndex = iface->previousHighlightIndex( index ); break; case Last: newIndex = iface->lastHighlightIndex(); break; } qCDebug(PLUGIN_STANDARDOUTPUTVIEW) << "old:" << index << "- new:" << newIndex; activateIndex(newIndex, view, iface); } void OutputWidget::activate(const QModelIndex& index) { auto iface = outputViewModel(); auto view = outputView(); if( ! view || ! iface ) return; activateIndex(index, view, iface); } QTreeView* OutputWidget::createListView(int id) { auto createHelper = [&]() -> QTreeView* { KDevelop::FocusedTreeView* listview = new KDevelop::FocusedTreeView(this); listview->setEditTriggers( QAbstractItemView::NoEditTriggers ); listview->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); //Always enable the scrollbar, so it doesn't flash around listview->setHeaderHidden(true); listview->setUniformRowHeights(true); listview->setRootIsDecorated(false); listview->setSelectionMode( QAbstractItemView::ContiguousSelection ); if (data->outputdata.value(id)->behaviour & KDevelop::IOutputView::AutoScroll) { listview->setAutoScrollAtEnd(true); } connect(listview, &QTreeView::activated, this, &OutputWidget::activate); connect(listview, &QTreeView::clicked, this, &OutputWidget::activate); return listview; }; QTreeView* listview = nullptr; if( !m_views.contains(id) ) { bool newView = true; if( data->type & KDevelop::IOutputView::MultipleView || data->type & KDevelop::IOutputView::HistoryView ) { qCDebug(PLUGIN_STANDARDOUTPUTVIEW) << "creating listview"; listview = createHelper(); if( data->type & KDevelop::IOutputView::MultipleView ) { m_tabwidget->addTab( listview, data->outputdata.value(id)->title ); } else { m_stackwidget->addWidget( listview ); m_stackwidget->setCurrentWidget( listview ); } } else { if( m_views.isEmpty() ) { listview = createHelper(); layout()->addWidget( listview ); } else { listview = m_views.begin().value(); newView = false; } } m_views[id] = listview; changeModel( id ); changeDelegate( id ); if (newView) listview->scrollToBottom(); } else { listview = m_views.value(id); } enableActions(); return listview; } void OutputWidget::raiseOutput(int id) { if( m_views.contains(id) ) { if( data->type & KDevelop::IOutputView::MultipleView ) { int idx = m_tabwidget->indexOf( m_views.value(id) ); if( idx >= 0 ) { m_tabwidget->setCurrentIndex( idx ); } } else if( data->type & KDevelop::IOutputView::HistoryView ) { int idx = m_stackwidget->indexOf( m_views.value(id) ); if( idx >= 0 ) { m_stackwidget->setCurrentIndex( idx ); } } } enableActions(); } void OutputWidget::nextOutput() { if( m_stackwidget && m_stackwidget->currentIndex() < m_stackwidget->count()-1 ) { m_stackwidget->setCurrentIndex( m_stackwidget->currentIndex()+1 ); } enableActions(); } void OutputWidget::previousOutput() { if( m_stackwidget && m_stackwidget->currentIndex() > 0 ) { m_stackwidget->setCurrentIndex( m_stackwidget->currentIndex()-1 ); } enableActions(); } void OutputWidget::enableActions() { if( data->type == KDevelop::IOutputView::HistoryView ) { Q_ASSERT(m_stackwidget); Q_ASSERT(m_nextAction); Q_ASSERT(m_previousAction); m_previousAction->setEnabled( ( m_stackwidget->currentIndex() > 0 ) ); m_nextAction->setEnabled( ( m_stackwidget->currentIndex() < m_stackwidget->count() - 1 ) ); } } void OutputWidget::scrollToIndex( const QModelIndex& idx ) { QWidget* w = currentWidget(); if( !w ) return; QAbstractItemView *view = dynamic_cast(w); view->scrollTo( idx ); } void OutputWidget::copySelection() { QWidget* widget = currentWidget(); if( !widget ) return; QAbstractItemView *view = dynamic_cast(widget); if( !view ) return; QClipboard *cb = QApplication::clipboard(); QModelIndexList indexes = view->selectionModel()->selectedRows(); QString content; Q_FOREACH( const QModelIndex& index, indexes) { content += index.data().toString() + '\n'; } cb->setText(content); } void OutputWidget::selectAll() { if (QAbstractItemView *view = qobject_cast(currentWidget())) view->selectAll(); } int OutputWidget::currentOutputIndex() { int index = 0; if( data->type & KDevelop::IOutputView::MultipleView ) { index = m_tabwidget->currentIndex(); } else if( data->type & KDevelop::IOutputView::HistoryView ) { index = m_stackwidget->currentIndex(); } return index; } void OutputWidget::outputFilter(const QString& filter) { QAbstractItemView *view = qobject_cast(currentWidget()); if( !view ) return; int index = currentOutputIndex(); auto proxyModel = qobject_cast(view->model()); if( !proxyModel ) { proxyModel = new QSortFilterProxyModel(view->model()); proxyModel->setDynamicSortFilter(true); proxyModel->setSourceModel(view->model()); m_proxyModels.insert(index, proxyModel); view->setModel(proxyModel); } QRegExp regExp(filter, Qt::CaseInsensitive); proxyModel->setFilterRegExp(regExp); m_filters[index] = filter; } void OutputWidget::updateFilter(int index) { if(m_filters.contains(index)) { m_filterInput->setText(m_filters[index]); } else { m_filterInput->clear(); } } void OutputWidget::setTitle(int outputId, const QString& title) { if( data->type & KDevelop::IOutputView::MultipleView ) { m_tabwidget->setTabText(outputId - 1, title); } } diff --git a/plugins/standardoutputview/tests/CMakeLists.txt b/plugins/standardoutputview/tests/CMakeLists.txt index f96299c62..2a00380c1 100644 --- a/plugins/standardoutputview/tests/CMakeLists.txt +++ b/plugins/standardoutputview/tests/CMakeLists.txt @@ -1,10 +1,15 @@ +include_directories( + .. + ${CMAKE_CURRENT_BINARY_DIR}/.. +) set(test_standardOutputView_SRCS test_standardoutputview.cpp ../outputwidget.cpp ../toolviewdata.cpp ../standardoutputview.cpp + ${standardoutputview_LOG_PART_SRCS} ) ecm_add_test(${test_standardOutputView_SRCS} TEST_NAME test_standardoutputview LINK_LIBRARIES Qt5::Test KDev::Tests) diff --git a/plugins/subversion/CMakeLists.txt b/plugins/subversion/CMakeLists.txt index 0336574f6..57b8df13f 100644 --- a/plugins/subversion/CMakeLists.txt +++ b/plugins/subversion/CMakeLists.txt @@ -1,98 +1,104 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kdevsubversion\") # silence the deprecation warnings # if someone wants to fix the code, I'd welcome it # but for now, we won't spend time on it... add_definitions(-DSVN_DEPRECATED=) string(REPLACE "-Wdocumentation" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") add_subdirectory(tests) add_subdirectory(icons) include_directories( SYSTEM ${SUBVERSION_INCLUDE_DIRS} ) kde_enable_exceptions() ########### next target ############### set(kdevsubversion_WRAPPER_SRCS kdevsvncpp/apr.cpp kdevsvncpp/client_annotate.cpp kdevsvncpp/client_cat.cpp kdevsvncpp/client.cpp kdevsvncpp/client_diff.cpp kdevsvncpp/client_ls.cpp kdevsvncpp/client_modify.cpp kdevsvncpp/client_property.cpp kdevsvncpp/client_status.cpp kdevsvncpp/context.cpp kdevsvncpp/datetime.cpp kdevsvncpp/dirent.cpp kdevsvncpp/entry.cpp kdevsvncpp/exception.cpp kdevsvncpp/info.cpp kdevsvncpp/log_entry.cpp kdevsvncpp/path.cpp kdevsvncpp/pool.cpp kdevsvncpp/property.cpp kdevsvncpp/revision.cpp kdevsvncpp/status.cpp kdevsvncpp/status_selection.cpp kdevsvncpp/targets.cpp kdevsvncpp/url.cpp kdevsvncpp/wc.cpp ) set(kdevsubversion_JOB_SRCS svninternaljobbase.cpp svnjobbase.cpp svncommitjob.cpp svnstatusjob.cpp svnaddjob.cpp svnupdatejob.cpp svnrevertjob.cpp svnremovejob.cpp svninfojob.cpp svndiffjob.cpp svncatjob.cpp svncopyjob.cpp svnmovejob.cpp svnlogjob.cpp svnblamejob.cpp svnimportjob.cpp svncheckoutjob.cpp ) set(kdevsubversion_PART_SRCS kdevsvnplugin.cpp svnssldialog.cpp svnimportmetadatawidget.cpp svncheckoutmetadatawidget.cpp svnclient.cpp svnlocationwidget.cpp ) +ecm_qt_declare_logging_category(kdevsubversion_PART_SRCS + HEADER debug.h + IDENTIFIER PLUGIN_SVN + CATEGORY_NAME "kdevplatform.plugins.svn" +) + set(kdevsubversion_PART_UI ui/ssltrustdialog.ui ui/importmetadatawidget.ui ui/checkoutmetadatawidget.ui ) ki18n_wrap_ui(kdevsubversion_PART_SRCS ${kdevsubversion_PART_UI}) kdevplatform_add_plugin(kdevsubversion JSON kdevsubversion.json SOURCES ${kdevsubversion_PART_SRCS} ${kdevsubversion_JOB_SRCS} ${kdevsubversion_WRAPPER_SRCS}) target_link_libraries(kdevsubversion ${SUBVERSION_LIBRARIES} KF5::KIOCore KF5::TextEditor KF5::ThreadWeaver KF5::Parts KDev::Interfaces KDev::Vcs KDev::OutputView KDev::Project ) diff --git a/plugins/subversion/debug.h b/plugins/subversion/debug.h deleted file mode 100644 index 7f35106f2..000000000 --- a/plugins/subversion/debug.h +++ /dev/null @@ -1,26 +0,0 @@ -/* - * This file is part of KDevelop - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#ifndef KDEVPLATFORM_SUBVERSION_DEBUG_H -#define KDEVPLATFORM_SUBVERSION_DEBUG_H - -#include -Q_DECLARE_LOGGING_CATEGORY(PLUGIN_SVN) - -#endif diff --git a/plugins/subversion/kdevsvnplugin.cpp b/plugins/subversion/kdevsvnplugin.cpp index c40a0c952..e2016856d 100644 --- a/plugins/subversion/kdevsvnplugin.cpp +++ b/plugins/subversion/kdevsvnplugin.cpp @@ -1,536 +1,535 @@ /*************************************************************************** * Copyright 2007 Dukju Ahn * * Copyright 2008 Andreas Pakulat * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "kdevsvnplugin.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 #include #include "kdevsvncpp/apr.hpp" #include "svncommitjob.h" #include "svnstatusjob.h" #include "svnaddjob.h" #include "svnrevertjob.h" #include "svnremovejob.h" #include "svnupdatejob.h" #include "svninfojob.h" #include "svndiffjob.h" #include "svncopyjob.h" #include "svnmovejob.h" #include "svnlogjob.h" #include "svnblamejob.h" #include "svnimportjob.h" #include "svncheckoutjob.h" #include "svnimportmetadatawidget.h" #include "svncheckoutmetadatawidget.h" #include #include #include "svnlocationwidget.h" #include "debug.h" -Q_LOGGING_CATEGORY(PLUGIN_SVN, "kdevplatform.plugins.svn") K_PLUGIN_FACTORY_WITH_JSON(KDevSvnFactory, "kdevsubversion.json", registerPlugin();) KDevSvnPlugin::KDevSvnPlugin(QObject *parent, const QVariantList &) : KDevelop::IPlugin(QStringLiteral("kdevsubversion"), parent) , m_common(new KDevelop::VcsPluginHelper(this, this)) , copy_action( nullptr ) , move_action( nullptr ) , m_jobQueue(new ThreadWeaver::Queue(this)) { qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); } KDevSvnPlugin::~KDevSvnPlugin() { } bool KDevSvnPlugin::isValidRemoteRepositoryUrl(const QUrl& remoteLocation) { const QString scheme = remoteLocation.scheme(); if (scheme == QLatin1String("svn") || scheme == QLatin1String("svn+ssh")) { return true; } return false; } bool KDevSvnPlugin::isVersionControlled(const QUrl &localLocation) { ///TODO: also check this in the other functions? if (!localLocation.isValid()) { return false; } SvnInfoJob* job = new SvnInfoJob(this); job->setLocation(localLocation); if (job->exec()) { QVariant result = job->fetchResults(); if (result.isValid()) { SvnInfoHolder h = result.value(); return !h.name.isEmpty(); } } else { qCDebug(PLUGIN_SVN) << "Couldn't execute job"; } return false; } KDevelop::VcsJob* KDevSvnPlugin::repositoryLocation(const QUrl &localLocation) { SvnInfoJob* job = new SvnInfoJob(this); job->setLocation(localLocation); job->setProvideInformation(SvnInfoJob::RepoUrlOnly); return job; } KDevelop::VcsJob* KDevSvnPlugin::status(const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode mode) { SvnStatusJob* job = new SvnStatusJob(this); job->setLocations(localLocations); job->setRecursive((mode == KDevelop::IBasicVersionControl::Recursive)); return job; } KDevelop::VcsJob* KDevSvnPlugin::add(const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion) { SvnAddJob* job = new SvnAddJob(this); job->setLocations(localLocations); job->setRecursive((recursion == KDevelop::IBasicVersionControl::Recursive)); return job; } KDevelop::VcsJob* KDevSvnPlugin::remove(const QList& localLocations) { SvnRemoveJob* job = new SvnRemoveJob(this); job->setLocations(localLocations); return job; } KDevelop::VcsJob* KDevSvnPlugin::edit(const QUrl& /*localLocation*/) { return nullptr; } KDevelop::VcsJob* KDevSvnPlugin::unedit(const QUrl& /*localLocation*/) { return nullptr; } KDevelop::VcsJob* KDevSvnPlugin::localRevision(const QUrl &localLocation, KDevelop::VcsRevision::RevisionType type) { SvnInfoJob* job = new SvnInfoJob(this); job->setLocation(localLocation); job->setProvideInformation(SvnInfoJob::RevisionOnly); job->setProvideRevisionType(type); return job; } KDevelop::VcsJob* KDevSvnPlugin::copy(const QUrl &localLocationSrc, const QUrl& localLocationDstn) { SvnCopyJob* job = new SvnCopyJob(this); job->setSourceLocation(localLocationSrc); job->setDestinationLocation(localLocationDstn); return job; } KDevelop::VcsJob* KDevSvnPlugin::move(const QUrl &localLocationSrc, const QUrl& localLocationDst) { SvnMoveJob* job = new SvnMoveJob(this); job->setSourceLocation(localLocationSrc); job->setDestinationLocation(localLocationDst); return job; } KDevelop::VcsJob* KDevSvnPlugin::revert(const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion) { SvnRevertJob* job = new SvnRevertJob(this); job->setLocations(localLocations); job->setRecursive((recursion == KDevelop::IBasicVersionControl::Recursive)); return job; } KDevelop::VcsJob* KDevSvnPlugin::update(const QList& localLocations, const KDevelop::VcsRevision& rev, KDevelop::IBasicVersionControl::RecursionMode recursion) { SvnUpdateJob* job = new SvnUpdateJob(this); job->setLocations(localLocations); job->setRevision(rev); job->setRecursive((recursion == KDevelop::IBasicVersionControl::Recursive)); return job; } KDevelop::VcsJob* KDevSvnPlugin::commit(const QString& message, const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion) { SvnCommitJob* job = new SvnCommitJob(this); qCDebug(PLUGIN_SVN) << "Committing locations:" << localLocations << endl; job->setUrls(localLocations); job->setCommitMessage(message) ; job->setRecursive((recursion == KDevelop::IBasicVersionControl::Recursive)); return job; } KDevelop::VcsJob* KDevSvnPlugin::diff(const QUrl &fileOrDirectory, const KDevelop::VcsRevision& srcRevision, const KDevelop::VcsRevision& dstRevision, KDevelop::VcsDiff::Type diffType, KDevelop::IBasicVersionControl::RecursionMode recurse) { KDevelop::VcsLocation loc(fileOrDirectory); return diff2(loc, loc, srcRevision, dstRevision, diffType, recurse); } KDevelop::VcsJob* KDevSvnPlugin::diff2(const KDevelop::VcsLocation& src, const KDevelop::VcsLocation& dst, const KDevelop::VcsRevision& srcRevision, const KDevelop::VcsRevision& dstRevision, KDevelop::VcsDiff::Type diffType, KDevelop::IBasicVersionControl::RecursionMode recurse) { SvnDiffJob* job = new SvnDiffJob(this); job->setSource(src); job->setDestination(dst); job->setSrcRevision(srcRevision); job->setDstRevision(dstRevision); job->setDiffType(diffType); job->setRecursive((recurse == KDevelop::IBasicVersionControl::Recursive)); return job; } KDevelop::VcsJob* KDevSvnPlugin::log(const QUrl &localLocation, const KDevelop::VcsRevision& rev, unsigned long limit) { SvnLogJob* job = new SvnLogJob(this); job->setLocation(localLocation); job->setStartRevision(rev); job->setLimit(limit); return job; } KDevelop::VcsJob* KDevSvnPlugin::log(const QUrl &localLocation, const KDevelop::VcsRevision& startRev, const KDevelop::VcsRevision& endRev) { SvnLogJob* job = new SvnLogJob(this); job->setLocation(localLocation); job->setStartRevision(startRev); job->setEndRevision(endRev); return job; } KDevelop::VcsJob* KDevSvnPlugin::annotate(const QUrl &localLocation, const KDevelop::VcsRevision& rev) { SvnBlameJob* job = new SvnBlameJob(this); job->setLocation(localLocation); job->setEndRevision(rev); return job; } KDevelop::VcsJob* KDevSvnPlugin::merge(const KDevelop::VcsLocation& localOrRepoLocationSrc, const KDevelop::VcsLocation& localOrRepoLocationDst, const KDevelop::VcsRevision& srcRevision, const KDevelop::VcsRevision& dstRevision, const QUrl &localLocation) { // TODO implement merge Q_UNUSED(localOrRepoLocationSrc) Q_UNUSED(localOrRepoLocationDst) Q_UNUSED(srcRevision) Q_UNUSED(dstRevision) Q_UNUSED(localLocation) return nullptr; } KDevelop::VcsJob* KDevSvnPlugin::resolve(const QList& /*localLocations*/, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/) { return nullptr; } KDevelop::VcsJob* KDevSvnPlugin::import(const QString & commitMessage, const QUrl &sourceDirectory, const KDevelop::VcsLocation & destinationRepository) { SvnImportJob* job = new SvnImportJob(this); job->setMapping(sourceDirectory, destinationRepository); job->setMessage(commitMessage); return job; } KDevelop::VcsJob* KDevSvnPlugin::createWorkingCopy(const KDevelop::VcsLocation & sourceRepository, const QUrl &destinationDirectory, KDevelop::IBasicVersionControl::RecursionMode recursion) { SvnCheckoutJob* job = new SvnCheckoutJob(this); job->setMapping(sourceRepository, destinationDirectory, recursion); return job; } KDevelop::ContextMenuExtension KDevSvnPlugin::contextMenuExtension(KDevelop::Context* context) { m_common->setupFromContext(context); const QList & ctxUrlList = m_common->contextUrlList(); bool hasVersionControlledEntries = false; foreach(const QUrl &url, ctxUrlList) { if (isVersionControlled(url) || isVersionControlled(KIO::upUrl(url))) { hasVersionControlledEntries = true; break; } } qCDebug(PLUGIN_SVN) << "version controlled?" << hasVersionControlledEntries; if (!hasVersionControlledEntries) return IPlugin::contextMenuExtension(context); QMenu* svnmenu= m_common->commonActions(); svnmenu->addSeparator(); if( !copy_action ) { copy_action = new QAction(i18n("Copy..."), this); connect(copy_action, &QAction::triggered, this, &KDevSvnPlugin::ctxCopy); } svnmenu->addAction(copy_action); if( !move_action ) { move_action = new QAction(i18n("Move..."), this); connect(move_action, &QAction::triggered, this, &KDevSvnPlugin::ctxMove); } svnmenu->addAction(move_action); KDevelop::ContextMenuExtension menuExt; menuExt.addAction(KDevelop::ContextMenuExtension::VcsGroup, svnmenu->menuAction()); return menuExt; } void KDevSvnPlugin::ctxInfo() { QList const & ctxUrlList = m_common->contextUrlList(); if (ctxUrlList.count() != 1) { KMessageBox::error(nullptr, i18n("Please select only one item for this operation")); return; } } void KDevSvnPlugin::ctxStatus() { QList const & ctxUrlList = m_common->contextUrlList(); if (ctxUrlList.count() > 1) { KMessageBox::error(nullptr, i18n("Please select only one item for this operation")); return; } } void KDevSvnPlugin::ctxCopy() { QList const & ctxUrlList = m_common->contextUrlList(); if (ctxUrlList.count() > 1) { KMessageBox::error(nullptr, i18n("Please select only one item for this operation")); return; } QUrl source = ctxUrlList.first(); if (source.isLocalFile()) { QUrl dir = source; bool isFile = QFileInfo(source.toLocalFile()).isFile(); if (isFile) { dir = dir.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash); } KUrlRequesterDialog dlg(dir, i18n("Destination file/directory"), nullptr); if (isFile) { dlg.urlRequester()->setMode(KFile::File | KFile::Directory | KFile::LocalOnly); } else { dlg.urlRequester()->setMode(KFile::Directory | KFile::LocalOnly); } if (dlg.exec() == QDialog::Accepted) { KDevelop::ICore::self()->runController()->registerJob(copy(source, dlg.selectedUrl())); } } else { KMessageBox::error(nullptr, i18n("Copying only works on local files")); return; } } void KDevSvnPlugin::ctxMove() { QList const & ctxUrlList = m_common->contextUrlList(); if (ctxUrlList.count() != 1) { KMessageBox::error(nullptr, i18n("Please select only one item for this operation")); return; } QUrl source = ctxUrlList.first(); if (source.isLocalFile()) { QUrl dir = source; bool isFile = QFileInfo(source.toLocalFile()).isFile(); if (isFile) { dir = source.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash); } KUrlRequesterDialog dlg(dir, i18n("Destination file/directory"), nullptr); if (isFile) { dlg.urlRequester()->setMode(KFile::File | KFile::Directory | KFile::LocalOnly); } else { dlg.urlRequester()->setMode(KFile::Directory | KFile::LocalOnly); } if (dlg.exec() == QDialog::Accepted) { KDevelop::ICore::self()->runController()->registerJob(move(source, dlg.selectedUrl())); } } else { KMessageBox::error(nullptr, i18n("Moving only works on local files/dirs")); return; } } void KDevSvnPlugin::ctxCat() { QList const & ctxUrlList = m_common->contextUrlList(); if (ctxUrlList.count() != 1) { KMessageBox::error(nullptr, i18n("Please select only one item for this operation")); return; } } QString KDevSvnPlugin::name() const { return i18n("Subversion"); } KDevelop::VcsImportMetadataWidget* KDevSvnPlugin::createImportMetadataWidget(QWidget* parent) { return new SvnImportMetadataWidget(parent); } void KDevSvnPlugin::ctxImport() { QList const & ctxUrlList = m_common->contextUrlList(); if (ctxUrlList.count() != 1) { KMessageBox::error(nullptr, i18n("Please select only one item for this operation")); return; } QDialog dlg; dlg.setWindowTitle(i18n("Import into Subversion repository")); SvnImportMetadataWidget* widget = new SvnImportMetadataWidget(&dlg); widget->setSourceLocation(KDevelop::VcsLocation(ctxUrlList.first())); widget->setSourceLocationEditable(false); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); auto layout = new QVBoxLayout(); dlg.setLayout(layout); layout->addWidget(widget); layout->addWidget(buttonBox); connect(buttonBox, &QDialogButtonBox::accepted, &dlg, &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, &dlg, &QDialog::reject); if (dlg.exec() == QDialog::Accepted) { KDevelop::ICore::self()->runController()->registerJob(import(widget->message(), widget->source(), widget->destination())); } } void KDevSvnPlugin::ctxCheckout() { QList const & ctxUrlList = m_common->contextUrlList(); if (ctxUrlList.count() != 1) { KMessageBox::error(nullptr, i18n("Please select only one item for this operation")); return; } QDialog dlg; dlg.setWindowTitle(i18n("Checkout from Subversion repository")); SvnCheckoutMetadataWidget* widget = new SvnCheckoutMetadataWidget(&dlg); QUrl tmp = KIO::upUrl(ctxUrlList.first()); widget->setDestinationLocation(tmp); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); auto layout = new QVBoxLayout(); dlg.setLayout(layout); layout->addWidget(widget); layout->addWidget(buttonBox); connect(buttonBox, &QDialogButtonBox::accepted, &dlg, &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, &dlg, &QDialog::reject); if (dlg.exec() == QDialog::Accepted) { KDevelop::ICore::self()->runController()->registerJob(createWorkingCopy(widget->source(), widget->destination(), widget->recursionMode())); } } KDevelop::VcsLocationWidget* KDevSvnPlugin::vcsLocation(QWidget* parent) const { return new SvnLocationWidget(parent); } ThreadWeaver::Queue* KDevSvnPlugin::jobQueue() const { return m_jobQueue; } #include "kdevsvnplugin.moc" diff --git a/plugins/switchtobuddy/CMakeLists.txt b/plugins/switchtobuddy/CMakeLists.txt index 3b430c08d..9b0151e9b 100644 --- a/plugins/switchtobuddy/CMakeLists.txt +++ b/plugins/switchtobuddy/CMakeLists.txt @@ -1,16 +1,21 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kdevswitchtobuddy\") project(codeutils) ########### install target ############### set(kdevswitchtobuddy_PART_SRCS switchtobuddyplugin.cpp ) +ecm_qt_declare_logging_category(kdevswitchtobuddy_PART_SRCS + HEADER debug.h + IDENTIFIER PLUGIN_SWITCHTOBUDDY + CATEGORY_NAME "kdevplatform.plugins.switchtobuddy" +) qt5_add_resources(kdevswitchtobuddy_PART_SRCS kdevswitchtobuddy.qrc) kdevplatform_add_plugin(kdevswitchtobuddy JSON kdevswitchtobuddy.json SOURCES ${kdevswitchtobuddy_PART_SRCS}) target_link_libraries(kdevswitchtobuddy KDev::Interfaces KDev::Language ) diff --git a/plugins/switchtobuddy/switchtobuddyplugin.cpp b/plugins/switchtobuddy/switchtobuddyplugin.cpp index 18edb7d4e..ae12768c7 100644 --- a/plugins/switchtobuddy/switchtobuddyplugin.cpp +++ b/plugins/switchtobuddy/switchtobuddyplugin.cpp @@ -1,317 +1,316 @@ /* * This file is part of KDevelop * Copyright 2012 André Stein * Copyright 2014 Kevin Funk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "switchtobuddyplugin.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include - -Q_LOGGING_CATEGORY(PLUGIN_SWITCHTOBUDDY, "kdevplatform.plugins.switchtobuddy") +#include using namespace KDevelop; namespace { KTextEditor::Cursor normalizeCursor(KTextEditor::Cursor c) { c.setColumn(0); return c; } ///Tries to find a definition for the declaration at given cursor-position and document-url. DUChain must be locked. Declaration* definitionForCursorDeclaration(const KTextEditor::Cursor& cursor, const QUrl& url) { QList topContexts = DUChain::self()->chainsForDocument(url); foreach (TopDUContext* ctx, topContexts) { Declaration* decl = DUChainUtils::declarationInLine(cursor, ctx); if (!decl) { continue; } if (auto definition = FunctionDefinition::definition(decl)) { return definition; } } return nullptr; } QString findSwitchCandidate(const QUrl& docUrl) { QMimeDatabase db; IBuddyDocumentFinder* finder = IBuddyDocumentFinder::finderForMimeType(db.mimeTypeForUrl(docUrl).name()); if (finder) { // get the first entry that exists, use that as candidate foreach(const QUrl& buddyUrl, finder->getPotentialBuddies(docUrl)) { if (!QFile::exists(buddyUrl.toLocalFile())) { continue; } return buddyUrl.toLocalFile(); } } return QString(); } } K_PLUGIN_FACTORY_WITH_JSON(SwitchToBuddyPluginFactory, "kdevswitchtobuddy.json", registerPlugin(); ) SwitchToBuddyPlugin::SwitchToBuddyPlugin ( QObject* parent, const QVariantList& ) : IPlugin ( QStringLiteral("kdevswitchtobuddy"), parent ) , m_signalMapper(nullptr) { setXMLFile(QStringLiteral("kdevswitchtobuddy.rc")); } SwitchToBuddyPlugin::~SwitchToBuddyPlugin() { } ContextMenuExtension SwitchToBuddyPlugin::contextMenuExtension(Context* context) { EditorContext* ctx = dynamic_cast(context); if (!ctx) { return ContextMenuExtension(); } QUrl currentUrl = ctx->url(); IBuddyDocumentFinder* buddyFinder = IBuddyDocumentFinder::finderForMimeType(QMimeDatabase().mimeTypeForUrl(currentUrl).name()); if (!buddyFinder) return ContextMenuExtension(); // Get all potential buddies for the current document and add a switch-to action // for each buddy who really exists in the file system. Note: if no buddies could be calculated // no extension actions are generated. const QVector& potentialBuddies = buddyFinder->getPotentialBuddies(currentUrl); ContextMenuExtension extension; if (m_signalMapper) { delete m_signalMapper; } m_signalMapper = new QSignalMapper(this); foreach(const QUrl& url, potentialBuddies) { if (!QFile::exists(url.toLocalFile())) { continue; } QAction* action = new QAction(i18n("Switch to '%1'", url.fileName()), this); connect(action, &QAction::triggered, m_signalMapper, static_cast(&QSignalMapper::map), Qt::QueuedConnection); m_signalMapper->setMapping(action, url.toLocalFile()); connect(m_signalMapper, static_cast(&QSignalMapper::mapped), this, &SwitchToBuddyPlugin::switchToBuddy, Qt::QueuedConnection); extension.addAction(ContextMenuExtension::NavigationGroup, action); } return extension; } void SwitchToBuddyPlugin::createActionsForMainWindow(Sublime::MainWindow* /*window*/, QString& xmlFile, KActionCollection& actions) { xmlFile = this->xmlFile(); QAction* switchDefinitionDeclaration = actions.addAction(QStringLiteral("switch_definition_declaration")); switchDefinitionDeclaration->setText(i18n("&Switch Definition/Declaration")); actions.setDefaultShortcut(switchDefinitionDeclaration, Qt::CTRL | Qt::SHIFT | Qt::Key_C); connect(switchDefinitionDeclaration, &QAction::triggered, this, &SwitchToBuddyPlugin::switchDefinitionDeclaration); QAction* switchHeaderSource = actions.addAction(QStringLiteral("switch_header_source")); switchHeaderSource->setText(i18n("Switch Header/Source")); actions.setDefaultShortcut(switchHeaderSource, Qt::CTRL | Qt::Key_Slash); connect(switchHeaderSource, &QAction::triggered, this, &SwitchToBuddyPlugin::switchHeaderSource); } void SwitchToBuddyPlugin::switchHeaderSource() { qCDebug(PLUGIN_SWITCHTOBUDDY) << "switching header/source"; auto doc = ICore::self()->documentController()->activeDocument(); if (!doc) return; QString buddyUrl = findSwitchCandidate(doc->url()); if (!buddyUrl.isEmpty()) switchToBuddy(buddyUrl); } void SwitchToBuddyPlugin::switchToBuddy(const QString& url) { KDevelop::ICore::self()->documentController()->openDocument(QUrl::fromLocalFile(url)); } void SwitchToBuddyPlugin::switchDefinitionDeclaration() { qCDebug(PLUGIN_SWITCHTOBUDDY) << "switching definition/declaration"; QUrl docUrl; KTextEditor::Cursor cursor; ///Step 1: Find the current top-level context of type DUContext::Other(the highest code-context). ///-- If it belongs to a function-declaration or definition, it can be retrieved through owner(), and we are in a definition. ///-- If no such context could be found, search for a declaration on the same line as the cursor, and switch to the according definition { auto view = ICore::self()->documentController()->activeTextDocumentView(); if (!view) { qCDebug(PLUGIN_SWITCHTOBUDDY) << "No active document"; return; } docUrl = view->document()->url(); cursor = view->cursorPosition(); } QString switchCandidate = findSwitchCandidate(docUrl); if(!switchCandidate.isEmpty()) { DUChainReadLocker lock; //If the file has not been parsed yet, update it TopDUContext* ctx = DUChainUtils::standardContextForUrl(docUrl); //At least 'VisibleDeclarationsAndContexts' is required so we can do a switch if (!ctx || (ctx->parsingEnvironmentFile() && !ctx->parsingEnvironmentFile()->featuresSatisfied(TopDUContext::AllDeclarationsContextsAndUses))) { lock.unlock(); qCDebug(PLUGIN_SWITCHTOBUDDY) << "Parsing switch-candidate before switching" << switchCandidate; ReferencedTopDUContext updatedContext = DUChain::self()->waitForUpdate(IndexedString(switchCandidate), TopDUContext::AllDeclarationsContextsAndUses); if (!updatedContext) { qCDebug(PLUGIN_SWITCHTOBUDDY) << "Failed to update document:" << switchCandidate; return; } } } qCDebug(PLUGIN_SWITCHTOBUDDY) << "Document:" << docUrl; DUChainReadLocker lock; TopDUContext* standardCtx = DUChainUtils::standardContextForUrl(docUrl); bool wasSignal = false; if (standardCtx) { Declaration* definition = nullptr; DUContext* ctx = standardCtx->findContext(standardCtx->transformToLocalRevision(cursor)); if (!ctx) { ctx = standardCtx; } while (ctx && ctx->parentContext() && (ctx->parentContext()->type() == DUContext::Other || ctx->parentContext()->type() == DUContext::Function)) { ctx = ctx->parentContext(); } if (ctx && ctx->owner() && (ctx->type() == DUContext::Other || ctx->type() == DUContext::Function) && ctx->owner()->isDefinition()) { definition = ctx->owner(); qCDebug(PLUGIN_SWITCHTOBUDDY) << "found definition while traversing:" << definition->toString(); } if (!definition && ctx) { definition = DUChainUtils::declarationInLine(cursor, ctx); } if (ClassFunctionDeclaration* cDef = dynamic_cast(definition)) { if (cDef->isSignal()) { qCDebug(PLUGIN_SWITCHTOBUDDY) << "found definition is a signal, not switching to .moc implementation"; definition = nullptr; wasSignal = true; } } FunctionDefinition* def = dynamic_cast(definition); if (def && def->declaration()) { Declaration* declaration = def->declaration(); KTextEditor::Range targetRange = declaration->rangeInCurrentRevision(); const auto url = declaration->url().toUrl(); qCDebug(PLUGIN_SWITCHTOBUDDY) << "found definition that has declaration: " << definition->toString() << "range" << targetRange << "url" << url; lock.unlock(); auto view = ICore::self()->documentController()->activeTextDocumentView(); if (view && !targetRange.contains(view->cursorPosition())) { const auto pos = normalizeCursor(targetRange.start()); ICore::self()->documentController()->openDocument(url, KTextEditor::Range(pos, pos)); } else { ICore::self()->documentController()->openDocument(url); } return; } else { qCDebug(PLUGIN_SWITCHTOBUDDY) << "Definition has no assigned declaration"; } qCDebug(PLUGIN_SWITCHTOBUDDY) << "Could not get definition/declaration from context"; } else { qCDebug(PLUGIN_SWITCHTOBUDDY) << "Got no context for the current document"; } Declaration* def = nullptr; if (!wasSignal) { def = definitionForCursorDeclaration(cursor, docUrl); } if (def) { const auto url = def->url().toUrl(); KTextEditor::Range targetRange = def->rangeInCurrentRevision(); if (def->internalContext()) { targetRange.end() = def->internalContext()->rangeInCurrentRevision().end(); } else { qCDebug(PLUGIN_SWITCHTOBUDDY) << "Declaration does not have internal context"; } lock.unlock(); auto view = ICore::self()->documentController()->activeTextDocumentView(); if (view && !targetRange.contains(view->cursorPosition())) { KTextEditor::Cursor pos(normalizeCursor(targetRange.start())); ICore::self()->documentController()->openDocument(url, KTextEditor::Range(pos, pos)); } else { //The cursor is already in the target range, only open the document ICore::self()->documentController()->openDocument(url); } return; } else if (!wasSignal) { qWarning() << "Found no definition assigned to cursor position"; } lock.unlock(); ///- If no definition/declaration could be found to switch to, just switch the document using normal header/source heuristic by file-extension if (!switchCandidate.isEmpty()) { ICore::self()->documentController()->openDocument(QUrl::fromUserInput(switchCandidate)); } else { qCDebug(PLUGIN_SWITCHTOBUDDY) << "Found no source/header candidate to switch"; } } #include "switchtobuddyplugin.moc" diff --git a/plugins/switchtobuddy/switchtobuddyplugin.h b/plugins/switchtobuddy/switchtobuddyplugin.h index 1fb3af80c..0d1ced658 100644 --- a/plugins/switchtobuddy/switchtobuddyplugin.h +++ b/plugins/switchtobuddy/switchtobuddyplugin.h @@ -1,86 +1,84 @@ /* * This file is part of KDevelop * Copyright 2012 André Stein * Copyright 2014 Kevin Funk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_PLUGIN_SWITCHTOBUDDYPLUGIN_H #define KDEVPLATFORM_PLUGIN_SWITCHTOBUDDYPLUGIN_H #include #include #include -#include -Q_DECLARE_LOGGING_CATEGORY(PLUGIN_SWITCHTOBUDDY) /** * @short Implements a context menu extension in an editor context which provides * an action that allows switching to associated buddy documents. * * Using the @c IBuddyDocumentFinder interface, the current document's * language plugin provides potential buddy candidates. Depending on their * existence on the file system the @c SwitchToBuddyPlugin * enables a 'Switch To XXX' action which opens that buddy document * using the @c IDocumentController. * * If a language plugin either doesn't provide the @c IBuddyDocumentFinder * interface or no buddy exists on the file system, no context menu * extension is performed. * * @see IBuddyDocumentFinder * @see IDocumentController */ class SwitchToBuddyPlugin : public KDevelop::IPlugin { Q_OBJECT public: explicit SwitchToBuddyPlugin( QObject *parent, const QVariantList & = QVariantList()); ~SwitchToBuddyPlugin() override; KDevelop::ContextMenuExtension contextMenuExtension(KDevelop::Context* context) override; void createActionsForMainWindow(Sublime::MainWindow* window, QString& xmlFile, KActionCollection& actions) override; private Q_SLOTS: /** * Context menu slot which switches to the QUrl provided * in the data part of the sending QAction. */ void switchToBuddy(const QString& url); /** * Switch between header and source files */ void switchHeaderSource(); /** * @brief Switch between definitions and declarations * * E.g. if the cursor in the currently active view points to an implementation file * this shortcut will open the header document (or any buddy file). * * Furthermore, if the cursor points to a definition, and the buddy document contains its declaration, * the cursor will be also set to the declaration's position when the buddy document is opened */ void switchDefinitionDeclaration(); private: class QSignalMapper* m_signalMapper; }; #endif // KDEVPLATFORM_PLUGIN_SWITCHTOBUDDYPLUGIN_H diff --git a/plugins/testview/CMakeLists.txt b/plugins/testview/CMakeLists.txt index bf7521e65..f42853d97 100644 --- a/plugins/testview/CMakeLists.txt +++ b/plugins/testview/CMakeLists.txt @@ -1,12 +1,16 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kdevtestview\") ########### next target ############### set(kdevtestview_PLUGIN_SRCS testview.cpp testviewplugin.cpp - testviewdebug.cpp ) +ecm_qt_declare_logging_category(kdevtestview_PLUGIN_SRCS + HEADER debug.h + IDENTIFIER PLUGIN_TESTVIEW + CATEGORY_NAME "kdevplatform.plugins.testview" +) qt5_add_resources(kdevtestview_PLUGIN_SRCS kdevtestview.qrc) kdevplatform_add_plugin(kdevtestview JSON kdevtestview.json SOURCES ${kdevtestview_PLUGIN_SRCS}) target_link_libraries(kdevtestview KF5::ItemModels KDev::Interfaces KDev::Util KDev::Language) diff --git a/plugins/testview/testview.cpp b/plugins/testview/testview.cpp index 576566e10..498a5ca1a 100644 --- a/plugins/testview/testview.cpp +++ b/plugins/testview/testview.cpp @@ -1,410 +1,410 @@ /* 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 "testviewdebug.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("")); } } 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(); 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; } diff --git a/plugins/testview/testviewdebug.cpp b/plugins/testview/testviewdebug.cpp deleted file mode 100644 index b2649ce03..000000000 --- a/plugins/testview/testviewdebug.cpp +++ /dev/null @@ -1,24 +0,0 @@ -/* - This plugin is part of KDevelop. - - Copyright (C) 2011 Milian Wolff - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - -*/ - -#include "testviewdebug.h" - -Q_LOGGING_CATEGORY(PLUGIN_TESTVIEW, "kdevplatform.plugins.testview") diff --git a/plugins/testview/testviewdebug.h b/plugins/testview/testviewdebug.h deleted file mode 100644 index 83e2c2ebb..000000000 --- a/plugins/testview/testviewdebug.h +++ /dev/null @@ -1,28 +0,0 @@ -/* - This plugin is part of KDevelop. - - Copyright (C) 2011 Milian Wolff - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - -*/ - -#ifndef KDEVPLATFORM_PLUGIN_TESTVIEWDEBUG_H -#define KDEVPLATFORM_PLUGIN_TESTVIEWDEBUG_H - -#include -Q_DECLARE_LOGGING_CATEGORY(PLUGIN_TESTVIEW) - -#endif // KDEVPLATFORM_PLUGIN_TESTVIEWDEBUG_H diff --git a/project/debug.cpp b/project/debug.cpp index 126c93437..4cb97cba4 100644 --- a/project/debug.cpp +++ b/project/debug.cpp @@ -1,23 +1,30 @@ /* * This file is part of KDevelop * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "debug.h" -Q_LOGGING_CATEGORY(PROJECT, "kdevplatform.project") -Q_LOGGING_CATEGORY(FILEMANAGER, "kdevplatform.filemanager") +// TODO: ecm_qt_declare_logging_category only supports one category +// so generate two separate files with a wrapper debug.h, or have code include explicit matching header? +#if QT_VERSION >= 0x050500 +const QtMsgType defaultMsgType = QtInfoMsg; +#else +const QtMsgType defaultMsgType = QtWarningMsg; +#endif +Q_LOGGING_CATEGORY(PROJECT, "kdevplatform.project", defaultMsgType) +Q_LOGGING_CATEGORY(FILEMANAGER, "kdevplatform.filemanager", defaultMsgType) diff --git a/project/projectmodel.cpp b/project/projectmodel.cpp index 5798629ca..ed9e82418 100644 --- a/project/projectmodel.cpp +++ b/project/projectmodel.cpp @@ -1,1171 +1,1170 @@ /* This file is part of KDevelop Copyright 2005 Roberto Raggi Copyright 2007 Andreas Pakulat Copyright 2007 Aleix Pol 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 "projectmodel.h" -#include #include #include #include #include #include #include #include #include #include "interfaces/iprojectfilemanager.h" #include #include "debug.h" #include "path.h" namespace KDevelop { QStringList removeProjectBasePath( const QStringList& fullpath, KDevelop::ProjectBaseItem* item ) { QStringList result = fullpath; if( item ) { KDevelop::ProjectModel* model = KDevelop::ICore::self()->projectController()->projectModel(); QStringList basePath = model->pathFromIndex( model->indexFromItem( item ) ); if( basePath.count() >= fullpath.count() ) { return QStringList(); } for( int i = 0; i < basePath.count(); i++ ) { result.takeFirst(); } } return result; } QStringList joinProjectBasePath( const QStringList& partialpath, KDevelop::ProjectBaseItem* item ) { QStringList basePath; if( item ) { KDevelop::ProjectModel* model = KDevelop::ICore::self()->projectController()->projectModel(); basePath = model->pathFromIndex( model->indexFromItem( item ) ); } return basePath + partialpath; } inline uint indexForPath( const Path& path ) { return IndexedString::indexForString(path.pathOrUrl()); } class ProjectModelPrivate { public: explicit ProjectModelPrivate( ProjectModel* model ): model( model ) { } ProjectBaseItem* rootItem; ProjectModel* model; ProjectBaseItem* itemFromIndex( const QModelIndex& idx ) { if( !idx.isValid() ) { return rootItem; } if( idx.model() != model ) { return nullptr; } return model->itemFromIndex( idx ); } // a hash of IndexedString::indexForString(path) <-> ProjectBaseItem for fast lookup QMultiHash pathLookupTable; }; class ProjectBaseItemPrivate { public: ProjectBaseItemPrivate() : project(nullptr), parent(nullptr), row(-1), model(nullptr), m_pathIndex(0) {} IProject* project; ProjectBaseItem* parent; int row; QList children; QString text; ProjectBaseItem::ProjectItemType type; Qt::ItemFlags flags; ProjectModel* model; Path m_path; uint m_pathIndex; QString iconName; ProjectBaseItem::RenameStatus renameBaseItem(ProjectBaseItem* item, const QString& newName) { if (item->parent()) { foreach(ProjectBaseItem* sibling, item->parent()->children()) { if (sibling->text() == newName) { return ProjectBaseItem::ExistingItemSameName; } } } item->setText( newName ); return ProjectBaseItem::RenameOk; } ProjectBaseItem::RenameStatus renameFileOrFolder(ProjectBaseItem* item, const QString& newName) { Q_ASSERT(item->file() || item->folder()); if (newName.contains('/')) { return ProjectBaseItem::InvalidNewName; } if (item->text() == newName) { return ProjectBaseItem::RenameOk; } Path newPath = item->path(); newPath.setLastPathSegment(newName); auto job = KIO::stat(newPath.toUrl(), KIO::StatJob::SourceSide, 0, KIO::HideProgressInfo); if (job->exec()) { // file/folder exists already return ProjectBaseItem::ExistingItemSameName; } if( !item->project() || !item->project()->projectFileManager() ) { return renameBaseItem(item, newName); } else if( item->folder() && item->project()->projectFileManager()->renameFolder(item->folder(), newPath) ) { return ProjectBaseItem::RenameOk; } else if ( item->file() && item->project()->projectFileManager()->renameFile(item->file(), newPath) ) { return ProjectBaseItem::RenameOk; } else { return ProjectBaseItem::ProjectManagerRenameFailed; } } }; ProjectBaseItem::ProjectBaseItem( IProject* project, const QString &name, ProjectBaseItem *parent ) : d_ptr(new ProjectBaseItemPrivate) { Q_ASSERT(!name.isEmpty() || !parent); Q_D(ProjectBaseItem); d->project = project; d->text = name; d->flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled; if( parent ) { parent->appendRow( this ); } } ProjectBaseItem::~ProjectBaseItem() { Q_D(ProjectBaseItem); if (model() && d->m_pathIndex) { model()->d->pathLookupTable.remove(d->m_pathIndex, this); } if( parent() ) { parent()->takeRow( d->row ); } else if( model() ) { model()->takeRow( d->row ); } removeRows(0, d->children.size()); delete d; } ProjectBaseItem* ProjectBaseItem::child( int row ) const { Q_D(const ProjectBaseItem); if( row < 0 || row >= d->children.length() ) { return nullptr; } return d->children.at( row ); } QList< ProjectBaseItem* > ProjectBaseItem::children() const { Q_D(const ProjectBaseItem); return d->children; } ProjectBaseItem* ProjectBaseItem::takeRow(int row) { Q_D(ProjectBaseItem); Q_ASSERT(row >= 0 && row < d->children.size()); if( model() ) { model()->beginRemoveRows(index(), row, row); } ProjectBaseItem* olditem = d->children.takeAt( row ); olditem->d_func()->parent = nullptr; olditem->d_func()->row = -1; olditem->setModel( nullptr ); for(int i=row; id_func()->row--; Q_ASSERT(child(i)->d_func()->row==i); } if( model() ) { model()->endRemoveRows(); } return olditem; } void ProjectBaseItem::removeRow( int row ) { delete takeRow( row ); } void ProjectBaseItem::removeRows(int row, int count) { if (!count) { return; } Q_D(ProjectBaseItem); Q_ASSERT(row >= 0 && row + count <= d->children.size()); if( model() ) { model()->beginRemoveRows(index(), row, row + count - 1); } //NOTE: we unset parent, row and model manually to speed up the deletion if (row == 0 && count == d->children.size()) { // optimize if we want to delete all foreach(ProjectBaseItem* item, d->children) { item->d_func()->parent = nullptr; item->d_func()->row = -1; item->setModel( nullptr ); delete item; } d->children.clear(); } else { for (int i = row; i < count; ++i) { ProjectBaseItem* item = d->children.at(i); item->d_func()->parent = nullptr; item->d_func()->row = -1; item->setModel( nullptr ); delete d->children.takeAt( row ); } for(int i = row; i < d->children.size(); ++i) { d->children.at(i)->d_func()->row--; Q_ASSERT(child(i)->d_func()->row==i); } } if( model() ) { model()->endRemoveRows(); } } QModelIndex ProjectBaseItem::index() const { if( model() ) { return model()->indexFromItem( this ); } return QModelIndex(); } int ProjectBaseItem::rowCount() const { Q_D(const ProjectBaseItem); return d->children.count(); } int ProjectBaseItem::type() const { return ProjectBaseItem::BaseItem; } ProjectModel* ProjectBaseItem::model() const { Q_D(const ProjectBaseItem); return d->model; } ProjectBaseItem* ProjectBaseItem::parent() const { Q_D(const ProjectBaseItem); if( model() && model()->d->rootItem == d->parent ) { return nullptr; } return d->parent; } int ProjectBaseItem::row() const { Q_D(const ProjectBaseItem); return d->row; } QString ProjectBaseItem::text() const { Q_D(const ProjectBaseItem); if( project() && !parent() ) { return project()->name(); } else { return d->text; } } void ProjectBaseItem::setModel( ProjectModel* model ) { Q_D(ProjectBaseItem); if (model == d->model) { return; } if (d->model && d->m_pathIndex) { d->model->d->pathLookupTable.remove(d->m_pathIndex, this); } d->model = model; if (model && d->m_pathIndex) { model->d->pathLookupTable.insert(d->m_pathIndex, this); } foreach( ProjectBaseItem* item, d->children ) { item->setModel( model ); } } void ProjectBaseItem::setRow( int row ) { Q_D(ProjectBaseItem); d->row = row; } void ProjectBaseItem::setText( const QString& text ) { Q_ASSERT(!text.isEmpty() || !parent()); Q_D(ProjectBaseItem); d->text = text; if( d->model ) { QModelIndex idx = index(); emit d->model->dataChanged(idx, idx); } } ProjectBaseItem::RenameStatus ProjectBaseItem::rename(const QString& newName) { Q_D(ProjectBaseItem); return d->renameBaseItem(this, newName); } KDevelop::ProjectBaseItem::ProjectItemType baseType( int type ) { if( type == KDevelop::ProjectBaseItem::Folder || type == KDevelop::ProjectBaseItem::BuildFolder ) return KDevelop::ProjectBaseItem::Folder; if( type == KDevelop::ProjectBaseItem::Target || type == KDevelop::ProjectBaseItem::ExecutableTarget || type == KDevelop::ProjectBaseItem::LibraryTarget) return KDevelop::ProjectBaseItem::Target; return static_cast( type ); } bool ProjectBaseItem::lessThan( const KDevelop::ProjectBaseItem* item ) const { if(item->type() >= KDevelop::ProjectBaseItem::CustomProjectItemType ) { // For custom types we want to make sure that if they override lessThan, then we // prefer their lessThan implementation return !item->lessThan( this ); } KDevelop::ProjectBaseItem::ProjectItemType leftType=baseType(type()), rightType=baseType(item->type()); if(leftType==rightType) { if(leftType==KDevelop::ProjectBaseItem::File) { return file()->fileName().compare(item->file()->fileName(), Qt::CaseInsensitive) < 0; } return this->text()text(); } else { return leftTypepath() < item2->path(); } IProject* ProjectBaseItem::project() const { Q_D(const ProjectBaseItem); return d->project; } void ProjectBaseItem::appendRow( ProjectBaseItem* item ) { Q_D(ProjectBaseItem); if( !item ) { return; } if( item->parent() ) { // Proper way is to first removeRow() on the original parent, then appendRow on this one qCWarning(PROJECT) << "Ignoring double insertion of item" << item; return; } // this is too slow... O(n) and thankfully not a problem anyways // Q_ASSERT(!d->children.contains(item)); int startrow,endrow; if( model() ) { startrow = endrow = d->children.count(); model()->beginInsertRows(index(), startrow, endrow); } d->children.append( item ); item->setRow( d->children.count() - 1 ); item->d_func()->parent = this; item->setModel( model() ); if( model() ) { model()->endInsertRows(); } } Path ProjectBaseItem::path() const { Q_D(const ProjectBaseItem); return d->m_path; } QString ProjectBaseItem::baseName() const { return text(); } void ProjectBaseItem::setPath( const Path& path) { Q_D(ProjectBaseItem); if (model() && d->m_pathIndex) { model()->d->pathLookupTable.remove(d->m_pathIndex, this); } d->m_path = path; d->m_pathIndex = indexForPath(path); setText( path.lastPathSegment() ); if (model() && d->m_pathIndex) { model()->d->pathLookupTable.insert(d->m_pathIndex, this); } } Qt::ItemFlags ProjectBaseItem::flags() { Q_D(ProjectBaseItem); return d->flags; } Qt::DropActions ProjectModel::supportedDropActions() const { return (Qt::DropActions)(Qt::MoveAction); } void ProjectBaseItem::setFlags(Qt::ItemFlags flags) { Q_D(ProjectBaseItem); d->flags = flags; if(d->model) emit d->model->dataChanged(index(), index()); } QString ProjectBaseItem::iconName() const { return QString(); } ProjectFolderItem *ProjectBaseItem::folder() const { return nullptr; } ProjectTargetItem *ProjectBaseItem::target() const { return nullptr; } ProjectExecutableTargetItem *ProjectBaseItem::executable() const { return nullptr; } ProjectFileItem *ProjectBaseItem::file() const { return nullptr; } QList ProjectBaseItem::folderList() const { QList lst; for ( int i = 0; i < rowCount(); ++i ) { ProjectBaseItem* item = child( i ); if ( item->type() == Folder || item->type() == BuildFolder ) { ProjectFolderItem *kdevitem = dynamic_cast( item ); if ( kdevitem ) lst.append( kdevitem ); } } return lst; } QList ProjectBaseItem::targetList() const { QList lst; for ( int i = 0; i < rowCount(); ++i ) { ProjectBaseItem* item = child( i ); if ( item->type() == Target || item->type() == LibraryTarget || item->type() == ExecutableTarget ) { ProjectTargetItem *kdevitem = dynamic_cast( item ); if ( kdevitem ) lst.append( kdevitem ); } } return lst; } QList ProjectBaseItem::fileList() const { QList lst; for ( int i = 0; i < rowCount(); ++i ) { ProjectBaseItem* item = child( i ); Q_ASSERT(item); if ( item && item->type() == File ) { ProjectFileItem *kdevitem = dynamic_cast( item ); if ( kdevitem ) lst.append( kdevitem ); } } return lst; } void ProjectModel::clear() { d->rootItem->removeRows(0, d->rootItem->rowCount()); } ProjectFolderItem::ProjectFolderItem(IProject* project, const Path& path, ProjectBaseItem* parent) : ProjectBaseItem( project, path.lastPathSegment(), parent ) { setPath( path ); setFlags(flags() | Qt::ItemIsDropEnabled); if (project && project->path() != path) setFlags(flags() | Qt::ItemIsDragEnabled); } ProjectFolderItem::ProjectFolderItem( const QString & name, ProjectBaseItem * parent ) : ProjectBaseItem( parent->project(), name, parent ) { setPath( Path(parent->path(), name) ); setFlags(flags() | Qt::ItemIsDropEnabled); if (project() && project()->path() != path()) setFlags(flags() | Qt::ItemIsDragEnabled); } ProjectFolderItem::~ProjectFolderItem() { } void ProjectFolderItem::setPath( const Path& path ) { ProjectBaseItem::setPath(path); propagateRename(path); } ProjectFolderItem *ProjectFolderItem::folder() const { return const_cast(this); } int ProjectFolderItem::type() const { return ProjectBaseItem::Folder; } QString ProjectFolderItem::folderName() const { return baseName(); } void ProjectFolderItem::propagateRename( const Path& newBase ) const { Path path = newBase; path.addPath(QStringLiteral("dummy")); foreach( KDevelop::ProjectBaseItem* child, children() ) { path.setLastPathSegment( child->text() ); child->setPath( path ); const ProjectFolderItem* folder = child->folder(); if ( folder ) { folder->propagateRename( path ); } } } ProjectBaseItem::RenameStatus ProjectFolderItem::rename(const QString& newName) { return d_ptr->renameFileOrFolder(this, newName); } bool ProjectFolderItem::hasFileOrFolder(const QString& name) const { foreach ( ProjectBaseItem* item, children() ) { if ( (item->type() == Folder || item->type() == File || item->type() == BuildFolder) && name == item->baseName() ) { return true; } } return false; } bool ProjectBaseItem::isProjectRoot() const { return parent()==nullptr; } ProjectBuildFolderItem::ProjectBuildFolderItem(IProject* project, const Path& path, ProjectBaseItem *parent) : ProjectFolderItem( project, path, parent ) { } ProjectBuildFolderItem::ProjectBuildFolderItem( const QString& name, ProjectBaseItem* parent ) : ProjectFolderItem( name, parent ) { } QString ProjectFolderItem::iconName() const { return QStringLiteral("folder"); } int ProjectBuildFolderItem::type() const { return ProjectBaseItem::BuildFolder; } QString ProjectBuildFolderItem::iconName() const { return QStringLiteral("folder-development"); } ProjectFileItem::ProjectFileItem( IProject* project, const Path& path, ProjectBaseItem* parent ) : ProjectBaseItem( project, path.lastPathSegment(), parent ) { setFlags(flags() | Qt::ItemIsDragEnabled); setPath( path ); } ProjectFileItem::ProjectFileItem( const QString& name, ProjectBaseItem* parent ) : ProjectBaseItem( parent->project(), name, parent ) { setFlags(flags() | Qt::ItemIsDragEnabled); setPath( Path(parent->path(), name) ); } ProjectFileItem::~ProjectFileItem() { if( project() && d_ptr->m_pathIndex ) { project()->removeFromFileSet( this ); } } IndexedString ProjectFileItem::indexedPath() const { return IndexedString::fromIndex( d_ptr->m_pathIndex ); } ProjectBaseItem::RenameStatus ProjectFileItem::rename(const QString& newName) { return d_ptr->renameFileOrFolder(this, newName); } QString ProjectFileItem::fileName() const { return baseName(); } // Maximum length of a string to still consider it as a file extension which we cache // This has to be a slow value, so that we don't fill our file extension cache with crap static const int maximumCacheExtensionLength = 3; bool isNumeric(const QStringRef& str) { int len = str.length(); if(len == 0) return false; for(int a = 0; a < len; ++a) if(!str.at(a).isNumber()) return false; return true; } class IconNameCache { public: QString iconNameForPath(const Path& path, const QString& fileName) { // find icon name based on file extension, if possible QString extension; int extensionStart = fileName.lastIndexOf(QLatin1Char('.')); if( extensionStart != -1 && fileName.length() - extensionStart - 1 <= maximumCacheExtensionLength ) { QStringRef extRef = fileName.midRef(extensionStart + 1); if( isNumeric(extRef) ) { // don't cache numeric extensions extRef.clear(); } if( !extRef.isEmpty() ) { extension = extRef.toString(); QMutexLocker lock(&mutex); QHash< QString, QString >::const_iterator it = fileExtensionToIcon.constFind( extension ); if( it != fileExtensionToIcon.constEnd() ) { return *it; } } } QMimeType mime = QMimeDatabase().mimeTypeForFile(path.lastPathSegment(), QMimeDatabase::MatchExtension); // no I/O QMutexLocker lock(&mutex); QHash< QString, QString >::const_iterator it = mimeToIcon.constFind(mime.name()); QString iconName; if ( it == mimeToIcon.constEnd() ) { iconName = mime.iconName(); if (iconName.isEmpty()) { iconName = QStringLiteral("none"); } mimeToIcon.insert(mime.name(), iconName); } else { iconName = *it; } if ( !extension.isEmpty() ) { fileExtensionToIcon.insert(extension, iconName); } return iconName; } QMutex mutex; QHash mimeToIcon; QHash fileExtensionToIcon; }; Q_GLOBAL_STATIC(IconNameCache, s_cache); QString ProjectFileItem::iconName() const { // think of d_ptr->iconName as mutable, possible since d_ptr is not const if (d_ptr->iconName.isEmpty()) { // lazy load implementation of icon lookup d_ptr->iconName = s_cache->iconNameForPath( d_ptr->m_path, d_ptr->text ); // we should always get *some* icon name back Q_ASSERT(!d_ptr->iconName.isEmpty()); } return d_ptr->iconName; } void ProjectFileItem::setPath( const Path& path ) { if (path == d_ptr->m_path) { return; } if( project() && d_ptr->m_pathIndex ) { // remove from fileset if we are in there project()->removeFromFileSet( this ); } ProjectBaseItem::setPath( path ); if( project() && d_ptr->m_pathIndex ) { // add to fileset with new path project()->addToFileSet( this ); } // invalidate icon name for future lazy-loaded updated d_ptr->iconName.clear(); } int ProjectFileItem::type() const { return ProjectBaseItem::File; } ProjectFileItem *ProjectFileItem::file() const { return const_cast( this ); } ProjectTargetItem::ProjectTargetItem( IProject* project, const QString &name, ProjectBaseItem *parent ) : ProjectBaseItem( project, name, parent ) { setFlags(flags() | Qt::ItemIsDropEnabled); } QString ProjectTargetItem::iconName() const { return QStringLiteral("system-run"); } void ProjectTargetItem::setPath( const Path& path ) { // don't call base class, it calls setText with the new path's filename // which we do not want for target items d_ptr->m_path = path; } int ProjectTargetItem::type() const { return ProjectBaseItem::Target; } ProjectTargetItem *ProjectTargetItem::target() const { return const_cast( this ); } ProjectExecutableTargetItem::ProjectExecutableTargetItem( IProject* project, const QString &name, ProjectBaseItem *parent ) : ProjectTargetItem(project, name, parent) { } ProjectExecutableTargetItem *ProjectExecutableTargetItem::executable() const { return const_cast( this ); } int ProjectExecutableTargetItem::type() const { return ProjectBaseItem::ExecutableTarget; } ProjectLibraryTargetItem::ProjectLibraryTargetItem( IProject* project, const QString &name, ProjectBaseItem *parent ) : ProjectTargetItem(project, name, parent) {} int ProjectLibraryTargetItem::type() const { return ProjectBaseItem::LibraryTarget; } QModelIndex ProjectModel::pathToIndex(const QStringList& tofetch_) const { if(tofetch_.isEmpty()) return QModelIndex(); QStringList tofetch(tofetch_); if(tofetch.last().isEmpty()) tofetch.takeLast(); QModelIndex current=index(0,0, QModelIndex()); QModelIndex ret; for(int a = 0; a < tofetch.size(); ++a) { const QString& currentName = tofetch[a]; bool matched = false; QModelIndexList l = match(current, Qt::DisplayRole, currentName, -1, Qt::MatchExactly); foreach(const QModelIndex& idx, l) { //If this is not the last item, only match folders, as there may be targets and folders with the same name if(a == tofetch.size()-1 || itemFromIndex(idx)->folder()) { ret = idx; current = index(0,0, ret); matched = true; break; } } if(!matched) { ret = QModelIndex(); break; } } Q_ASSERT(!ret.isValid() || data(ret).toString()==tofetch.last()); return ret; } QStringList ProjectModel::pathFromIndex(const QModelIndex& index) const { if (!index.isValid()) return QStringList(); QModelIndex idx = index; QStringList list; do { QString t = data(idx, Qt::DisplayRole).toString(); list.prepend(t); QModelIndex parent = idx.parent(); idx = parent.sibling(parent.row(), index.column()); } while (idx.isValid()); return list; } int ProjectModel::columnCount( const QModelIndex& ) const { return 1; } int ProjectModel::rowCount( const QModelIndex& parent ) const { ProjectBaseItem* item = d->itemFromIndex( parent ); return item ? item->rowCount() : 0; } QModelIndex ProjectModel::parent( const QModelIndex& child ) const { if( child.isValid() ) { ProjectBaseItem* item = static_cast( child.internalPointer() ); return indexFromItem( item ); } return QModelIndex(); } QModelIndex ProjectModel::indexFromItem( const ProjectBaseItem* item ) const { if( item && item->d_func()->parent ) { return createIndex( item->row(), 0, item->d_func()->parent ); } return QModelIndex(); } ProjectBaseItem* ProjectModel::itemFromIndex( const QModelIndex& index ) const { if( index.row() >= 0 && index.column() == 0 && index.model() == this ) { ProjectBaseItem* parent = static_cast( index.internalPointer() ); if( parent ) { return parent->child( index.row() ); } } return nullptr; } QVariant ProjectModel::data( const QModelIndex& index, int role ) const { static const QSet allowedRoles = { Qt::DisplayRole, Qt::ToolTipRole, Qt::DecorationRole, ProjectItemRole, ProjectRole, UrlRole }; if( allowedRoles.contains(role) && index.isValid() ) { ProjectBaseItem* item = itemFromIndex( index ); if( item ) { switch(role) { case Qt::DecorationRole: return QIcon::fromTheme(item->iconName()); case Qt::ToolTipRole: return item->path().pathOrUrl(); case Qt::DisplayRole: return item->text(); case ProjectItemRole: return QVariant::fromValue(item); case UrlRole: return item->path().toUrl(); case ProjectRole: return QVariant::fromValue(item->project()); } } } return QVariant(); } ProjectModel::ProjectModel( QObject *parent ) : QAbstractItemModel( parent ), d( new ProjectModelPrivate( this ) ) { d->rootItem = new ProjectBaseItem( nullptr, QString(), nullptr ); d->rootItem->setModel( this ); } ProjectModel::~ProjectModel() { d->rootItem->setModel(nullptr); delete d->rootItem; delete d; } ProjectVisitor::ProjectVisitor() { } QModelIndex ProjectModel::index( int row, int column, const QModelIndex& parent ) const { ProjectBaseItem* parentItem = d->itemFromIndex( parent ); if( parentItem && row >= 0 && row < parentItem->rowCount() && column == 0 ) { return createIndex( row, column, parentItem ); } return QModelIndex(); } void ProjectModel::appendRow( ProjectBaseItem* item ) { d->rootItem->appendRow( item ); } void ProjectModel::removeRow( int row ) { d->rootItem->removeRow( row ); } ProjectBaseItem* ProjectModel::takeRow( int row ) { return d->rootItem->takeRow( row ); } ProjectBaseItem* ProjectModel::itemAt(int row) const { return d->rootItem->child(row); } QList< ProjectBaseItem* > ProjectModel::topItems() const { return d->rootItem->children(); } Qt::ItemFlags ProjectModel::flags(const QModelIndex& index) const { ProjectBaseItem* item = itemFromIndex( index ); if(item) return item->flags(); else return nullptr; } bool ProjectModel::insertColumns(int, int, const QModelIndex&) { // Not supported return false; } bool ProjectModel::insertRows(int, int, const QModelIndex&) { // Not supported return false; } bool ProjectModel::setData(const QModelIndex&, const QVariant&, int) { // Not supported return false; } QList ProjectModel::itemsForPath(const IndexedString& path) const { return d->pathLookupTable.values(path.index()); } ProjectBaseItem* ProjectModel::itemForPath(const IndexedString& path) const { return d->pathLookupTable.value(path.index()); } void ProjectVisitor::visit( ProjectModel* model ) { foreach( ProjectBaseItem* item, model->topItems() ) { visit( item->project() ); } } void ProjectVisitor::visit ( IProject* prj ) { visit( prj->projectItem() ); } void ProjectVisitor::visit ( ProjectBuildFolderItem* folder ) { visit(static_cast(folder)); } void ProjectVisitor::visit ( ProjectExecutableTargetItem* exec ) { foreach( ProjectFileItem* item, exec->fileList() ) { visit( item ); } } void ProjectVisitor::visit ( ProjectFolderItem* folder ) { foreach( ProjectFileItem* item, folder->fileList() ) { visit( item ); } foreach( ProjectTargetItem* item, folder->targetList() ) { if( item->type() == ProjectBaseItem::LibraryTarget ) { visit( dynamic_cast( item ) ); } else if( item->type() == ProjectBaseItem::ExecutableTarget ) { visit( dynamic_cast( item ) ); } } foreach( ProjectFolderItem* item, folder->folderList() ) { if( item->type() == ProjectBaseItem::BuildFolder ) { visit( dynamic_cast( item ) ); } else if( item->type() == ProjectBaseItem::Folder ) { visit( dynamic_cast( item ) ); } } } void ProjectVisitor::visit ( ProjectFileItem* ) { } void ProjectVisitor::visit ( ProjectLibraryTargetItem* lib ) { foreach( ProjectFileItem* item, lib->fileList() ) { visit( item ); } } ProjectVisitor::~ProjectVisitor() { } } diff --git a/project/projectproxymodel.cpp b/project/projectproxymodel.cpp index 9e98e83f3..03c70ec78 100644 --- a/project/projectproxymodel.cpp +++ b/project/projectproxymodel.cpp @@ -1,77 +1,76 @@ /* This file is part of KDevelop Copyright 2008 Aleix Pol 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 "projectproxymodel.h" #include -#include ProjectProxyModel::ProjectProxyModel(QObject * parent) : QSortFilterProxyModel(parent) , m_showTargets(true) { setDynamicSortFilter(true); sort(0); //initiate sorting regardless of the view } KDevelop::ProjectModel * ProjectProxyModel::projectModel() const { return qobject_cast( sourceModel() ); } bool ProjectProxyModel::lessThan(const QModelIndex & left, const QModelIndex & right) const { KDevelop::ProjectBaseItem *iLeft=projectModel()->itemFromIndex(left), *iRight=projectModel()->itemFromIndex(right); if(!iLeft || !iRight) return false; return( iLeft->lessThan( iRight ) ); } void ProjectProxyModel::showTargets(bool visible) { if (visible != m_showTargets) { m_showTargets = visible; invalidateFilter(); } } bool ProjectProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { if (m_showTargets) { return true; } else { // Get the base item for the associated parent and row. QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent); auto *item = projectModel()->itemFromIndex(index); // If it's a target, return false, otherwise true. return item->type() != KDevelop::ProjectBaseItem::Target && item->type() != KDevelop::ProjectBaseItem::LibraryTarget && item->type() != KDevelop::ProjectBaseItem::ExecutableTarget; } } QModelIndex ProjectProxyModel::proxyIndexFromItem(KDevelop::ProjectBaseItem* item) const { return mapFromSource(projectModel()->indexFromItem(item)); } KDevelop::ProjectBaseItem* ProjectProxyModel::itemFromProxyIndex( const QModelIndex& idx ) const { return static_cast( projectModel()->itemFromIndex( mapToSource( idx ) ) ); } diff --git a/serialization/CMakeLists.txt b/serialization/CMakeLists.txt index d40a081b3..0229fd57d 100644 --- a/serialization/CMakeLists.txt +++ b/serialization/CMakeLists.txt @@ -1,30 +1,35 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kdevplatform\") set(KDevPlatformSerialization_LIB_SRCS abstractitemrepository.cpp indexedstring.cpp itemrepositoryregistry.cpp referencecounting.cpp - debug.cpp +) + +ecm_qt_declare_logging_category(KDevPlatformSerialization_LIB_SRCS + HEADER debug.h + IDENTIFIER SERIALIZATION + CATEGORY_NAME "kdevplatform.serialization" ) kdevplatform_add_library(KDevPlatformSerialization SOURCES ${KDevPlatformSerialization_LIB_SRCS}) target_link_libraries(KDevPlatformSerialization LINK_PUBLIC KDev::Interfaces LINK_PRIVATE KDev::Util ) install(FILES abstractitemrepository.h referencecounting.h indexedstring.h itemrepositoryexampleitem.h itemrepository.h itemrepositoryregistry.h repositorymanager.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/kdevplatform/serialization COMPONENT Devel ) add_subdirectory(tests) diff --git a/serialization/debug.cpp b/serialization/debug.cpp deleted file mode 100644 index c8be49a97..000000000 --- a/serialization/debug.cpp +++ /dev/null @@ -1,22 +0,0 @@ -/* - * This file is part of KDevelop - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#include "debug.h" - -Q_LOGGING_CATEGORY(SERIALIZATION, "kdevplatform.serialization") diff --git a/serialization/debug.h b/serialization/debug.h deleted file mode 100644 index a2b16197b..000000000 --- a/serialization/debug.h +++ /dev/null @@ -1,26 +0,0 @@ -/* - * This file is part of KDevelop - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#ifndef KDEVPLATFORM_SERIALIZATION_DEBUG_H -#define KDEVPLATFORM_SERIALIZATION_DEBUG_H - -#include -Q_DECLARE_LOGGING_CATEGORY(SERIALIZATION) - -#endif diff --git a/serialization/itemrepositoryregistry.cpp b/serialization/itemrepositoryregistry.cpp index f0dd3d75b..690e8658c 100644 --- a/serialization/itemrepositoryregistry.cpp +++ b/serialization/itemrepositoryregistry.cpp @@ -1,410 +1,409 @@ /* Copyright 2008 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "itemrepositoryregistry.h" #include #include #include -#include #include #include #include #include #include "abstractitemrepository.h" #include "debug.h" using namespace KDevelop; namespace { //If KDevelop crashed this many times consicutively, clean up the repository const int crashesBeforeCleanup = 1; void setCrashCounter(QFile& crashesFile, int count) { crashesFile.close(); crashesFile.open(QIODevice::WriteOnly | QIODevice::Truncate); QDataStream writeStream(&crashesFile); writeStream << count; } QString repositoryPathForSession(const KDevelop::ISessionLock::Ptr& session) { QString cacheDir = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation); cacheDir += QStringLiteral("/kdevduchain"); QString baseDir = QProcessEnvironment::systemEnvironment().value(QStringLiteral("KDEV_DUCHAIN_DIR"), cacheDir); baseDir += QStringLiteral("/%1-%2").arg(qApp->applicationName(), session->id()); return baseDir; } bool shouldClear(const QString& path) { QDir dir(path); if (!dir.exists()) { return false; } if (getenv("CLEAR_DUCHAIN_DIR")) { qCDebug(SERIALIZATION) << "clearing duchain directory because CLEAR_DUCHAIN_DIR is set"; return true; } if (dir.exists(QStringLiteral("is_writing"))) { qCWarning(SERIALIZATION) << "repository" << path << "was write-locked, it probably is inconsistent"; return true; } if (!dir.exists(QStringLiteral("version_%1").arg(staticItemRepositoryVersion()))) { qCWarning(SERIALIZATION) << "version-hint not found, seems to be an old version"; return true; } QFile crashesFile(dir.filePath(QStringLiteral("crash_counter"))); if (crashesFile.open(QIODevice::ReadOnly)) { int count; QDataStream stream(&crashesFile); stream >> count; qCDebug(SERIALIZATION) << "current count of crashes: " << count; if (count >= crashesBeforeCleanup && !getenv("DONT_CLEAR_DUCHAIN_DIR")) { bool userAnswer = askUser(i18np("The previous session crashed", "Session crashed %1 times in a row", count), i18nc("@action", "Clear cache"), i18nc("@title", "Session crashed"), i18n("The crash may be caused by a corruption of cached data.\n\n" "Press OK if you want KDevelop to clear the cache, otherwise press Cancel if you are sure the crash has another origin.")); if (userAnswer) { qCDebug(SERIALIZATION) << "User chose to clean repository"; return true; } else { setCrashCounter(crashesFile, 1); qCDebug(SERIALIZATION) << "User chose to reset crash counter"; } } else { ///Increase the crash-count. It will be reset if kdevelop is shut down cleanly. setCrashCounter(crashesFile, ++count); } } else { setCrashCounter(crashesFile, 1); } return false; } } namespace KDevelop { struct ItemRepositoryRegistryPrivate { ItemRepositoryRegistry* m_owner; bool m_shallDelete; QString m_path; ISessionLock::Ptr m_sessionLock; QMap m_repositories; QMap m_customCounters; mutable QMutex m_mutex; explicit ItemRepositoryRegistryPrivate(ItemRepositoryRegistry* owner) : m_owner(owner) , m_shallDelete(false) , m_mutex(QMutex::Recursive) { } void lockForWriting(); void unlockForWriting(); void deleteDataDirectory(const QString& path, bool recreate = true); /// @param path A shared directory-path that the item-repositories are to be loaded from. /// @returns Whether the repository registry has been opened successfully. /// If @c false, then all registered repositories should have been deleted. /// @note Currently the given path must reference a hidden directory, just to make sure we're /// not accidentally deleting something important. bool open(const QString& path); /// Close all contained repositories. /// @warning The current state is not stored to disk. void close(); }; //The global item-reposity registry ItemRepositoryRegistry* ItemRepositoryRegistry::m_self = nullptr; ItemRepositoryRegistry::ItemRepositoryRegistry(const ISessionLock::Ptr& session) : d(new ItemRepositoryRegistryPrivate(this)) { Q_ASSERT(session); d->open(repositoryPathForSession(session)); } void ItemRepositoryRegistry::initialize(const ISessionLock::Ptr& session) { if (!m_self) { ///We intentionally leak the registry, to prevent problems in the destruction order, where ///the actual repositories might get deleted later than the repository registry. m_self = new ItemRepositoryRegistry(session); } } ItemRepositoryRegistry* ItemRepositoryRegistry::self() { Q_ASSERT(m_self); return m_self; } void ItemRepositoryRegistry::deleteRepositoryFromDisk(const ISessionLock::Ptr& session) { // Now, as we have only the global item-repository registry, assume that if and only if // the given session is ours, its cache path is used by the said global item-repository registry. const QString repositoryPath = repositoryPathForSession(session); if(m_self && m_self->d->m_path == repositoryPath) { // remove later m_self->d->m_shallDelete = true; } else { // Otherwise, given session is not ours. // remove its item-repository directory directly. QDir(repositoryPath).removeRecursively(); } } QMutex& ItemRepositoryRegistry::mutex() { return d->m_mutex; } QAtomicInt& ItemRepositoryRegistry::getCustomCounter(const QString& identity, int initialValue) { if(!d->m_customCounters.contains(identity)) d->m_customCounters.insert(identity, new QAtomicInt(initialValue)); return *d->m_customCounters[identity]; } ///The global item-repository registry that is used by default ItemRepositoryRegistry& globalItemRepositoryRegistry() { return *ItemRepositoryRegistry::self(); } void ItemRepositoryRegistry::registerRepository(AbstractItemRepository* repository, AbstractRepositoryManager* manager) { QMutexLocker lock(&d->m_mutex); d->m_repositories.insert(repository, manager); if(!d->m_path.isEmpty()) { if(!repository->open(d->m_path)) { d->deleteDataDirectory(d->m_path); qCritical() << "failed to open a repository"; abort(); } } } QString ItemRepositoryRegistry::path() const { //We cannot lock the mutex here, since this may be called with one of the repositories locked, //and that may lead to a deadlock when at the same time a storing is requested return d->m_path; } void ItemRepositoryRegistryPrivate::lockForWriting() { QMutexLocker lock(&m_mutex); //Create is_writing QFile f(m_path + "/is_writing"); f.open(QIODevice::WriteOnly); f.close(); } void ItemRepositoryRegistry::lockForWriting() { d->lockForWriting(); } void ItemRepositoryRegistryPrivate::unlockForWriting() { QMutexLocker lock(&m_mutex); //Delete is_writing QFile::remove(m_path + "/is_writing"); } void ItemRepositoryRegistry::unlockForWriting() { d->unlockForWriting(); } void ItemRepositoryRegistry::unRegisterRepository(AbstractItemRepository* repository) { QMutexLocker lock(&d->m_mutex); Q_ASSERT(d->m_repositories.contains(repository)); repository->close(); d->m_repositories.remove(repository); } //After calling this, the data-directory may be a new one void ItemRepositoryRegistryPrivate::deleteDataDirectory(const QString& path, bool recreate) { QMutexLocker lock(&m_mutex); //lockForWriting creates a file, that prevents any other KDevelop instance from using the directory as it is. //Instead, the other instance will try to delete the directory as well. lockForWriting(); bool result = QDir(path).removeRecursively(); Q_ASSERT(result); Q_UNUSED(result); // Just recreate the directory then; leave old path (as it is dependent on appname and session only). if(recreate) { QDir().mkpath(path); } } bool ItemRepositoryRegistryPrivate::open(const QString& path) { QMutexLocker mlock(&m_mutex); if(m_path == path) { return true; } // Check if the repository shall be cleared if (shouldClear(path)) { qCWarning(SERIALIZATION) << QStringLiteral("The data-repository at %1 has to be cleared.").arg(path); deleteDataDirectory(path); } QDir().mkpath(path); foreach(AbstractItemRepository* repository, m_repositories.keys()) { if(!repository->open(path)) { deleteDataDirectory(path); qCritical() << "failed to open a repository"; abort(); } } QFile f(path + "/Counters"); if(f.open(QIODevice::ReadOnly)) { QDataStream stream(&f); while(!stream.atEnd()) { //Read in all custom counter values QString counterName; stream >> counterName; int counterValue; stream >> counterValue; m_owner->getCustomCounter(counterName, 0) = counterValue; } } m_path = path; return true; } void ItemRepositoryRegistry::store() { QMutexLocker lock(&d->m_mutex); foreach(AbstractItemRepository* repository, d->m_repositories.keys()) { repository->store(); } QFile versionFile(d->m_path + QStringLiteral("/version_%1").arg(staticItemRepositoryVersion())); if(versionFile.open(QIODevice::WriteOnly)) { versionFile.close(); } else { qCWarning(SERIALIZATION) << "Could not open version file for writing"; } //Store all custom counter values QFile f(d->m_path + "/Counters"); if(f.open(QIODevice::WriteOnly)) { f.resize(0); QDataStream stream(&f); for(QMap::const_iterator it = d->m_customCounters.constBegin(); it != d->m_customCounters.constEnd(); ++it) { stream << it.key(); stream << it.value()->fetchAndAddRelaxed(0); } } else { qCWarning(SERIALIZATION) << "Could not open counter file for writing"; } } void ItemRepositoryRegistry::printAllStatistics() const { QMutexLocker lock(&d->m_mutex); foreach(AbstractItemRepository* repository, d->m_repositories.keys()) { qCDebug(SERIALIZATION) << "statistics in" << repository->repositoryName() << ":"; qCDebug(SERIALIZATION) << repository->printStatistics(); } } int ItemRepositoryRegistry::finalCleanup() { QMutexLocker lock(&d->m_mutex); int changed = false; foreach(AbstractItemRepository* repository, d->m_repositories.keys()) { int added = repository->finalCleanup(); changed += added; qCDebug(SERIALIZATION) << "cleaned in" << repository->repositoryName() << ":" << added; } return changed; } void ItemRepositoryRegistryPrivate::close() { QMutexLocker lock(&m_mutex); foreach(AbstractItemRepository* repository, m_repositories.keys()) { repository->close(); } m_path.clear(); } ItemRepositoryRegistry::~ItemRepositoryRegistry() { QMutexLocker lock(&d->m_mutex); d->close(); foreach(QAtomicInt * counter, d->m_customCounters) { delete counter; } delete d; } void ItemRepositoryRegistry::shutdown() { QMutexLocker lock(&d->m_mutex); QString path = d->m_path; // FIXME: we don't close since this can trigger crashes at shutdown // since some items are still referenced, e.g. in static variables // d->close(); if(d->m_shallDelete) { d->deleteDataDirectory(path, false); } else { QFile::remove(path + QLatin1String("/crash_counter")); } } } diff --git a/shell/CMakeLists.txt b/shell/CMakeLists.txt index f9df5b331..c279f7cb1 100644 --- a/shell/CMakeLists.txt +++ b/shell/CMakeLists.txt @@ -1,183 +1,188 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kdevplatform\") add_subdirectory(tests) set(KDevPlatformShell_LIB_SRCS workingsetcontroller.cpp workingsets/workingset.cpp workingsets/workingsetfilelabel.cpp workingsets/workingsettoolbutton.cpp workingsets/workingsettooltipwidget.cpp workingsets/workingsetwidget.cpp workingsets/closedworkingsetswidget.cpp workingsets/workingsethelpers.cpp mainwindow.cpp mainwindow_p.cpp plugincontroller.cpp ktexteditorpluginintegration.cpp shellextension.cpp core.cpp uicontroller.cpp colorschemechooser.cpp projectcontroller.cpp project.cpp partcontroller.cpp #document.cpp partdocument.cpp textdocument.cpp documentcontroller.cpp languagecontroller.cpp statusbar.cpp runcontroller.cpp unitylauncher.cpp sessioncontroller.cpp session.cpp sessionlock.cpp sessionchooserdialog.cpp savedialog.cpp sourceformattercontroller.cpp completionsettings.cpp openprojectpage.cpp openprojectdialog.cpp projectinfopage.cpp selectioncontroller.cpp documentationcontroller.cpp debugcontroller.cpp launchconfiguration.cpp launchconfigurationdialog.cpp loadedpluginsdialog.cpp testcontroller.cpp projectsourcepage.cpp - debug.cpp configdialog.cpp editorconfigpage.cpp environmentconfigurebutton.cpp checkerstatus.cpp problem.cpp problemmodelset.cpp problemmodel.cpp problemstore.cpp watcheddocumentset.cpp filteredproblemstore.cpp progresswidget/progressmanager.cpp progresswidget/statusbarprogresswidget.cpp progresswidget/overlaywidget.cpp progresswidget/progressdialog.cpp areadisplay.cpp settings/uipreferences.cpp settings/pluginpreferences.cpp settings/sourceformattersettings.cpp settings/editstyledialog.cpp settings/projectpreferences.cpp settings/environmentwidget.cpp settings/environmentprofilemodel.cpp settings/environmentprofilelistmodel.cpp settings/environmentpreferences.cpp settings/languagepreferences.cpp settings/bgpreferences.cpp settings/templateconfig.cpp settings/templatepage.cpp settings/analyzerspreferences.cpp settings/documentationpreferences.cpp ) if(APPLE) set(KDevPlatformShell_LIB_SRCS ${KDevPlatformShell_LIB_SRCS} macdockprogressview.mm ) endif() +ecm_qt_declare_logging_category(KDevPlatformShell_LIB_SRCS + HEADER debug.h + IDENTIFIER SHELL + CATEGORY_NAME "kdevplatform.shell" +) + kconfig_add_kcfg_files(KDevPlatformShell_LIB_SRCS settings/uiconfig.kcfgc settings/projectconfig.kcfgc settings/languageconfig.kcfgc settings/bgconfig.kcfgc ) ki18n_wrap_ui(KDevPlatformShell_LIB_SRCS projectinfopage.ui launchconfigurationdialog.ui projectsourcepage.ui settings/uiconfig.ui settings/editstyledialog.ui settings/sourceformattersettings.ui settings/projectpreferences.ui settings/environmentwidget.ui settings/languagepreferences.ui settings/bgpreferences.ui settings/templateconfig.ui settings/templatepage.ui ) qt5_add_resources(KDevPlatformShell_LIB_SRCS kdevplatformshell.qrc) kdevplatform_add_library(KDevPlatformShell SOURCES ${KDevPlatformShell_LIB_SRCS}) target_link_libraries(KDevPlatformShell LINK_PUBLIC KF5::XmlGui KDev::Sublime KDev::OutputView KDev::Interfaces LINK_PRIVATE KF5::GuiAddons KF5::ConfigWidgets KF5::IconThemes KF5::KIOFileWidgets KF5::KIOWidgets KF5::Parts KF5::Notifications KF5::NotifyConfig KF5::TextEditor KF5::ThreadWeaver KF5::JobWidgets KF5::ItemViews KF5::WindowSystem KF5::KCMUtils #for KPluginSelector, not sure why it is in kcmutils KF5::NewStuff # template config page KF5::Archive # template config page KDev::Debugger KDev::Project KDev::Vcs KDev::Language KDev::Util KDev::Documentation ) if(APPLE) target_link_libraries(KDevPlatformShell PRIVATE "-framework AppKit") endif() install(FILES mainwindow.h plugincontroller.h shellextension.h core.h uicontroller.h colorschemechooser.h projectcontroller.h project.h partcontroller.h partdocument.h textdocument.h documentcontroller.h languagecontroller.h session.h sessioncontroller.h sessionlock.h sourceformattercontroller.h selectioncontroller.h runcontroller.h launchconfiguration.h environmentconfigurebutton.h checkerstatus.h problem.h problemmodel.h problemmodelset.h problemconstants.h filteredproblemstore.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/kdevplatform/shell COMPONENT Devel ) diff --git a/shell/configdialog.cpp b/shell/configdialog.cpp index d01daf919..f8932cbbf 100644 --- a/shell/configdialog.cpp +++ b/shell/configdialog.cpp @@ -1,216 +1,215 @@ /* * This file is part of KDevelop * Copyright 2014 Alex Richardson * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "configdialog.h" #include "debug.h" #include -#include #include #include #include #include #include #include #include #include using namespace KDevelop; //FIXME: unit test this code! ConfigDialog::ConfigDialog(QWidget* parent) : KPageDialog(parent) , m_currentPageHasChanges(false) { setWindowTitle(i18n("Configure")); setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Apply | QDialogButtonBox::Cancel | QDialogButtonBox::RestoreDefaults); button(QDialogButtonBox::Apply)->setEnabled(false); setObjectName(QStringLiteral("configdialog")); auto onApplyClicked = [this] { auto page = qobject_cast(currentPage()->widget()); Q_ASSERT(page); applyChanges(page); }; connect(button(QDialogButtonBox::Apply), &QPushButton::clicked, onApplyClicked); connect(button(QDialogButtonBox::Ok), &QPushButton::clicked, onApplyClicked); connect(button(QDialogButtonBox::RestoreDefaults), &QPushButton::clicked, [this]() { auto page = qobject_cast(currentPage()->widget()); Q_ASSERT(page); page->defaults(); }); connect(this, &KPageDialog::currentPageChanged, this, &ConfigDialog::checkForUnsavedChanges); // make sure we don't keep any entries for unloaded plugins connect(ICore::self()->pluginController(), &IPluginController::unloadingPlugin, this, &ConfigDialog::removePagesForPlugin); } KPageWidgetItem* ConfigDialog::itemForPage(ConfigPage* page) const { for (auto& item : m_pages) { if (item->widget() == page) { return item; } } return nullptr; } int ConfigDialog::checkForUnsavedChanges(KPageWidgetItem* current, KPageWidgetItem* before) { Q_UNUSED(current); if (!m_currentPageHasChanges) { return KMessageBox::Yes; } // before must be non-null, because if we change from nothing to a new page m_currentPageHasChanges must also be false! Q_ASSERT(before); auto oldPage = qobject_cast(before->widget()); Q_ASSERT(oldPage); auto dialogResult = KMessageBox::warningYesNoCancel(this, i18n("The settings of the current module have changed.\n" "Do you want to apply the changes or discard them?"), i18n("Apply Settings"), KStandardGuiItem::apply(), KStandardGuiItem::discard(), KStandardGuiItem::cancel()); if (dialogResult == KMessageBox::No) { oldPage->reset(); m_currentPageHasChanges = false; button(QDialogButtonBox::Apply)->setEnabled(false); } else if (dialogResult == KMessageBox::Yes) { applyChanges(oldPage); } else if (dialogResult == KMessageBox::Cancel) { // restore old state QSignalBlocker block(this); // prevent recursion setCurrentPage(before); } return dialogResult; } void ConfigDialog::closeEvent(QCloseEvent* event) { if (checkForUnsavedChanges(currentPage(), currentPage()) == KMessageBox::Cancel) { // if the user clicked cancel he wants to continue editing the current page -> don't close event->ignore(); } else { event->accept(); } } void ConfigDialog::removeConfigPage(ConfigPage* page) { auto item = itemForPage(page); Q_ASSERT(item); removePage(item); m_pages.removeAll(QPointer(item)); // also remove all items that were deleted because a parent KPageWidgetItem was removed m_pages.removeAll(QPointer()); } void ConfigDialog::removePagesForPlugin(IPlugin* plugin) { Q_ASSERT(plugin); foreach (auto&& item, m_pages) { if (!item) { continue; } auto page = qobject_cast(item->widget()); if (page && page->plugin() == plugin) { removePage(item); // this also deletes the config page -> QPointer is set to null } }; // also remove all items that were deleted because a parent KPageWidgetItem was removed m_pages.removeAll(QPointer()); } void ConfigDialog::addConfigPage(ConfigPage* page, ConfigPage* previous) { if (previous) { auto previousItem = itemForPage(previous); Q_ASSERT(previousItem); addConfigPageInternal(insertPage(previousItem, page, page->name()), page); } else { addConfigPageInternal(addPage(page, page->name()), page); } } void ConfigDialog::addSubConfigPage(ConfigPage* parentPage, ConfigPage* page) { auto item = itemForPage(parentPage); Q_ASSERT(item); addConfigPageInternal(addSubPage(item, page, page->name()), page); } void ConfigDialog::addConfigPageInternal(KPageWidgetItem* item, ConfigPage* page) { item->setHeader(page->fullName()); item->setIcon(page->icon()); page->initConfigManager(); page->reset(); // make sure all widgets are in the correct state // make sure that we only connect to changed after calling reset() connect(page, &ConfigPage::changed, this, &ConfigDialog::onPageChanged); m_pages.append(item); for (int i = 0; i < page->childPages(); ++i) { auto child = page->childPage(i); addSubConfigPage(page, child); } } void ConfigDialog::onPageChanged() { QObject* from = sender(); if (from && from != currentPage()->widget()) { qCWarning(SHELL) << "Settings in config page" << from << "changed, while" << currentPage()->widget() << "is currently selected. This case is not implemented yet."; return; // TODO: add a QHash as a member to make sure the apply button is always correct // TODO: when pressing okay show confirm dialog if other pages have changed or just silently apply every page? "Items on other pages have changed, do you wish to review those changes? + list with changed pages." } if (!m_currentlyApplyingChanges) { // e.g. PluginPreferences emits changed() from its apply method, better fix this here than having to // ensure that no plugin emits changed() from apply() // together with KPageDialog emitting currentPageChanged("Plugins", nullptr) this could cause a crash // when we dereference before m_currentPageHasChanges = true; button(QDialogButtonBox::Apply)->setEnabled(true); } } void ConfigDialog::applyChanges(ConfigPage* page) { // must set this to false before calling apply, otherwise we get the confirmation dialog // whenever we enable/disable plugins. // This is because KPageWidget then emits currentPageChanged("Plugins", nullptr), which seems like a bug to me, // it should rather emit currentPageChanged("Plugins", "Plugins") or even better nothing at all, since the current // page did not actually change! // TODO: fix KPageWidget m_currentPageHasChanges = false; m_currentlyApplyingChanges = true; page->apply(); m_currentlyApplyingChanges = false; Q_ASSERT(!m_currentPageHasChanges); button(QDialogButtonBox::Apply)->setEnabled(false); emit configSaved(page); } diff --git a/shell/debug.cpp b/shell/debug.cpp deleted file mode 100644 index 1b4e74e8e..000000000 --- a/shell/debug.cpp +++ /dev/null @@ -1,22 +0,0 @@ -/* - * This file is part of KDevelop - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#include "debug.h" - -Q_LOGGING_CATEGORY(SHELL, "kdevplatform.shell") diff --git a/shell/debug.h b/shell/debug.h deleted file mode 100644 index 6a2f9975d..000000000 --- a/shell/debug.h +++ /dev/null @@ -1,26 +0,0 @@ -/* - * This file is part of KDevelop - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#ifndef KDEVPLATFORM_SHELL_DEBUG_H -#define KDEVPLATFORM_SHELL_DEBUG_H - -#include -Q_DECLARE_LOGGING_CATEGORY(SHELL) - -#endif diff --git a/shell/loadedpluginsdialog.cpp b/shell/loadedpluginsdialog.cpp index a87c0aae3..ed13541e4 100644 --- a/shell/loadedpluginsdialog.cpp +++ b/shell/loadedpluginsdialog.cpp @@ -1,306 +1,305 @@ /************************************************************************** * Copyright 2009 Andreas Pakulat * * Copyright 2010 Niko Sams * * * * 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 "loadedpluginsdialog.h" #include -#include #include #include #include #include #include #include #include #include #include #include #include #include "core.h" #include "plugincontroller.h" #define MARGIN 5 namespace { KPluginMetaData pluginInfo(KDevelop::IPlugin* plugin) { return KDevelop::Core::self()->pluginControllerInternal()->pluginInfo(plugin); }; QString displayName(KDevelop::IPlugin* plugin) { const auto name = pluginInfo(plugin).name(); return !name.isEmpty() ? name : plugin->componentName(); } bool sortPlugins(KDevelop::IPlugin* l, KDevelop::IPlugin* r) { return displayName(l) < displayName(r); } } class PluginsModel : public QAbstractListModel { Q_OBJECT public: enum ExtraRoles { DescriptionRole = Qt::UserRole+1 }; explicit PluginsModel(QObject* parent = nullptr) : QAbstractListModel(parent) { m_plugins = KDevelop::Core::self()->pluginControllerInternal()->loadedPlugins(); std::sort(m_plugins.begin(), m_plugins.end(), sortPlugins); } KDevelop::IPlugin *pluginForIndex(const QModelIndex& index) const { if (!index.isValid()) return nullptr; if (index.parent().isValid()) return nullptr; if (index.column() != 0) return nullptr; if (index.row() >= m_plugins.count()) return nullptr; return m_plugins[index.row()]; } QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override { KDevelop::IPlugin* plugin = pluginForIndex(index); if (!plugin) return QVariant(); switch (role) { case Qt::DisplayRole: return displayName(plugin); case DescriptionRole: return pluginInfo(plugin).description(); case Qt::DecorationRole: return pluginInfo(plugin).iconName(); default: return QVariant(); }; } int rowCount(const QModelIndex& parent = QModelIndex()) const override { if (!parent.isValid()) { return m_plugins.count(); } return 0; } private: QList m_plugins; }; class LoadedPluginsDelegate : public KWidgetItemDelegate { Q_OBJECT public: explicit LoadedPluginsDelegate(QAbstractItemView *itemView, QObject *parent = nullptr) : KWidgetItemDelegate(itemView, parent) , pushButton(new QPushButton) { pushButton->setIcon(QIcon::fromTheme(QStringLiteral("dialog-information"))); // only for getting size matters } ~LoadedPluginsDelegate() override { delete pushButton; } QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override { int i = 5; int j = 1; QFont font = titleFont(option.font); QFontMetrics fmTitle(font); return QSize(qMax(fmTitle.width(index.model()->data(index, Qt::DisplayRole).toString()), option.fontMetrics.width(index.model()->data(index, PluginsModel::DescriptionRole).toString())) + KIconLoader::SizeMedium + MARGIN * i + pushButton->sizeHint().width() * j, qMax(KIconLoader::SizeMedium + MARGIN * 2, fmTitle.height() + option.fontMetrics.height() + MARGIN * 2)); } void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override { if (!index.isValid()) { return; } painter->save(); QApplication::style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, nullptr); int iconSize = option.rect.height() - MARGIN * 2; QIcon icon = QIcon::fromTheme(index.model()->data(index, Qt::DecorationRole).toString()); icon.paint(painter, QRect(dependantLayoutValue(MARGIN + option.rect.left(), iconSize, option.rect.width()), MARGIN + option.rect.top(), iconSize, iconSize)); QRect contentsRect(dependantLayoutValue(MARGIN * 2 + iconSize + option.rect.left(), option.rect.width() - MARGIN * 3 - iconSize, option.rect.width()), MARGIN + option.rect.top(), option.rect.width() - MARGIN * 3 - iconSize, option.rect.height() - MARGIN * 2); int lessHorizontalSpace = MARGIN * 2 + pushButton->sizeHint().width(); contentsRect.setWidth(contentsRect.width() - lessHorizontalSpace); if (option.state & QStyle::State_Selected) { painter->setPen(option.palette.highlightedText().color()); } if (itemView()->layoutDirection() == Qt::RightToLeft) { contentsRect.translate(lessHorizontalSpace, 0); } painter->save(); painter->save(); QFont font = titleFont(option.font); QFontMetrics fmTitle(font); painter->setFont(font); painter->drawText(contentsRect, Qt::AlignLeft | Qt::AlignTop, fmTitle.elidedText(index.model()->data(index, Qt::DisplayRole).toString(), Qt::ElideRight, contentsRect.width())); painter->restore(); painter->drawText(contentsRect, Qt::AlignLeft | Qt::AlignBottom, option.fontMetrics.elidedText(index.model()->data(index, PluginsModel::DescriptionRole).toString(), Qt::ElideRight, contentsRect.width())); painter->restore(); painter->restore(); } QList createItemWidgets(const QModelIndex &index) const override { Q_UNUSED(index); QPushButton *button = new QPushButton(); button->setIcon(QIcon::fromTheme(QStringLiteral("dialog-information"))); setBlockedEventTypes(button, QList() << QEvent::MouseButtonPress << QEvent::MouseButtonRelease << QEvent::MouseButtonDblClick); connect(button, &QPushButton::clicked, this, &LoadedPluginsDelegate::info); return QList() << button; } void updateItemWidgets(const QList widgets, const QStyleOptionViewItem &option, const QPersistentModelIndex &index) const override { Q_UNUSED(index); if (widgets.isEmpty()) { return; } QPushButton *aboutPushButton = static_cast(widgets[0]); QSize aboutPushButtonSizeHint = aboutPushButton->sizeHint(); aboutPushButton->resize(aboutPushButtonSizeHint); aboutPushButton->move(dependantLayoutValue(option.rect.width() - MARGIN - aboutPushButtonSizeHint.width(), aboutPushButtonSizeHint.width(), option.rect.width()), option.rect.height() / 2 - aboutPushButtonSizeHint.height() / 2); } int dependantLayoutValue(int value, int width, int totalWidth) const { if (itemView()->layoutDirection() == Qt::LeftToRight) { return value; } return totalWidth - width - value; } QFont titleFont(const QFont &baseFont) const { QFont retFont(baseFont); retFont.setBold(true); return retFont; } private Q_SLOTS: void info() { PluginsModel *m = static_cast(itemView()->model()); KDevelop::IPlugin *p = m->pluginForIndex(focusedIndex()); if (p) { KAboutData aboutData = KAboutData::fromPluginMetaData(pluginInfo(p)); if (!aboutData.componentName().isEmpty()) { // Be sure the about data is not completely empty KAboutApplicationDialog aboutPlugin(aboutData, itemView()); aboutPlugin.exec(); return; } } } private: QPushButton *pushButton; }; class PluginsView : public QListView { Q_OBJECT public: explicit PluginsView(QWidget* parent = nullptr) :QListView(parent) { setModel(new PluginsModel(this)); setItemDelegate(new LoadedPluginsDelegate(this)); setVerticalScrollMode(QListView::ScrollPerPixel); } ~PluginsView() override { // explicitly delete the delegate here since otherwise // we get spammed by warnings that the QPushButton we return // in createItemWidgets is deleted before the delegate // *sigh* - even dfaure says KWidgetItemDelegate is a crude hack delete itemDelegate(); } QSize sizeHint() const override { QSize ret = QListView::sizeHint(); ret.setWidth(qMax(ret.width(), sizeHintForColumn(0) + 30)); return ret; } }; LoadedPluginsDialog::LoadedPluginsDialog( QWidget* parent ) : QDialog( parent ) { setWindowTitle(i18n("Loaded Plugins")); QVBoxLayout* vbox = new QVBoxLayout(this); KTitleWidget* title = new KTitleWidget(this); title->setPixmap(QIcon::fromTheme(KAboutData::applicationData().programIconName()), KTitleWidget::ImageLeft); title->setText(i18n("Plugins loaded for %1", KAboutData::applicationData().displayName())); vbox->addWidget(title); vbox->addWidget(new PluginsView()); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close); connect(buttonBox, &QDialogButtonBox::accepted, this, &LoadedPluginsDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &LoadedPluginsDialog::reject); buttonBox->button(QDialogButtonBox::Close)->setDefault(true); vbox->addWidget(buttonBox); } #include "moc_loadedpluginsdialog.cpp" #include "loadedpluginsdialog.moc" diff --git a/shell/openprojectdialog.cpp b/shell/openprojectdialog.cpp index df391abd1..e621edadc 100644 --- a/shell/openprojectdialog.cpp +++ b/shell/openprojectdialog.cpp @@ -1,362 +1,362 @@ /*************************************************************************** * Copyright (C) 2008 by Andreas Pakulat #include #include -#include #include #include #include #include #include #include "core.h" #include "uicontroller.h" #include "plugincontroller.h" #include "mainwindow.h" #include "shellextension.h" #include "projectsourcepage.h" +#include #include namespace { struct URLInfo { bool isValid; bool isDir; QString extension; }; URLInfo getUrlInfo(const QUrl& url) { URLInfo ret; ret.isValid = false; if (url.isLocalFile()) { QFileInfo info(url.toLocalFile()); ret.isValid = info.exists(); if (ret.isValid) { ret.isDir = info.isDir(); ret.extension = info.suffix(); } } else if (url.isValid()) { KIO::StatJob* statJob = KIO::stat(url, KIO::HideProgressInfo); KJobWidgets::setWindow(statJob, KDevelop::Core::self()->uiControllerInternal()->defaultMainWindow()); ret.isValid = statJob->exec(); // TODO: do this asynchronously so that the user isn't blocked while typing every letter of the hostname in sftp://hostname if (ret.isValid) { KIO::UDSEntry entry = statJob->statResult(); ret.isDir = entry.isDir(); ret.extension = QFileInfo(entry.stringValue(KIO::UDSEntry::UDS_NAME)).suffix(); } } return ret; } } namespace KDevelop { OpenProjectDialog::OpenProjectDialog(bool fetch, const QUrl& startUrl, const QUrl& repoUrl, IPlugin* vcsOrProviderPlugin, QWidget* parent) : KAssistantDialog( parent ) , m_urlIsDirectory(false) , sourcePage(nullptr) , openPage(nullptr) , projectInfoPage(nullptr) { resize(QSize(700, 500)); // KAssistantDialog creates a help button by default, no option to prevent that auto helpButton = button(QDialogButtonBox::Help); if (helpButton) { buttonBox()->removeButton(helpButton); delete helpButton; } const bool useKdeFileDialog = qEnvironmentVariableIsSet("KDE_FULL_SESSION"); QStringList filters, allEntry; QString filterFormat = useKdeFileDialog ? QStringLiteral("%1|%2 (%1)") : QStringLiteral("%2 (%1)"); allEntry << "*." + ShellExtension::getInstance()->projectFileExtension(); filters << filterFormat.arg("*." + ShellExtension::getInstance()->projectFileExtension(), ShellExtension::getInstance()->projectFileDescription()); QVector plugins = ICore::self()->pluginController()->queryExtensionPlugins(QStringLiteral("org.kdevelop.IProjectFileManager")); foreach(const KPluginMetaData& info, plugins) { QStringList filter = KPluginMetaData::readStringList(info.rawData(), QStringLiteral("X-KDevelop-ProjectFilesFilter")); QString desc = info.value(QStringLiteral("X-KDevelop-ProjectFilesFilterDescription")); m_projectFilters.insert(info.name(), filter); m_projectPlugins.insert(info.name(), info); allEntry += filter; filters << filterFormat.arg(filter.join(QStringLiteral(" ")), desc); } if (useKdeFileDialog) filters.prepend(i18n("%1|All Project Files (%1)", allEntry.join(QStringLiteral(" ")))); QUrl start = startUrl.isValid() ? startUrl : Core::self()->projectController()->projectsBaseDirectory(); start = start.adjusted(QUrl::NormalizePathSegments); KPageWidgetItem* currentPage = nullptr; if( fetch ) { sourcePageWidget = new ProjectSourcePage(start, repoUrl, vcsOrProviderPlugin, this); connect( sourcePageWidget, &ProjectSourcePage::isCorrect, this, &OpenProjectDialog::validateSourcePage ); sourcePage = addPage( sourcePageWidget, i18n("Select Source") ); currentPage = sourcePage; } if (useKdeFileDialog) { openPageWidget = new OpenProjectPage( start, filters, this ); connect( openPageWidget, &OpenProjectPage::urlSelected, this, &OpenProjectDialog::validateOpenUrl ); connect( openPageWidget, &OpenProjectPage::accepted, this, &OpenProjectDialog::openPageAccepted ); openPage = addPage( openPageWidget, i18n("Select a build system setup file, existing KDevelop project, " "or any folder to open as a project") ); if (!currentPage) { currentPage = openPage; } } else { nativeDialog = new QFileDialog(this, i18n("Open Project")); nativeDialog->setDirectoryUrl(start); nativeDialog->setFileMode(QFileDialog::Directory); } ProjectInfoPage* page = new ProjectInfoPage( this ); connect( page, &ProjectInfoPage::projectNameChanged, this, &OpenProjectDialog::validateProjectName ); connect( page, &ProjectInfoPage::projectManagerChanged, this, &OpenProjectDialog::validateProjectManager ); projectInfoPage = addPage( page, i18n("Project Information") ); if (!currentPage) { currentPage = projectInfoPage; } setValid( sourcePage, false ); setValid( openPage, false ); setValid( projectInfoPage, false); setAppropriate( projectInfoPage, false ); setCurrentPage( currentPage ); setWindowTitle(i18n("Open Project")); } bool OpenProjectDialog::execNativeDialog() { while (true) { if (nativeDialog->exec()) { QUrl selectedUrl = nativeDialog->selectedUrls().at(0); if (getUrlInfo(selectedUrl).isValid) { // validate directory first to populate m_projectName and m_projectManager validateOpenUrl(selectedUrl.adjusted(QUrl::RemoveFilename)); validateOpenUrl(selectedUrl); return true; } } else { return false; } } } int OpenProjectDialog::exec() { if (nativeDialog && !execNativeDialog()) { reject(); return QDialog::Rejected; } return KAssistantDialog::exec(); } void OpenProjectDialog::validateSourcePage(bool valid) { setValid(sourcePage, valid); if (!nativeDialog) { openPageWidget->setUrl(sourcePageWidget->workingDir()); } } void OpenProjectDialog::validateOpenUrl( const QUrl& url_ ) { URLInfo urlInfo = getUrlInfo(url_); const QUrl url = url_.adjusted(QUrl::StripTrailingSlash); // openPage is used only in KDE if (openPage) { if ( urlInfo.isValid ) { // reset header openPage->setHeader(i18n("Open \"%1\" as project", url.fileName())); } else { // report error KColorScheme scheme(palette().currentColorGroup()); const QString errorMsg = i18n("Selected URL is invalid"); openPage->setHeader(QStringLiteral("%2") .arg(scheme.foreground(KColorScheme::NegativeText).color().name(), errorMsg) ); setAppropriate( projectInfoPage, false ); setAppropriate( openPage, true ); setValid( openPage, false ); return; } } m_selected = url; if( urlInfo.isDir || urlInfo.extension != ShellExtension::getInstance()->projectFileExtension() ) { m_urlIsDirectory = urlInfo.isDir; setAppropriate( projectInfoPage, true ); m_url = url; if( !urlInfo.isDir ) { m_url = m_url.adjusted(QUrl::StripTrailingSlash | QUrl::RemoveFilename); } ProjectInfoPage* page = qobject_cast( projectInfoPage->widget() ); if( page ) { page->setProjectName( m_url.fileName() ); // clear the filelist m_fileList.clear(); if( urlInfo.isDir ) { // If a dir was selected fetch all files in it KIO::ListJob* job = KIO::listDir( m_url ); connect( job, &KIO::ListJob::entries, this, &OpenProjectDialog::storeFileList); KJobWidgets::setWindow(job, Core::self()->uiController()->activeMainWindow()); job->exec(); } else { // Else we'll just take the given file m_fileList << url.fileName(); } // Now find a manager for the file(s) in our filelist. QVector choices; Q_FOREACH ( const auto& file, m_fileList ) { auto plugins = projectManagerForFile(file); if ( plugins.contains(QStringLiteral("")) ) { plugins.removeAll(QStringLiteral("")); choices.append({i18n("Open existing file \"%1\"", file), "", QString()}); } Q_FOREACH ( const auto& plugin, plugins ) { auto meta = m_projectPlugins.value(plugin); choices.append({file + QString(" (%1)").arg(plugin), meta.pluginId(), meta.iconName(), file}); } } Q_FOREACH ( const auto& plugin, m_projectFilters.keys() ) { - qDebug() << plugin << m_projectFilters.value(plugin); + qCDebug(SHELL) << plugin << m_projectFilters.value(plugin); if ( m_projectFilters.value(plugin).isEmpty() ) { // that works in any case auto meta = m_projectPlugins.value(plugin); choices.append({plugin, meta.pluginId(), meta.iconName()}); } } page->populateProjectFileCombo(choices); } m_url.setPath( m_url.path() + '/' + m_url.fileName() + '.' + ShellExtension::getInstance()->projectFileExtension() ); } else { setAppropriate( projectInfoPage, false ); m_url = url; m_urlIsDirectory = false; } validateProjectInfo(); setValid( openPage, true ); } QStringList OpenProjectDialog::projectManagerForFile(const QString& file) const { QStringList ret; foreach( const QString& manager, m_projectFilters.keys() ) { foreach( const QString& filterexp, m_projectFilters.value(manager) ) { QRegExp exp( filterexp, Qt::CaseSensitive, QRegExp::Wildcard ); if ( exp.exactMatch(file) ) { ret.append(manager); } } } if ( file.endsWith(ShellExtension::getInstance()->projectFileExtension()) ) { ret.append(QStringLiteral("")); } return ret; } void OpenProjectDialog::openPageAccepted() { if ( isValid( openPage ) ) { next(); } } void OpenProjectDialog::validateProjectName( const QString& name ) { m_projectName = name; validateProjectInfo(); } void OpenProjectDialog::validateProjectInfo() { setValid( projectInfoPage, (!projectName().isEmpty() && !projectManager().isEmpty()) ); } void OpenProjectDialog::validateProjectManager( const QString& manager, const QString & fileName ) { m_projectManager = manager; if ( m_urlIsDirectory ) { m_selected = m_url.resolved( QUrl("./" + fileName) ); } validateProjectInfo(); } QUrl OpenProjectDialog::projectFileUrl() const { return m_url; } QUrl OpenProjectDialog::selectedUrl() const { return m_selected; } QString OpenProjectDialog::projectName() const { return m_projectName; } QString OpenProjectDialog::projectManager() const { return m_projectManager; } void OpenProjectDialog::storeFileList(KIO::Job*, const KIO::UDSEntryList& list) { foreach( const KIO::UDSEntry& entry, list ) { QString name = entry.stringValue( KIO::UDSEntry::UDS_NAME ); if( name != QLatin1String(".") && name != QLatin1String("..") && !entry.isDir() ) { m_fileList << name; } } } } diff --git a/shell/partcontroller.cpp b/shell/partcontroller.cpp index c2089e6ac..814953b29 100644 --- a/shell/partcontroller.cpp +++ b/shell/partcontroller.cpp @@ -1,327 +1,326 @@ /*************************************************************************** * Copyright 2006 Adam Treat * * Copyright 2007 Alexander Dymo * * Copyright 2015 Kevin Funk * * * * 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 "partcontroller.h" #include -#include #include #include #include #include #include #include #include #include #include #include #include #include "core.h" #include "textdocument.h" #include "debug.h" #include "uicontroller.h" #include "mainwindow.h" #include #include #include #include namespace KDevelop { class PartControllerPrivate { public: PartControllerPrivate() {} bool m_showTextEditorStatusBar = false; QString m_editor; QStringList m_textTypes; Core *m_core; }; PartController::PartController(Core *core, QWidget *toplevel) : IPartController( toplevel ), d(new PartControllerPrivate) { setObjectName(QStringLiteral("PartController")); d->m_core = core; //Cache this as it is too expensive when creating parts // KConfig * config = Config::standard(); // config->setGroup( "General" ); // // d->m_textTypes = config->readEntry( "TextTypes", QStringList() ); // // config ->setGroup( "Editor" ); // d->m_editor = config->readPathEntry( "EmbeddedKTextEditor", QString() ); // required early because some actions are checkable and need to be initialized loadSettings(false); if (!(Core::self()->setupFlags() & Core::NoUi)) setupActions(); } PartController::~PartController() { delete d; } bool PartController::showTextEditorStatusBar() const { return d->m_showTextEditorStatusBar; } void PartController::setShowTextEditorStatusBar(bool show) { if (d->m_showTextEditorStatusBar == show) return; d->m_showTextEditorStatusBar = show; // update foreach (Sublime::Area* area, Core::self()->uiControllerInternal()->allAreas()) { foreach (Sublime::View* view, area->views()) { if (!view->hasWidget()) continue; auto textView = qobject_cast(view->widget()); if (textView) { textView->setStatusBarEnabled(show); } } } // also notify active view that it should update the "view status" TextView* textView = qobject_cast(Core::self()->uiControllerInternal()->activeSublimeWindow()->activeView()); if (textView) { emit textView->statusChanged(textView); } } //MOVE BACK TO DOCUMENTCONTROLLER OR MULTIBUFFER EVENTUALLY bool PartController::isTextType(const QMimeType& mimeType) { bool isTextType = false; if (d->m_textTypes.contains(mimeType.name())) { isTextType = true; } // is this regular text - open in editor return ( isTextType || mimeType.inherits(QStringLiteral("text/plain")) || mimeType.inherits(QStringLiteral("text/html")) || mimeType.inherits(QStringLiteral("application/x-zerosize"))); } KTextEditor::Editor* PartController::editorPart() const { return KTextEditor::Editor::instance(); } KTextEditor::Document* PartController::createTextPart(const QString &encoding) { KTextEditor::Document* doc = editorPart()->createDocument(this); if ( !encoding.isNull() ) { KParts::OpenUrlArguments args = doc->arguments(); args.setMimeType( QLatin1String( "text/plain;" ) + encoding ); doc->setArguments( args ); } return doc; } KParts::Part* PartController::createPart( const QString & mimeType, const QString & partType, const QString & className, const QString & preferredName ) { KPluginFactory * editorFactory = findPartFactory( mimeType, partType, preferredName ); if ( !className.isEmpty() && editorFactory ) { return editorFactory->create( nullptr, this, className.toLatin1() ); } return nullptr; } bool PartController::canCreatePart(const QUrl& url) { if (!url.isValid()) return false; QString mimeType; if ( url.isEmpty() ) mimeType = QStringLiteral("text/plain"); else mimeType = QMimeDatabase().mimeTypeForUrl(url).name(); KService::List offers = KMimeTypeTrader::self()->query( mimeType, QStringLiteral("KParts/ReadOnlyPart") ); return offers.count() > 0; } KParts::Part* PartController::createPart( const QUrl & url, const QString& preferredPart ) { qCDebug(SHELL) << "creating part with url" << url << "and pref part:" << preferredPart; QString mimeType; if ( url.isEmpty() ) //create a part for empty text file mimeType = QStringLiteral("text/plain"); else if ( !url.isValid() ) return nullptr; else mimeType = QMimeDatabase().mimeTypeForUrl(url).name(); KParts::Part* part = createPart( mimeType, preferredPart ); if( part ) { readOnly( part ) ->openUrl( url ); return part; } return nullptr; } KParts::ReadOnlyPart* PartController::activeReadOnly() const { return readOnly( activePart() ); } KParts::ReadWritePart* PartController::activeReadWrite() const { return readWrite( activePart() ); } KParts::ReadOnlyPart* PartController::readOnly( KParts::Part * part ) const { return qobject_cast( part ); } KParts::ReadWritePart* PartController::readWrite( KParts::Part * part ) const { return qobject_cast( part ); } void PartController::loadSettings( bool projectIsLoaded ) { Q_UNUSED( projectIsLoaded ); KConfigGroup cg(KSharedConfig::openConfig(), "UiSettings"); d->m_showTextEditorStatusBar = cg.readEntry("ShowTextEditorStatusBar", false); } void PartController::saveSettings( bool projectIsLoaded ) { Q_UNUSED( projectIsLoaded ); KConfigGroup cg(KSharedConfig::openConfig(), "UiSettings"); cg.writeEntry("ShowTextEditorStatusBar", d->m_showTextEditorStatusBar); } void PartController::initialize() { } void PartController::cleanup() { saveSettings(false); } void PartController::setupActions() { KActionCollection* actionCollection = d->m_core->uiControllerInternal()->defaultMainWindow()->actionCollection(); QAction* action; action = KStandardAction::showStatusbar(this, SLOT(setShowTextEditorStatusBar(bool)), actionCollection); action->setWhatsThis(i18n("Use this command to show or hide the view's statusbar")); action->setChecked(showTextEditorStatusBar()); } //BEGIN KTextEditor::MdiContainer void PartController::setActiveView(KTextEditor::View *view) { Q_UNUSED(view) // NOTE: not implemented } KTextEditor::View *PartController::activeView() { TextView* textView = dynamic_cast(Core::self()->uiController()->activeArea()->activeView()); if (textView) { return textView->textView(); } return nullptr; } KTextEditor::Document *PartController::createDocument() { // NOTE: not implemented qCWarning(SHELL) << "WARNING: interface call not implemented"; return nullptr; } bool PartController::closeDocument(KTextEditor::Document *doc) { Q_UNUSED(doc) // NOTE: not implemented qCWarning(SHELL) << "WARNING: interface call not implemented"; return false; } KTextEditor::View *PartController::createView(KTextEditor::Document *doc) { Q_UNUSED(doc) // NOTE: not implemented qCWarning(SHELL) << "WARNING: interface call not implemented"; return nullptr; } bool PartController::closeView(KTextEditor::View *view) { Q_UNUSED(view) // NOTE: not implemented qCWarning(SHELL) << "WARNING: interface call not implemented"; return false; } //END KTextEditor::MdiContainer } diff --git a/shell/plugincontroller.cpp b/shell/plugincontroller.cpp index 254729397..91331da91 100644 --- a/shell/plugincontroller.cpp +++ b/shell/plugincontroller.cpp @@ -1,792 +1,791 @@ /* This file is part of the KDE project Copyright 2004, 2007 Alexander Dymo Copyright 2006 Matt Rogers Based on code from Kopete Copyright (c) 2002-2003 Martijn Klingens 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 "plugincontroller.h" -#include #include #include #include #include #include #include #include #include #include #include #include #include #include "core.h" #include "shellextension.h" #include "runcontroller.h" #include "debugcontroller.h" #include "documentationcontroller.h" #include "sourceformattercontroller.h" #include "projectcontroller.h" #include "ktexteditorpluginintegration.h" #include "debug.h" namespace { inline QString KEY_Plugins() { return QStringLiteral("Plugins"); } inline QString KEY_Suffix_Enabled() { return QStringLiteral("Enabled"); } inline QString KEY_LoadMode() { return QStringLiteral("X-KDevelop-LoadMode"); } inline QString KEY_Category() { return QStringLiteral("X-KDevelop-Category"); } inline QString KEY_Mode() { return QStringLiteral("X-KDevelop-Mode"); } inline QString KEY_Version() { return QStringLiteral("X-KDevelop-Version"); } inline QString KEY_Interfaces() { return QStringLiteral("X-KDevelop-Interfaces"); } inline QString KEY_Required() { return QStringLiteral("X-KDevelop-IRequired"); } inline QString KEY_Optional() { return QStringLiteral("X-KDevelop-IOptional"); } inline QString KEY_Global() { return QStringLiteral("Global"); } inline QString KEY_Project() { return QStringLiteral("Project"); } inline QString KEY_Gui() { return QStringLiteral("GUI"); } inline QString KEY_AlwaysOn() { return QStringLiteral("AlwaysOn"); } inline QString KEY_UserSelectable() { return QStringLiteral("UserSelectable"); } bool isUserSelectable( const KPluginMetaData& info ) { QString loadMode = info.value(KEY_LoadMode()); return loadMode.isEmpty() || loadMode == KEY_UserSelectable(); } bool isGlobalPlugin( const KPluginMetaData& info ) { return info.value(KEY_Category()) == KEY_Global(); } bool hasMandatoryProperties( const KPluginMetaData& info ) { QString mode = info.value(KEY_Mode()); if (mode.isEmpty()) { return false; } // when the plugin is installed into the versioned plugin path, it's good to go if (info.fileName().contains(QLatin1String("/kdevplatform/" QT_STRINGIFY(KDEVELOP_PLUGIN_VERSION) "/"))) { return true; } // the version property is only required when the plugin is not installed into the right directory QVariant version = info.rawData().value(KEY_Version()).toVariant(); if (version.isValid() && version.value() == KDEVELOP_PLUGIN_VERSION) { return true; } return false; } bool constraintsMatch( const KPluginMetaData& info, const QVariantMap& constraints) { for (auto it = constraints.begin(); it != constraints.end(); ++it) { const auto property = info.rawData().value(it.key()).toVariant(); if (!property.isValid()) { return false; } else if (property.canConvert()) { QSet values = property.toStringList().toSet(); QSet expected = it.value().toStringList().toSet(); if (!values.contains(expected)) { return false; } } else if (it.value() != property) { return false; } } return true; } struct Dependency { explicit Dependency(const QString &dependency) : interface(dependency) { if (dependency.contains('@')) { const auto list = dependency.split('@', QString::SkipEmptyParts); if (list.size() == 2) { interface = list.at(0); pluginName = list.at(1); } } } QString interface; QString pluginName; }; } namespace KDevelop { class PluginControllerPrivate { public: QVector plugins; //map plugin infos to currently loaded plugins typedef QHash InfoToPluginMap; InfoToPluginMap loadedPlugins; // The plugin manager's mode. The mode is StartingUp until loadAllPlugins() // has finished loading the plugins, after which it is set to Running. // ShuttingDown and DoneShutdown are used during shutdown by the // async unloading of plugins. enum CleanupMode { Running /**< the plugin manager is running */, CleaningUp /**< the plugin manager is cleaning up for shutdown */, CleanupDone /**< the plugin manager has finished cleaning up */ }; CleanupMode cleanupMode; bool canUnload(const KPluginMetaData& plugin) { qCDebug(SHELL) << "checking can unload for:" << plugin.name() << plugin.value(KEY_LoadMode()); if (plugin.value(KEY_LoadMode()) == KEY_AlwaysOn()) { return false; } const QStringList interfaces = KPluginMetaData::readStringList(plugin.rawData(), KEY_Interfaces()); qCDebug(SHELL) << "checking dependencies:" << interfaces; foreach (const KPluginMetaData& info, loadedPlugins.keys()) { if (info.pluginId() != plugin.pluginId()) { QStringList dependencies = KPluginMetaData::readStringList(plugin.rawData(), KEY_Required()); dependencies += KPluginMetaData::readStringList(plugin.rawData(), KEY_Optional()); foreach (const QString& dep, dependencies) { Dependency dependency(dep); if (!dependency.pluginName.isEmpty() && dependency.pluginName != plugin.pluginId()) { continue; } if (interfaces.contains(dependency.interface) && !canUnload(info)) { return false; } } } } return true; } KPluginMetaData infoForId( const QString& id ) const { foreach (const KPluginMetaData& info, plugins) { if (info.pluginId() == id) { return info; } } return KPluginMetaData(); } /** * Iterate over all cached plugin infos, and call the functor for every enabled plugin. * * If an extension and/or pluginName is given, the functor will only be called for * those plugins matching this information. * * The functor should return false when the iteration can be stopped, and true if it * should be continued. */ template void foreachEnabledPlugin(F func, const QString &extension = {}, const QVariantMap& constraints = QVariantMap(), const QString &pluginName = {}) { foreach (const auto& info, plugins) { if ((pluginName.isEmpty() || info.pluginId() == pluginName) && (extension.isEmpty() || KPluginMetaData::readStringList(info.rawData(), KEY_Interfaces()).contains(extension)) && constraintsMatch(info, constraints) && isEnabled(info)) { if (!func(info)) { break; } } } } /** * Decide whether a plugin is enabled */ bool isEnabled(const KPluginMetaData& info) const { static const QStringList disabledPlugins = QString::fromLatin1(qgetenv("KDEV_DISABLE_PLUGINS")).split(';'); if (disabledPlugins.contains(info.pluginId())) { return false; } if (!isUserSelectable( info )) return true; // in case there's a user preference, prefer that const KConfigGroup grp = Core::self()->activeSession()->config()->group( KEY_Plugins() ); const QString pluginEnabledKey = info.pluginId() + KEY_Suffix_Enabled(); if (grp.hasKey(pluginEnabledKey)) { return grp.readEntry(pluginEnabledKey, true); } // in all other cases: figure out if we want to load that plugin by default const auto defaultPlugins = ShellExtension::getInstance()->defaultPlugins(); const bool isDefaultPlugin = defaultPlugins.isEmpty() || defaultPlugins.contains(info.pluginId()); if (isDefaultPlugin) { return true; } if (!isGlobalPlugin( info )) { QJsonValue enabledByDefault = info.rawData()[QStringLiteral("KPlugin")].toObject()[QStringLiteral("EnabledByDefault")]; return enabledByDefault.isNull() || enabledByDefault.toBool(); //We consider plugins enabled until specified otherwise } return false; } Core *core; }; PluginController::PluginController(Core *core) : IPluginController(), d(new PluginControllerPrivate) { setObjectName(QStringLiteral("PluginController")); d->core = core; QSet foundPlugins; auto newPlugins = KPluginLoader::findPlugins(QStringLiteral("kdevplatform/" QT_STRINGIFY(KDEVELOP_PLUGIN_VERSION)), [&](const KPluginMetaData& meta) { if (meta.serviceTypes().contains(QStringLiteral("KDevelop/Plugin"))) { foundPlugins.insert(meta.pluginId()); return true; } else { qCWarning(SHELL) << "Plugin" << meta.fileName() << "is installed into the kdevplatform plugin directory, but does not have" " \"KDevelop/Plugin\" set as the service type. This plugin will not be loaded."; return false; } }); qCDebug(SHELL) << "Found" << newPlugins.size() << " plugins:" << foundPlugins; d->plugins = newPlugins; KTextEditorIntegration::initialize(); const QVector ktePlugins = KPluginLoader::findPlugins(QStringLiteral("ktexteditor"), [](const KPluginMetaData & md) { return md.serviceTypes().contains(QStringLiteral("KTextEditor/Plugin")) && md.serviceTypes().contains(QStringLiteral("KDevelop/Plugin")); }); foundPlugins.clear(); std::for_each(ktePlugins.cbegin(), ktePlugins.cend(), [&foundPlugins](const KPluginMetaData& data) { foundPlugins << data.pluginId(); }); qCDebug(SHELL) << "Found" << ktePlugins.size() << " KTextEditor plugins:" << foundPlugins; foreach (const auto& info, ktePlugins) { auto data = info.rawData(); // add some KDevelop specific JSON data data[KEY_Category()] = KEY_Global(); data[KEY_Mode()] = KEY_Gui(); data[KEY_Version()] = KDEVELOP_PLUGIN_VERSION; d->plugins.append({data, info.fileName(), info.metaDataFileName()}); } d->cleanupMode = PluginControllerPrivate::Running; // Register the KDevelop::IPlugin* metatype so we can properly unload it qRegisterMetaType( "KDevelop::IPlugin*" ); } PluginController::~PluginController() { if ( d->cleanupMode != PluginControllerPrivate::CleanupDone ) { qCWarning(SHELL) << "Destructing plugin controller without going through the shutdown process!"; } delete d; } KPluginMetaData PluginController::pluginInfo( const IPlugin* plugin ) const { return d->loadedPlugins.key(const_cast(plugin)); } void PluginController::cleanup() { if(d->cleanupMode != PluginControllerPrivate::Running) { //qCDebug(SHELL) << "called when not running. state =" << d->cleanupMode; return; } d->cleanupMode = PluginControllerPrivate::CleaningUp; // Ask all plugins to unload while ( !d->loadedPlugins.isEmpty() ) { //Let the plugin do some stuff before unloading unloadPlugin(d->loadedPlugins.begin().value(), Now); } d->cleanupMode = PluginControllerPrivate::CleanupDone; } IPlugin* PluginController::loadPlugin( const QString& pluginName ) { return loadPluginInternal( pluginName ); } bool PluginController::isEnabled( const KPluginMetaData& info ) const { return d->isEnabled(info); } void PluginController::initialize() { QElapsedTimer timer; timer.start(); QMap pluginMap; if( ShellExtension::getInstance()->defaultPlugins().isEmpty() ) { foreach( const KPluginMetaData& pi, d->plugins ) { pluginMap.insert( pi.pluginId(), true ); } } else { // Get the default from the ShellExtension foreach( const QString& s, ShellExtension::getInstance()->defaultPlugins() ) { pluginMap.insert( s, true ); } } KConfigGroup grp = Core::self()->activeSession()->config()->group( KEY_Plugins() ); QMap entries = grp.entryMap(); QMap::Iterator it; for ( it = entries.begin(); it != entries.end(); ++it ) { const QString key = it.key(); if (key.endsWith(KEY_Suffix_Enabled())) { bool enabled = false; const QString pluginid = key.left(key.length() - 7); const bool defValue = pluginMap.value( pluginid, false ); enabled = grp.readEntry(key, defValue); pluginMap.insert( pluginid, enabled ); } } foreach( const KPluginMetaData& pi, d->plugins ) { if( isGlobalPlugin( pi ) ) { QMap::const_iterator it = pluginMap.constFind( pi.pluginId() ); if( it != pluginMap.constEnd() && ( it.value() || !isUserSelectable( pi ) ) ) { // Plugin is mentioned in pluginmap and the value is true, so try to load it loadPluginInternal( pi.pluginId() ); if(!grp.hasKey(pi.pluginId() + KEY_Suffix_Enabled())) { if( isUserSelectable( pi ) ) { // If plugin isn't listed yet, add it with true now grp.writeEntry(pi.pluginId() + KEY_Suffix_Enabled(), true); } } else if( grp.hasKey( pi.pluginId() + "Disabled" ) && !isUserSelectable( pi ) ) { // Remove now-obsolete entries grp.deleteEntry( pi.pluginId() + "Disabled" ); } } } } // Synchronize so we're writing out to the file. grp.sync(); qCDebug(SHELL) << "Done loading plugins - took:" << timer.elapsed() << "ms"; } QList PluginController::loadedPlugins() const { return d->loadedPlugins.values(); } bool PluginController::unloadPlugin( const QString & pluginId ) { IPlugin *thePlugin = plugin( pluginId ); bool canUnload = d->canUnload( d->infoForId( pluginId ) ); qCDebug(SHELL) << "Unloading plugin:" << pluginId << "?" << thePlugin << canUnload; if( thePlugin && canUnload ) { return unloadPlugin(thePlugin, Later); } return (canUnload && thePlugin); } bool PluginController::unloadPlugin(IPlugin* plugin, PluginDeletion deletion) { qCDebug(SHELL) << "unloading plugin:" << plugin << pluginInfo( plugin ).name(); emit unloadingPlugin(plugin); plugin->unload(); emit pluginUnloaded(plugin); //Remove the plugin from our list of plugins so we create a new //instance when we're asked for it again. //This is important to do right here, not later when the plugin really //vanishes. For example project re-opening might try to reload the plugin //and then would get the "old" pointer which will be deleted in the next //event loop run and thus causing crashes. for ( PluginControllerPrivate::InfoToPluginMap::Iterator it = d->loadedPlugins.begin(); it != d->loadedPlugins.end(); ++it ) { if ( it.value() == plugin ) { d->loadedPlugins.erase( it ); break; } } if (deletion == Later) plugin->deleteLater(); else delete plugin; return true; } KPluginMetaData PluginController::infoForPluginId( const QString &pluginId ) const { foreach (const KPluginMetaData& info, d->plugins) { if (info.pluginId() == pluginId) { return info; } } return KPluginMetaData(); } IPlugin *PluginController::loadPluginInternal( const QString &pluginId ) { QElapsedTimer timer; timer.start(); KPluginMetaData info = infoForPluginId( pluginId ); if ( !info.isValid() ) { qCWarning(SHELL) << "Unable to find a plugin named '" << pluginId << "'!" ; return nullptr; } if ( IPlugin* plugin = d->loadedPlugins.value( info ) ) { return plugin; } if ( !isEnabled( info ) ) { // Do not load disabled plugins qCWarning(SHELL) << "Not loading plugin named" << pluginId << "because it has been disabled!"; return nullptr; } if ( !hasMandatoryProperties( info ) ) { qCWarning(SHELL) << "Unable to load plugin named" << pluginId << "because not all mandatory properties are set."; return nullptr; } if ( info.value(KEY_Mode()) == KEY_Gui() && Core::self()->setupFlags() == Core::NoUi ) { qCDebug(SHELL) << "Not loading plugin named" << pluginId << "- Running in No-Ui mode, but the plugin says it needs a GUI"; return nullptr; } qCDebug(SHELL) << "Attempting to load" << pluginId << "- name:" << info.name(); emit loadingPlugin( info.pluginId() ); // first, ensure all dependencies are available and not disabled // this is unrelated to whether they are loaded already or not. // when we depend on e.g. A and B, but B cannot be found, then we // do not want to load A first and then fail on B and leave A loaded. // this would happen if we'd skip this step here and directly loadDependencies. QStringList missingInterfaces; if ( !hasUnresolvedDependencies( info, missingInterfaces ) ) { qCWarning(SHELL) << "Can't load plugin" << pluginId << "some of its required dependencies could not be fulfilled:" << missingInterfaces.join(QStringLiteral(",")); return nullptr; } // now ensure all dependencies are loaded QString failedDependency; if( !loadDependencies( info, failedDependency ) ) { qCWarning(SHELL) << "Can't load plugin" << pluginId << "because a required dependency could not be loaded:" << failedDependency; return nullptr; } // same for optional dependencies, but don't error out if anything fails loadOptionalDependencies( info ); // now we can finally load the plugin itself KPluginLoader loader(info.fileName()); auto factory = loader.factory(); if (!factory) { qCWarning(SHELL) << "Can't load plugin" << pluginId << "because a factory to load the plugin could not be obtained:" << loader.errorString(); return nullptr; } // now create it auto plugin = factory->create(d->core); if (!plugin) { if (auto katePlugin = factory->create(d->core, QVariantList() << info.pluginId())) { plugin = new KTextEditorIntegration::Plugin(katePlugin, d->core); } else { qCWarning(SHELL) << "Creating plugin" << pluginId << "failed."; return nullptr; } } KConfigGroup group = Core::self()->activeSession()->config()->group(KEY_Plugins()); // runtime errors such as missing executables on the system or such get checked now if (plugin->hasError()) { qCWarning(SHELL) << "Could not load plugin" << pluginId << ", it reported the error:" << plugin->errorDescription() << "Disabling the plugin now."; group.writeEntry(info.pluginId() + KEY_Suffix_Enabled(), false); // do the same as KPluginInfo did group.sync(); unloadPlugin(pluginId); return nullptr; } // yay, it all worked - the plugin is loaded d->loadedPlugins.insert(info, plugin); group.writeEntry(info.pluginId() + KEY_Suffix_Enabled(), true); // do the same as KPluginInfo did group.sync(); qCDebug(SHELL) << "Successfully loaded plugin" << pluginId << "from" << loader.fileName() << "- took:" << timer.elapsed() << "ms"; emit pluginLoaded( plugin ); return plugin; } IPlugin* PluginController::plugin( const QString& pluginId ) { KPluginMetaData info = infoForPluginId( pluginId ); if ( !info.isValid() ) return nullptr; return d->loadedPlugins.value( info ); } bool PluginController::hasUnresolvedDependencies( const KPluginMetaData& info, QStringList& missing ) const { QSet required = KPluginMetaData::readStringList(info.rawData(), KEY_Required()).toSet(); if (!required.isEmpty()) { d->foreachEnabledPlugin([&required] (const KPluginMetaData& plugin) -> bool { foreach (const QString& iface, KPluginMetaData::readStringList(plugin.rawData(), KEY_Interfaces())) { required.remove(iface); required.remove(iface + '@' + plugin.pluginId()); } return !required.isEmpty(); }); } // if we found all dependencies required should be empty now if (!required.isEmpty()) { missing = required.toList(); return false; } return true; } void PluginController::loadOptionalDependencies( const KPluginMetaData& info ) { const QStringList dependencies = KPluginMetaData::readStringList(info.rawData(), KEY_Optional()); foreach (const QString& dep, dependencies) { Dependency dependency(dep); if (!pluginForExtension(dependency.interface, dependency.pluginName)) { qCDebug(SHELL) << "Couldn't load optional dependency:" << dep << info.pluginId(); } } } bool PluginController::loadDependencies( const KPluginMetaData& info, QString& failedDependency ) { const QStringList dependencies = KPluginMetaData::readStringList(info.rawData(), KEY_Required()); foreach (const QString& value, dependencies) { Dependency dependency(value); if (!pluginForExtension(dependency.interface, dependency.pluginName)) { failedDependency = value; return false; } } return true; } IPlugin *PluginController::pluginForExtension(const QString &extension, const QString &pluginName, const QVariantMap& constraints) { IPlugin* plugin = nullptr; d->foreachEnabledPlugin([this, &plugin] (const KPluginMetaData& info) -> bool { plugin = d->loadedPlugins.value( info ); if( !plugin ) { plugin = loadPluginInternal( info.pluginId() ); } return !plugin; }, extension, constraints, pluginName); return plugin; } QList PluginController::allPluginsForExtension(const QString &extension, const QVariantMap& constraints) { //qCDebug(SHELL) << "Finding all Plugins for Extension:" << extension << "|" << constraints; QList plugins; d->foreachEnabledPlugin([this, &plugins] (const KPluginMetaData& info) -> bool { IPlugin* plugin = d->loadedPlugins.value( info ); if( !plugin) { plugin = loadPluginInternal( info.pluginId() ); } if (plugin && !plugins.contains(plugin)) { plugins << plugin; } return true; }, extension, constraints); return plugins; } QVector PluginController::queryExtensionPlugins(const QString& extension, const QVariantMap& constraints) const { QVector plugins; d->foreachEnabledPlugin([&plugins] (const KPluginMetaData& info) -> bool { plugins << info; return true; }, extension, constraints); return plugins; } QStringList PluginController::allPluginNames() { QStringList names; Q_FOREACH( const KPluginMetaData& info , d->plugins ) { names << info.pluginId(); } return names; } QList PluginController::queryPluginsForContextMenuExtensions(KDevelop::Context* context) const { // This fixes random order of extension menu items between different runs of KDevelop. // Without sorting we have random reordering of "Analyze With" submenu for example: // 1) "Cppcheck" actions, "Vera++" actions - first run // 2) "Vera++" actions, "Cppcheck" actions - some other run. QMultiMap sortedPlugins; for (auto it = d->loadedPlugins.constBegin(); it != d->loadedPlugins.constEnd(); ++it) { sortedPlugins.insert(it.key().name(), it.value()); } QList exts; foreach (IPlugin* plugin, sortedPlugins) { exts << plugin->contextMenuExtension(context); } exts << Core::self()->debugControllerInternal()->contextMenuExtension(context); exts << Core::self()->documentationControllerInternal()->contextMenuExtension(context); exts << Core::self()->sourceFormatterControllerInternal()->contextMenuExtension(context); exts << Core::self()->runControllerInternal()->contextMenuExtension(context); exts << Core::self()->projectControllerInternal()->contextMenuExtension(context); return exts; } QStringList PluginController::projectPlugins() { QStringList names; foreach (const KPluginMetaData& info, d->plugins) { if (info.value(KEY_Category()) == KEY_Project()) { names << info.pluginId(); } } return names; } void PluginController::loadProjectPlugins() { Q_FOREACH( const QString& name, projectPlugins() ) { loadPluginInternal( name ); } } void PluginController::unloadProjectPlugins() { Q_FOREACH( const QString& name, projectPlugins() ) { unloadPlugin( name ); } } QVector PluginController::allPluginInfos() const { return d->plugins; } void PluginController::updateLoadedPlugins() { QStringList defaultPlugins = ShellExtension::getInstance()->defaultPlugins(); KConfigGroup grp = Core::self()->activeSession()->config()->group( KEY_Plugins() ); foreach( const KPluginMetaData& info, d->plugins ) { if( isGlobalPlugin( info ) ) { bool enabled = grp.readEntry(info.pluginId() + KEY_Suffix_Enabled(), ( defaultPlugins.isEmpty() || defaultPlugins.contains( info.pluginId() ) ) ) || !isUserSelectable( info ); bool loaded = d->loadedPlugins.contains( info ); if( loaded && !enabled ) { qCDebug(SHELL) << "unloading" << info.pluginId(); if( !unloadPlugin( info.pluginId() ) ) { grp.writeEntry( info.pluginId() + KEY_Suffix_Enabled(), false ); } } else if( !loaded && enabled ) { loadPluginInternal( info.pluginId() ); } } } } void PluginController::resetToDefaults() { KSharedConfigPtr cfg = Core::self()->activeSession()->config(); cfg->deleteGroup( KEY_Plugins() ); cfg->sync(); KConfigGroup grp = cfg->group( KEY_Plugins() ); QStringList plugins = ShellExtension::getInstance()->defaultPlugins(); if( plugins.isEmpty() ) { foreach( const KPluginMetaData& info, d->plugins ) { plugins << info.pluginId(); } } foreach( const QString& s, plugins ) { grp.writeEntry(s + KEY_Suffix_Enabled(), true); } grp.sync(); } } diff --git a/shell/progresswidget/progressmanager.cpp b/shell/progresswidget/progressmanager.cpp index c6ae9924a..b59d96c3c 100644 --- a/shell/progresswidget/progressmanager.cpp +++ b/shell/progresswidget/progressmanager.cpp @@ -1,269 +1,269 @@ /* progressmanager.cpp This file is part of libkdepim. Copyright (c) 2004 Till Adam 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 "progressmanager.h" -#include "../debug.h" +#include "debug.h" #include namespace KDevelop { unsigned int KDevelop::ProgressManager::uID = 42; ProgressItem::ProgressItem( ProgressItem *parent, const QString &id, const QString &label, const QString &status, bool canBeCanceled, bool usesCrypto ) : mId( id ), mLabel( label ), mStatus( status ), mParent( parent ), mCanBeCanceled( canBeCanceled ), mProgress( 0 ), mTotal( 0 ), mCompleted( 0 ), mWaitingForKids( false ), mCanceled( false ), mUsesCrypto( usesCrypto ), mUsesBusyIndicator( false ), mCompletedCalled( false ) { } ProgressItem::~ProgressItem() { } void ProgressItem::setComplete() { // qCDebug(SHELL) << label(); if ( mChildren.isEmpty() ) { if ( mCompletedCalled ) return; if ( !mCanceled ) { setProgress( 100 ); } mCompletedCalled = true; if ( parent() ) { parent()->removeChild( this ); } emit progressItemCompleted( this ); } else { mWaitingForKids = true; } } void ProgressItem::addChild( ProgressItem *kiddo ) { mChildren.insert( kiddo, true ); } void ProgressItem::removeChild( ProgressItem *kiddo ) { if ( mChildren.isEmpty() ) { mWaitingForKids = false; return; } if ( mChildren.remove( kiddo ) == 0 ) { // do nothing if the specified item is not in the map return; } // in case we were waiting for the last kid to go away, now is the time if ( mChildren.count() == 0 && mWaitingForKids ) { emit progressItemCompleted( this ); } } void ProgressItem::cancel() { if ( mCanceled || !mCanBeCanceled ) { return; } qCDebug(SHELL) << label(); mCanceled = true; // Cancel all children. QList kids = mChildren.keys(); QList::Iterator it( kids.begin() ); QList::Iterator end( kids.end() ); for ( ; it != end; it++ ) { ProgressItem *kid = *it; if ( kid->canBeCanceled() ) { kid->cancel(); } } setStatus( i18n( "Aborting..." ) ); emit progressItemCanceled( this ); } void ProgressItem::setProgress( unsigned int v ) { mProgress = v; // qCDebug(SHELL) << label() << " :" << v; emit progressItemProgress( this, mProgress ); } void ProgressItem::setLabel( const QString &v ) { mLabel = v; emit progressItemLabel( this, mLabel ); } void ProgressItem::setStatus( const QString &v ) { mStatus = v; emit progressItemStatus( this, mStatus ); } void ProgressItem::setUsesCrypto( bool v ) { mUsesCrypto = v; emit progressItemUsesCrypto( this, v ); } void ProgressItem::setUsesBusyIndicator( bool useBusyIndicator ) { mUsesBusyIndicator = useBusyIndicator; emit progressItemUsesBusyIndicator( this, useBusyIndicator ); } // ====================================== struct ProgressManagerPrivate { ProgressManager instance; }; Q_GLOBAL_STATIC( ProgressManagerPrivate, progressManagerPrivate ) ProgressManager::ProgressManager() : QObject() { } ProgressManager::~ProgressManager() {} ProgressManager *ProgressManager::instance() { return progressManagerPrivate.isDestroyed() ? nullptr : &progressManagerPrivate->instance ; } ProgressItem *ProgressManager::createProgressItemImpl( ProgressItem *parent, const QString &id, const QString &label, const QString &status, bool cancellable, bool usesCrypto ) { ProgressItem *t = nullptr; if ( !mTransactions.value( id ) ) { t = new ProgressItem ( parent, id, label, status, cancellable, usesCrypto ); mTransactions.insert( id, t ); if ( parent ) { ProgressItem *p = mTransactions.value( parent->id() ); if ( p ) { p->addChild( t ); } } // connect all signals connect ( t, &ProgressItem::progressItemCompleted, this, &ProgressManager::slotTransactionCompleted ); connect ( t, &ProgressItem::progressItemProgress, this, &ProgressManager::progressItemProgress ); connect ( t, &ProgressItem::progressItemAdded, this, &ProgressManager::progressItemAdded ); connect ( t, &ProgressItem::progressItemCanceled, this, &ProgressManager::progressItemCanceled ); connect ( t, &ProgressItem::progressItemStatus, this, &ProgressManager::progressItemStatus ); connect ( t, &ProgressItem::progressItemLabel, this, &ProgressManager::progressItemLabel ); connect ( t, &ProgressItem::progressItemUsesCrypto, this, &ProgressManager::progressItemUsesCrypto ); connect ( t, &ProgressItem::progressItemUsesBusyIndicator, this, &ProgressManager::progressItemUsesBusyIndicator ); emit progressItemAdded( t ); } else { // Hm, is this what makes the most sense? t = mTransactions.value( id ); } return t; } ProgressItem *ProgressManager::createProgressItemImpl( const QString &parent, const QString &id, const QString &label, const QString &status, bool canBeCanceled, bool usesCrypto ) { ProgressItem *p = mTransactions.value( parent ); return createProgressItemImpl( p, id, label, status, canBeCanceled, usesCrypto ); } void ProgressManager::emitShowProgressDialogImpl() { emit showProgressDialog(); } // slots void ProgressManager::slotTransactionCompleted( ProgressItem *item ) { mTransactions.remove( item->id() ); emit progressItemCompleted( item ); } void ProgressManager::slotStandardCancelHandler( ProgressItem *item ) { item->setComplete(); } ProgressItem *ProgressManager::singleItem() const { ProgressItem *item = nullptr; QHash< QString, ProgressItem* >::const_iterator it = mTransactions.constBegin(); QHash< QString, ProgressItem* >::const_iterator end = mTransactions.constEnd(); while ( it != end ) { // No single item for progress possible, as one of them is a busy indicator one. if ( (*it)->usesBusyIndicator() ) return nullptr; if ( !(*it)->parent() ) { // if it's a top level one, only those count if ( item ) { return nullptr; // we found more than one } else { item = (*it); } } ++it; } return item; } void ProgressManager::slotAbortAll() { QHashIterator it(mTransactions); while (it.hasNext()) { it.next(); it.value()->cancel(); } } } // namespace diff --git a/shell/project.cpp b/shell/project.cpp index 4714b09dc..a1c3bf859 100644 --- a/shell/project.cpp +++ b/shell/project.cpp @@ -1,651 +1,650 @@ /* This file is part of the KDE project Copyright 2001 Matthias Hoelzer-Kluepfel Copyright 2002-2003 Roberto Raggi Copyright 2002 Simon Hausmann Copyright 2003 Jens Dagerbo Copyright 2003 Mario Scalas Copyright 2003-2004 Alexander Dymo Copyright 2006 Matt Rogers Copyright 2007 Andreas Pakulat 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 "project.h" -#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "core.h" #include "mainwindow.h" #include "projectcontroller.h" #include "uicontroller.h" #include "debug.h" namespace KDevelop { class ProjectProgress : public QObject, public IStatus { Q_OBJECT Q_INTERFACES(KDevelop::IStatus) public: ProjectProgress(); ~ProjectProgress() override; QString statusName() const override; /*! Show indeterminate mode progress bar */ void setBuzzy(); /*! Hide progress bar */ void setDone(); QString projectName; private Q_SLOTS: void slotClean(); Q_SIGNALS: void clearMessage(KDevelop::IStatus*) override; void showMessage(KDevelop::IStatus*,const QString & message, int timeout = 0) override; void showErrorMessage(const QString & message, int timeout = 0) override; void hideProgress(KDevelop::IStatus*) override; void showProgress(KDevelop::IStatus*,int minimum, int maximum, int value) override; private: QTimer* m_timer; }; ProjectProgress::ProjectProgress() { m_timer = new QTimer(this); m_timer->setSingleShot( true ); m_timer->setInterval( 1000 ); connect(m_timer, &QTimer::timeout,this, &ProjectProgress::slotClean); } ProjectProgress::~ProjectProgress() { } QString ProjectProgress::statusName() const { return i18n("Loading Project %1", projectName); } void ProjectProgress::setBuzzy() { qCDebug(SHELL) << "showing busy progress" << statusName(); // show an indeterminate progressbar emit showProgress(this, 0,0,0); emit showMessage(this, i18nc("%1: Project name", "Loading %1", projectName)); } void ProjectProgress::setDone() { qCDebug(SHELL) << "showing done progress" << statusName(); // first show 100% bar for a second, then hide. emit showProgress(this, 0,1,1); m_timer->start(); } void ProjectProgress::slotClean() { emit hideProgress(this); emit clearMessage(this); } class ProjectPrivate { public: Path projectPath; Path projectFile; Path developerFile; QString developerTempFile; QTemporaryFile projectTempFile; IPlugin* manager; QPointer vcsPlugin; ProjectFolderItem* topItem; QString name; KSharedConfigPtr m_cfg; IProject *project; QSet fileSet; bool loading; bool fullReload; bool scheduleReload; ProjectProgress* progress; void reloadDone(KJob* job) { progress->setDone(); loading = false; ProjectController* projCtrl = Core::self()->projectControllerInternal(); if (job->error() == 0 && !Core::self()->shuttingDown()) { if(fullReload) projCtrl->projectModel()->appendRow(topItem); if (scheduleReload) { scheduleReload = false; project->reloadModel(); } } else { projCtrl->abortOpeningProject(project); } } QList itemsForPath( const IndexedString& path ) const { if ( path.isEmpty() ) { return QList(); } if (!topItem->model()) { // this gets hit when the project has not yet been added to the model // i.e. during import phase // TODO: should we handle this somehow? // possible idea: make the item<->path hash per-project return QList(); } Q_ASSERT(topItem->model()); QList items = topItem->model()->itemsForPath(path); QList::iterator it = items.begin(); while(it != items.end()) { if ((*it)->project() != project) { it = items.erase(it); } else { ++it; } } return items; } void importDone( KJob* job) { progress->setDone(); ProjectController* projCtrl = Core::self()->projectControllerInternal(); if(job->error() == 0 && !Core::self()->shuttingDown()) { loading=false; projCtrl->projectModel()->appendRow(topItem); projCtrl->projectImportingFinished( project ); } else { projCtrl->abortOpeningProject(project); } } void initProject(const Path& projectFile_) { // helper method for open() projectFile = projectFile_; } bool initProjectFiles() { KIO::StatJob* statJob = KIO::stat( projectFile.toUrl(), KIO::HideProgressInfo ); if ( !statJob->exec() ) //be sync for right now { KMessageBox::sorry( Core::self()->uiControllerInternal()->defaultMainWindow(), i18n( "Unable to load the project file %1.
" "The project has been removed from the session.", projectFile.pathOrUrl() ) ); return false; } // developerfile == dirname(projectFileUrl) ."/.kdev4/". basename(projectfileUrl) developerFile = projectFile; developerFile.setLastPathSegment( QStringLiteral(".kdev4") ); developerFile.addPath( projectFile.lastPathSegment() ); statJob = KIO::stat( developerFile.toUrl(), KIO::HideProgressInfo ); if( !statJob->exec() ) { // the developerfile does not exist yet, check if its folder exists // the developerfile itself will get created below QUrl dir = developerFile.parent().toUrl(); statJob = KIO::stat( dir, KIO::HideProgressInfo ); if( !statJob->exec() ) { KIO::SimpleJob* mkdirJob = KIO::mkdir( dir ); if( !mkdirJob->exec() ) { KMessageBox::sorry( Core::self()->uiController()->activeMainWindow(), i18n("Unable to create hidden dir (%1) for developer file", dir.toDisplayString(QUrl::PreferLocalFile) ) ); return false; } } } projectTempFile.open(); auto copyJob = KIO::file_copy(projectFile.toUrl(), QUrl::fromLocalFile(projectTempFile.fileName()), -1, KIO::HideProgressInfo | KIO::Overwrite); KJobWidgets::setWindow(copyJob, Core::self()->uiController()->activeMainWindow()); if (!copyJob->exec()) { qCDebug(SHELL) << "Job failed:" << copyJob->errorString(); KMessageBox::sorry( Core::self()->uiController()->activeMainWindow(), i18n("Unable to get project file: %1", projectFile.pathOrUrl() ) ); return false; } if(developerFile.isLocalFile()) { developerTempFile = developerFile.toLocalFile(); } else { QTemporaryFile tmp; tmp.open(); developerTempFile = tmp.fileName(); auto job = KIO::file_copy(developerFile.toUrl(), QUrl::fromLocalFile(developerTempFile), -1, KIO::HideProgressInfo | KIO::Overwrite); KJobWidgets::setWindow(job, Core::self()->uiController()->activeMainWindow()); job->exec(); } return true; } KConfigGroup initKConfigObject() { // helper method for open() qCDebug(SHELL) << "Creating KConfig object for project files" << developerTempFile << projectTempFile.fileName(); m_cfg = KSharedConfig::openConfig( developerTempFile ); m_cfg->addConfigSources( QStringList() << projectTempFile.fileName() ); KConfigGroup projectGroup( m_cfg, "Project" ); return projectGroup; } bool projectNameUsed(const KConfigGroup& projectGroup) { // helper method for open() name = projectGroup.readEntry( "Name", projectFile.lastPathSegment() ); progress->projectName = name; if( Core::self()->projectController()->isProjectNameUsed( name ) ) { KMessageBox::sorry( Core::self()->uiControllerInternal()->defaultMainWindow(), i18n( "Could not load %1, a project with the same name '%2' is already open.", projectFile.pathOrUrl(), name ) ); qCWarning(SHELL) << "Trying to open a project with a name that is already used by another open project"; return true; } return false; } IProjectFileManager* fetchFileManager(const KConfigGroup& projectGroup) { if (manager) { IProjectFileManager* iface = manager->extension(); Q_ASSERT(iface); return iface; } // helper method for open() QString managerSetting = projectGroup.readEntry( "Manager", "KDevGenericManager" ); //Get our importer IPluginController* pluginManager = Core::self()->pluginController(); manager = pluginManager->pluginForExtension( QStringLiteral("org.kdevelop.IProjectFileManager"), managerSetting ); IProjectFileManager* iface = nullptr; if ( manager ) iface = manager->extension(); else { KMessageBox::sorry( Core::self()->uiControllerInternal()->defaultMainWindow(), i18n( "Could not load project management plugin %1.
Check that the required programs are installed," " or see console output for more information.", managerSetting ) ); manager = nullptr; return nullptr; } if (iface == nullptr) { KMessageBox::sorry( Core::self()->uiControllerInternal()->defaultMainWindow(), i18n( "project importing plugin (%1) does not support the IProjectFileManager interface.", managerSetting ) ); delete manager; manager = nullptr; } return iface; } void loadVersionControlPlugin(KConfigGroup& projectGroup) { // helper method for open() IPluginController* pluginManager = Core::self()->pluginController(); if( projectGroup.hasKey( "VersionControlSupport" ) ) { QString vcsPluginName = projectGroup.readEntry("VersionControlSupport", ""); if( !vcsPluginName.isEmpty() ) { vcsPlugin = pluginManager->pluginForExtension( QStringLiteral( "org.kdevelop.IBasicVersionControl" ), vcsPluginName ); } } else { QList plugins = pluginManager->allPluginsForExtension( QStringLiteral( "org.kdevelop.IBasicVersionControl" ) ); foreach( IPlugin* p, plugins ) { IBasicVersionControl* iface = p->extension(); if( iface && iface->isVersionControlled( topItem->path().toUrl() ) ) { vcsPlugin = p; projectGroup.writeEntry("VersionControlSupport", pluginManager->pluginInfo(p).pluginId()); projectGroup.sync(); } } } } bool importTopItem(IProjectFileManager* fileManager) { if (!fileManager) { return false; } topItem = fileManager->import( project ); if( !topItem ) { KMessageBox::sorry( Core::self()->uiControllerInternal()->defaultMainWindow(), i18n("Could not open project") ); return false; } return true; } }; Project::Project( QObject *parent ) : IProject( parent ) , d( new ProjectPrivate ) { d->project = this; d->manager = nullptr; d->topItem = nullptr; d->loading = false; d->scheduleReload = false; d->progress = new ProjectProgress; Core::self()->uiController()->registerStatus( d->progress ); } Project::~Project() { delete d->progress; delete d; } QString Project::name() const { return d->name; } QString Project::developerTempFile() const { return d->developerTempFile; } QString Project::projectTempFile() const { return d->projectTempFile.fileName(); } KSharedConfigPtr Project::projectConfiguration() const { return d->m_cfg; } Path Project::path() const { return d->projectPath; } void Project::reloadModel() { if (d->loading) { d->scheduleReload = true; return; } d->loading = true; d->fileSet.clear(); // delete topItem and remove it from model ProjectModel* model = Core::self()->projectController()->projectModel(); model->removeRow( d->topItem->row() ); d->topItem = nullptr; IProjectFileManager* iface = d->manager->extension(); if (!d->importTopItem(iface)) { d->loading = false; d->scheduleReload = false; return; } KJob* importJob = iface->createImportJob(d->topItem ); setReloadJob(importJob); d->fullReload = true; Core::self()->runController()->registerJob( importJob ); } void Project::setReloadJob(KJob* job) { d->loading = true; d->fullReload = false; d->progress->setBuzzy(); connect(job, &KJob::finished, this, [&] (KJob* job) { d->reloadDone(job); }); } bool Project::open( const Path& projectFile ) { d->initProject(projectFile); if (!d->initProjectFiles()) return false; KConfigGroup projectGroup = d->initKConfigObject(); if (d->projectNameUsed(projectGroup)) return false; d->projectPath = d->projectFile.parent(); IProjectFileManager* iface = d->fetchFileManager(projectGroup); if (!iface) return false; Q_ASSERT(d->manager); emit aboutToOpen(this); if (!d->importTopItem(iface) ) { return false; } d->loading=true; d->loadVersionControlPlugin(projectGroup); d->progress->setBuzzy(); KJob* importJob = iface->createImportJob(d->topItem ); connect( importJob, &KJob::result, this, [&] (KJob* job) { d->importDone(job); } ); Core::self()->runController()->registerJob( importJob ); return true; } void Project::close() { Q_ASSERT(d->topItem); if (d->topItem->row() == -1) { qCWarning(SHELL) << "Something went wrong. ProjectFolderItem detached. Project closed during reload?"; return; } Core::self()->projectController()->projectModel()->removeRow( d->topItem->row() ); if (!d->developerFile.isLocalFile()) { auto copyJob = KIO::file_copy(QUrl::fromLocalFile(d->developerTempFile), d->developerFile.toUrl(), -1, KIO::HideProgressInfo); KJobWidgets::setWindow(copyJob, Core::self()->uiController()->activeMainWindow()); if (!copyJob->exec()) { qCDebug(SHELL) << "Job failed:" << copyJob->errorString(); KMessageBox::sorry(Core::self()->uiController()->activeMainWindow(), i18n("Could not store developer specific project configuration.\n" "Attention: The project settings you changed will be lost.")); } } } bool Project::inProject( const IndexedString& path ) const { if (d->fileSet.contains( path )) { return true; } return !d->itemsForPath( path ).isEmpty(); } QList< ProjectBaseItem* > Project::itemsForPath(const IndexedString& path) const { return d->itemsForPath(path); } QList< ProjectFileItem* > Project::filesForPath(const IndexedString& file) const { QList items; foreach(ProjectBaseItem* item, d->itemsForPath( file ) ) { if( item->type() == ProjectBaseItem::File ) items << dynamic_cast( item ); } return items; } QList Project::foldersForPath(const IndexedString& folder) const { QList items; foreach(ProjectBaseItem* item, d->itemsForPath( folder ) ) { if( item->type() == ProjectBaseItem::Folder || item->type() == ProjectBaseItem::BuildFolder ) items << dynamic_cast( item ); } return items; } IProjectFileManager* Project::projectFileManager() const { return d->manager->extension(); } IBuildSystemManager* Project::buildSystemManager() const { return d->manager->extension(); } IPlugin* Project::managerPlugin() const { return d->manager; } void Project::setManagerPlugin( IPlugin* manager ) { d->manager = manager; } Path Project::projectFile() const { return d->projectFile; } Path Project::developerFile() const { return d->developerFile; } ProjectFolderItem* Project::projectItem() const { return d->topItem; } IPlugin* Project::versionControlPlugin() const { return d->vcsPlugin.data(); } void Project::addToFileSet( ProjectFileItem* file ) { if (d->fileSet.contains(file->indexedPath())) { return; } d->fileSet.insert( file->indexedPath() ); emit fileAddedToSet( file ); } void Project::removeFromFileSet( ProjectFileItem* file ) { QSet::iterator it = d->fileSet.find(file->indexedPath()); if (it == d->fileSet.end()) { return; } d->fileSet.erase( it ); emit fileRemovedFromSet( file ); } QSet Project::fileSet() const { return d->fileSet; } bool Project::isReady() const { return !d->loading; } } // namespace KDevelop #include "project.moc" #include "moc_project.cpp" diff --git a/shell/runcontroller.cpp b/shell/runcontroller.cpp index 029a7c705..aceddd7f2 100644 --- a/shell/runcontroller.cpp +++ b/shell/runcontroller.cpp @@ -1,1048 +1,1047 @@ /* This file is part of KDevelop Copyright 2007-2008 Hamish Rodda Copyright 2008 Aleix Pol 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 "runcontroller.h" -#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "core.h" #include "uicontroller.h" #include "projectcontroller.h" #include "mainwindow.h" #include "launchconfiguration.h" #include "launchconfigurationdialog.h" #include "unitylauncher.h" #include "debug.h" #include #include #include #include using namespace KDevelop; namespace { namespace Strings { QString LaunchConfigurationsGroup() { return QStringLiteral("Launch"); } QString LaunchConfigurationsListEntry() { return QStringLiteral("Launch Configurations"); } QString CurrentLaunchConfigProjectEntry() { return QStringLiteral("Current Launch Config Project"); } QString CurrentLaunchConfigNameEntry() { return QStringLiteral("Current Launch Config GroupName"); } QString ConfiguredFromProjectItemEntry() { return QStringLiteral("Configured from ProjectItem"); } } } typedef QPair Target; Q_DECLARE_METATYPE(Target) //TODO: Doesn't handle add/remove of launch configs in the dialog or renaming of configs //TODO: Doesn't auto-select launch configs opened from projects class DebugMode : public ILaunchMode { public: DebugMode() {} QIcon icon() const override { return QIcon::fromTheme(QStringLiteral("debug-run")); } QString id() const override { return QStringLiteral("debug"); } QString name() const override { return i18n("Debug"); } }; class ProfileMode : public ILaunchMode { public: ProfileMode() {} QIcon icon() const override { return QIcon::fromTheme(QStringLiteral("office-chart-area")); } QString id() const override { return QStringLiteral("profile"); } QString name() const override { return i18n("Profile"); } }; class ExecuteMode : public ILaunchMode { public: ExecuteMode() {} QIcon icon() const override { return QIcon::fromTheme(QStringLiteral("system-run")); } QString id() const override { return QStringLiteral("execute"); } QString name() const override { return i18n("Execute"); } }; class RunController::RunControllerPrivate { public: QItemDelegate* delegate; IRunController::State state; RunController* q; QHash jobs; QAction* stopAction; KActionMenu* stopJobsMenu; QAction* runAction; QAction* dbgAction; KSelectAction* currentTargetAction; QMap launchConfigurationTypes; QList launchConfigurations; QMap launchModes; QSignalMapper* launchChangeMapper; QSignalMapper* launchAsMapper; QMap > launchAsInfo; KDevelop::ProjectBaseItem* contextItem; DebugMode* debugMode; ExecuteMode* executeMode; ProfileMode* profileMode; UnityLauncher* unityLauncher; bool hasLaunchConfigType( const QString& typeId ) { return launchConfigurationTypes.contains( typeId ); } void saveCurrentLaunchAction() { if (!currentTargetAction) return; if( currentTargetAction->currentAction() ) { KConfigGroup grp = Core::self()->activeSession()->config()->group( Strings::LaunchConfigurationsGroup() ); LaunchConfiguration* l = static_cast( currentTargetAction->currentAction()->data().value() ); grp.writeEntry( Strings::CurrentLaunchConfigProjectEntry(), l->project() ? l->project()->name() : QLatin1String("") ); grp.writeEntry( Strings::CurrentLaunchConfigNameEntry(), l->configGroupName() ); grp.sync(); } } QString launchActionText( LaunchConfiguration* l ) { QString label; if( l->project() ) { label = QStringLiteral("%1 : %2").arg( l->project()->name(), l->name()); } else { label = l->name(); } return label; } void launchAs( int id ) { //qCDebug(SHELL) << "Launching id:" << id; QPair info = launchAsInfo[id]; //qCDebug(SHELL) << "fetching type and mode:" << info.first << info.second; LaunchConfigurationType* type = launchConfigurationTypeForId( info.first ); ILaunchMode* mode = q->launchModeForId( info.second ); //qCDebug(SHELL) << "got mode and type:" << type << type->id() << mode << mode->id(); if( type && mode ) { ILauncher* launcher = nullptr; foreach (ILauncher *l, type->launchers()) { //qCDebug(SHELL) << "available launcher" << l << l->id() << l->supportedModes(); if (l->supportedModes().contains(mode->id())) { launcher = l; break; } } if (launcher) { QStringList itemPath = Core::self()->projectController()->projectModel()->pathFromIndex(contextItem->index()); ILaunchConfiguration* ilaunch = nullptr; foreach (LaunchConfiguration *l, launchConfigurations) { QStringList path = l->config().readEntry(Strings::ConfiguredFromProjectItemEntry(), QStringList()); if (l->type() == type && path == itemPath) { qCDebug(SHELL) << "already generated ilaunch" << path; ilaunch = l; break; } } if (!ilaunch) { ilaunch = q->createLaunchConfiguration( type, qMakePair( mode->id(), launcher->id() ), contextItem->project(), contextItem->text() ); LaunchConfiguration* launch = dynamic_cast( ilaunch ); type->configureLaunchFromItem( launch->config(), contextItem ); launch->config().writeEntry(Strings::ConfiguredFromProjectItemEntry(), itemPath); //qCDebug(SHELL) << "created config, launching"; } else { //qCDebug(SHELL) << "reusing generated config, launching"; } q->setDefaultLaunch(ilaunch); q->execute( mode->id(), ilaunch ); } } } void updateCurrentLaunchAction() { if (!currentTargetAction) return; KConfigGroup launchGrp = Core::self()->activeSession()->config()->group( Strings::LaunchConfigurationsGroup() ); QString currentLaunchProject = launchGrp.readEntry( Strings::CurrentLaunchConfigProjectEntry(), "" ); QString currentLaunchName = launchGrp.readEntry( Strings::CurrentLaunchConfigNameEntry(), "" ); LaunchConfiguration* l = nullptr; if( currentTargetAction->currentAction() ) { l = static_cast( currentTargetAction->currentAction()->data().value() ); } else if( !launchConfigurations.isEmpty() ) { l = launchConfigurations.at( 0 ); } if( l && ( ( !currentLaunchProject.isEmpty() && ( !l->project() || l->project()->name() != currentLaunchProject ) ) || l->configGroupName() != currentLaunchName ) ) { foreach( QAction* a, currentTargetAction->actions() ) { LaunchConfiguration* l = static_cast( qvariant_cast( a->data() ) ); if( currentLaunchName == l->configGroupName() && ( ( currentLaunchProject.isEmpty() && !l->project() ) || ( l->project() && l->project()->name() == currentLaunchProject ) ) ) { a->setChecked( true ); break; } } } if( !currentTargetAction->currentAction() ) { qCDebug(SHELL) << "oops no current action, using first if list is non-empty"; if( !currentTargetAction->actions().isEmpty() ) { currentTargetAction->actions().at(0)->setChecked( true ); } } } void addLaunchAction( LaunchConfiguration* l ) { if (!currentTargetAction) return; QAction* action = currentTargetAction->addAction(launchActionText( l )); action->setData(qVariantFromValue(l)); } void readLaunchConfigs( KSharedConfigPtr cfg, IProject* prj ) { KConfigGroup group(cfg, Strings::LaunchConfigurationsGroup()); QStringList configs = group.readEntry( Strings::LaunchConfigurationsListEntry(), QStringList() ); foreach( const QString& cfg, configs ) { KConfigGroup grp = group.group( cfg ); if( launchConfigurationTypeForId( grp.readEntry( LaunchConfiguration::LaunchConfigurationTypeEntry(), "" ) ) ) { q->addLaunchConfiguration( new LaunchConfiguration( grp, prj ) ); } } } LaunchConfigurationType* launchConfigurationTypeForId( const QString& id ) { QMap::iterator it = launchConfigurationTypes.find( id ); if( it != launchConfigurationTypes.end() ) { return it.value(); } else { qCWarning(SHELL) << "couldn't find type for id:" << id << ". Known types:" << launchConfigurationTypes.keys(); } return nullptr; } }; RunController::RunController(QObject *parent) : IRunController(parent) , d(new RunControllerPrivate) { setObjectName(QStringLiteral("RunController")); QDBusConnection::sessionBus().registerObject(QStringLiteral("/org/kdevelop/RunController"), this, QDBusConnection::ExportScriptableSlots); // TODO: need to implement compile only if needed before execute // TODO: need to implement abort all running programs when project closed d->currentTargetAction = nullptr; d->state = Idle; d->q = this; d->delegate = new RunDelegate(this); d->launchChangeMapper = new QSignalMapper( this ); d->launchAsMapper = nullptr; d->contextItem = nullptr; d->executeMode = nullptr; d->debugMode = nullptr; d->profileMode = nullptr; d->unityLauncher = new UnityLauncher(this); d->unityLauncher->setLauncherId(KAboutData::applicationData().desktopFileName()); if(!(Core::self()->setupFlags() & Core::NoUi)) { // Note that things like registerJob() do not work without the actions, it'll simply crash. setupActions(); } } RunController::~RunController() { delete d; } void KDevelop::RunController::launchChanged( LaunchConfiguration* l ) { foreach( QAction* a, d->currentTargetAction->actions() ) { if( static_cast( a->data().value() ) == l ) { a->setText( d->launchActionText( l ) ); break; } } } void RunController::cleanup() { delete d->executeMode; d->executeMode = nullptr; delete d->profileMode; d->profileMode = nullptr; delete d->debugMode; d->debugMode = nullptr; stopAllProcesses(); d->saveCurrentLaunchAction(); } void RunController::initialize() { d->executeMode = new ExecuteMode(); addLaunchMode( d->executeMode ); d->profileMode = new ProfileMode(); addLaunchMode( d->profileMode ); d->debugMode = new DebugMode; addLaunchMode( d->debugMode ); d->readLaunchConfigs( Core::self()->activeSession()->config(), nullptr ); foreach (IProject* project, Core::self()->projectController()->projects()) { slotProjectOpened(project); } connect(Core::self()->projectController(), &IProjectController::projectOpened, this, &RunController::slotProjectOpened); connect(Core::self()->projectController(), &IProjectController::projectClosing, this, &RunController::slotProjectClosing); connect(Core::self()->projectController(), &IProjectController::projectConfigurationChanged, this, &RunController::slotRefreshProject); if( (Core::self()->setupFlags() & Core::NoUi) == 0 ) { // Only do this in GUI mode d->updateCurrentLaunchAction(); } } KJob* RunController::execute(const QString& runMode, ILaunchConfiguration* launch) { if( !launch ) { qCDebug(SHELL) << "execute called without launch config!"; return nullptr; } LaunchConfiguration *run = dynamic_cast(launch); //TODO: Port to launch framework, probably needs to be part of the launcher //if(!run.dependencies().isEmpty()) // ICore::self()->documentController()->saveAllDocuments(IDocument::Silent); //foreach(KJob* job, run.dependencies()) //{ // jobs.append(job); //} qCDebug(SHELL) << "mode:" << runMode; QString launcherId = run->launcherForMode( runMode ); qCDebug(SHELL) << "launcher id:" << launcherId; ILauncher* launcher = run->type()->launcherForId( launcherId ); if( !launcher ) { KMessageBox::error( qApp->activeWindow(), i18n("The current launch configuration does not support the '%1' mode.", runMode), QLatin1String("")); return nullptr; } KJob* launchJob = launcher->start(runMode, run); registerJob(launchJob); return launchJob; } void RunController::setupActions() { QAction* action; // TODO not multi-window friendly, FIXME KActionCollection* ac = Core::self()->uiControllerInternal()->defaultMainWindow()->actionCollection(); action = new QAction(i18n("Configure Launches..."), this); ac->addAction(QStringLiteral("configure_launches"), action); action->setMenuRole(QAction::NoRole); // OSX: Be explicit about role, prevent hiding due to conflict with "Preferences..." menu item action->setStatusTip(i18n("Open Launch Configuration Dialog")); action->setToolTip(i18nc("@info:tooltip", "Open Launch Configuration Dialog")); action->setWhatsThis(i18nc("@info:whatsthis", "Opens a dialog to setup new launch configurations, or to change the existing ones.")); connect(action, &QAction::triggered, this, &RunController::showConfigurationDialog); d->runAction = new QAction( QIcon::fromTheme(QStringLiteral("system-run")), i18n("Execute Launch"), this); d->runAction->setIconText( i18nc("Short text for 'Execute launch' used in the toolbar", "Execute") ); ac->setDefaultShortcut( d->runAction, Qt::SHIFT + Qt::Key_F9); d->runAction->setToolTip(i18nc("@info:tooltip", "Execute current launch")); d->runAction->setStatusTip(i18n("Execute current launch")); d->runAction->setWhatsThis(i18nc("@info:whatsthis", "Executes the target or the program specified in currently active launch configuration.")); ac->addAction(QStringLiteral("run_execute"), d->runAction); connect(d->runAction, &QAction::triggered, this, &RunController::slotExecute); d->dbgAction = new QAction( QIcon::fromTheme(QStringLiteral("debug-run")), i18n("Debug Launch"), this); ac->setDefaultShortcut( d->dbgAction, Qt::Key_F9); d->dbgAction->setIconText( i18nc("Short text for 'Debug launch' used in the toolbar", "Debug") ); d->dbgAction->setToolTip(i18nc("@info:tooltip", "Debug current launch")); d->dbgAction->setStatusTip(i18n("Debug current launch")); d->dbgAction->setWhatsThis(i18nc("@info:whatsthis", "Executes the target or the program specified in currently active launch configuration inside a Debugger.")); ac->addAction(QStringLiteral("run_debug"), d->dbgAction); connect(d->dbgAction, &QAction::triggered, this, &RunController::slotDebug); Core::self()->uiControllerInternal()->area(0, QStringLiteral("code"))->addAction(d->dbgAction); // TODO: at least get a profile target, it's sad to have the menu entry without a profiler // QAction* profileAction = new QAction( QIcon::fromTheme(""), i18n("Profile Launch"), this); // profileAction->setToolTip(i18nc("@info:tooltip", "Profile current launch")); // profileAction->setStatusTip(i18n("Profile current launch")); // profileAction->setWhatsThis(i18nc("@info:whatsthis", "Executes the target or the program specified in currently active launch configuration inside a Profiler.")); // ac->addAction("run_profile", profileAction); // connect(profileAction, SIGNAL(triggered(bool)), this, SLOT(slotProfile())); action = d->stopAction = new QAction( QIcon::fromTheme(QStringLiteral("process-stop")), i18n("Stop All Jobs"), this); action->setIconText(i18nc("Short text for 'Stop All Jobs' used in the toolbar", "Stop All")); // Ctrl+Escape would be nicer, but that is taken by the ksysguard desktop shortcut ac->setDefaultShortcut( action, QKeySequence(QStringLiteral("Ctrl+Shift+Escape"))); action->setToolTip(i18nc("@info:tooltip", "Stop all currently running jobs")); action->setWhatsThis(i18nc("@info:whatsthis", "Requests that all running jobs are stopped.")); action->setEnabled(false); ac->addAction(QStringLiteral("run_stop_all"), action); connect(action, &QAction::triggered, this, &RunController::stopAllProcesses); Core::self()->uiControllerInternal()->area(0, QStringLiteral("debug"))->addAction(action); action = d->stopJobsMenu = new KActionMenu( QIcon::fromTheme(QStringLiteral("process-stop")), i18n("Stop"), this); action->setIconText(i18nc("Short text for 'Stop' used in the toolbar", "Stop")); action->setToolTip(i18nc("@info:tooltip", "Menu allowing to stop individual jobs")); action->setWhatsThis(i18nc("@info:whatsthis", "List of jobs that can be stopped individually.")); action->setEnabled(false); ac->addAction(QStringLiteral("run_stop_menu"), action); d->currentTargetAction = new KSelectAction( i18n("Current Launch Configuration"), this); d->currentTargetAction->setToolTip(i18nc("@info:tooltip", "Current launch configuration")); d->currentTargetAction->setStatusTip(i18n("Current launch Configuration")); d->currentTargetAction->setWhatsThis(i18nc("@info:whatsthis", "Select which launch configuration to run when run is invoked.")); ac->addAction(QStringLiteral("run_default_target"), d->currentTargetAction); } LaunchConfigurationType* RunController::launchConfigurationTypeForId( const QString& id ) { return d->launchConfigurationTypeForId( id ); } void KDevelop::RunController::slotProjectOpened(KDevelop::IProject * project) { d->readLaunchConfigs( project->projectConfiguration(), project ); d->updateCurrentLaunchAction(); } void KDevelop::RunController::slotProjectClosing(KDevelop::IProject * project) { if (!d->currentTargetAction) return; foreach (QAction* action, d->currentTargetAction->actions()) { LaunchConfiguration* l = static_cast(qvariant_cast(action->data())); if ( project == l->project() ) { l->save(); d->launchConfigurations.removeAll(l); delete l; bool wasSelected = action->isChecked(); delete action; if (wasSelected && !d->currentTargetAction->actions().isEmpty()) d->currentTargetAction->actions().at(0)->setChecked(true); } } } void KDevelop::RunController::slotRefreshProject(KDevelop::IProject* project) { slotProjectClosing(project); slotProjectOpened(project); } void RunController::slotDebug() { if (d->launchConfigurations.isEmpty()) { showConfigurationDialog(); } if (!d->launchConfigurations.isEmpty()) { executeDefaultLaunch( QStringLiteral("debug") ); } } void RunController::slotProfile() { if (d->launchConfigurations.isEmpty()) { showConfigurationDialog(); } if (!d->launchConfigurations.isEmpty()) { executeDefaultLaunch( QStringLiteral("profile") ); } } void RunController::slotExecute() { if (d->launchConfigurations.isEmpty()) { showConfigurationDialog(); } if (!d->launchConfigurations.isEmpty()) { executeDefaultLaunch( QStringLiteral("execute") ); } } void KDevelop::RunController::showConfigurationDialog() const { LaunchConfigurationDialog dlg; dlg.exec(); } LaunchConfiguration* KDevelop::RunController::defaultLaunch() const { QAction* projectAction = d->currentTargetAction->currentAction(); if( projectAction ) return static_cast(qvariant_cast(projectAction->data())); return nullptr; } void KDevelop::RunController::registerJob(KJob * job) { if (!job) return; if (!(job->capabilities() & KJob::Killable)) { // see e.g. https://bugs.kde.org/show_bug.cgi?id=314187 qCWarning(SHELL) << "non-killable job" << job << "registered - this might lead to crashes on shutdown."; } if (!d->jobs.contains(job)) { QAction* stopJobAction = nullptr; if (Core::self()->setupFlags() != Core::NoUi) { stopJobAction = new QAction(job->objectName().isEmpty() ? i18n("<%1> Unnamed job", job->staticMetaObject.className()) : job->objectName(), this); stopJobAction->setData(QVariant::fromValue(static_cast(job))); d->stopJobsMenu->addAction(stopJobAction); connect (stopJobAction, &QAction::triggered, this, &RunController::slotKillJob); job->setUiDelegate( new KDialogJobUiDelegate() ); } d->jobs.insert(job, stopJobAction); connect( job, &KJob::finished, this, &RunController::finished ); connect( job, &KJob::destroyed, this, &RunController::jobDestroyed ); // FIXME percent is a private signal and thus we cannot use new connext syntax connect(job, SIGNAL(percent(KJob*,ulong)), this, SLOT(jobPercentChanged())); IRunController::registerJob(job); emit jobRegistered(job); } job->start(); checkState(); } void KDevelop::RunController::unregisterJob(KJob * job) { IRunController::unregisterJob(job); Q_ASSERT(d->jobs.contains(job)); // Delete the stop job action QAction *action = d->jobs.take(job); if (action) action->deleteLater(); checkState(); emit jobUnregistered(job); } void KDevelop::RunController::checkState() { bool running = false; int jobCount = 0; int totalProgress = 0; for (auto it = d->jobs.constBegin(), end = d->jobs.constEnd(); it != end; ++it) { KJob *job = it.key(); if (!job->isSuspended()) { running = true; ++jobCount; totalProgress += job->percent(); } } d->unityLauncher->setProgressVisible(running); if (jobCount > 0) { d->unityLauncher->setProgress((totalProgress + 1) / jobCount); } else { d->unityLauncher->setProgress(0); } if ( ( d->state != Running ? false : true ) == running ) { d->state = running ? Running : Idle; emit runStateChanged(d->state); } if (Core::self()->setupFlags() != Core::NoUi) { d->stopAction->setEnabled(running); d->stopJobsMenu->setEnabled(running); } } void KDevelop::RunController::stopAllProcesses() { // composite jobs might remove child jobs, see also: // https://bugs.kde.org/show_bug.cgi?id=258904 // foreach already iterates over a copy foreach (KJob* job, d->jobs.keys()) { // now we check the real list whether it was deleted if (!d->jobs.contains(job)) continue; if (job->capabilities() & KJob::Killable) { job->kill(KJob::EmitResult); } else { qCWarning(SHELL) << "cannot stop non-killable job: " << job; } } } void KDevelop::RunController::slotKillJob() { QAction* action = dynamic_cast(sender()); Q_ASSERT(action); KJob* job = static_cast(qvariant_cast(action->data())); if (job->capabilities() & KJob::Killable) job->kill(); } void KDevelop::RunController::finished(KJob * job) { unregisterJob(job); switch (job->error()) { case KJob::NoError: case KJob::KilledJobError: case OutputJob::FailedShownError: break; default: { ///WARNING: do *not* use a nested event loop here, it might cause /// random crashes later on, see e.g.: /// https://bugs.kde.org/show_bug.cgi?id=309811 auto dialog = new QDialog(qApp->activeWindow()); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->setWindowTitle(i18n("Process Error")); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Close, dialog); KMessageBox::createKMessageBox(dialog, buttonBox, QMessageBox::Warning, job->errorString(), QStringList(), QString(), nullptr, KMessageBox::NoExec); dialog->show(); } } } void RunController::jobDestroyed(QObject* job) { KJob* kjob = static_cast(job); if (d->jobs.contains(kjob)) { qCWarning(SHELL) << "job destroyed without emitting finished signal!"; unregisterJob(kjob); } } void RunController::jobPercentChanged() { checkState(); } void KDevelop::RunController::suspended(KJob * job) { Q_UNUSED(job); checkState(); } void KDevelop::RunController::resumed(KJob * job) { Q_UNUSED(job); checkState(); } QList< KJob * > KDevelop::RunController::currentJobs() const { return d->jobs.keys(); } QList RunController::launchConfigurations() const { QList configs; foreach (LaunchConfiguration *config, launchConfigurationsInternal()) configs << config; return configs; } QList RunController::launchConfigurationsInternal() const { return d->launchConfigurations; } QList RunController::launchConfigurationTypes() const { return d->launchConfigurationTypes.values(); } void RunController::addConfigurationType( LaunchConfigurationType* type ) { if( !d->launchConfigurationTypes.contains( type->id() ) ) { d->launchConfigurationTypes.insert( type->id(), type ); } } void RunController::removeConfigurationType( LaunchConfigurationType* type ) { foreach( LaunchConfiguration* l, d->launchConfigurations ) { if( l->type() == type ) { removeLaunchConfigurationInternal( l ); } } d->launchConfigurationTypes.remove( type->id() ); } void KDevelop::RunController::addLaunchMode(KDevelop::ILaunchMode* mode) { if( !d->launchModes.contains( mode->id() ) ) { d->launchModes.insert( mode->id(), mode ); } } QList< KDevelop::ILaunchMode* > KDevelop::RunController::launchModes() const { return d->launchModes.values(); } void KDevelop::RunController::removeLaunchMode(KDevelop::ILaunchMode* mode) { d->launchModes.remove( mode->id() ); } KDevelop::ILaunchMode* KDevelop::RunController::launchModeForId(const QString& id) const { QMap::iterator it = d->launchModes.find( id ); if( it != d->launchModes.end() ) { return it.value(); } return nullptr; } void KDevelop::RunController::addLaunchConfiguration(KDevelop::LaunchConfiguration* l) { if( !d->launchConfigurations.contains( l ) ) { d->addLaunchAction( l ); d->launchConfigurations << l; if( !d->currentTargetAction->currentAction() ) { if( !d->currentTargetAction->actions().isEmpty() ) { d->currentTargetAction->actions().at(0)->setChecked( true ); } } connect( l, &LaunchConfiguration::nameChanged, this, &RunController::launchChanged ); } } void KDevelop::RunController::removeLaunchConfiguration(KDevelop::LaunchConfiguration* l) { KConfigGroup launcherGroup; if( l->project() ) { launcherGroup = l->project()->projectConfiguration()->group( Strings::LaunchConfigurationsGroup() ); } else { launcherGroup = Core::self()->activeSession()->config()->group( Strings::LaunchConfigurationsGroup() ); } QStringList configs = launcherGroup.readEntry( Strings::LaunchConfigurationsListEntry(), QStringList() ); configs.removeAll( l->configGroupName() ); launcherGroup.deleteGroup( l->configGroupName() ); launcherGroup.writeEntry( Strings::LaunchConfigurationsListEntry(), configs ); launcherGroup.sync(); removeLaunchConfigurationInternal( l ); } void RunController::removeLaunchConfigurationInternal(LaunchConfiguration *l) { foreach( QAction* a, d->currentTargetAction->actions() ) { if( static_cast( a->data().value() ) == l ) { bool wasSelected = a->isChecked(); d->currentTargetAction->removeAction( a ); if( wasSelected && !d->currentTargetAction->actions().isEmpty() ) { d->currentTargetAction->actions().at(0)->setChecked( true ); } break; } } d->launchConfigurations.removeAll( l ); delete l; } void KDevelop::RunController::executeDefaultLaunch(const QString& runMode) { if (auto dl = defaultLaunch()) { execute(runMode, dl); } else { qCWarning(SHELL) << "no default launch!"; } } void RunController::setDefaultLaunch(ILaunchConfiguration* l) { foreach( QAction* a, d->currentTargetAction->actions() ) { if( static_cast( a->data().value() ) == l ) { a->setChecked(true); break; } } } bool launcherNameExists(const QString& name) { foreach(ILaunchConfiguration* config, Core::self()->runControllerInternal()->launchConfigurations()) { if(config->name()==name) return true; } return false; } QString makeUnique(const QString& name) { if(launcherNameExists(name)) { for(int i=2; ; i++) { QString proposed = QStringLiteral("%1 (%2)").arg(name).arg(i); if(!launcherNameExists(proposed)) { return proposed; } } } return name; } ILaunchConfiguration* RunController::createLaunchConfiguration ( LaunchConfigurationType* type, const QPair& launcher, IProject* project, const QString& name ) { KConfigGroup launchGroup; if( project ) { launchGroup = project->projectConfiguration()->group( Strings::LaunchConfigurationsGroup() ); } else { launchGroup = Core::self()->activeSession()->config()->group( Strings::LaunchConfigurationsGroup() ); } QStringList configs = launchGroup.readEntry( Strings::LaunchConfigurationsListEntry(), QStringList() ); uint num = 0; QString baseName = QStringLiteral("Launch Configuration"); while( configs.contains( QStringLiteral( "%1 %2" ).arg( baseName ).arg( num ) ) ) { num++; } QString groupName = QStringLiteral( "%1 %2" ).arg( baseName ).arg( num ); KConfigGroup launchConfigGroup = launchGroup.group( groupName ); QString cfgName = name; if( name.isEmpty() ) { cfgName = i18n("New %1 Launcher", type->name() ); cfgName = makeUnique(cfgName); } launchConfigGroup.writeEntry(LaunchConfiguration::LaunchConfigurationNameEntry(), cfgName ); launchConfigGroup.writeEntry(LaunchConfiguration::LaunchConfigurationTypeEntry(), type->id() ); launchConfigGroup.sync(); configs << groupName; launchGroup.writeEntry( Strings::LaunchConfigurationsListEntry(), configs ); launchGroup.sync(); LaunchConfiguration* l = new LaunchConfiguration( launchConfigGroup, project ); l->setLauncherForMode( launcher.first, launcher.second ); Core::self()->runControllerInternal()->addLaunchConfiguration( l ); return l; } QItemDelegate * KDevelop::RunController::delegate() const { return d->delegate; } ContextMenuExtension RunController::contextMenuExtension ( Context* ctx ) { delete d->launchAsMapper; d->launchAsMapper = new QSignalMapper( this ); connect( d->launchAsMapper, static_cast(&QSignalMapper::mapped), this, [&] (int id) { d->launchAs(id); } ); d->launchAsInfo.clear(); d->contextItem = nullptr; ContextMenuExtension ext; if( ctx->type() == Context::ProjectItemContext ) { KDevelop::ProjectItemContext* prjctx = dynamic_cast( ctx ); if( prjctx->items().count() == 1 ) { ProjectBaseItem* itm = prjctx->items().at( 0 ); int i = 0; foreach( ILaunchMode* mode, d->launchModes ) { KActionMenu* menu = new KActionMenu( i18n("%1 As...", mode->name() ), this ); foreach( LaunchConfigurationType* type, launchConfigurationTypes() ) { bool hasLauncher = false; foreach( ILauncher* launcher, type->launchers() ) { if( launcher->supportedModes().contains( mode->id() ) ) { hasLauncher = true; } } if( hasLauncher && type->canLaunch(itm) ) { d->launchAsInfo[i] = qMakePair( type->id(), mode->id() ); QAction* act = new QAction( d->launchAsMapper ); act->setText( type->name() ); qCDebug(SHELL) << "Setting up mapping for:" << i << "for action" << act->text() << "in mode" << mode->name(); d->launchAsMapper->setMapping( act, i ); connect( act, &QAction::triggered, d->launchAsMapper, static_cast(&QSignalMapper::map) ); menu->addAction(act); i++; } } if( menu->menu()->actions().count() > 0 ) { ext.addAction( ContextMenuExtension::RunGroup, menu); } } if( ext.actions( ContextMenuExtension::RunGroup ).count() > 0 ) { d->contextItem = itm; } } } return ext; } RunDelegate::RunDelegate( QObject* parent ) : QItemDelegate(parent), runProviderBrush( KColorScheme::View, KColorScheme::PositiveText ), errorBrush( KColorScheme::View, KColorScheme::NegativeText ) { } void RunDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const { QStyleOptionViewItem opt = option; // if( status.isValid() && status.canConvert() ) // { // IRunProvider::OutputTypes type = status.value(); // if( type == IRunProvider::RunProvider ) // { // opt.palette.setBrush( QPalette::Text, runProviderBrush.brush( option.palette ) ); // } else if( type == IRunProvider::StandardError ) // { // opt.palette.setBrush( QPalette::Text, errorBrush.brush( option.palette ) ); // } // } QItemDelegate::paint(painter, opt, index); } #include "moc_runcontroller.cpp" diff --git a/shell/sessionlock.cpp b/shell/sessionlock.cpp index e83e85b3e..3b26f057f 100644 --- a/shell/sessionlock.cpp +++ b/shell/sessionlock.cpp @@ -1,227 +1,226 @@ /* * This file is part of KDevelop * Copyright 2013 Milian Wolff * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "sessionlock.h" #include "debug.h" #include "sessioncontroller.h" #include #include -#include #include #include #include using namespace KDevelop; namespace { QString lockFileForSession( const QString& id ) { return SessionController::sessionDirectory( id ) + QLatin1String("/lock"); } QString dBusServiceNameForSession( const QString& id ) { // We remove starting "{" and ending "}" from the string UUID representation // as D-Bus apparently doesn't allow them in service names return QStringLiteral( "org.kdevelop.kdevplatform-lock-" ) + id.midRef( 1, id.size() - 2 ); } /// Force-removes the lock-file. void forceRemoveLockfile(const QString& lockFilename) { if( QFile::exists( lockFilename ) ) { QFile::remove( lockFilename ); } } } TryLockSessionResult SessionLock::tryLockSession(const QString& sessionId, bool doLocking) { ///FIXME: if this is hit, someone tried to lock a non-existing session /// this should be fixed by using a proper data type distinct from /// QString for id's, i.e. QUuid or similar. Q_ASSERT(QFile::exists(SessionController::sessionDirectory( sessionId ))); /* * We've got two locking mechanisms here: D-Bus unique service name (based on the session id) * and a plain lockfile (QLockFile). * The latter is required to get the appname/pid of the locking instance * in case if it's stale/hanging/crashed (to make user know which PID he needs to kill). * D-Bus mechanism is the primary one. * * Since there is a kind of "logic tree", the code is a bit hard. */ const QString service = dBusServiceNameForSession( sessionId ); QDBusConnection connection = QDBusConnection::sessionBus(); QDBusConnectionInterface* connectionInterface = connection.interface(); const QString lockFilename = lockFileForSession( sessionId ); QSharedPointer lockFile(new QLockFile( lockFilename )); const bool haveDBus = connection.isConnected(); const bool canLockDBus = haveDBus && connectionInterface && !connectionInterface->isServiceRegistered( service ); bool lockedDBus = false; // Lock D-Bus if we can and we need to if( doLocking && canLockDBus ) { lockedDBus = connection.registerService( service ); } // Attempt to lock file, despite the possibility to do so and presence of the request (doLocking) // This is required as QLockFile::getLockInfo() works only after QLockFile::lock() is called bool lockResult = lockFile->tryLock(); SessionRunInfo runInfo; if (lockResult) { // Unlock immediately if we shouldn't have locked it if( haveDBus && !lockedDBus ) { lockFile->unlock(); } } else { // If locking failed, retrieve the lock's metadata lockFile->getLockInfo(&runInfo.holderPid, &runInfo.holderHostname, &runInfo.holderApp ); runInfo.isRunning = !haveDBus || !canLockDBus; if( haveDBus && lockedDBus ) { // Since the lock-file is secondary, try to force-lock it if D-Bus locking succeeded forceRemoveLockfile(lockFilename); lockResult = lockFile->tryLock(); Q_ASSERT(lockResult); } } // Set the result by D-Bus status if (doLocking && (haveDBus ? lockedDBus : lockResult)) { return TryLockSessionResult(QSharedPointer(new SessionLock(sessionId, lockFile))); } else { return TryLockSessionResult(runInfo); } } QString SessionLock::id() { return m_sessionId; } SessionLock::SessionLock(const QString& sessionId, const QSharedPointer& lockFile) : m_sessionId(sessionId) , m_lockFile(lockFile) { Q_ASSERT(lockFile->isLocked()); } SessionLock::~SessionLock() { m_lockFile->unlock(); bool unregistered = QDBusConnection::sessionBus().unregisterService( dBusServiceNameForSession(m_sessionId) ); Q_UNUSED(unregistered); } void SessionLock::removeFromDisk() { Q_ASSERT(m_lockFile->isLocked()); // unlock first to prevent warnings: "Could not remove our own lock file ..." m_lockFile->unlock(); QDir(SessionController::sessionDirectory(m_sessionId)).removeRecursively(); } QString SessionLock::handleLockedSession(const QString& sessionName, const QString& sessionId, const SessionRunInfo& runInfo) { if( !runInfo.isRunning ) { return sessionId; } // try to make the locked session active { // The timeout for "ensureVisible" call // Leave it sufficiently low to avoid waiting for hung instances. static const int timeout_ms = 1000; QDBusMessage message = QDBusMessage::createMethodCall( dBusServiceNameForSession(sessionId), QStringLiteral("/kdevelop/MainWindow"), QStringLiteral("org.kdevelop.MainWindow"), QStringLiteral("ensureVisible") ); QDBusMessage reply = QDBusConnection::sessionBus().call( message, QDBus::Block, timeout_ms ); if( reply.type() == QDBusMessage::ReplyMessage ) { QTextStream out(stdout); out << i18nc( "@info:shell", "Running %1 instance (PID: %2) detected, making this one visible instead of starting a new one", runInfo.holderApp, runInfo.holderPid ) << endl; return QString(); } else { qCWarning(SHELL) << i18nc("@info:shell", "Running %1 instance (PID: %2) is apparently hung", runInfo.holderApp, runInfo.holderPid); qCWarning(SHELL) << i18nc("@info:shell", "running %1 instance (PID: %2) is apparently hung", runInfo.holderApp, runInfo.holderPid); } } // otherwise ask the user whether we should retry QString problemDescription = i18nc("@info", "The given application did not respond to a DBUS call, " "it may have crashed or is hanging."); QString problemHeader; if( runInfo.holderPid != -1 ) { problemHeader = i18nc("@info", "Failed to lock the session %1, " "already locked by %2 on %3 (PID %4).", sessionName, runInfo.holderApp, runInfo.holderHostname, runInfo.holderPid); } else { problemHeader = i18nc("@info", "Failed to lock the session %1 (lock-file unavailable).", sessionName); } QString problemResolution = i18nc("@info", "

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

"); QString errmsg = "

" + problemHeader + "
" + problemDescription + "

" + problemResolution; KGuiItem retry = KStandardGuiItem::cont(); retry.setText(i18nc("@action:button", "Retry startup")); KGuiItem choose = KStandardGuiItem::configure(); choose.setText(i18nc("@action:button", "Choose another session")); KGuiItem cancel = KStandardGuiItem::quit(); int ret = KMessageBox::warningYesNoCancel(nullptr, errmsg, i18nc("@title:window", "Failed to Lock Session %1", sessionName), retry, choose, cancel); switch( ret ) { case KMessageBox::Yes: return sessionId; break; case KMessageBox::No: { QString errmsg = i18nc("@info", "The session %1 is already active in another running instance.", sessionName); return SessionController::showSessionChooserDialog(errmsg); break; } case KMessageBox::Cancel: default: break; } return QString(); } diff --git a/shell/settings/environmentwidget.cpp b/shell/settings/environmentwidget.cpp index 8e1c8662b..df43dcb76 100644 --- a/shell/settings/environmentwidget.cpp +++ b/shell/settings/environmentwidget.cpp @@ -1,338 +1,338 @@ /* This file is part of KDevelop Copyright 2006 Adam Treat Copyright 2007 Dukju Ahn Copyright 2008 Andreas Pakuat Copyright 2017 Friedrich W. H. Kossebau 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 "environmentwidget.h" #include #include #include #include #include #include #include #include #include #include #include #include "environmentprofilelistmodel.h" #include "environmentprofilemodel.h" #include "placeholderitemproxymodel.h" -#include "../debug.h" +#include "debug.h" using namespace KDevelop; class ProfileNameValidator : public QValidator { Q_OBJECT public: explicit ProfileNameValidator(EnvironmentProfileListModel* environmentProfileListModel, QObject* parent = nullptr); QValidator::State validate(QString& input, int& pos) const override; private: const EnvironmentProfileListModel* const m_environmentProfileListModel; }; ProfileNameValidator::ProfileNameValidator(EnvironmentProfileListModel* environmentProfileListModel, QObject* parent) : QValidator(parent) , m_environmentProfileListModel(environmentProfileListModel) { } QValidator::State ProfileNameValidator::validate(QString& input, int& pos) const { Q_UNUSED(pos); if (input.isEmpty()) { return QValidator::Intermediate; } if (m_environmentProfileListModel->hasProfile(input)) { return QValidator::Intermediate; } return QValidator::Acceptable; } EnvironmentWidget::EnvironmentWidget( QWidget *parent ) : QWidget(parent) , m_environmentProfileListModel(new EnvironmentProfileListModel(this)) , m_environmentProfileModel(new EnvironmentProfileModel(m_environmentProfileListModel, this)) , m_proxyModel(new QSortFilterProxyModel(this)) { // setup ui ui.setupUi( this ); ui.profileSelect->setModel(m_environmentProfileListModel); m_proxyModel->setSourceModel(m_environmentProfileModel); PlaceholderItemProxyModel* topProxyModel = new PlaceholderItemProxyModel(this); topProxyModel->setSourceModel(m_proxyModel); topProxyModel->setColumnHint(0, i18n("Enter variable...")); connect(topProxyModel, &PlaceholderItemProxyModel::dataInserted, this, &EnvironmentWidget::onVariableInserted); ui.variableTable->setModel( topProxyModel ); ui.variableTable->horizontalHeader()->setSectionResizeMode( 0, QHeaderView::ResizeToContents ); ui.variableTable->horizontalHeader()->setSectionResizeMode( 1, QHeaderView::Stretch ); ui.removeVariableButton->setShortcut(Qt::Key_Delete); connect(ui.removeVariableButton, &QPushButton::clicked, this, &EnvironmentWidget::removeSelectedVariables); connect(ui.batchModeEditButton, &QPushButton::clicked, this, &EnvironmentWidget::batchModeEditButtonClicked); connect(ui.cloneProfileButton, &QPushButton::clicked, this, &EnvironmentWidget::cloneSelectedProfile); connect(ui.addProfileButton, &QPushButton::clicked, this, &EnvironmentWidget::addProfile); connect(ui.removeProfileButton, &QPushButton::clicked, this, &EnvironmentWidget::removeSelectedProfile); connect(ui.setAsDefaultProfileButton, &QPushButton::clicked, this, &EnvironmentWidget::setSelectedProfileAsDefault); connect(ui.profileSelect, static_cast(&KComboBox::currentIndexChanged), this, &EnvironmentWidget::onSelectedProfileChanged); connect(m_environmentProfileListModel, &EnvironmentProfileListModel::defaultProfileChanged, this, &EnvironmentWidget::onDefaultProfileChanged); connect(m_environmentProfileListModel, &EnvironmentProfileListModel::rowsInserted, this, &EnvironmentWidget::changed); connect(m_environmentProfileListModel, &EnvironmentProfileListModel::rowsRemoved, this, &EnvironmentWidget::changed); connect(m_environmentProfileListModel, &EnvironmentProfileListModel::defaultProfileChanged, this, &EnvironmentWidget::changed); connect(ui.variableTable->selectionModel(), &QItemSelectionModel::selectionChanged, this, &EnvironmentWidget::updateDeleteVariableButton); connect(m_environmentProfileModel, &EnvironmentProfileModel::rowsInserted, this, &EnvironmentWidget::updateDeleteVariableButton); connect(m_environmentProfileModel, &EnvironmentProfileModel::rowsRemoved, this, &EnvironmentWidget::updateDeleteVariableButton); connect(m_environmentProfileModel, &EnvironmentProfileModel::modelReset, this, &EnvironmentWidget::updateDeleteVariableButton); connect(m_environmentProfileModel, &EnvironmentProfileModel::dataChanged, this, &EnvironmentWidget::changed); connect(m_environmentProfileModel, &EnvironmentProfileModel::rowsInserted, this, &EnvironmentWidget::changed); connect(m_environmentProfileModel, &EnvironmentProfileModel::rowsRemoved, this, &EnvironmentWidget::changed); } void EnvironmentWidget::selectProfile(const QString& profileName) { const int profileIndex = m_environmentProfileListModel->profileIndex(profileName); if (profileIndex < 0) { return; } ui.profileSelect->setCurrentIndex(profileIndex); } void EnvironmentWidget::updateDeleteVariableButton() { const auto selectedRows = ui.variableTable->selectionModel()->selectedRows(); ui.removeVariableButton->setEnabled(!selectedRows.isEmpty()); } void EnvironmentWidget::setSelectedProfileAsDefault() { const int selectedIndex = ui.profileSelect->currentIndex(); m_environmentProfileListModel->setDefaultProfile(selectedIndex); } void EnvironmentWidget::loadSettings( KConfig* config ) { qCDebug(SHELL) << "Loading profiles from config"; m_environmentProfileListModel->loadFromConfig(config); const int defaultProfileIndex = m_environmentProfileListModel->defaultProfileIndex(); ui.profileSelect->setCurrentIndex(defaultProfileIndex); } void EnvironmentWidget::saveSettings( KConfig* config ) { m_environmentProfileListModel->saveToConfig(config); } void EnvironmentWidget::defaults( KConfig* config ) { loadSettings( config ); } QString EnvironmentWidget::askNewProfileName(const QString& defaultName) { QDialog dialog(this); dialog.setWindowTitle(i18n("Enter Name of New Environment Profile")); QVBoxLayout *layout = new QVBoxLayout(&dialog); auto editLayout = new QHBoxLayout; auto label = new QLabel(i18n("Name:")); editLayout->addWidget(label); auto edit = new QLineEdit; editLayout->addWidget(edit); layout->addLayout(editLayout); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); auto okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setEnabled(false); okButton->setDefault(true); dialog.connect(buttonBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept); dialog.connect(buttonBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); layout->addWidget(buttonBox); auto validator = new ProfileNameValidator(m_environmentProfileListModel, &dialog); connect(edit, &QLineEdit::textChanged, validator, [validator, okButton](const QString& text) { int pos; QString t(text); const bool isValidProfileName = (validator->validate(t, pos) == QValidator::Acceptable); okButton->setEnabled(isValidProfileName); }); edit->setText(defaultName); edit->selectAll(); if (dialog.exec() != QDialog::Accepted) { return {}; } return edit->text(); } void EnvironmentWidget::removeSelectedVariables() { const auto selectedRows = ui.variableTable->selectionModel()->selectedRows(); if (selectedRows.isEmpty()) { return; } QStringList variables; for (const auto& idx : selectedRows) { const QString variable = idx.data(EnvironmentProfileModel::VariableRole).toString(); variables << variable; } m_environmentProfileModel->removeVariables(variables); } void EnvironmentWidget::onVariableInserted(int column, const QVariant& value) { Q_UNUSED(column); m_environmentProfileModel->addVariable(value.toString(), QString()); } void EnvironmentWidget::batchModeEditButtonClicked() { QDialog dialog(this); dialog.setWindowTitle( i18n( "Batch Edit Mode" ) ); QVBoxLayout *layout = new QVBoxLayout(&dialog); auto edit = new QPlainTextEdit; edit->setPlaceholderText(QStringLiteral("VARIABLE1=VALUE1\nVARIABLE2=VALUE2")); QString text; for (int i = 0; i < m_proxyModel->rowCount(); ++i) { const auto variable = m_proxyModel->index(i, EnvironmentProfileModel::VariableColumn).data().toString(); const auto value = m_proxyModel->index(i, EnvironmentProfileModel::ValueColumn).data().toString(); text.append(QStringLiteral("%1=%2\n").arg(variable, value)); } edit->setPlainText(text); layout->addWidget( edit ); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel); auto okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); dialog.connect(buttonBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept); dialog.connect(buttonBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); layout->addWidget(buttonBox); dialog.resize(600, 400); if ( dialog.exec() != QDialog::Accepted ) { return; } m_environmentProfileModel->setVariablesFromString(edit->toPlainText()); } void EnvironmentWidget::addProfile() { const auto profileName = askNewProfileName(QString()); if (profileName.isEmpty()) { return; } const int profileIndex = m_environmentProfileListModel->addProfile(profileName); ui.profileSelect->setCurrentIndex(profileIndex); ui.variableTable->setFocus(Qt::OtherFocusReason); } void EnvironmentWidget::cloneSelectedProfile() { const int currentIndex = ui.profileSelect->currentIndex(); const auto currentProfileName = m_environmentProfileListModel->profileName(currentIndex); // pass original name as starting name, as the user might want to enter a variant of it const auto profileName = askNewProfileName(currentProfileName); if (profileName.isEmpty()) { return; } const int profileIndex = m_environmentProfileListModel->cloneProfile(profileName, currentProfileName); ui.profileSelect->setCurrentIndex(profileIndex); ui.variableTable->setFocus(Qt::OtherFocusReason); } void EnvironmentWidget::removeSelectedProfile() { if (ui.profileSelect->count() <= 1) { return; } const int selectedProfileIndex = ui.profileSelect->currentIndex(); m_environmentProfileListModel->removeProfile(selectedProfileIndex); const int defaultProfileIndex = m_environmentProfileListModel->defaultProfileIndex(); ui.profileSelect->setCurrentIndex(defaultProfileIndex); } void EnvironmentWidget::onDefaultProfileChanged(int defaultProfileIndex) { const int selectedProfileIndex = ui.profileSelect->currentIndex(); const bool isDefaultProfile = (defaultProfileIndex == selectedProfileIndex); ui.removeProfileButton->setEnabled(ui.profileSelect->count() > 1 && !isDefaultProfile); ui.setAsDefaultProfileButton->setEnabled(!isDefaultProfile); } void EnvironmentWidget::onSelectedProfileChanged(int selectedProfileIndex) { const auto selectedProfileName = m_environmentProfileListModel->profileName(selectedProfileIndex); m_environmentProfileModel->setCurrentProfile(selectedProfileName); const bool isDefaultProfile = (m_environmentProfileListModel->defaultProfileIndex() == selectedProfileIndex); ui.removeProfileButton->setEnabled(ui.profileSelect->count() > 1 && !isDefaultProfile); ui.setAsDefaultProfileButton->setEnabled(!isDefaultProfile); } #include "environmentwidget.moc" diff --git a/shell/settings/pluginpreferences.cpp b/shell/settings/pluginpreferences.cpp index 55d286b5b..abf80793b 100644 --- a/shell/settings/pluginpreferences.cpp +++ b/shell/settings/pluginpreferences.cpp @@ -1,108 +1,107 @@ /* KDevelop Project Settings * * Copyright 2008 Andreas Pakulat * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * 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 "pluginpreferences.h" -#include #include #include #include #include #include "../core.h" #include "../plugincontroller.h" -#include "../debug.h" +#include "debug.h" namespace KDevelop { PluginPreferences::PluginPreferences(QWidget* parent) : ConfigPage(nullptr, nullptr, parent) { QVBoxLayout* lay = new QVBoxLayout(this ); lay->setMargin(0); selector = new KPluginSelector( this ); lay->addWidget( selector ); QMap> plugins; const QMap categories = { { "Core", i18nc("@title:group", "Core") }, { "Project Management", i18nc("@title:group", "Project Management") }, { "Version Control", i18nc("@title:group", "Version Control") }, { "Utilities", i18nc("@title:group", "Utilities") }, { "Documentation", i18nc("@title:group", "Documentation") }, { "Language Support", i18nc("@title:group", "Language Support") }, { "Debugging", i18nc("@title:group", "Debugging") }, { "Testing", i18nc("@title:group", "Testing") }, { "Analyzers", i18nc("@title:group", "Analyzers") }, { "Other", i18nc("@title:group", "Other") } }; foreach (const KPluginMetaData& info, Core::self()->pluginControllerInternal()->allPluginInfos()) { const QString loadMode = info.value(QStringLiteral("X-KDevelop-LoadMode")); if( loadMode.isEmpty() || loadMode == QLatin1String("UserSelectable") ) { QString category = info.category(); if (!categories.contains(category)) { if (!category.isEmpty()) { qCWarning(SHELL) << "unknown category for plugin" << info.name() << ":" << info.category(); } category = QStringLiteral("Other"); } KPluginInfo kpi(info); kpi.setPluginEnabled(Core::self()->pluginControllerInternal()->isEnabled(info)); plugins[category] << kpi; } else qDebug() << "skipping..." << info.pluginId() << info.value(QStringLiteral("X-KDevelop-Category")) << loadMode; } for (auto it = plugins.constBegin(), end = plugins.constEnd(); it != end; ++it) { selector->addPlugins(it.value(), KPluginSelector::ReadConfigFile, categories.value(it.key()), it.key(), Core::self()->activeSession()->config() ); } connect(selector, &KPluginSelector::changed, this, &PluginPreferences::changed); selector->load(); } void PluginPreferences::defaults() { Core::self()->pluginControllerInternal()->resetToDefaults(); selector->load(); } void PluginPreferences::apply() { selector->save(); qCDebug(SHELL) << "Plugins before apply: " << Core::self()->pluginControllerInternal()->allPluginNames(); Core::self()->pluginControllerInternal()->updateLoadedPlugins(); qCDebug(SHELL) << "Plugins after apply: " << Core::self()->pluginControllerInternal()->allPluginNames(); selector->load(); // Some plugins may have failed to load, they must be unchecked. } void PluginPreferences::reset() { selector->load(); } } diff --git a/shell/settings/sourceformattersettings.cpp b/shell/settings/sourceformattersettings.cpp index 4057fb5dd..19e036ea7 100644 --- a/shell/settings/sourceformattersettings.cpp +++ b/shell/settings/sourceformattersettings.cpp @@ -1,538 +1,538 @@ /* This file is part of KDevelop * Copyright (C) 2008 Cédric Pasteur 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 "sourceformattersettings.h" #include #include #include #include -#include + #include #include #include #include #include #include #include #include #include #include #include #include #include #include "editstyledialog.h" -#include "../debug.h" +#include "debug.h" #define STYLE_ROLE (Qt::UserRole+1) using KDevelop::Core; using KDevelop::ISourceFormatter; using KDevelop::SourceFormatterStyle; using KDevelop::SourceFormatterController; using KDevelop::SourceFormatter; namespace { namespace Strings { QString userStylePrefix() { return QStringLiteral("User"); } } } LanguageSettings::LanguageSettings() : selectedFormatter(nullptr), selectedStyle(nullptr) { } SourceFormatterSettings::SourceFormatterSettings(QWidget* parent) : KDevelop::ConfigPage(nullptr, nullptr, parent) { setupUi(this); connect( cbLanguages, static_cast(&KComboBox::currentIndexChanged), this, &SourceFormatterSettings::selectLanguage ); connect( cbFormatters, static_cast(&KComboBox::currentIndexChanged), this, &SourceFormatterSettings::selectFormatter ); connect( chkKateModelines, &QCheckBox::toggled, this, &SourceFormatterSettings::somethingChanged ); connect( chkKateOverrideIndentation, &QCheckBox::toggled, this, &SourceFormatterSettings::somethingChanged ); connect( styleList, &QListWidget::currentRowChanged, this, &SourceFormatterSettings::selectStyle ); connect( btnDelStyle, &QPushButton::clicked, this, &SourceFormatterSettings::deleteStyle ); connect( btnNewStyle, &QPushButton::clicked, this, &SourceFormatterSettings::newStyle ); connect( btnEditStyle, &QPushButton::clicked, this, &SourceFormatterSettings::editStyle ); connect( styleList, &QListWidget::itemChanged, this, &SourceFormatterSettings::styleNameChanged ); m_document = KTextEditor::Editor::instance()->createDocument(this); m_document->setReadWrite(false); m_view = m_document->createView(textEditor); m_view->setStatusBarEnabled(false); QVBoxLayout *layout2 = new QVBoxLayout(textEditor); layout2->addWidget(m_view); textEditor->setLayout(layout2); m_view->show(); KTextEditor::ConfigInterface *iface = qobject_cast(m_view); if (iface) { iface->setConfigValue(QStringLiteral("dynamic-word-wrap"), false); iface->setConfigValue(QStringLiteral("icon-bar"), false); } } SourceFormatterSettings::~SourceFormatterSettings() { qDeleteAll(formatters); } void selectAvailableStyle(LanguageSettings& lang) { Q_ASSERT(!lang.selectedFormatter->styles.empty()); lang.selectedStyle = *lang.selectedFormatter->styles.begin(); } void SourceFormatterSettings::reset() { SourceFormatterController* fmtctrl = Core::self()->sourceFormatterControllerInternal(); QList plugins = KDevelop::ICore::self()->pluginController()->allPluginsForExtension( QStringLiteral("org.kdevelop.ISourceFormatter") ); foreach( KDevelop::IPlugin* plugin, plugins ) { KDevelop::ISourceFormatter* ifmt = plugin->extension(); auto info = KDevelop::Core::self()->pluginControllerInternal()->pluginInfo( plugin ); KDevelop::SourceFormatter* formatter; FormatterMap::const_iterator iter = formatters.constFind(ifmt->name()); if (iter == formatters.constEnd()) { formatter = fmtctrl->createFormatterForPlugin(ifmt); formatters[ifmt->name()] = formatter; } else { formatter = iter.value(); } foreach ( const SourceFormatterStyle* style, formatter->styles ) { foreach ( const SourceFormatterStyle::MimeHighlightPair& item, style->mimeTypes() ) { QMimeType mime = QMimeDatabase().mimeTypeForName(item.mimeType); if (!mime.isValid()) { qCWarning(SHELL) << "plugin" << info.name() << "supports unknown mimetype entry" << item.mimeType; continue; } QString languageName = item.highlightMode; LanguageSettings& l = languages[languageName]; l.mimetypes.append(mime); l.formatters.insert( formatter ); } } } // Sort the languages, preferring firstly active, then loaded languages QList sortedLanguages; foreach(const auto language, KDevelop::ICore::self()->languageController()->activeLanguages() + KDevelop::ICore::self()->languageController()->loadedLanguages()) { if( languages.contains( language->name() ) && !sortedLanguages.contains(language->name()) ) { sortedLanguages.push_back( language->name() ); } } foreach( const QString& name, languages.keys() ) if( !sortedLanguages.contains( name ) ) sortedLanguages.push_back( name ); foreach( const QString& name, sortedLanguages ) { // Pick the first appropriate mimetype for this language KConfigGroup grp = fmtctrl->sessionConfig(); LanguageSettings& l = languages[name]; const QList mimetypes = l.mimetypes; foreach (const QMimeType& mimetype, mimetypes) { QStringList formatterAndStyleName = grp.readEntry(mimetype.name(), QString()).split(QStringLiteral("||"), QString::KeepEmptyParts); FormatterMap::const_iterator formatterIter = formatters.constFind(formatterAndStyleName.first()); if (formatterIter == formatters.constEnd()) { qCDebug(SHELL) << "Reference to unknown formatter" << formatterAndStyleName.first(); Q_ASSERT(!l.formatters.empty()); // otherwise there should be no entry for 'name' l.selectedFormatter = *l.formatters.begin(); selectAvailableStyle(l); } else { l.selectedFormatter = formatterIter.value(); SourceFormatter::StyleMap::const_iterator styleIter = l.selectedFormatter->styles.constFind(formatterAndStyleName.at( 1 )); if (styleIter == l.selectedFormatter->styles.constEnd()) { qCDebug(SHELL) << "No style" << formatterAndStyleName.at( 1 ) << "found for formatter" << formatterAndStyleName.first(); selectAvailableStyle(l); } else { l.selectedStyle = styleIter.value(); } } } if (!l.selectedFormatter) { Q_ASSERT(!l.formatters.empty()); l.selectedFormatter = *l.formatters.begin(); } if (!l.selectedStyle) { selectAvailableStyle(l); } } bool b = blockSignals( true ); cbLanguages->blockSignals( !b ); cbFormatters->blockSignals( !b ); styleList->blockSignals( !b ); chkKateModelines->blockSignals( !b ); chkKateOverrideIndentation->blockSignals( !b ); cbLanguages->clear(); cbFormatters->clear(); styleList->clear(); chkKateModelines->setChecked( fmtctrl->sessionConfig().readEntry( SourceFormatterController::kateModeLineConfigKey(), false ) ); chkKateOverrideIndentation->setChecked( fmtctrl->sessionConfig().readEntry( SourceFormatterController::kateOverrideIndentationConfigKey(), false ) ); foreach( const QString& name, sortedLanguages ) { cbLanguages->addItem( name ); } if( cbLanguages->count() == 0 ) { cbLanguages->setEnabled( false ); selectLanguage( -1 ); } else { cbLanguages->setCurrentIndex( 0 ); selectLanguage( 0 ); } updatePreview(); blockSignals( b ); cbLanguages->blockSignals( b ); cbFormatters->blockSignals( b ); styleList->blockSignals( b ); chkKateModelines->blockSignals( b ); chkKateOverrideIndentation->blockSignals( b ); } void SourceFormatterSettings::apply() { KConfigGroup globalConfig = Core::self()->sourceFormatterControllerInternal()->globalConfig(); foreach( SourceFormatter* fmt, formatters ) { KConfigGroup fmtgrp = globalConfig.group( fmt->formatter->name() ); // delete all styles so we don't leave any behind when all user styles are deleted foreach( const QString& subgrp, fmtgrp.groupList() ) { if( subgrp.startsWith( Strings::userStylePrefix() ) ) { fmtgrp.deleteGroup( subgrp ); } } foreach( const SourceFormatterStyle* style, fmt->styles ) { if( style->name().startsWith( Strings::userStylePrefix() ) ) { KConfigGroup stylegrp = fmtgrp.group( style->name() ); stylegrp.writeEntry( SourceFormatterController::styleCaptionKey(), style->caption() ); stylegrp.writeEntry( SourceFormatterController::styleContentKey(), style->content() ); stylegrp.writeEntry( SourceFormatterController::styleMimeTypesKey(), style->mimeTypesVariant() ); stylegrp.writeEntry( SourceFormatterController::styleSampleKey(), style->overrideSample() ); } } } KConfigGroup sessionConfig = Core::self()->sourceFormatterControllerInternal()->sessionConfig(); for ( LanguageMap::const_iterator iter = languages.constBegin(); iter != languages.constEnd(); ++iter ) { foreach(const QMimeType& mime, iter.value().mimetypes) { sessionConfig.writeEntry(mime.name(), QStringLiteral("%1||%2").arg(iter.value().selectedFormatter->formatter->name(), iter.value().selectedStyle->name())); } } sessionConfig.writeEntry( SourceFormatterController::kateModeLineConfigKey(), chkKateModelines->isChecked() ); sessionConfig.writeEntry( SourceFormatterController::kateOverrideIndentationConfigKey(), chkKateOverrideIndentation->isChecked() ); sessionConfig.sync(); globalConfig.sync(); Core::self()->sourceFormatterControllerInternal()->settingsChanged(); } void SourceFormatterSettings::defaults() { // do nothing } void SourceFormatterSettings::enableStyleButtons() { bool userEntry = styleList->currentItem() && styleList->currentItem()->data( STYLE_ROLE ).toString().startsWith( Strings::userStylePrefix() ); QString languageName = cbLanguages->currentText(); QMap< QString, LanguageSettings >::const_iterator it = languages.constFind(languageName); bool hasEditWidget = false; if (it != languages.constEnd()) { const LanguageSettings& l = it.value(); Q_ASSERT(l.selectedFormatter); ISourceFormatter* fmt = l.selectedFormatter->formatter; hasEditWidget = ( fmt && QScopedPointer(fmt->editStyleWidget( l.mimetypes.first() )) ); } btnDelStyle->setEnabled( userEntry ); btnEditStyle->setEnabled( userEntry && hasEditWidget ); btnNewStyle->setEnabled( cbFormatters->currentIndex() >= 0 && hasEditWidget ); } void SourceFormatterSettings::selectLanguage( int idx ) { cbFormatters->clear(); if( idx < 0 ) { cbFormatters->setEnabled( false ); selectFormatter( -1 ); return; } cbFormatters->setEnabled( true ); { QSignalBlocker blocker(cbFormatters); LanguageSettings& l = languages[cbLanguages->itemText( idx )]; foreach( const SourceFormatter* fmt, l.formatters ) { cbFormatters->addItem( fmt->formatter->caption(), fmt->formatter->name() ); } cbFormatters->setCurrentIndex(cbFormatters->findData(l.selectedFormatter->formatter->name())); } selectFormatter( cbFormatters->currentIndex() ); emit changed(); } void SourceFormatterSettings::selectFormatter( int idx ) { styleList->clear(); if( idx < 0 ) { styleList->setEnabled( false ); enableStyleButtons(); return; } styleList->setEnabled( true ); LanguageSettings& l = languages[ cbLanguages->currentText() ]; Q_ASSERT( idx < l.formatters.size() ); FormatterMap::const_iterator formatterIter = formatters.constFind(cbFormatters->itemData( idx ).toString()); Q_ASSERT( formatterIter != formatters.constEnd() ); Q_ASSERT( l.formatters.contains(formatterIter.value()) ); if (l.selectedFormatter != formatterIter.value()) { l.selectedFormatter = formatterIter.value(); l.selectedStyle = nullptr; // will hold 0 until a style is picked } foreach( const SourceFormatterStyle* style, formatterIter.value()->styles ) { if ( ! style->supportsLanguage(cbLanguages->currentText())) { // do not list items which do not support the selected language continue; } QListWidgetItem* item = addStyle( *style ); if (style == l.selectedStyle) { styleList->setCurrentItem(item); } } if (l.selectedStyle == nullptr) { styleList->setCurrentRow(0); } enableStyleButtons(); emit changed(); } void SourceFormatterSettings::selectStyle( int row ) { if( row < 0 ) { enableStyleButtons(); return; } styleList->setCurrentRow( row ); LanguageSettings& l = languages[ cbLanguages->currentText() ]; l.selectedStyle = l.selectedFormatter->styles[styleList->item( row )->data( STYLE_ROLE ).toString()]; enableStyleButtons(); updatePreview(); emit changed(); } void SourceFormatterSettings::deleteStyle() { Q_ASSERT( styleList->currentRow() >= 0 ); QListWidgetItem* item = styleList->currentItem(); LanguageSettings& l = languages[ cbLanguages->currentText() ]; SourceFormatter* fmt = l.selectedFormatter; SourceFormatter::StyleMap::iterator styleIter = fmt->styles.find(item->data( STYLE_ROLE ).toString()); QStringList otherLanguageNames; QList otherlanguages; for ( LanguageMap::iterator languageIter = languages.begin(); languageIter != languages.end(); ++languageIter ) { if ( &languageIter.value() != &l && languageIter.value().selectedStyle == styleIter.value() ) { otherLanguageNames.append(languageIter.key()); otherlanguages.append(&languageIter.value()); } } if (!otherLanguageNames.empty() && KMessageBox::warningContinueCancel(this, i18n("The style %1 is also used for the following languages:\n%2.\nAre you sure you want to delete it?", styleIter.value()->caption(), otherLanguageNames.join(QStringLiteral("\n"))), i18n("Style being deleted")) != KMessageBox::Continue) { return; } styleList->takeItem( styleList->currentRow() ); fmt->styles.erase(styleIter); delete item; selectStyle( styleList->count() > 0 ? 0 : -1 ); foreach (LanguageSettings* lang, otherlanguages) { selectAvailableStyle(*lang); } updatePreview(); emit changed(); } void SourceFormatterSettings::editStyle() { QString language = cbLanguages->currentText(); Q_ASSERT( languages.contains( language ) ); LanguageSettings& l = languages[ language ]; SourceFormatter* fmt = l.selectedFormatter; QMimeType mimetype = l.mimetypes.first(); if( QScopedPointer(fmt->formatter->editStyleWidget( mimetype )) ) { EditStyleDialog dlg( fmt->formatter, mimetype, *l.selectedStyle, this ); if( dlg.exec() == QDialog::Accepted ) { l.selectedStyle->setContent(dlg.content()); } updatePreview(); emit changed(); } } void SourceFormatterSettings::newStyle() { QListWidgetItem* item = styleList->currentItem(); LanguageSettings& l = languages[ cbLanguages->currentText() ]; SourceFormatter* fmt = l.selectedFormatter; int idx = 0; for( int i = 0; i < styleList->count(); i++ ) { QString name = styleList->item( i )->data( STYLE_ROLE ).toString(); if( name.startsWith( Strings::userStylePrefix() ) && name.midRef( Strings::userStylePrefix().length() ).toInt() >= idx ) { idx = name.midRef( Strings::userStylePrefix().length() ).toInt(); } } // Increase number for next style idx++; SourceFormatterStyle* s = new SourceFormatterStyle( QStringLiteral( "%1%2" ).arg( Strings::userStylePrefix() ).arg( idx ) ); if( item ) { SourceFormatterStyle* existstyle = fmt->styles[ item->data( STYLE_ROLE ).toString() ]; s->setCaption( i18n( "New %1", existstyle->caption() ) ); s->copyDataFrom( existstyle ); } else { s->setCaption( i18n( "New Style" ) ); } fmt->styles[ s->name() ] = s; QListWidgetItem* newitem = addStyle( *s ); selectStyle( styleList->row( newitem ) ); styleList->editItem( newitem ); emit changed(); } void SourceFormatterSettings::styleNameChanged( QListWidgetItem* item ) { if ( !item->isSelected() ) { return; } LanguageSettings& l = languages[ cbLanguages->currentText() ]; l.selectedStyle->setCaption( item->text() ); emit changed(); } QListWidgetItem* SourceFormatterSettings::addStyle( const SourceFormatterStyle& s ) { QListWidgetItem* item = new QListWidgetItem( styleList ); item->setText( s.caption() ); item->setData( STYLE_ROLE, s.name() ); if( s.name().startsWith( Strings::userStylePrefix() ) ) { item->setFlags( item->flags() | Qt::ItemIsEditable ); } styleList->addItem( item ); return item; } void SourceFormatterSettings::updatePreview() { m_document->setReadWrite( true ); QString langName = cbLanguages->itemText( cbLanguages->currentIndex() ); if( !langName.isEmpty() ) { LanguageSettings& l = languages[ langName ]; SourceFormatter* fmt = l.selectedFormatter; SourceFormatterStyle* style = l.selectedStyle; descriptionLabel->setText( style->description() ); if( style->usePreview() ) { ISourceFormatter* ifmt = fmt->formatter; QMimeType mime = l.mimetypes.first(); m_document->setHighlightingMode( style->modeForMimetype( mime ) ); //NOTE: this is ugly, but otherwise kate might remove tabs again :-/ // see also: https://bugs.kde.org/show_bug.cgi?id=291074 KTextEditor::ConfigInterface* iface = qobject_cast(m_document); QVariant oldReplaceTabs; if (iface) { oldReplaceTabs = iface->configValue(QStringLiteral("replace-tabs")); iface->setConfigValue(QStringLiteral("replace-tabs"), false); } m_document->setText( ifmt->formatSourceWithStyle( *style, ifmt->previewText( *style, mime ), QUrl(), mime ) ); if (iface) { iface->setConfigValue(QStringLiteral("replace-tabs"), oldReplaceTabs); } previewLabel->show(); textEditor->show(); }else{ previewLabel->hide(); textEditor->hide(); } } else { m_document->setText( i18n( "No Language selected" ) ); } m_view->setCursorPosition( KTextEditor::Cursor( 0, 0 ) ); m_document->setReadWrite( false ); } void SourceFormatterSettings::somethingChanged() { // Widgets are managed manually, so we have to explicitly tell KCModule // that we have some changes, otherwise it won't call "save" and/or will not activate // "Appy" emit changed(); } QString SourceFormatterSettings::name() const { return i18n("Source Formatter"); } QString SourceFormatterSettings::fullName() const { return i18n("Configure Source Formatter"); } QIcon SourceFormatterSettings::icon() const { return QIcon::fromTheme(QStringLiteral("text-field")); } diff --git a/shell/textdocument.cpp b/shell/textdocument.cpp index ae93e78d8..7d767ab9b 100644 --- a/shell/textdocument.cpp +++ b/shell/textdocument.cpp @@ -1,794 +1,793 @@ /*************************************************************************** * Copyright 2007 Alexander Dymo * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "textdocument.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 #include #include #include #include "core.h" #include "mainwindow.h" #include "uicontroller.h" #include "partcontroller.h" #include "plugincontroller.h" #include "documentcontroller.h" #include "ktexteditorpluginintegration.h" #include "debug.h" #include #include namespace KDevelop { const int MAX_DOC_SETTINGS = 20; // This sets cursor position and selection on the view to the given // range. Selection is set only for non-empty ranges // Factored into a function since its needed in 3 places already static void selectAndReveal( KTextEditor::View* view, const KTextEditor::Range& range ) { Q_ASSERT(view); if (range.isValid()) { view->setCursorPosition(range.start()); if (!range.isEmpty()) { view->setSelection(range); } } } struct TextDocumentPrivate { explicit TextDocumentPrivate(TextDocument *textDocument) : document(nullptr), state(IDocument::Clean), encoding(), q(textDocument) , m_loaded(false), m_addedContextMenu(nullptr) { } ~TextDocumentPrivate() { delete m_addedContextMenu; m_addedContextMenu = nullptr; saveSessionConfig(); delete document; } QPointer document; IDocument::DocumentState state; QString encoding; void setStatus(KTextEditor::Document* document, bool dirty) { QIcon statusIcon; if (document->isModified()) if (dirty) { state = IDocument::DirtyAndModified; statusIcon = QIcon::fromTheme(QStringLiteral("edit-delete")); } else { state = IDocument::Modified; statusIcon = QIcon::fromTheme(QStringLiteral("document-save")); } else if (dirty) { state = IDocument::Dirty; statusIcon = QIcon::fromTheme(QStringLiteral("document-revert")); } else { state = IDocument::Clean; } q->notifyStateChanged(); Core::self()->uiControllerInternal()->setStatusIcon(q, statusIcon); } inline KConfigGroup katePartSettingsGroup() const { return KSharedConfig::openConfig()->group("KatePart Settings"); } inline QString docConfigGroupName() const { return document->url().toDisplayString(QUrl::PreferLocalFile); } inline KConfigGroup docConfigGroup() const { return katePartSettingsGroup().group(docConfigGroupName()); } void saveSessionConfig() { if(document && document->url().isValid()) { // make sure only MAX_DOC_SETTINGS entries are stored KConfigGroup katePartSettings = katePartSettingsGroup(); // ordered list of documents QStringList documents = katePartSettings.readEntry("documents", QStringList()); // ensure this document is "new", i.e. at the end of the list documents.removeOne(docConfigGroupName()); documents.append(docConfigGroupName()); // remove "old" documents + their group while(documents.size() >= MAX_DOC_SETTINGS) { katePartSettings.group(documents.takeFirst()).deleteGroup(); } // update order katePartSettings.writeEntry("documents", documents); // actually save session config KConfigGroup group = docConfigGroup(); document->writeSessionConfig(group); } } void loadSessionConfig() { if (!document || !katePartSettingsGroup().hasGroup(docConfigGroupName())) { return; } document->readSessionConfig(docConfigGroup(), {QStringLiteral("SkipUrl")}); } // Determines whether the current contents of this document in the editor // could be retrieved from the VCS if they were dismissed. void queryCanRecreateFromVcs(KTextEditor::Document* document) const { IProject* project = nullptr; // Find projects by checking which one contains the file's parent directory, // to avoid issues with the cmake manager temporarily removing files from a project // during reloading. KDevelop::Path path(document->url()); foreach ( KDevelop::IProject* current, Core::self()->projectController()->projects() ) { if ( current->path().isParentOf(path) ) { project = current; break; } } if (!project) { return; } IContentAwareVersionControl* iface; iface = qobject_cast< KDevelop::IContentAwareVersionControl* >(project->versionControlPlugin()); if (!iface) { return; } if ( !qobject_cast( document ) ) { return; } CheckInRepositoryJob* req = iface->isInRepository( document ); if ( !req ) { return; } QObject::connect(req, &CheckInRepositoryJob::finished, q, &TextDocument::repositoryCheckFinished); // Abort the request when the user edits the document QObject::connect(q->textDocument(), &KTextEditor::Document::textChanged, req, &CheckInRepositoryJob::abort); } void modifiedOnDisk(KTextEditor::Document *document, bool /*isModified*/, KTextEditor::ModificationInterface::ModifiedOnDiskReason reason) { bool dirty = false; switch (reason) { case KTextEditor::ModificationInterface::OnDiskUnmodified: break; case KTextEditor::ModificationInterface::OnDiskModified: case KTextEditor::ModificationInterface::OnDiskCreated: case KTextEditor::ModificationInterface::OnDiskDeleted: dirty = true; break; } // In some cases, the VCS (e.g. git) can know whether the old contents are "valuable", i.e. // not retrieveable from the VCS. If that is not the case, then the document can safely be // reloaded without displaying a dialog asking the user. if ( dirty ) { queryCanRecreateFromVcs(document); } setStatus(document, dirty); } TextDocument * const q; bool m_loaded; // we want to remove the added stuff when the menu hides QMenu* m_addedContextMenu; }; struct TextViewPrivate { explicit TextViewPrivate(TextView* q) : q(q) {} TextView* const q; QPointer view; KTextEditor::Range initialRange; }; TextDocument::TextDocument(const QUrl &url, ICore* core, const QString& encoding) :PartDocument(url, core), d(new TextDocumentPrivate(this)) { d->encoding = encoding; } TextDocument::~TextDocument() { delete d; } bool TextDocument::isTextDocument() const { if( !d->document ) { /// @todo Somehow it can happen that d->document is zero, which makes /// code relying on "isTextDocument() == (bool)textDocument()" crash qCWarning(SHELL) << "Broken text-document: " << url(); return false; } return true; } KTextEditor::Document *TextDocument::textDocument() const { return d->document; } QWidget *TextDocument::createViewWidget(QWidget *parent) { KTextEditor::View* view = nullptr; if (!d->document) { d->document = Core::self()->partControllerInternal()->createTextPart(); // Connect to the first text changed signal, it occurs before the completed() signal connect(d->document.data(), &KTextEditor::Document::textChanged, this, &TextDocument::slotDocumentLoaded); // Also connect to the completed signal, sometimes the first text changed signal is missed because the part loads too quickly (? TODO - confirm this is necessary) connect(d->document.data(), static_cast(&KTextEditor::Document::completed), this, &TextDocument::slotDocumentLoaded); // force a reparse when a document gets reloaded connect(d->document.data(), &KTextEditor::Document::reloaded, this, [] (KTextEditor::Document* document) { ICore::self()->languageController()->backgroundParser()->addDocument(IndexedString(document->url()), (TopDUContext::Features) ( TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdate ), BackgroundParser::BestPriority, nullptr); }); // Set encoding passed via constructor // Needs to be done before openUrl, else katepart won't use the encoding // @see KTextEditor::Document::setEncoding if (!d->encoding.isEmpty()) d->document->setEncoding(d->encoding); if (!url().isEmpty() && !DocumentController::isEmptyDocumentUrl(url())) d->document->openUrl( url() ); d->setStatus(d->document, false); /* It appears, that by default a part will be deleted the first view containing it is deleted. Since we do want to have several views, disable that behaviour. */ d->document->setAutoDeletePart(false); Core::self()->partController()->addPart(d->document, false); d->loadSessionConfig(); connect(d->document.data(), &KTextEditor::Document::modifiedChanged, this, &TextDocument::newDocumentStatus); connect(d->document.data(), &KTextEditor::Document::textChanged, this, &TextDocument::textChanged); connect(d->document.data(), &KTextEditor::Document::documentUrlChanged, this, &TextDocument::documentUrlChanged); connect(d->document.data(), &KTextEditor::Document::documentSavedOrUploaded, this, &TextDocument::documentSaved ); if (qobject_cast(d->document)) { // can't use new signal/slot syntax here, MarkInterface is not a QObject connect(d->document.data(), SIGNAL(marksChanged(KTextEditor::Document*)), this, SLOT(saveSessionConfig())); } if (auto iface = qobject_cast(d->document)) { iface->setModifiedOnDiskWarning(true); // can't use new signal/slot syntax here, ModificationInterface is not a QObject connect(d->document.data(), SIGNAL(modifiedOnDisk(KTextEditor::Document*,bool,KTextEditor::ModificationInterface::ModifiedOnDiskReason)), this, SLOT(modifiedOnDisk(KTextEditor::Document*,bool,KTextEditor::ModificationInterface::ModifiedOnDiskReason))); } notifyTextDocumentCreated(); } view = d->document->createView(parent, Core::self()->uiControllerInternal()->defaultMainWindow()->kateWrapper()->interface()); // get rid of some actions regarding the config dialog, we merge that one into the kdevelop menu already delete view->actionCollection()->action(QStringLiteral("set_confdlg")); delete view->actionCollection()->action(QStringLiteral("editor_options")); view->setStatusBarEnabled(Core::self()->partControllerInternal()->showTextEditorStatusBar()); connect(view, &KTextEditor::View::contextMenuAboutToShow, this, &TextDocument::populateContextMenu); if (KTextEditor::CodeCompletionInterface* cc = dynamic_cast(view)) cc->setAutomaticInvocationEnabled(core()->languageController()->completionSettings()->automaticCompletionEnabled()); if (KTextEditor::ConfigInterface *config = qobject_cast(view)) { config->setConfigValue(QStringLiteral("allow-mark-menu"), false); config->setConfigValue(QStringLiteral("default-mark-type"), KTextEditor::MarkInterface::BreakpointActive); } return view; } KParts::Part *TextDocument::partForView(QWidget *view) const { if (d->document && d->document->views().contains((KTextEditor::View*)view)) return d->document; return nullptr; } // KDevelop::IDocument implementation void TextDocument::reload() { if (!d->document) return; KTextEditor::ModificationInterface* modif=nullptr; if(d->state==Dirty) { modif = qobject_cast(d->document); modif->setModifiedOnDiskWarning(false); } d->document->documentReload(); if(modif) modif->setModifiedOnDiskWarning(true); } bool TextDocument::save(DocumentSaveMode mode) { if (!d->document) return true; if (mode & Discard) return true; switch (d->state) { case IDocument::Clean: return true; case IDocument::Modified: break; case IDocument::Dirty: case IDocument::DirtyAndModified: if (!(mode & Silent)) { int code = KMessageBox::warningYesNoCancel( Core::self()->uiController()->activeMainWindow(), i18n("The file \"%1\" is modified on disk.\n\nAre " "you sure you want to overwrite it? (External " "changes will be lost.)", d->document->url().toLocalFile()), i18nc("@title:window", "Document Externally Modified")); if (code != KMessageBox::Yes) return false; } break; } if (!KDevelop::ensureWritable(QList() << url())) { return false; } QUrl urlBeforeSave = d->document->url(); if (d->document->documentSave()) { if (d->document->url() != urlBeforeSave) notifyUrlChanged(); return true; } return false; } IDocument::DocumentState TextDocument::state() const { return d->state; } KTextEditor::Cursor KDevelop::TextDocument::cursorPosition() const { if (!d->document) { return KTextEditor::Cursor::invalid(); } KTextEditor::View *view = activeTextView(); if (view) return view->cursorPosition(); return KTextEditor::Cursor::invalid(); } void TextDocument::setCursorPosition(const KTextEditor::Cursor &cursor) { if (!cursor.isValid() || !d->document) return; KTextEditor::View *view = activeTextView(); // Rodda: Cursor must be accurate here, to the definition of accurate for KTextEditor::Cursor. // ie, starting from 0,0 if (view) view->setCursorPosition(cursor); } KTextEditor::Range TextDocument::textSelection() const { if (!d->document) { return KTextEditor::Range::invalid(); } KTextEditor::View *view = activeTextView(); if (view && view->selection()) { return view->selectionRange(); } return PartDocument::textSelection(); } QString TextDocument::text(const KTextEditor::Range &range) const { if (!d->document) { return QString(); } return d->document->text( range ); } QString TextDocument::textLine() const { if (!d->document) { return QString(); } KTextEditor::View *view = activeTextView(); if (view) { return d->document->line( view->cursorPosition().line() ); } return PartDocument::textLine(); } QString TextDocument::textWord() const { if (!d->document) { return QString(); } KTextEditor::View *view = activeTextView(); if (view) { KTextEditor::Cursor start = view->cursorPosition(); qCDebug(SHELL) << "got start position from view:" << start.line() << start.column(); QString linestr = textLine(); int startPos = qMax( qMin( start.column(), linestr.length() - 1 ), 0 ); int endPos = startPos; startPos --; while( startPos >= 0 && ( linestr[startPos].isLetterOrNumber() || linestr[startPos] == '_' || linestr[startPos] == '~' ) ) { --startPos; } while( endPos < linestr.length() && ( linestr[endPos].isLetterOrNumber() || linestr[endPos] == '_' || linestr[endPos] == '~' ) ) { ++endPos; } if( startPos != endPos ) { qCDebug(SHELL) << "found word" << startPos << endPos << linestr.mid( startPos+1, endPos - startPos - 1 ); return linestr.mid( startPos + 1, endPos - startPos - 1 ); } } return PartDocument::textWord(); } void TextDocument::setTextSelection(const KTextEditor::Range &range) { if (!range.isValid() || !d->document) return; KTextEditor::View *view = activeTextView(); if (view) { selectAndReveal(view, range); } } bool TextDocument::close(DocumentSaveMode mode) { if (!PartDocument::close(mode)) return false; if ( d->document ) { d->saveSessionConfig(); delete d->document; //We have to delete the document right now, to prevent random crashes in the event handler } return true; } Sublime::View* TextDocument::newView(Sublime::Document* doc) { Q_UNUSED(doc); return new TextView(this); } } KDevelop::TextView::TextView(TextDocument * doc) : View(doc, View::TakeOwnership), d(new TextViewPrivate(this)) { } KDevelop::TextView::~TextView() { delete d; } QWidget * KDevelop::TextView::createWidget(QWidget * parent) { auto textDocument = qobject_cast(document()); Q_ASSERT(textDocument); QWidget* widget = textDocument->createViewWidget(parent); d->view = qobject_cast(widget); Q_ASSERT(d->view); connect(d->view.data(), &KTextEditor::View::cursorPositionChanged, this, &KDevelop::TextView::sendStatusChanged); return widget; } QString KDevelop::TextView::viewState() const { if (d->view) { if (d->view->selection()) { KTextEditor::Range selection = d->view->selectionRange(); return QStringLiteral("Selection=%1,%2,%3,%4").arg(selection.start().line()) .arg(selection.start().column()) .arg(selection.end().line()) .arg(selection.end().column()); } else { KTextEditor::Cursor cursor = d->view->cursorPosition(); return QStringLiteral("Cursor=%1,%2").arg(cursor.line()).arg(cursor.column()); } } else { KTextEditor::Range selection = d->initialRange; return QStringLiteral("Selection=%1,%2,%3,%4").arg(selection.start().line()) .arg(selection.start().column()) .arg(selection.end().line()) .arg(selection.end().column()); } } void KDevelop::TextView::setInitialRange(const KTextEditor::Range& range) { if (d->view) { selectAndReveal(d->view, range); } else { d->initialRange = range; } } KTextEditor::Range KDevelop::TextView::initialRange() const { return d->initialRange; } void KDevelop::TextView::setState(const QString & state) { static QRegExp reCursor("Cursor=([\\d]+),([\\d]+)"); static QRegExp reSelection("Selection=([\\d]+),([\\d]+),([\\d]+),([\\d]+)"); if (reCursor.exactMatch(state)) { setInitialRange(KTextEditor::Range(KTextEditor::Cursor(reCursor.cap(1).toInt(), reCursor.cap(2).toInt()), 0)); } else if (reSelection.exactMatch(state)) { KTextEditor::Range range(reSelection.cap(1).toInt(), reSelection.cap(2).toInt(), reSelection.cap(3).toInt(), reSelection.cap(4).toInt()); setInitialRange(range); } } QString KDevelop::TextDocument::documentType() const { return QStringLiteral("Text"); } QIcon KDevelop::TextDocument::defaultIcon() const { if (d->document) { QMimeType mime = QMimeDatabase().mimeTypeForName(d->document->mimeType()); QIcon icon = QIcon::fromTheme(mime.iconName()); if (!icon.isNull()) { return icon; } } return PartDocument::defaultIcon(); } KTextEditor::View *KDevelop::TextView::textView() const { return d->view; } QString KDevelop::TextView::viewStatus() const { // only show status when KTextEditor's own status bar isn't already enabled const bool showStatus = !Core::self()->partControllerInternal()->showTextEditorStatusBar(); if (!showStatus) { return QString(); } const KTextEditor::Cursor pos = d->view ? d->view->cursorPosition() : KTextEditor::Cursor::invalid(); return i18n(" Line: %1 Col: %2 ", pos.line() + 1, pos.column() + 1); } void KDevelop::TextView::sendStatusChanged() { emit statusChanged(this); } KTextEditor::View* KDevelop::TextDocument::activeTextView() const { KTextEditor::View* fallback = nullptr; for (auto view : views()) { auto textView = qobject_cast(view)->textView(); if (!textView) { continue; } if (textView->hasFocus()) { return textView; } else if (textView->isVisible()) { fallback = textView; } else if (!fallback) { fallback = textView; } } return fallback; } void KDevelop::TextDocument::newDocumentStatus(KTextEditor::Document *document) { bool dirty = (d->state == IDocument::Dirty || d->state == IDocument::DirtyAndModified); d->setStatus(document, dirty); } void KDevelop::TextDocument::textChanged(KTextEditor::Document *document) { Q_UNUSED(document); notifyContentChanged(); } void KDevelop::TextDocument::populateContextMenu( KTextEditor::View* v, QMenu* menu ) { if (d->m_addedContextMenu) { foreach ( QAction* action, d->m_addedContextMenu->actions() ) { menu->removeAction(action); } delete d->m_addedContextMenu; } d->m_addedContextMenu = new QMenu(); EditorContext c(v, v->cursorPosition()); auto extensions = Core::self()->pluginController()->queryPluginsForContextMenuExtensions(&c); ContextMenuExtension::populateMenu(d->m_addedContextMenu, extensions); { QUrl url = v->document()->url(); QList< ProjectBaseItem* > items = Core::self()->projectController()->projectModel()->itemsForPath( IndexedString(url) ); if (!items.isEmpty()) { populateParentItemsMenu( items.front(), d->m_addedContextMenu ); } } foreach ( QAction* action, d->m_addedContextMenu->actions() ) { menu->addAction(action); } } void KDevelop::TextDocument::repositoryCheckFinished(bool canRecreate) { if ( d->state != IDocument::Dirty && d->state != IDocument::DirtyAndModified ) { // document is not dirty for whatever reason, nothing to do. return; } if ( ! canRecreate ) { return; } KTextEditor::ModificationInterface* modIface = qobject_cast( d->document ); Q_ASSERT(modIface); // Ok, all safe, we can clean up the document. Close it if the file is gone, // and reload if it's still there. d->setStatus(d->document, false); modIface->setModifiedOnDisk(KTextEditor::ModificationInterface::OnDiskUnmodified); if ( QFile::exists(d->document->url().path()) ) { reload(); } else { close(KDevelop::IDocument::Discard); } } void KDevelop::TextDocument::slotDocumentLoaded() { if (d->m_loaded) return; // Tell the editor integrator first d->m_loaded = true; notifyLoaded(); } void KDevelop::TextDocument::documentSaved(KTextEditor::Document* document, bool saveAs) { Q_UNUSED(document); Q_UNUSED(saveAs); notifySaved(); notifyStateChanged(); } void KDevelop::TextDocument::documentUrlChanged(KTextEditor::Document* document) { Q_UNUSED(document); if (url() != d->document->url()) setUrl(d->document->url()); } #include "moc_textdocument.cpp" diff --git a/shell/workingsets/closedworkingsetswidget.cpp b/shell/workingsets/closedworkingsetswidget.cpp index 27a87d8d1..5e12234d2 100644 --- a/shell/workingsets/closedworkingsetswidget.cpp +++ b/shell/workingsets/closedworkingsetswidget.cpp @@ -1,131 +1,131 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2010 Milian Wolff * * * * 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 "closedworkingsetswidget.h" #include #include #include "mainwindow.h" #include "workingsetcontroller.h" #include "workingset.h" #include "workingsettoolbutton.h" -#include "../debug.h" +#include "debug.h" using namespace KDevelop; WorkingSet* getWorkingSet(const QString& id) { return Core::self()->workingSetControllerInternal()->getWorkingSet(id); } ClosedWorkingSetsWidget::ClosedWorkingSetsWidget( MainWindow* window ) : QWidget(nullptr), m_mainWindow(window) { connect(window, &MainWindow::areaChanged, this, &ClosedWorkingSetsWidget::areaChanged); m_layout = new QHBoxLayout(this); m_layout->setMargin(0); if (window->area()) { areaChanged(window->area()); } connect(Core::self()->workingSetControllerInternal(), &WorkingSetController::aboutToRemoveWorkingSet, this, &ClosedWorkingSetsWidget::removeWorkingSet); connect(Core::self()->workingSetControllerInternal(), &WorkingSetController::workingSetAdded, this, &ClosedWorkingSetsWidget::addWorkingSet); } void ClosedWorkingSetsWidget::areaChanged( Sublime::Area* area ) { if (m_connectedArea) { disconnect(area, &Sublime::Area::changedWorkingSet, this, &ClosedWorkingSetsWidget::changedWorkingSet); } m_connectedArea = area; connect(m_connectedArea.data(), &Sublime::Area::changedWorkingSet, this, &ClosedWorkingSetsWidget::changedWorkingSet); // clear layout qDeleteAll(m_buttons); m_buttons.clear(); // add sets from new area foreach(WorkingSet* set, Core::self()->workingSetControllerInternal()->allWorkingSets()) { addWorkingSet(set); } } void ClosedWorkingSetsWidget::changedWorkingSet( Sublime::Area* area, const QString& from, const QString& to ) { Q_ASSERT(area == m_connectedArea); Q_UNUSED(area); if (!from.isEmpty()) { WorkingSet* oldSet = getWorkingSet(from); addWorkingSet(oldSet); } if (!to.isEmpty()) { WorkingSet* newSet = getWorkingSet(to); removeWorkingSet(newSet); } } void ClosedWorkingSetsWidget::removeWorkingSet( WorkingSet* set ) { delete m_buttons.take(set); Q_ASSERT(m_buttons.size() == m_layout->count()); setVisible(!m_buttons.isEmpty()); } void ClosedWorkingSetsWidget::addWorkingSet( WorkingSet* set ) { if (m_buttons.contains(set)) { return; } // Don't show working-sets that are active in an area belong to this main-window, as those // can be activated directly through the icons in the tabs if (set->hasConnectedAreas(m_mainWindow->areas())) { return; } if (set->isEmpty()) { // qCDebug(SHELL) << "skipping" << set->id() << "because empty"; return; } // qCDebug(SHELL) << "adding button for" << set->id(); WorkingSetToolButton* button = new WorkingSetToolButton(this, set); button->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Ignored)); m_layout->addWidget(button); m_buttons[set] = button; setVisible(!m_buttons.isEmpty()); } diff --git a/shell/workingsets/workingset.cpp b/shell/workingsets/workingset.cpp index 8d15fbc2c..1d483762f 100644 --- a/shell/workingsets/workingset.cpp +++ b/shell/workingsets/workingset.cpp @@ -1,564 +1,564 @@ /* Copyright David Nolden This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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 "workingset.h" -#include "../debug.h" +#include "debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #define SYNC_OFTEN using namespace KDevelop; bool WorkingSet::m_loading = false; namespace { QIcon generateIcon(const WorkingSetIconParameters& params) { QImage pixmap(16, 16, QImage::Format_ARGB32); // fill the background with a transparent color pixmap.fill(QColor::fromRgba(qRgba(0, 0, 0, 0))); const uint coloredCount = params.coloredCount; // coordinates of the rectangles to draw, for 16x16 icons specifically QList rects; rects << QRect(1, 1, 5, 5) << QRect(1, 9, 5, 5) << QRect(9, 1, 5, 5) << QRect(9, 9, 5, 5); if ( params.swapDiagonal ) { rects.swap(1, 2); } QPainter painter(&pixmap); // color for non-colored squares, paint them brighter if the working set is the active one const int inact = 40; QColor darkColor = QColor::fromRgb(inact, inact, inact); // color for colored squares // this code is not fragile, you can just tune the magic formulas at random and see what looks good. // just make sure to keep it within the 0-360 / 0-255 / 0-255 space of the HSV model QColor brightColor = QColor::fromHsv(params.hue, qMin(255, 215 + (params.setId*5) % 150), 205 + (params.setId*11) % 50); // Y'UV "Y" value, the approximate "lightness" of the color // If it is above 0.6, then making the color darker a bit is okay, // if it is below 0.35, then the color should be a bit brighter. float brightY = 0.299 * brightColor.redF() + 0.587 * brightColor.greenF() + 0.114 * brightColor.blueF(); if ( brightY > 0.6 ) { if ( params.setId % 7 < 2 ) { // 2/7 chance to make the color significantly darker brightColor = brightColor.darker(120 + (params.setId*7) % 35); } else if ( params.setId % 5 == 0 ) { // 1/5 chance to make it a bit darker brightColor = brightColor.darker(110 + (params.setId*3) % 10); } } if ( brightY < 0.35 ) { // always make the color brighter to avoid very dark colors (like rgb(0, 0, 255)) brightColor = brightColor.lighter(120 + (params.setId*13) % 55); } int at = 0; foreach ( const QRect rect, rects ) { QColor currentColor; // pick the colored squares; you can get different patterns by re-ordering the "rects" list if ( (at + params.setId*7) % 4 < coloredCount ) { currentColor = brightColor; } else { currentColor = darkColor; } // draw the filling of the square painter.setPen(QColor(currentColor)); painter.setBrush(QBrush(currentColor)); painter.drawRect(rect); // draw a slight set-in shadow for the square -- it's barely recognizeable, // but it looks way better than without painter.setBrush(Qt::NoBrush); painter.setPen(QColor(0, 0, 0, 50)); painter.drawRect(rect); painter.setPen(QColor(0, 0, 0, 25)); painter.drawRect(rect.x() + 1, rect.y() + 1, rect.width() - 2, rect.height() - 2); at += 1; } return QIcon(QPixmap::fromImage(pixmap)); } } WorkingSet::WorkingSet(const QString& id) : QObject() , m_id(id) , m_icon(generateIcon(WorkingSetIconParameters(id))) { } void WorkingSet::saveFromArea( Sublime::Area* a, Sublime::AreaIndex * area, KConfigGroup setGroup, KConfigGroup areaGroup ) { if (area->isSplit()) { setGroup.writeEntry("Orientation", area->orientation() == Qt::Horizontal ? "Horizontal" : "Vertical"); if (area->first()) { saveFromArea(a, area->first(), KConfigGroup(&setGroup, "0"), KConfigGroup(&areaGroup, "0")); } if (area->second()) { saveFromArea(a, area->second(), KConfigGroup(&setGroup, "1"), KConfigGroup(&areaGroup, "1")); } } else { setGroup.writeEntry("View Count", area->viewCount()); areaGroup.writeEntry("View Count", area->viewCount()); int index = 0; foreach (Sublime::View* view, area->views()) { //The working set config gets an updated list of files QString docSpec = view->document()->documentSpecifier(); //only save the documents from protocols KIO understands //otherwise we try to load kdev:// too early if (!KProtocolInfo::isKnownProtocol(QUrl(docSpec))) { continue; } setGroup.writeEntry(QStringLiteral("View %1").arg(index), docSpec); //The area specific config stores the working set documents in order along with their state areaGroup.writeEntry(QStringLiteral("View %1").arg(index), docSpec); areaGroup.writeEntry(QStringLiteral("View %1 State").arg(index), view->viewState()); ++index; } } } bool WorkingSet::isEmpty() const { KConfigGroup setConfig(Core::self()->activeSession()->config(), "Working File Sets"); KConfigGroup group = setConfig.group(m_id); return !group.hasKey("Orientation") && group.readEntry("View Count", 0) == 0; } struct DisableMainWindowUpdatesFromArea { explicit DisableMainWindowUpdatesFromArea(Sublime::Area* area) : m_area(area) { if(area) { foreach(Sublime::MainWindow* window, Core::self()->uiControllerInternal()->mainWindows()) { if(window->area() == area) { if(window->updatesEnabled()) { wasUpdatesEnabled.insert(window); window->setUpdatesEnabled(false); } } } } } ~DisableMainWindowUpdatesFromArea() { if(m_area) { foreach(Sublime::MainWindow* window, wasUpdatesEnabled) { window->setUpdatesEnabled(wasUpdatesEnabled.contains(window)); } } } Sublime::Area* m_area; QSet wasUpdatesEnabled; }; void loadFileList(QStringList& ret, KConfigGroup group) { if (group.hasKey("Orientation")) { QStringList subgroups = group.groupList(); if (subgroups.contains(QStringLiteral("0"))) { { KConfigGroup subgroup(&group, "0"); loadFileList(ret, subgroup); } if (subgroups.contains(QStringLiteral("1"))) { KConfigGroup subgroup(&group, "1"); loadFileList(ret, subgroup); } } } else { int viewCount = group.readEntry("View Count", 0); for (int i = 0; i < viewCount; ++i) { QString specifier = group.readEntry(QStringLiteral("View %1").arg(i), QString()); ret << specifier; } } } QStringList WorkingSet::fileList() const { QStringList ret; KConfigGroup setConfig(Core::self()->activeSession()->config(), "Working File Sets"); KConfigGroup group = setConfig.group(m_id); loadFileList(ret, group); return ret; } void WorkingSet::loadToArea(Sublime::Area* area, Sublime::AreaIndex* areaIndex) { PushValue enableLoading(m_loading, true); /// We cannot disable the updates here, because (probably) due to a bug in Qt, /// which causes the updates to stay disabled forever after some complex operations /// on the sub-views. This could be reproduced by creating two working-sets with complex /// split-view configurations and switching between them. Re-enabling the updates doesn't help. // DisableMainWindowUpdatesFromArea updatesDisabler(area); qCDebug(SHELL) << "loading working-set" << m_id << "into area" << area; QMultiMap recycle; foreach( Sublime::View* view, area->views() ) recycle.insert( view->document()->documentSpecifier(), area->removeView(view) ); qCDebug(SHELL) << "recycling" << recycle.size() << "old views"; Q_ASSERT( area->views().empty() ); KConfigGroup setConfig(Core::self()->activeSession()->config(), "Working File Sets"); KConfigGroup setGroup = setConfig.group(m_id); KConfigGroup areaGroup = setConfig.group(m_id + '|' + area->title()); loadToArea(area, areaIndex, setGroup, areaGroup, recycle); // Delete views which were not recycled qCDebug(SHELL) << "deleting " << recycle.size() << " old views"; qDeleteAll( recycle ); area->setActiveView(nullptr); //activate view in the working set /// @todo correctly select one out of multiple equal views QString activeView = areaGroup.readEntry("Active View", QString()); foreach (Sublime::View *v, area->views()) { if (v->document()->documentSpecifier() == activeView) { area->setActiveView(v); break; } } if( !area->activeView() && !area->views().isEmpty() ) area->setActiveView( area->views().at(0) ); if( area->activeView() ) { foreach(Sublime::MainWindow* window, Core::self()->uiControllerInternal()->mainWindows()) { if(window->area() == area) { window->activateView( area->activeView() ); } } } } void WorkingSet::loadToArea(Sublime::Area* area, Sublime::AreaIndex* areaIndex, KConfigGroup setGroup, KConfigGroup areaGroup, QMultiMap& recycle) { Q_ASSERT( !areaIndex->isSplit() ); if (setGroup.hasKey("Orientation")) { QStringList subgroups = setGroup.groupList(); /// @todo also save and restore the ratio if (subgroups.contains(QStringLiteral("0")) && subgroups.contains(QStringLiteral("1"))) { // qCDebug(SHELL) << "has zero, split:" << split; Qt::Orientation orientation = setGroup.readEntry("Orientation", "Horizontal") == QLatin1String("Vertical") ? Qt::Vertical : Qt::Horizontal; if(!areaIndex->isSplit()){ areaIndex->split(orientation); }else{ areaIndex->setOrientation(orientation); } loadToArea(area, areaIndex->first(), KConfigGroup(&setGroup, "0"), KConfigGroup(&areaGroup, "0"), recycle); loadToArea(area, areaIndex->second(), KConfigGroup(&setGroup, "1"), KConfigGroup(&areaGroup, "1"), recycle); if( areaIndex->first()->viewCount() == 0 ) areaIndex->unsplit(areaIndex->first()); else if( areaIndex->second()->viewCount() == 0 ) areaIndex->unsplit(areaIndex->second()); } } else { //Load all documents from the workingset into this areaIndex int viewCount = setGroup.readEntry("View Count", 0); QMap createdViews; for (int i = 0; i < viewCount; ++i) { QString specifier = setGroup.readEntry(QStringLiteral("View %1").arg(i), QString()); if (specifier.isEmpty()) { continue; } Sublime::View* previousView = area->views().empty() ? nullptr : area->views().at(area->views().size() - 1); QMultiMap::iterator it = recycle.find( specifier ); if( it != recycle.end() ) { area->addView( *it, areaIndex, previousView ); recycle.erase( it ); continue; } IDocument* doc = Core::self()->documentControllerInternal()->openDocument(QUrl::fromUserInput(specifier), KTextEditor::Cursor::invalid(), IDocumentController::DoNotActivate | IDocumentController::DoNotCreateView); Sublime::Document *document = dynamic_cast(doc); if (document) { Sublime::View* view = document->createView(); area->addView(view, areaIndex, previousView); createdViews[i] = view; } else { qCWarning(SHELL) << "Unable to create view" << specifier; } } //Load state for (int i = 0; i < viewCount; ++i) { QString state = areaGroup.readEntry(QStringLiteral("View %1 State").arg(i)); if (!state.isEmpty() && createdViews.contains(i)) createdViews[i]->setState(state); } } } void deleteGroupRecursive(KConfigGroup group) { // qCDebug(SHELL) << "deleting" << group.name(); foreach(const QString& entry, group.entryMap().keys()) { group.deleteEntry(entry); } Q_ASSERT(group.entryMap().isEmpty()); foreach(const QString& subGroup, group.groupList()) { deleteGroupRecursive(group.group(subGroup)); group.deleteGroup(subGroup); } //Why doesn't this work? // Q_ASSERT(group.groupList().isEmpty()); group.deleteGroup(); } void WorkingSet::deleteSet(bool force, bool silent) { if(m_areas.isEmpty() || force) { emit aboutToRemove(this); KConfigGroup setConfig(Core::self()->activeSession()->config(), "Working File Sets"); KConfigGroup group = setConfig.group(m_id); deleteGroupRecursive(group); #ifdef SYNC_OFTEN setConfig.sync(); #endif if(!silent) emit setChangedSignificantly(); } } void WorkingSet::saveFromArea(Sublime::Area* area, Sublime::AreaIndex* areaIndex) { qCDebug(SHELL) << "saving" << m_id << "from area"; bool wasPersistent = isPersistent(); KConfigGroup setConfig(Core::self()->activeSession()->config(), "Working File Sets"); KConfigGroup setGroup = setConfig.group(m_id); deleteGroupRecursive(setGroup); KConfigGroup areaGroup = setConfig.group(m_id + '|' + area->title()); QString lastActiveView = areaGroup.readEntry("Active View", ""); deleteGroupRecursive(areaGroup); if (area->activeView() && area->activeView()->document()) areaGroup.writeEntry("Active View", area->activeView()->document()->documentSpecifier()); else areaGroup.writeEntry("Active View", lastActiveView); saveFromArea(area, areaIndex, setGroup, areaGroup); if(isEmpty()) { deleteGroupRecursive(setGroup); deleteGroupRecursive(areaGroup); } setPersistent(wasPersistent); #ifdef SYNC_OFTEN setConfig.sync(); #endif emit setChangedSignificantly(); } void WorkingSet::areaViewAdded(Sublime::AreaIndex*, Sublime::View*) { Sublime::Area* area = qobject_cast(sender()); Q_ASSERT(area); Q_ASSERT(area->workingSet() == m_id); qCDebug(SHELL) << "added view in" << area << ", id" << m_id; if (m_loading) { qCDebug(SHELL) << "doing nothing because loading"; return; } changed(area); } void WorkingSet::areaViewRemoved(Sublime::AreaIndex*, Sublime::View* view) { Sublime::Area* area = qobject_cast(sender()); Q_ASSERT(area); Q_ASSERT(area->workingSet() == m_id); qCDebug(SHELL) << "removed view in" << area << ", id" << m_id; if (m_loading) { qCDebug(SHELL) << "doing nothing because loading"; return; } foreach(Sublime::Area* otherArea, m_areas) { if(otherArea == area) continue; bool hadDocument = false; foreach(Sublime::View* areaView, otherArea->views()) if(view->document() == areaView->document()) hadDocument = true; if(!hadDocument) { // We do this to prevent UI flicker. The view has already been removed from // one of the connected areas, so the working-set has already recorded the change. return; } } changed(area); } void WorkingSet::setPersistent(bool persistent) { KConfigGroup setConfig(Core::self()->activeSession()->config(), "Working File Sets"); KConfigGroup group = setConfig.group(m_id); group.writeEntry("persistent", persistent); #ifdef SYNC_OFTEN group.sync(); #endif qCDebug(SHELL) << "setting" << m_id << "persistent:" << persistent; } bool WorkingSet::isPersistent() const { KConfigGroup setConfig(Core::self()->activeSession()->config(), "Working File Sets"); KConfigGroup group = setConfig.group(m_id); return group.readEntry("persistent", false); } QIcon WorkingSet::icon() const { return m_icon; } bool WorkingSet::isConnected( Sublime::Area* area ) { return m_areas.contains( area ); } QString WorkingSet::id() const { return m_id; } bool WorkingSet::hasConnectedAreas() const { return !m_areas.isEmpty(); } bool WorkingSet::hasConnectedAreas( QList< Sublime::Area* > areas ) const { foreach( Sublime::Area* area, areas ) if ( m_areas.contains( area ) ) return true; return false; } void WorkingSet::connectArea( Sublime::Area* area ) { if ( m_areas.contains( area ) ) { qCDebug(SHELL) << "tried to double-connect area"; return; } qCDebug(SHELL) << "connecting" << m_id << "to area" << area; // Q_ASSERT(area->workingSet() == m_id); m_areas.push_back( area ); connect( area, &Sublime::Area::viewAdded, this, &WorkingSet::areaViewAdded ); connect( area, &Sublime::Area::viewRemoved, this, &WorkingSet::areaViewRemoved ); } void WorkingSet::disconnectArea( Sublime::Area* area ) { if ( !m_areas.contains( area ) ) { qCDebug(SHELL) << "tried to disconnect not connected area"; return; } qCDebug(SHELL) << "disconnecting" << m_id << "from area" << area; // Q_ASSERT(area->workingSet() == m_id); disconnect( area, &Sublime::Area::viewAdded, this, &WorkingSet::areaViewAdded ); disconnect( area, &Sublime::Area::viewRemoved, this, &WorkingSet::areaViewRemoved ); m_areas.removeAll( area ); } void WorkingSet::changed( Sublime::Area* area ) { if ( m_loading ) { return; } { //Do not capture changes done while loading PushValue enableLoading( m_loading, true ); qCDebug(SHELL) << "recording change done to" << m_id; saveFromArea( area, area->rootIndex() ); for ( QList< QPointer< Sublime::Area > >::iterator it = m_areas.begin(); it != m_areas.end(); ++it ) { if (( *it ) != area ) { loadToArea(( *it ), ( *it )->rootIndex() ); } } } emit setChangedSignificantly(); } diff --git a/shell/workingsets/workingsettooltipwidget.cpp b/shell/workingsets/workingsettooltipwidget.cpp index 912e231b6..081acae37 100644 --- a/shell/workingsets/workingsettooltipwidget.cpp +++ b/shell/workingsets/workingsettooltipwidget.cpp @@ -1,393 +1,392 @@ /* Copyright David Nolden This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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 "workingsettooltipwidget.h" #include #include #include -#include #include "debug.h" #include "core.h" #include "documentcontroller.h" #include "mainwindow.h" #include #include #include #include #include #include #include #include "workingset.h" #include "workingsetcontroller.h" #include "workingsetfilelabel.h" #include "workingsettoolbutton.h" #include "workingsethelpers.h" using namespace KDevelop; class FileWidget : public QWidget { Q_OBJECT public: QToolButton* m_button; class WorkingSetFileLabel* m_label; }; WorkingSetToolTipWidget::WorkingSetToolTipWidget(QWidget* parent, WorkingSet* set, MainWindow* mainwindow) : QWidget(parent), m_set(set) { QVBoxLayout* layout = new QVBoxLayout(this); layout->setSpacing(0); layout->setMargin(0); connect(static_cast(mainwindow)->area(), &Sublime::Area::viewAdded, this, &WorkingSetToolTipWidget::updateFileButtons, Qt::QueuedConnection); connect(static_cast(mainwindow)->area(), &Sublime::Area::viewRemoved, this, &WorkingSetToolTipWidget::updateFileButtons, Qt::QueuedConnection); connect(Core::self()->workingSetControllerInternal(), &WorkingSetController::workingSetSwitched, this, &WorkingSetToolTipWidget::updateFileButtons); // title bar { QHBoxLayout* topLayout = new QHBoxLayout; m_setButton = new WorkingSetToolButton(this, set); m_setButton->hide(); topLayout->addSpacing(5); QLabel* icon = new QLabel; topLayout->addWidget(icon); topLayout->addSpacing(5); QString label; if (m_set->isConnected(mainwindow->area())) { label = i18n("Active Working Set"); } else { label = i18n("Working Set"); } QLabel* name = new QLabel(label); name->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred); topLayout->addWidget(name); topLayout->addSpacing(10); icon->setPixmap(m_setButton->icon().pixmap(name->sizeHint().height()+8, name->sizeHint().height()+8)); topLayout->addStretch(); m_openButton = new QPushButton; m_openButton->setFlat(true); topLayout->addWidget(m_openButton); m_deleteButton = new QPushButton; m_deleteButton->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete"))); m_deleteButton->setText(i18n("Delete")); m_deleteButton->setToolTip(i18n("Remove this working set. The contained documents are not affected.")); m_deleteButton->setFlat(true); connect(m_deleteButton, &QPushButton::clicked, m_set, [&] { m_set->deleteSet(false); }); connect(m_deleteButton, &QPushButton::clicked, this, &WorkingSetToolTipWidget::shouldClose); topLayout->addWidget(m_deleteButton); layout->addLayout(topLayout); // horizontal line QFrame* line = new QFrame(); line->setFrameShape(QFrame::HLine); line->setFrameShadow(QFrame::Raised); layout->addWidget(line); } // everything else is added to the following widget which just has a different background color QVBoxLayout* bodyLayout = new QVBoxLayout; { QWidget* body = new QWidget(); body->setLayout(bodyLayout); layout->addWidget(body); body->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); } // document list actions { QHBoxLayout* actionsLayout = new QHBoxLayout; m_documentsLabel = new QLabel(i18n("Documents:")); m_documentsLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); actionsLayout->addWidget(m_documentsLabel); actionsLayout->addStretch(); m_mergeButton = new QPushButton; m_mergeButton->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); m_mergeButton->setText(i18n("Add All")); m_mergeButton->setToolTip(i18n("Add all documents that are part of this working set to the currently active working set.")); m_mergeButton->setFlat(true); connect(m_mergeButton, &QPushButton::clicked, m_setButton, &WorkingSetToolButton::mergeSet); actionsLayout->addWidget(m_mergeButton); m_subtractButton = new QPushButton; m_subtractButton->setIcon(QIcon::fromTheme(QStringLiteral("list-remove"))); m_subtractButton->setText(i18n("Remove All")); m_subtractButton->setToolTip(i18n("Remove all documents that are part of this working set from the currently active working set.")); m_subtractButton->setFlat(true); connect(m_subtractButton, &QPushButton::clicked, m_setButton, &WorkingSetToolButton::subtractSet); actionsLayout->addWidget(m_subtractButton); bodyLayout->addLayout(actionsLayout); } QSet hadFiles; QVBoxLayout* filesLayout = new QVBoxLayout; filesLayout->setMargin(0); foreach(const QString& file, m_set->fileList()) { if(hadFiles.contains(file)) continue; hadFiles.insert(file); FileWidget* widget = new FileWidget; widget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); QHBoxLayout* fileLayout = new QHBoxLayout(widget); QToolButton* plusButton = new QToolButton; plusButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Maximum); fileLayout->addWidget(plusButton); WorkingSetFileLabel* fileLabel = new WorkingSetFileLabel; fileLabel->setTextFormat(Qt::RichText); // We add spaces behind and after, to make it look nicer fileLabel->setText(" " + Core::self()->projectController()->prettyFileName(QUrl::fromUserInput(file)) + " "); fileLabel->setToolTip(i18nc("@info:tooltip", "Click to open and activate this document.")); fileLabel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); fileLayout->addWidget(fileLabel); fileLayout->setMargin(0); plusButton->setMaximumHeight(fileLabel->sizeHint().height() + 4); plusButton->setMaximumWidth(plusButton->maximumHeight()); plusButton->setObjectName(file); fileLabel->setObjectName(file); fileLabel->setCursor(QCursor(Qt::PointingHandCursor)); widget->m_button = plusButton; widget->m_label = fileLabel; filesLayout->addWidget(widget); m_fileWidgets.insert(file, widget); m_orderedFileWidgets.push_back(widget); connect(plusButton, &QToolButton::clicked, this, &WorkingSetToolTipWidget::buttonClicked); connect(fileLabel, &WorkingSetFileLabel::clicked, this, &WorkingSetToolTipWidget::labelClicked); } bodyLayout->addLayout(filesLayout); updateFileButtons(); connect(set, &WorkingSet::setChangedSignificantly, this, &WorkingSetToolTipWidget::updateFileButtons); connect(mainwindow->area(), &Sublime::Area::changedWorkingSet, this, &WorkingSetToolTipWidget::updateFileButtons, Qt::QueuedConnection); QMetaObject::invokeMethod(this, "updateFileButtons"); } void WorkingSetToolTipWidget::nextDocument() { int active = -1; for(int a = 0; a < m_orderedFileWidgets.size(); ++a) if(m_orderedFileWidgets[a]->m_label->isActive()) active = a; if(active == -1) { qCWarning(SHELL) << "Found no active document"; return; } int next = (active + 1) % m_orderedFileWidgets.size(); while(m_orderedFileWidgets[next]->isHidden() && next != active) next = (next + 1) % m_orderedFileWidgets.size(); m_orderedFileWidgets[next]->m_label->emitClicked(); } void WorkingSetToolTipWidget::previousDocument() { int active = -1; for(int a = 0; a < m_orderedFileWidgets.size(); ++a) if(m_orderedFileWidgets[a]->m_label->isActive()) active = a; if(active == -1) { qCWarning(SHELL) << "Found no active document"; return; } int next = active - 1; if(next < 0) next += m_orderedFileWidgets.size(); while(m_orderedFileWidgets[next]->isHidden() && next != active) { next -= 1; if(next < 0) next += m_orderedFileWidgets.size(); } m_orderedFileWidgets[next]->m_label->emitClicked(); } void WorkingSetToolTipWidget::updateFileButtons() { MainWindow* mainWindow = dynamic_cast(Core::self()->uiController()->activeMainWindow()); Q_ASSERT(mainWindow); WorkingSetController* controller = Core::self()->workingSetControllerInternal(); ActiveToolTip* tooltip = controller->tooltip(); QString activeFile; if(mainWindow->area()->activeView()) activeFile = mainWindow->area()->activeView()->document()->documentSpecifier(); WorkingSet* currentWorkingSet = nullptr; QSet openFiles; if(!mainWindow->area()->workingSet().isEmpty()) { currentWorkingSet = controller->getWorkingSet(mainWindow->area()->workingSet()); openFiles = currentWorkingSet->fileList().toSet(); } bool allOpen = true; bool noneOpen = true; bool needResize = false; bool allHidden = true; for(QMap< QString, FileWidget* >::iterator it = m_fileWidgets.begin(); it != m_fileWidgets.end(); ++it) { if(openFiles.contains(it.key())) { noneOpen = false; (*it)->m_button->setToolTip(i18n("Remove this file from the current working set")); (*it)->m_button->setIcon(QIcon::fromTheme(QStringLiteral("list-remove"))); (*it)->show(); }else{ allOpen = false; (*it)->m_button->setToolTip(i18n("Add this file to the current working set")); (*it)->m_button->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); if(currentWorkingSet == m_set) { (*it)->hide(); needResize = true; } } if(!(*it)->isHidden()) allHidden = false; (*it)->m_label->setIsActiveFile(it.key() == activeFile); } // NOTE: always hide merge&subtract all on current working set // if we want to enable mergeButton, we have to fix it's behavior since it operates directly on the // set contents and not on the m_fileWidgets m_mergeButton->setHidden(allOpen || currentWorkingSet == m_set); m_subtractButton->setHidden(noneOpen || currentWorkingSet == m_set); m_deleteButton->setHidden(m_set->hasConnectedAreas()); m_documentsLabel->setHidden(m_mergeButton->isHidden() && m_subtractButton->isHidden() && m_deleteButton->isHidden()); if(currentWorkingSet == m_set) { disconnect(m_openButton, &QPushButton::clicked, m_setButton, &WorkingSetToolButton::loadSet); connect(m_openButton, &QPushButton::clicked, m_setButton, &WorkingSetToolButton::closeSet); connect(m_openButton, &QPushButton::clicked, this, &WorkingSetToolTipWidget::shouldClose); m_openButton->setIcon(QIcon::fromTheme(QStringLiteral("project-development-close"))); m_openButton->setText(i18n("Stash")); }else{ disconnect(m_openButton, &QPushButton::clicked, m_setButton, &WorkingSetToolButton::closeSet); connect(m_openButton, &QPushButton::clicked, m_setButton, &WorkingSetToolButton::loadSet); disconnect(m_openButton, &QPushButton::clicked, this, &WorkingSetToolTipWidget::shouldClose); m_openButton->setIcon(QIcon::fromTheme(QStringLiteral("project-open"))); m_openButton->setText(i18n("Load")); } if(allHidden && tooltip) tooltip->hide(); if(needResize && tooltip) tooltip->resize(tooltip->sizeHint()); } void WorkingSetToolTipWidget::buttonClicked(bool) { QPointer stillExists(this); QToolButton* s = qobject_cast(sender()); Q_ASSERT(s); MainWindow* mainWindow = dynamic_cast(Core::self()->uiController()->activeMainWindow()); Q_ASSERT(mainWindow); QSet openFiles = Core::self()->workingSetControllerInternal()->getWorkingSet(mainWindow->area()->workingSet())->fileList().toSet(); if(!openFiles.contains(s->objectName())) { Core::self()->documentControllerInternal()->openDocument(QUrl::fromUserInput(s->objectName())); }else{ openFiles.remove(s->objectName()); filterViews(openFiles); } if(stillExists) updateFileButtons(); } void WorkingSetToolTipWidget::labelClicked() { QPointer stillExists(this); WorkingSetFileLabel* s = qobject_cast(sender()); Q_ASSERT(s); bool found = false; Sublime::MainWindow* window = static_cast(ICore::self()->uiController()->activeMainWindow()); foreach(Sublime::View* view, window->area()->views()) { if(view->document()->documentSpecifier() == s->objectName()) { window->activateView(view); found = true; break; } } if(!found) Core::self()->documentControllerInternal()->openDocument(QUrl::fromUserInput(s->objectName())); if(stillExists) updateFileButtons(); } #include "workingsettooltipwidget.moc" diff --git a/shell/workingsets/workingsetwidget.cpp b/shell/workingsets/workingsetwidget.cpp index 071376745..c2ce12f82 100644 --- a/shell/workingsets/workingsetwidget.cpp +++ b/shell/workingsets/workingsetwidget.cpp @@ -1,89 +1,89 @@ /* Copyright David Nolden Copyright 2010 Milian Wolff 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 "workingsetwidget.h" -#include "../debug.h" +#include "debug.h" #include #include "workingsetcontroller.h" #include "workingset.h" #include "workingsettoolbutton.h" #include using namespace KDevelop; WorkingSet* getSet(const QString& id) { if (id.isEmpty()) { return nullptr; } return Core::self()->workingSetControllerInternal()->getWorkingSet(id); } WorkingSetWidget::WorkingSetWidget(Sublime::Area* area, QWidget* parent) : WorkingSetToolButton(parent, nullptr) , m_area(area) { //Queued connect so the change is already applied to the area when we start processing connect(m_area.data(), &Sublime::Area::changingWorkingSet, this, &WorkingSetWidget::changingWorkingSet, Qt::QueuedConnection); setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Ignored)); changingWorkingSet(m_area, QString(), area->workingSet()); } void WorkingSetWidget::setVisible( bool visible ) { // never show empty working sets // TODO: I overloaded this only because hide() in the ctor does not work, other ideas? // It's not that it doesn't work from the constructor, it's that the value changes when the button is added on a layout. QWidget::setVisible( visible && (workingSet() && !workingSet()->isEmpty()) ); } void WorkingSetWidget::changingWorkingSet( Sublime::Area* area, const QString& /*from*/, const QString& newSet) { qCDebug(SHELL) << "re-creating widget" << m_area; Q_ASSERT(area == m_area); Q_UNUSED(area); if (workingSet()) { disconnect(workingSet(), &WorkingSet::setChangedSignificantly, this, &WorkingSetWidget::setChangedSignificantly); } WorkingSet* set = getSet(newSet); setWorkingSet(set); if (set) { connect(set, &WorkingSet::setChangedSignificantly, this, &WorkingSetWidget::setChangedSignificantly); } setVisible(set && !set->isEmpty()); } void WorkingSetWidget::setChangedSignificantly() { setVisible(!workingSet()->isEmpty()); } diff --git a/sublime/CMakeLists.txt b/sublime/CMakeLists.txt index 2144087ce..358c23f03 100644 --- a/sublime/CMakeLists.txt +++ b/sublime/CMakeLists.txt @@ -1,46 +1,51 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kdevplatform\") add_subdirectory(examples) add_subdirectory(tests) set(sublime_LIB_SRCS area.cpp areaindex.cpp container.cpp controller.cpp document.cpp mainwindow.cpp mainwindow_p.cpp mainwindowoperator.cpp urldocument.cpp tooldocument.cpp view.cpp sublimedefs.cpp aggregatemodel.cpp holdupdates.cpp idealcontroller.cpp ideallayout.cpp idealtoolbutton.cpp idealdockwidget.cpp idealbuttonbarwidget.cpp ) +ecm_qt_declare_logging_category(sublime_LIB_SRCS + HEADER debug.h + IDENTIFIER SUBLIME + CATEGORY_NAME "kdevplatform.sublime" +) kdevplatform_add_library(KDevPlatformSublime SOURCES ${sublime_LIB_SRCS}) target_link_libraries(KDevPlatformSublime LINK_PRIVATE KF5::KIOWidgets LINK_PUBLIC KF5::Parts) install(FILES area.h areaindex.h areawalkers.h container.h controller.h document.h mainwindow.h mainwindowoperator.h urldocument.h sublimedefs.h tooldocument.h view.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/kdevplatform/sublime COMPONENT Devel) diff --git a/sublime/area.cpp b/sublime/area.cpp index 73e5a590f..3863f820c 100644 --- a/sublime/area.cpp +++ b/sublime/area.cpp @@ -1,485 +1,485 @@ /*************************************************************************** * Copyright 2006-2007 Alexander Dymo * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "area.h" #include #include #include #include #include #include "view.h" #include "document.h" #include "areaindex.h" #include "controller.h" -#include "sublimedebug.h" +#include namespace Sublime { // struct AreaPrivate struct AreaPrivate { AreaPrivate() : rootIndex(new RootAreaIndex) , currentIndex(rootIndex.data()) , controller(nullptr) { } AreaPrivate(const AreaPrivate &p) : title(p.title) , rootIndex(new RootAreaIndex(*(p.rootIndex))) , currentIndex(rootIndex.data()) , controller(p.controller) , toolViewPositions() , desiredToolViews(p.desiredToolViews) , shownToolViews(p.shownToolViews) , iconName(p.iconName) , workingSet(p.workingSet) , m_actions(p.m_actions) { } ~AreaPrivate() { } struct ViewFinder { explicit ViewFinder(View *_view): view(_view), index(nullptr) {} Area::WalkerMode operator() (AreaIndex *idx) { if (idx->hasView(view)) { index = idx; return Area::StopWalker; } return Area::ContinueWalker; } View *view; AreaIndex *index; }; struct ViewLister { Area::WalkerMode operator()(AreaIndex *idx) { views += idx->views(); return Area::ContinueWalker; } QList views; }; QString title; QScopedPointer rootIndex; AreaIndex *currentIndex; Controller *controller; QList toolViews; QMap toolViewPositions; QMap desiredToolViews; QMap shownToolViews; QString iconName; QString workingSet; QPointer activeView; QList m_actions; }; // class Area Area::Area(Controller *controller, const QString &name, const QString &title) :QObject(controller), d( new AreaPrivate() ) { // FIXME: using objectName seems fishy. Introduce areaType method, // or some such. setObjectName(name); d->title = title; d->controller = controller; d->iconName = QStringLiteral("kdevelop"); d->workingSet.clear(); qCDebug(SUBLIME) << "initial working-set:" << d->workingSet; initialize(); } Area::Area(const Area &area) : QObject(area.controller()), d( new AreaPrivate( *(area.d) ) ) { setObjectName(area.objectName()); //clone toolviews d->toolViews.clear(); foreach (View *view, area.toolViews()) addToolView(view->document()->createView(), area.toolViewPosition(view)); initialize(); } void Area::initialize() { connect(this, &Area::viewAdded, d->controller, &Controller::notifyViewAdded); connect(this, &Area::aboutToRemoveView, d->controller, &Controller::notifyViewRemoved); connect(this, &Area::toolViewAdded, d->controller, &Controller::notifyToolViewAdded); connect(this, &Area::aboutToRemoveToolView, d->controller, &Controller::notifyToolViewRemoved); connect(this, &Area::toolViewMoved, d->controller, &Controller::toolViewMoved); /* In theory, ownership is passed to us, so should not bother detecting deletion outside. */ // Functor will be called after destructor has run -> capture controller pointer by value // otherwise we crash because we access the already freed pointer this->d auto controller = d->controller; connect(this, &Area::destroyed, controller, [controller] (QObject* obj) { controller->removeArea(static_cast(obj)); }); } Area::~Area() { delete d; } View* Area::activeView() { return d->activeView.data(); } void Area::setActiveView(View* view) { d->activeView = view; } void Area::addView(View *view, AreaIndex *index, View *after) { //View *after = 0; if (!after && controller()->openAfterCurrent()) { after = activeView(); } index->add(view, after); connect(view, &View::positionChanged, this, &Area::positionChanged); qCDebug(SUBLIME) << "view added in" << this; connect(this, &Area::destroyed, view, &View::deleteLater); emit viewAdded(index, view); } void Area::addView(View *view, View *after) { AreaIndex *index = d->currentIndex; if (after) { AreaIndex *i = indexOf(after); if (i) index = i; } addView(view, index); } void Area::addView(View *view, View *viewToSplit, Qt::Orientation orientation) { AreaIndex *indexToSplit = indexOf(viewToSplit); addView(view, indexToSplit, orientation); } void Area::addView(View* view, AreaIndex* indexToSplit, Qt::Orientation orientation) { indexToSplit->split(view, orientation); emit viewAdded(indexToSplit, view); connect(this, &Area::destroyed, view, &View::deleteLater); } View* Area::removeView(View *view) { AreaIndex *index = indexOf(view); if (!index) return nullptr; emit aboutToRemoveView(index, view); index->remove(view); emit viewRemoved(index, view); return view; } AreaIndex *Area::indexOf(View *view) { AreaPrivate::ViewFinder f(view); walkViews(f, d->rootIndex.data()); return f.index; } RootAreaIndex *Area::rootIndex() const { return d->rootIndex.data(); } void Area::addToolView(View *view, Position defaultPosition) { d->toolViews.append(view); const QString id = view->document()->documentSpecifier(); const Position position = d->desiredToolViews.value(id, defaultPosition); d->desiredToolViews[id] = position; d->toolViewPositions[view] = position; emit toolViewAdded(view, position); } void Sublime::Area::raiseToolView(View * toolView) { emit requestToolViewRaise(toolView); } View* Area::removeToolView(View *view) { if (!d->toolViews.contains(view)) return nullptr; emit aboutToRemoveToolView(view, d->toolViewPositions[view]); QString id = view->document()->documentSpecifier(); qCDebug(SUBLIME) << this << "removed tool view " << id; d->desiredToolViews.remove(id); d->toolViews.removeAll(view); d->toolViewPositions.remove(view); return view; } void Area::moveToolView(View *toolView, Position newPosition) { if (!d->toolViews.contains(toolView)) return; QString id = toolView->document()->documentSpecifier(); d->desiredToolViews[id] = newPosition; d->toolViewPositions[toolView] = newPosition; emit toolViewMoved(toolView, newPosition); } QList &Area::toolViews() const { return d->toolViews; } Position Area::toolViewPosition(View *toolView) const { return d->toolViewPositions[toolView]; } Controller *Area::controller() const { return d->controller; } QList Sublime::Area::views() { AreaPrivate::ViewLister lister; walkViews(lister, d->rootIndex.data()); return lister.views; } QString Area::title() const { return d->title; } void Area::setTitle(const QString &title) { d->title = title; } void Area::save(KConfigGroup& group) const { QStringList desired; QMap::iterator i, e; for (i = d->desiredToolViews.begin(), e = d->desiredToolViews.end(); i != e; ++i) { desired << i.key() + ':' + QString::number(static_cast(i.value())); } group.writeEntry("desired views", desired); qCDebug(SUBLIME) << "save " << this << "wrote" << group.readEntry("desired views", ""); group.writeEntry("view on left", shownToolViews(Sublime::Left)); group.writeEntry("view on right", shownToolViews(Sublime::Right)); group.writeEntry("view on top", shownToolViews(Sublime::Top)); group.writeEntry("view on bottom", shownToolViews(Sublime::Bottom)); group.writeEntry("working set", d->workingSet); } void Area::load(const KConfigGroup& group) { qCDebug(SUBLIME) << "loading areas config"; d->desiredToolViews.clear(); QStringList desired = group.readEntry("desired views", QStringList()); foreach (const QString &s, desired) { int i = s.indexOf(':'); if (i != -1) { QString id = s.left(i); int pos_i = s.midRef(i+1).toInt(); Sublime::Position pos = static_cast(pos_i); if (pos != Sublime::Left && pos != Sublime::Right && pos != Sublime::Top && pos != Sublime::Bottom) { pos = Sublime::Bottom; } d->desiredToolViews[id] = pos; } } setShownToolViews(Sublime::Left, group.readEntry("view on left", QStringList())); setShownToolViews(Sublime::Right, group.readEntry("view on right", QStringList())); setShownToolViews(Sublime::Top, group.readEntry("view on top", QStringList())); setShownToolViews(Sublime::Bottom, group.readEntry("view on bottom", QStringList())); setWorkingSet(group.readEntry("working set", d->workingSet)); } bool Area::wantToolView(const QString& id) { return (d->desiredToolViews.contains(id)); } void Area::setShownToolViews(Sublime::Position pos, const QStringList& ids) { d->shownToolViews[pos] = ids; } QStringList Area::shownToolViews(Sublime::Position pos) const { if (pos == Sublime::AllPositions) { QStringList allIds; std::for_each(d->shownToolViews.constBegin(), d->shownToolViews.constEnd(), [&](const QStringList& ids) { allIds << ids; }); return allIds; } return d->shownToolViews[pos]; } void Area::setDesiredToolViews( const QMap& desiredToolViews) { d->desiredToolViews = desiredToolViews; } QString Area::iconName() const { return d->iconName; } void Area::setIconName(const QString& iconName) { d->iconName = iconName; } void Area::positionChanged(View *view, int newPos) { qCDebug(SUBLIME) << view << newPos; AreaIndex *index = indexOf(view); index->views().move(index->views().indexOf(view), newPos); } QString Area::workingSet() const { return d->workingSet; } void Area::setWorkingSet(QString name) { if(name != d->workingSet) { qCDebug(SUBLIME) << this << "setting new working-set" << name; QString oldName = d->workingSet; emit changingWorkingSet(this, oldName, name); d->workingSet = name; emit changedWorkingSet(this, oldName, name); } } bool Area::closeView(View* view, bool silent) { QPointer doc = view->document(); // We don't just delete the view, because if silent is false, we might need to ask the user. if(doc && !silent) { // Do some counting to check whether we need to ask the user for feedback qCDebug(SUBLIME) << "Closing view for" << view->document()->documentSpecifier() << "views" << view->document()->views().size() << "in area" << this; int viewsInCurrentArea = 0; // Number of views for the same document in the current area int viewsInOtherAreas = 0; // Number of views for the same document in other areas int viewsInOtherWorkingSets = 0; // Number of views for the same document in areas with different working-set foreach(View* otherView, doc.data()->views()) { Area* area = controller()->areaForView(otherView); if(area == this) viewsInCurrentArea += 1; if(!area || (area != this)) viewsInOtherAreas += 1; if(area && area != this && area->workingSet() != workingSet()) viewsInOtherWorkingSets += 1; } if(viewsInCurrentArea == 1 && (viewsInOtherAreas == 0 || viewsInOtherWorkingSets == 0)) { // Time to ask the user for feedback, because the document will be completely closed // due to working-set synchronization if( !doc.data()->askForCloseFeedback() ) return false; } } // otherwise we can silently close the view, // the document will still have an opened view somewhere delete removeView(view); return true; } void Area::clearViews(bool silent) { foreach(Sublime::View* view, views()) { closeView(view, silent); } } void Area::clearDocuments() { if (views().isEmpty()) emit clearWorkingSet(this); else clearViews(true); } QList Area::actions() const { return d->m_actions; } void Area::addAction(QAction* action) { Q_ASSERT(!d->m_actions.contains(action)); connect(action, &QAction::destroyed, this, &Area::actionDestroyed); d->m_actions.append(action); } void Area::actionDestroyed(QObject* action) { d->m_actions.removeAll(qobject_cast(action)); } } diff --git a/sublime/areaindex.cpp b/sublime/areaindex.cpp index a80900998..c8737a077 100644 --- a/sublime/areaindex.cpp +++ b/sublime/areaindex.cpp @@ -1,251 +1,251 @@ /*************************************************************************** * Copyright 2006-2007 Alexander Dymo * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "areaindex.h" #include #include "view.h" #include "document.h" -#include "sublimedebug.h" +#include namespace Sublime { // struct AreaIndexPrivate struct AreaIndexPrivate { AreaIndexPrivate() :parent(nullptr), first(nullptr), second(nullptr), orientation(Qt::Horizontal) { } ~AreaIndexPrivate() { delete first; delete second; foreach( View* v, views ) { // Do the same as AreaIndex::remove(), seems like deletion of the view is happening elsewhere views.removeAll( v ); } } AreaIndexPrivate(const AreaIndexPrivate &p) { parent = nullptr; orientation = p.orientation; first = p.first ? new AreaIndex(*(p.first)) : nullptr; second = p.second ? new AreaIndex(*(p.second)) : nullptr; } bool isSplit() const { return first || second; } QList views; AreaIndex *parent; AreaIndex *first; AreaIndex *second; Qt::Orientation orientation; }; // class AreaIndex AreaIndex::AreaIndex() : d(new AreaIndexPrivate) { } AreaIndex::AreaIndex(AreaIndex *parent) : d(new AreaIndexPrivate) { d->parent = parent; } AreaIndex::AreaIndex(const AreaIndex &index) : d(new AreaIndexPrivate( *(index.d) ) ) { qCDebug(SUBLIME) << "copying area index"; if (d->first) d->first->setParent(this); if (d->second) d->second->setParent(this); //clone views in this index d->views.clear(); foreach (View *view, index.views()) add(view->document()->createView()); } AreaIndex::~AreaIndex() { delete d; } void AreaIndex::add(View *view, View *after) { //we can not add views to the areas that have already been split if (d->isSplit()) return; if (after) d->views.insert(d->views.indexOf(after)+1, view); else d->views.append(view); } void AreaIndex::remove(View *view) { if (d->isSplit()) return; d->views.removeAll(view); if (d->parent && (d->views.count() == 0)) d->parent->unsplit(this); } void AreaIndex::split(Qt::Orientation orientation, bool moveViewsToSecond) { //we can not split areas that have already been split if (d->isSplit()) return; d->first = new AreaIndex(this); d->second = new AreaIndex(this); d->orientation = orientation; if(moveViewsToSecond) moveViewsTo(d->second); else moveViewsTo(d->first); } void AreaIndex::split(View *newView, Qt::Orientation orientation) { split(orientation); //make new view as second widget in splitter d->second->add(newView); } void AreaIndex::unsplit(AreaIndex *childToRemove) { if (!d->isSplit()) return; AreaIndex *other = d->first == childToRemove ? d->second : d->first; other->moveViewsTo(this); d->orientation = other->orientation(); d->first = nullptr; d->second = nullptr; other->copyChildrenTo(this); delete other; delete childToRemove; } void AreaIndex::copyChildrenTo(AreaIndex *target) { if (!d->first || !d->second) return; target->d->first = d->first; target->d->second = d->second; target->d->first->setParent(target); target->d->second->setParent(target); d->first = nullptr; d->second = nullptr; } void AreaIndex::moveViewsTo(AreaIndex *target) { target->d->views = d->views; d->views.clear(); } QList &AreaIndex::views() const { return d->views; } View *AreaIndex::viewAt(int position) const { return d->views.value(position, nullptr); } int AreaIndex::viewCount() const { return d->views.count(); } bool AreaIndex::hasView(View *view) const { return d->views.contains(view); } AreaIndex *AreaIndex::parent() const { return d->parent; } void AreaIndex::setParent(AreaIndex *parent) { d->parent = parent; } AreaIndex *AreaIndex::first() const { return d->first; } AreaIndex *AreaIndex::second() const { return d->second; } Qt::Orientation AreaIndex::orientation() const { return d->orientation; } bool Sublime::AreaIndex::isSplit() const { return d->isSplit(); } void Sublime::AreaIndex::setOrientation(Qt::Orientation orientation) const { d->orientation = orientation; } // class RootAreaIndex RootAreaIndex::RootAreaIndex() :AreaIndex(), d(nullptr) { } QString AreaIndex::print() const { if(isSplit()) return " [ " + first()->print() + (orientation() == Qt::Horizontal ? " / " : " - ") + second()->print() + " ] "; QStringList ret; foreach(Sublime::View* view, views()) ret << view->document()->title(); return ret.join(QStringLiteral(" ")); } } diff --git a/sublime/controller.cpp b/sublime/controller.cpp index a401127d7..45f48dbce 100644 --- a/sublime/controller.cpp +++ b/sublime/controller.cpp @@ -1,418 +1,418 @@ /*************************************************************************** * Copyright 2006-2007 Alexander Dymo * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "controller.h" #include #include #include #include #include #include #include "area.h" #include "view.h" #include "document.h" #include "mainwindow.h" -#include "sublimedebug.h" +#include namespace Sublime { struct WidgetFinder { explicit WidgetFinder(QWidget *_w) :w(_w), view(nullptr) {} Area::WalkerMode operator()(AreaIndex *index) { foreach (View *v, index->views()) { if (v->hasWidget() && (v->widget() == w)) { view = v; return Area::StopWalker; } } return Area::ContinueWalker; } QWidget *w; View *view; }; struct ToolWidgetFinder { explicit ToolWidgetFinder(QWidget *_w) :w(_w), view(nullptr) {} Area::WalkerMode operator()(View *v, Sublime::Position /*position*/) { if (v->hasWidget() && (v->widget() == w)) { view = v; return Area::StopWalker; } return Area::ContinueWalker; } QWidget *w; View *view; }; // struct ControllerPrivate struct ControllerPrivate { ControllerPrivate() { } QList documents; QList areas; QList allAreas; QMap namedAreas; // FIXME: remove this. QMap shownAreas; QList controlledWindows; QVector< QList > mainWindowAreas; bool openAfterCurrent; bool arrangeBuddies; }; // class Controller Controller::Controller(QObject *parent) :QObject(parent), MainWindowOperator(), d( new ControllerPrivate() ) { init(); } void Controller::init() { loadSettings(); qApp->installEventFilter(this); } Controller::~Controller() { qDeleteAll(d->controlledWindows); delete d; } void Controller::showArea(Area *area, MainWindow *mainWindow) { Area *areaToShow = nullptr; //if the area is already shown in another mainwindow then we need to clone it if (d->shownAreas.contains(area) && (mainWindow != d->shownAreas[area])) areaToShow = new Area(*area); else areaToShow = area; d->shownAreas[areaToShow] = mainWindow; showAreaInternal(areaToShow, mainWindow); } void Controller::showAreaInternal(Area* area, MainWindow *mainWindow) { /* Disconnect the previous area. We really don't want to mess with main window if an area not visible now is modified. Further, if showAreaInternal is called with the same area as is current now, we don't want to connect the same signals twice. */ MainWindowOperator::setArea(mainWindow, area); } void Controller::removeArea(Area *obj) { d->areas.removeAll(obj); } void Controller::removeDocument(Document *obj) { d->documents.removeAll(obj); } void Controller::showArea(const QString& areaTypeId, MainWindow *mainWindow) { int index = d->controlledWindows.indexOf(mainWindow); Q_ASSERT(index != -1); Area* area = nullptr; foreach (Area* a, d->mainWindowAreas[index]) { qCDebug(SUBLIME) << "Object name: " << a->objectName() << " id " << areaTypeId; if (a->objectName() == areaTypeId) { area = a; break; } } Q_ASSERT (area); showAreaInternal(area, mainWindow); } void Controller::resetCurrentArea(MainWindow *mainWindow) { QString id = mainWindow->area()->objectName(); int areaIndex = 0; Area* def = nullptr; foreach (Area* a, d->areas) { if (a->objectName() == id) { def = a; break; } ++areaIndex; } Q_ASSERT(def); int index = d->controlledWindows.indexOf(mainWindow); Q_ASSERT(index != -1); Area* prev = d->mainWindowAreas[index][areaIndex]; d->mainWindowAreas[index][areaIndex] = new Area(*def); showAreaInternal(d->mainWindowAreas[index][areaIndex], mainWindow); delete prev; } const QList &Controller::defaultAreas() const { return d->areas; } const QList< Area* >& Controller::areas(MainWindow* mainWindow) const { int index = d->controlledWindows.indexOf(mainWindow); Q_ASSERT(index != -1); return areas(index); } const QList &Controller::areas(int mainWindow) const { return d->mainWindowAreas[mainWindow]; } const QList &Controller::allAreas() const { return d->allAreas; } const QList &Controller::documents() const { return d->documents; } void Controller::addDefaultArea(Area *area) { d->areas.append(area); d->allAreas.append(area); d->namedAreas[area->objectName()] = area; emit areaCreated(area); } void Controller::addMainWindow(MainWindow* mainWindow) { Q_ASSERT(mainWindow); Q_ASSERT (!d->controlledWindows.contains(mainWindow)); d->controlledWindows << mainWindow; d->mainWindowAreas.resize(d->controlledWindows.size()); int index = d->controlledWindows.size()-1; foreach (Area* area, defaultAreas()) { Area *na = new Area(*area); d->allAreas.append(na); d->mainWindowAreas[index].push_back(na); emit areaCreated(na); } showAreaInternal(d->mainWindowAreas[index][0], mainWindow); emit mainWindowAdded( mainWindow ); } void Controller::addDocument(Document *document) { d->documents.append(document); } void Controller::areaReleased() { MainWindow *w = reinterpret_cast(sender()); qCDebug(SUBLIME) << "marking areas as mainwindow-free" << w << d->controlledWindows.contains(w) << d->shownAreas.keys(w); foreach (Area *area, d->shownAreas.keys(w)) { qCDebug(SUBLIME) << "" << area->objectName(); areaReleased(area); disconnect(area, nullptr, w, nullptr); } d->controlledWindows.removeAll(w); } void Controller::areaReleased(Sublime::Area *area) { d->shownAreas.remove(area); d->namedAreas.remove(area->objectName()); } Area *Controller::defaultArea(const QString &id) const { return d->namedAreas[id]; } Area *Controller::area(int mainWindow, const QString& id) const { foreach (Area* area, areas(mainWindow)) { if (area->objectName() == id) return area; } return nullptr; } Area* Controller::areaForView(View* view) const { foreach (Area* area, allAreas()) if(area->views().contains(view)) return area; return nullptr; } /*We need this to catch activation of views and toolviews so that we can always tell what view and toolview is active. "Active" doesn't mean focused. It means that it is focused now or was focused before and no other view/toolview wasn't focused after that."*/ //implementation is based upon KParts::PartManager::eventFilter bool Controller::eventFilter(QObject *obj, QEvent *ev) { if (ev->type() != QEvent::MouseButtonPress && ev->type() != QEvent::MouseButtonDblClick && ev->type() != QEvent::FocusIn) return false; //not a widget? - return if (!obj->isWidgetType()) return false; //is dialog or popup? - return QWidget *w = static_cast(obj); if (((w->windowFlags().testFlag(Qt::Dialog)) && w->isModal()) || (w->windowFlags().testFlag(Qt::Popup)) || (w->windowFlags().testFlag(Qt::Tool))) return false; //not a mouse button that should activate the widget? - return QMouseEvent *mev = nullptr; if (ev->type() == QEvent::MouseButtonPress || ev->type() == QEvent::MouseButtonDblClick) { mev = static_cast(ev); int activationButtonMask = Qt::LeftButton | Qt::MidButton | Qt::RightButton; if ((mev->button() & activationButtonMask) == 0) return false; } while (w) { //not inside sublime mainwindow MainWindow *mw = qobject_cast(w->topLevelWidget()); if (!mw || !d->controlledWindows.contains(mw)) return false; Area *area = mw->area(); ///@todo adymo: this is extra slow - optimize //find this widget in views WidgetFinder widgetFinder(w); area->walkViews(widgetFinder, area->rootIndex()); if (widgetFinder.view && widgetFinder.view != mw->activeView()) { setActiveView(mw, widgetFinder.view); ///@todo adymo: shall we filter out the event? return false; } //find this widget in toolviews ToolWidgetFinder toolFinder(w); area->walkToolViews(toolFinder, Sublime::AllPositions); if (toolFinder.view && toolFinder.view != mw->activeToolView()) { setActiveToolView(mw, toolFinder.view); ///@todo adymo: shall we filter out the event? return false; } w = w->parentWidget(); } return false; } const QList< MainWindow * > & Controller::mainWindows() const { return d->controlledWindows; } void Controller::notifyToolViewRemoved(Sublime::View *view, Sublime::Position) { emit aboutToRemoveToolView(view); } void Controller::notifyToolViewAdded(Sublime::View *view, Sublime::Position) { emit toolViewAdded(view); } void Controller::notifyViewRemoved(Sublime::AreaIndex*, Sublime::View *view) { emit aboutToRemoveView(view); } void Controller::notifyViewAdded(Sublime::AreaIndex*, Sublime::View *view) { emit viewAdded(view); } void Controller::setStatusIcon(Document * document, const QIcon & icon) { document->setStatusIcon(icon); } void Controller::loadSettings() { KConfigGroup uiGroup = KSharedConfig::openConfig()->group("UiSettings"); d->openAfterCurrent = (uiGroup.readEntry("TabBarOpenAfterCurrent", 1) == 1); d->arrangeBuddies = (uiGroup.readEntry("TabBarArrangeBuddies", 1) == 1); } bool Controller::openAfterCurrent() const { return d->openAfterCurrent; } bool Controller::arrangeBuddies() const { return d->arrangeBuddies; } } #include "moc_controller.cpp" diff --git a/sublime/mainwindow.cpp b/sublime/mainwindow.cpp index 8c4e8d11e..122d76f90 100644 --- a/sublime/mainwindow.cpp +++ b/sublime/mainwindow.cpp @@ -1,446 +1,444 @@ /*************************************************************************** * Copyright 2006-2007 Alexander Dymo * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "mainwindow.h" #include "mainwindow_p.h" #include #include #include #include #include #include #include #include #include "area.h" #include "view.h" #include "controller.h" #include "container.h" #include "idealbuttonbarwidget.h" #include "idealcontroller.h" #include "holdupdates.h" -#include "sublimedebug.h" - -Q_LOGGING_CATEGORY(SUBLIME, "kdevplatform.sublime") +#include namespace Sublime { MainWindow::MainWindow(Controller *controller, Qt::WindowFlags flags) : KParts::MainWindow(nullptr, flags), d(new MainWindowPrivate(this, controller)) { connect(this, &MainWindow::destroyed, controller, static_cast(&Controller::areaReleased)); loadGeometry(KSharedConfig::openConfig()->group("Main Window")); // don't allow AllowTabbedDocks - that doesn't make sense for "ideal" UI setDockOptions(QMainWindow::AnimatedDocks); } bool MainWindow::containsView(View* view) const { foreach(Area* area, areas()) if(area->views().contains(view)) return true; return false; } QList< Area* > MainWindow::areas() const { QList< Area* > areas = controller()->areas(const_cast(this)); if(areas.isEmpty()) areas = controller()->defaultAreas(); return areas; } MainWindow::~MainWindow() { qCDebug(SUBLIME) << "destroying mainwindow"; delete d; } void MainWindow::reconstructViews(QList topViews) { d->reconstructViews(topViews); } QList MainWindow::getTopViews() const { QList topViews; foreach(View* view, d->area->views()) { if(view->hasWidget()) { QWidget* widget = view->widget(); if(widget->parent() && widget->parent()->parent()) { Container* container = qobject_cast(widget->parent()->parent()); if(container->currentWidget() == widget) topViews << view; } } } return topViews; } QList MainWindow::containers() const { return d->viewContainers.values(); } void MainWindow::setArea(Area *area) { if (d->area) disconnect(d->area, nullptr, d, nullptr); bool differentArea = (area != d->area); /* All views will be removed from dock area now. However, this does not mean those are removed from area, so prevent slotDockShown from recording those views as no longer shown in the area. */ d->ignoreDockShown = true; if (d->autoAreaSettingsSave && differentArea) saveSettings(); HoldUpdates hu(this); if (d->area) clearArea(); d->area = area; d->reconstruct(); if(d->area->activeView()) activateView(d->area->activeView()); else d->activateFirstVisibleView(); initializeStatusBar(); emit areaChanged(area); d->ignoreDockShown = false; hu.stop(); loadSettings(); connect(area, &Area::viewAdded, d, &MainWindowPrivate::viewAdded); connect(area, &Area::viewRemoved, d, &MainWindowPrivate::viewRemovedInternal); connect(area, &Area::requestToolViewRaise, d, &MainWindowPrivate::raiseToolView); connect(area, &Area::aboutToRemoveView, d, &MainWindowPrivate::aboutToRemoveView); connect(area, &Area::toolViewAdded, d, &MainWindowPrivate::toolViewAdded); connect(area, &Area::aboutToRemoveToolView, d, &MainWindowPrivate::aboutToRemoveToolView); connect(area, &Area::toolViewMoved, d, &MainWindowPrivate::toolViewMoved); } void MainWindow::initializeStatusBar() { //nothing here, reimplement in the subclasses if you want to have status bar //inside the bottom toolview buttons row } void MainWindow::resizeEvent(QResizeEvent* event) { return KParts::MainWindow::resizeEvent(event); } void MainWindow::clearArea() { emit areaCleared(d->area); d->clearArea(); } QList MainWindow::toolDocks() const { return d->docks; } Area *Sublime::MainWindow::area() const { return d->area; } Controller *MainWindow::controller() const { return d->controller; } View *MainWindow::activeView() const { return d->activeView; } View *MainWindow::activeToolView() const { return d->activeToolView; } void MainWindow::activateView(Sublime::View* view, bool focus) { if (!d->viewContainers.contains(view)) return; d->viewContainers[view]->setCurrentWidget(view->widget()); setActiveView(view, focus); d->area->setActiveView(view); } void MainWindow::setActiveView(View *view, bool focus) { View* oldActiveView = d->activeView; d->activeView = view; if (focus && view && !view->widget()->hasFocus()) view->widget()->setFocus(); if(d->activeView != oldActiveView) emit activeViewChanged(view); } void Sublime::MainWindow::setActiveToolView(View *view) { d->activeToolView = view; emit activeToolViewChanged(view); } void MainWindow::saveSettings() { d->disableConcentrationMode(); QString group = QStringLiteral("MainWindow"); if (area()) group += '_' + area()->objectName(); KConfigGroup cg = KSharedConfig::openConfig()->group(group); /* This will try to save window size, too. But it's OK, since we won't use this information when loading. */ saveMainWindowSettings(cg); //debugToolBar visibility is stored separately to allow a area dependent default value foreach (KToolBar* toolbar, toolBars()) { if (toolbar->objectName() == QLatin1String("debugToolBar")) { cg.writeEntry("debugToolBarVisibility", toolbar->isVisibleTo(this)); } } d->idealController->leftBarWidget->saveOrderSettings(cg); d->idealController->bottomBarWidget->saveOrderSettings(cg); d->idealController->rightBarWidget->saveOrderSettings(cg); cg.sync(); } void MainWindow::loadSettings() { HoldUpdates hu(this); qCDebug(SUBLIME) << "loading settings for " << (area() ? area()->objectName() : QLatin1String("")); QString group = QStringLiteral("MainWindow"); if (area()) group += '_' + area()->objectName(); KConfigGroup cg = KSharedConfig::openConfig()->group(group); // What follows is copy-paste from applyMainWindowSettings. Unfortunately, // we don't really want that one to try restoring window size, and we also // cannot stop it from doing that in any clean way. QStatusBar* sb = findChild(); if (sb) { QString entry = cg.readEntry("StatusBar", "Enabled"); if ( entry == QLatin1String("Disabled") ) sb->hide(); else sb->show(); } QMenuBar* mb = findChild(); if (mb) { QString entry = cg.readEntry ("MenuBar", "Enabled"); if ( entry == QLatin1String("Disabled") ) mb->hide(); else mb->show(); } if ( !autoSaveSettings() || cg.name() == autoSaveGroup() ) { QString entry = cg.readEntry ("ToolBarsMovable", "Enabled"); if ( entry == QLatin1String("Disabled") ) KToolBar::setToolBarsLocked(true); else KToolBar::setToolBarsLocked(false); } // Utilise the QMainWindow::restoreState() functionality // Note that we're fixing KMainWindow bug here -- the original // code has this fragment above restoring toolbar properties. // As result, each save/restore would move the toolbar a bit to // the left. if (cg.hasKey("State")) { QByteArray state; state = cg.readEntry("State", state); state = QByteArray::fromBase64(state); // One day will need to load the version number, but for now, assume 0 restoreState(state); } else { // If there's no state we use a default size of 870x650 // Resize only when showing "code" area. If we do that for other areas, // then we'll hit bug https://bugs.kde.org/show_bug.cgi?id=207990 // TODO: adymo: this is more like a hack, we need a proper first-start initialization if (area() && area()->objectName() == QLatin1String("code")) resize(870,650); } int n = 1; // Toolbar counter. toolbars are counted from 1, foreach (KToolBar* toolbar, toolBars()) { QString group(QStringLiteral("Toolbar")); // Give a number to the toolbar, but prefer a name if there is one, // because there's no real guarantee on the ordering of toolbars group += (toolbar->objectName().isEmpty() ? QString::number(n) : QStringLiteral(" ")+toolbar->objectName()); KConfigGroup toolbarGroup(&cg, group); toolbar->applySettings(toolbarGroup); if (toolbar->objectName() == QLatin1String("debugToolBar")) { //debugToolBar visibility is stored separately to allow a area dependent default value bool visibility = cg.readEntry("debugToolBarVisibility", area()->objectName() == QLatin1String("debug")); toolbar->setVisible(visibility); } n++; } const bool tabBarHidden = !Container::configTabBarVisible(); foreach (Container *container, d->viewContainers) { container->setTabBarHidden(tabBarHidden); } hu.stop(); d->idealController->leftBarWidget->loadOrderSettings(cg); d->idealController->bottomBarWidget->loadOrderSettings(cg); d->idealController->rightBarWidget->loadOrderSettings(cg); emit settingsLoaded(); d->disableConcentrationMode(); } bool MainWindow::queryClose() { // saveSettings(); KConfigGroup config(KSharedConfig::openConfig(), "Main Window"); saveGeometry(config); config.sync(); return KParts::MainWindow::queryClose(); } QString MainWindow::screenKey() const { const int scnum = QApplication::desktop()->screenNumber(parentWidget()); QRect desk = QApplication::desktop()->screenGeometry(scnum); // if the desktop is virtual then use virtual screen size if (QApplication::desktop()->isVirtualDesktop()) desk = QApplication::desktop()->screenGeometry(QApplication::desktop()->screen()); return QStringLiteral("Desktop %1 %2") .arg(desk.width()).arg(desk.height()); } void MainWindow::saveGeometry(KConfigGroup &config) { config.writeEntry(screenKey(), geometry()); } void MainWindow::loadGeometry(const KConfigGroup &config) { // The below code, essentially, is copy-paste from // KMainWindow::restoreWindowSize. Right now, that code is buggy, // as per http://permalink.gmane.org/gmane.comp.kde.devel.core/52423 // so we implement a less theoretically correct, but working, version // below QRect g = config.readEntry(screenKey(), QRect()); if (!g.isEmpty()) setGeometry(g); } void MainWindow::enableAreaSettingsSave() { d->autoAreaSettingsSave = true; } QWidget *MainWindow::statusBarLocation() const { return d->idealController->statusBarLocation(); } QWidget *MainWindow::viewBarContainer() const { return d->viewBarContainer; } void MainWindow::setTabBarLeftCornerWidget(QWidget* widget) { d->setTabBarLeftCornerWidget(widget); } void MainWindow::tabDoubleClicked(View* view) { Q_UNUSED(view); d->toggleDocksShown(); } void MainWindow::tabContextMenuRequested(View* , QMenu* ) { // do nothing } void MainWindow::tabToolTipRequested(View*, Container*, int) { // do nothing } void MainWindow::newTabRequested() { } void MainWindow::dockBarContextMenuRequested(Qt::DockWidgetArea , const QPoint& ) { // do nothing } View* MainWindow::viewForPosition(QPoint globalPos) const { foreach(Container* container, d->viewContainers) { QRect globalGeom = QRect(container->mapToGlobal(QPoint(0,0)), container->mapToGlobal(QPoint(container->width(), container->height()))); if(globalGeom.contains(globalPos)) { return d->widgetToView[container->currentWidget()]; } } return nullptr; } void MainWindow::setBackgroundCentralWidget(QWidget* w) { d->setBackgroundCentralWidget(w); } } #include "moc_mainwindow.cpp" diff --git a/sublime/mainwindow_p.cpp b/sublime/mainwindow_p.cpp index 26ffddebc..29575d4e7 100644 --- a/sublime/mainwindow_p.cpp +++ b/sublime/mainwindow_p.cpp @@ -1,815 +1,815 @@ /*************************************************************************** * Copyright 2006-2009 Alexander Dymo * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "mainwindow_p.h" #include #include #include #include #include #include #include #include #include #include "area.h" #include "view.h" #include "areaindex.h" #include "document.h" #include "container.h" #include "controller.h" #include "mainwindow.h" #include "idealcontroller.h" #include "holdupdates.h" #include "idealbuttonbarwidget.h" -#include "sublimedebug.h" +#include class IdealToolBar : public QToolBar { Q_OBJECT public: explicit IdealToolBar(const QString& title, bool hideWhenEmpty, Sublime::IdealButtonBarWidget* buttons, QMainWindow* parent) : QToolBar(title, parent) , m_buttons(buttons) , m_hideWhenEmpty(hideWhenEmpty) { setMovable(false); setFloatable(false); setObjectName(title); layout()->setMargin(0); addWidget(m_buttons); if (m_hideWhenEmpty) { connect(m_buttons, &Sublime::IdealButtonBarWidget::emptyChanged, this, &IdealToolBar::updateVisibilty); } } private Q_SLOTS: void updateVisibilty() { setVisible(!m_buttons->isEmpty()); } private: Sublime::IdealButtonBarWidget* m_buttons; const bool m_hideWhenEmpty; }; namespace Sublime { MainWindowPrivate::MainWindowPrivate(MainWindow *w, Controller* controller) :controller(controller), area(nullptr), activeView(nullptr), activeToolView(nullptr), bgCentralWidget(nullptr), ignoreDockShown(false), autoAreaSettingsSave(false), m_mainWindow(w) { KActionCollection *ac = m_mainWindow->actionCollection(); m_concentrationModeAction = new QAction(i18n("Concentration Mode"), this); m_concentrationModeAction->setIcon(QIcon::fromTheme(QStringLiteral("page-zoom"))); m_concentrationModeAction->setToolTip(i18n("Removes most of the controls so you can focus on what matters.")); m_concentrationModeAction->setCheckable(true); m_concentrationModeAction->setChecked(false); ac->setDefaultShortcut(m_concentrationModeAction, Qt::META | Qt::Key_C); connect(m_concentrationModeAction, &QAction::toggled, this, &MainWindowPrivate::restoreConcentrationMode); ac->addAction(QStringLiteral("toggle_concentration_mode"), m_concentrationModeAction); QAction* action = new QAction(i18n("Show Left Dock"), this); action->setCheckable(true); ac->setDefaultShortcut(action, Qt::META | Qt::CTRL | Qt::Key_Left); connect(action, &QAction::toggled, this, &MainWindowPrivate::showLeftDock); ac->addAction(QStringLiteral("show_left_dock"), action); action = new QAction(i18n("Show Right Dock"), this); action->setCheckable(true); ac->setDefaultShortcut(action, Qt::META | Qt::CTRL | Qt::Key_Right); connect(action, &QAction::toggled, this, &MainWindowPrivate::showRightDock); ac->addAction(QStringLiteral("show_right_dock"), action); action = new QAction(i18n("Show Bottom Dock"), this); action->setCheckable(true); ac->setDefaultShortcut(action, Qt::META | Qt::CTRL | Qt::Key_Down); connect(action, &QAction::toggled, this, &MainWindowPrivate::showBottomDock); ac->addAction(QStringLiteral("show_bottom_dock"), action); action = new QAction(i18nc("@action", "Focus Editor"), this); ac->setDefaultShortcut(action, Qt::META | Qt::CTRL | Qt::Key_E); connect(action, &QAction::triggered, this, &MainWindowPrivate::focusEditor); ac->addAction(QStringLiteral("focus_editor"), action); action = new QAction(i18n("Hide/Restore Docks"), this); ac->setDefaultShortcut(action, Qt::META | Qt::CTRL | Qt::Key_Up); connect(action, &QAction::triggered, this, &MainWindowPrivate::toggleDocksShown); ac->addAction(QStringLiteral("hide_all_docks"), action); action = new QAction(i18n("Next Tool View"), this); ac->setDefaultShortcut(action, Qt::META | Qt::CTRL | Qt::Key_N); action->setIcon(QIcon::fromTheme(QStringLiteral("go-next"))); connect(action, &QAction::triggered, this, &MainWindowPrivate::selectNextDock); ac->addAction(QStringLiteral("select_next_dock"), action); action = new QAction(i18n("Previous Tool View"), this); ac->setDefaultShortcut(action, Qt::META | Qt::CTRL | Qt::Key_P); action->setIcon(QIcon::fromTheme(QStringLiteral("go-previous"))); connect(action, &QAction::triggered, this, &MainWindowPrivate::selectPreviousDock); ac->addAction(QStringLiteral("select_previous_dock"), action); action = new KActionMenu(i18n("Tool Views"), this); ac->addAction(QStringLiteral("docks_submenu"), action); idealController = new IdealController(m_mainWindow); m_leftToolBar = new IdealToolBar(i18n("Left Button Bar"), true, idealController->leftBarWidget, m_mainWindow); m_mainWindow->addToolBar(Qt::LeftToolBarArea, m_leftToolBar); m_rightToolBar = new IdealToolBar(i18n("Right Button Bar"), true, idealController->rightBarWidget, m_mainWindow); m_mainWindow->addToolBar(Qt::RightToolBarArea, m_rightToolBar); m_bottomToolBar = new IdealToolBar(i18n("Bottom Button Bar"), false, idealController->bottomBarWidget, m_mainWindow); m_mainWindow->addToolBar(Qt::BottomToolBarArea, m_bottomToolBar); // adymo: intentionally do not add a toolbar for top buttonbar // this doesn't work well with toolbars added via xmlgui centralWidget = new QWidget; centralWidget->setObjectName(QStringLiteral("centralWidget")); QVBoxLayout* layout = new QVBoxLayout(centralWidget); layout->setMargin(0); centralWidget->setLayout(layout); splitterCentralWidget = new QSplitter(centralWidget); // take as much space as possible splitterCentralWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); layout->addWidget(splitterCentralWidget, 2); // this view bar container is used for the ktexteditor integration to show // all view bars at a central place, esp. for split view configurations viewBarContainer = new QWidget; viewBarContainer->setObjectName(QStringLiteral("viewBarContainer")); // hide by default viewBarContainer->setVisible(false); // only take as much as needed viewBarContainer->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); layout->addWidget(viewBarContainer); m_mainWindow->setCentralWidget(centralWidget); connect(idealController, &IdealController::dockShown, this, &MainWindowPrivate::slotDockShown); connect(idealController, &IdealController::widgetResized, this, &MainWindowPrivate::widgetResized); connect(idealController, &IdealController::dockBarContextMenuRequested, m_mainWindow, &MainWindow::dockBarContextMenuRequested); } MainWindowPrivate::~MainWindowPrivate() { delete m_leftTabbarCornerWidget.data(); m_leftTabbarCornerWidget.clear(); } void MainWindowPrivate::disableConcentrationMode() { m_concentrationModeAction->setChecked(false); restoreConcentrationMode(); } void MainWindowPrivate::restoreConcentrationMode() { const bool concentrationModeOn = m_concentrationModeAction->isChecked(); QWidget* cornerWidget = nullptr; if (m_concentrateToolBar) { QLayout* l = m_concentrateToolBar->layout(); QLayoutItem* li = l->takeAt(1); //ensure the cornerWidget isn't destroyed with the toolbar if (li) { cornerWidget = li->widget(); delete li; } m_concentrateToolBar->deleteLater(); } m_mainWindow->menuBar()->setVisible(!concentrationModeOn); m_bottomToolBar->setVisible(!concentrationModeOn); m_leftToolBar->setVisible(!concentrationModeOn); m_rightToolBar->setVisible(!concentrationModeOn); if (concentrationModeOn) { m_concentrateToolBar = new QToolBar(m_mainWindow); m_concentrateToolBar->setObjectName(QStringLiteral("concentrateToolBar")); m_concentrateToolBar->addAction(m_concentrationModeAction); QWidgetAction *action = new QWidgetAction(this); action->setDefaultWidget(m_mainWindow->menuBar()->cornerWidget(Qt::TopRightCorner)); m_concentrateToolBar->addAction(action); m_concentrateToolBar->setMovable(false); m_mainWindow->addToolBar(Qt::TopToolBarArea, m_concentrateToolBar); m_mainWindow->menuBar()->setCornerWidget(nullptr, Qt::TopRightCorner); } else if (cornerWidget) { m_mainWindow->menuBar()->setCornerWidget(cornerWidget, Qt::TopRightCorner); cornerWidget->show(); } if (concentrationModeOn) { m_mainWindow->installEventFilter(this); } else { m_mainWindow->removeEventFilter(this); } } bool MainWindowPrivate::eventFilter(QObject* obj, QEvent* event) { Q_ASSERT(m_mainWindow == obj); if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) { const auto ev = static_cast(event); Qt::KeyboardModifiers modifiers = ev->modifiers(); //QLineEdit banned mostly so that alt navigation can be used from QuickOpen const bool visible = modifiers == Qt::AltModifier && ev->type() == QEvent::KeyPress && !qApp->focusWidget()->inherits("QLineEdit"); m_mainWindow->menuBar()->setVisible(visible); } return false; } void MainWindowPrivate::showLeftDock(bool b) { idealController->showLeftDock(b); } void MainWindowPrivate::showBottomDock(bool b) { idealController->showBottomDock(b); } void MainWindowPrivate::showRightDock(bool b) { idealController->showRightDock(b); } void MainWindowPrivate::setBackgroundCentralWidget(QWidget* w) { delete bgCentralWidget; QLayout* l=m_mainWindow->centralWidget()->layout(); l->addWidget(w); bgCentralWidget=w; setBackgroundVisible(area->views().isEmpty()); } void MainWindowPrivate::setBackgroundVisible(bool v) { if(!bgCentralWidget) return; bgCentralWidget->setVisible(v); splitterCentralWidget->setVisible(!v); } void MainWindowPrivate::focusEditor() { if (View* view = m_mainWindow->activeView()) if (view->hasWidget()) view->widget()->setFocus(Qt::ShortcutFocusReason); } void MainWindowPrivate::toggleDocksShown() { idealController->toggleDocksShown(); } void MainWindowPrivate::selectNextDock() { idealController->goPrevNextDock(IdealController::NextDock); } void MainWindowPrivate::selectPreviousDock() { idealController->goPrevNextDock(IdealController::PrevDock); } Area::WalkerMode MainWindowPrivate::IdealToolViewCreator::operator() (View *view, Sublime::Position position) { if (!d->docks.contains(view)) { d->docks << view; //add view d->idealController->addView(d->positionToDockArea(position), view); } return Area::ContinueWalker; } Area::WalkerMode MainWindowPrivate::ViewCreator::operator() (AreaIndex *index) { QSplitter *splitter = d->m_indexSplitters.value(index); if (!splitter) { //no splitter - we shall create it and populate with views if (!index->parent()) { qCDebug(SUBLIME) << "reconstructing root area"; //this is root area splitter = d->splitterCentralWidget; d->m_indexSplitters[index] = splitter; } else { if (!d->m_indexSplitters.value(index->parent())) { // can happen in working set code, as that adds a view to a child index first // hence, recursively reconstruct the parent indizes first operator()(index->parent()); } QSplitter *parent = d->m_indexSplitters.value(index->parent()); splitter = new QSplitter(parent); d->m_indexSplitters[index] = splitter; if(index == index->parent()->first()) parent->insertWidget(0, splitter); else parent->addWidget(splitter); } Q_ASSERT(splitter); } if (index->isSplit()) //this is a visible splitter splitter->setOrientation(index->orientation()); else { Container *container = nullptr; while(splitter->count() && qobject_cast(splitter->widget(0))) { // After unsplitting, we might have to remove old splitters QWidget* widget = splitter->widget(0); qCDebug(SUBLIME) << "deleting" << widget; widget->setParent(nullptr); delete widget; } if (!splitter->widget(0)) { //we need to create view container container = new Container(splitter); connect(container, &Container::activateView, d->m_mainWindow, &MainWindow::activateViewAndFocus); connect(container, &Container::tabDoubleClicked, d->m_mainWindow, &MainWindow::tabDoubleClicked); connect(container, &Container::tabContextMenuRequested, d->m_mainWindow, &MainWindow::tabContextMenuRequested); connect(container, &Container::tabToolTipRequested, d->m_mainWindow, &MainWindow::tabToolTipRequested); connect(container, static_cast(&Container::requestClose), d, &MainWindowPrivate::widgetCloseRequest, Qt::QueuedConnection); connect(container, &Container::newTabRequested, d->m_mainWindow, &MainWindow::newTabRequested); splitter->addWidget(container); } else container = qobject_cast(splitter->widget(0)); container->show(); int position = 0; bool hadActiveView = false; Sublime::View* activeView = d->activeView; foreach (View *view, index->views()) { QWidget *widget = view->widget(container); if (widget) { if(!container->hasWidget(widget)) { container->addWidget(view, position); d->viewContainers[view] = container; d->widgetToView[widget] = view; } if(activeView == view) { hadActiveView = true; container->setCurrentWidget(widget); }else if(topViews.contains(view) && !hadActiveView) container->setCurrentWidget(widget); } position++; } } return Area::ContinueWalker; } void MainWindowPrivate::reconstructViews(QList topViews) { ViewCreator viewCreator(this, topViews); area->walkViews(viewCreator, area->rootIndex()); setBackgroundVisible(area->views().isEmpty()); } void MainWindowPrivate::reconstruct() { if(m_leftTabbarCornerWidget) { m_leftTabbarCornerWidget->hide(); m_leftTabbarCornerWidget->setParent(nullptr); } IdealToolViewCreator toolViewCreator(this); area->walkToolViews(toolViewCreator, Sublime::AllPositions); reconstructViews(); { QSignalBlocker blocker(m_mainWindow); qCDebug(SUBLIME) << "RECONSTRUCT" << area << area->shownToolViews(Sublime::Left); foreach (View *view, area->toolViews()) { QString id = view->document()->documentSpecifier(); if (!id.isEmpty()) { Sublime::Position pos = area->toolViewPosition(view); if (area->shownToolViews(pos).contains(id)) idealController->raiseView(view, IdealController::GroupWithOtherViews); } } } setTabBarLeftCornerWidget(m_leftTabbarCornerWidget.data()); } void MainWindowPrivate::clearArea() { if(m_leftTabbarCornerWidget) m_leftTabbarCornerWidget->setParent(nullptr); //reparent toolview widgets to 0 to prevent their deletion together with dockwidgets foreach (View *view, area->toolViews()) { // FIXME should we really delete here?? bool nonDestructive = true; idealController->removeView(view, nonDestructive); if (view->hasWidget()) view->widget()->setParent(nullptr); } docks.clear(); //reparent all view widgets to 0 to prevent their deletion together with central //widget. this reparenting is necessary when switching areas inside the same mainwindow foreach (View *view, area->views()) { if (view->hasWidget()) view->widget()->setParent(nullptr); } cleanCentralWidget(); m_mainWindow->setActiveView(nullptr); m_indexSplitters.clear(); area = nullptr; viewContainers.clear(); setTabBarLeftCornerWidget(m_leftTabbarCornerWidget.data()); } void MainWindowPrivate::cleanCentralWidget() { while(splitterCentralWidget->count()) delete splitterCentralWidget->widget(0); setBackgroundVisible(true); } struct ShownToolViewFinder { ShownToolViewFinder() {} Area::WalkerMode operator()(View *v, Sublime::Position /*position*/) { if (v->hasWidget() && v->widget()->isVisible()) views << v; return Area::ContinueWalker; } QList views; }; void MainWindowPrivate::slotDockShown(Sublime::View* /*view*/, Sublime::Position pos, bool /*shown*/) { if (ignoreDockShown) return; ShownToolViewFinder finder; m_mainWindow->area()->walkToolViews(finder, pos); QStringList ids; foreach (View *v, finder.views) { ids << v->document()->documentSpecifier(); } area->setShownToolViews(pos, ids); } void MainWindowPrivate::viewRemovedInternal(AreaIndex* index, View* view) { Q_UNUSED(index); Q_UNUSED(view); setBackgroundVisible(area->views().isEmpty()); } void MainWindowPrivate::viewAdded(Sublime::AreaIndex *index, Sublime::View *view) { if(m_leftTabbarCornerWidget) { m_leftTabbarCornerWidget->hide(); m_leftTabbarCornerWidget->setParent(nullptr); } // Remove container objects in the hierarchy from the parents, // because they are not needed anymore, and might lead to broken splitter hierarchy and crashes. for(Sublime::AreaIndex* current = index; current; current = current->parent()) { QSplitter *splitter = m_indexSplitters[current]; if (current->isSplit() && splitter) { // Also update the orientation splitter->setOrientation(current->orientation()); for(int w = 0; w < splitter->count(); ++w) { Container *container = qobject_cast(splitter->widget(w)); //we need to remove extra container before reconstruction //first reparent widgets in container so that they are not deleted if(container) { while (container->count()) { container->widget(0)->setParent(nullptr); } //and then delete the container delete container; } } } } ViewCreator viewCreator(this); area->walkViews(viewCreator, index); emit m_mainWindow->viewAdded( view ); setTabBarLeftCornerWidget(m_leftTabbarCornerWidget.data()); setBackgroundVisible(false); } void Sublime::MainWindowPrivate::raiseToolView(Sublime::View * view) { idealController->raiseView(view); } void MainWindowPrivate::aboutToRemoveView(Sublime::AreaIndex *index, Sublime::View *view) { QSplitter *splitter = m_indexSplitters[index]; if (!splitter) return; qCDebug(SUBLIME) << "index " << index << " root " << area->rootIndex(); qCDebug(SUBLIME) << "splitter " << splitter << " container " << splitter->widget(0); qCDebug(SUBLIME) << "structure: " << index->print() << " whole structure: " << area->rootIndex()->print(); //find the container for the view and remove the widget Container *container = qobject_cast(splitter->widget(0)); if (!container) { qCWarning(SUBLIME) << "Splitter does not have a left widget!"; return; } emit m_mainWindow->aboutToRemoveView( view ); if (view->widget()) widgetToView.remove(view->widget()); viewContainers.remove(view); const bool wasActive = m_mainWindow->activeView() == view; if (container->count() > 1) { //container is not empty or this is a root index //just remove a widget if( view->widget() ) { container->removeWidget(view->widget()); view->widget()->setParent(nullptr); //activate what is visible currently in the container if the removed view was active if (wasActive) return m_mainWindow->setActiveView(container->viewForWidget(container->currentWidget())); } } else { if(m_leftTabbarCornerWidget) { m_leftTabbarCornerWidget->hide(); m_leftTabbarCornerWidget->setParent(nullptr); } // We've about to remove the last view of this container. It will // be empty, so have to delete it, as well. // If we have a container, then it should be the only child of // the splitter. Q_ASSERT(splitter->count() == 1); container->removeWidget(view->widget()); if (view->widget()) view->widget()->setParent(nullptr); else qCWarning(SUBLIME) << "View does not have a widget!"; Q_ASSERT(container->count() == 0); // We can be called from signal handler of container // (which is tab widget), so defer deleting it. container->deleteLater(); container->setParent(nullptr); /* If we're not at the top level, we get to collapse split views. */ if (index->parent()) { /* The splitter used to have container as the only child, now it's time to get rid of it. Make sure deleting splitter does not delete container -- per above comment, we'll delete it later. */ container->setParent(nullptr); m_indexSplitters.remove(index); delete splitter; AreaIndex *parent = index->parent(); QSplitter *parentSplitter = m_indexSplitters[parent]; AreaIndex *sibling = parent->first() == index ? parent->second() : parent->first(); QSplitter *siblingSplitter = m_indexSplitters[sibling]; if(siblingSplitter) { HoldUpdates du(parentSplitter); //save sizes and orientation of the sibling splitter parentSplitter->setOrientation(siblingSplitter->orientation()); QList sizes = siblingSplitter->sizes(); /* Parent has two children -- 'index' that we've deleted and 'sibling'. We move all children of 'sibling' into parent, and delete 'sibling'. sibling either contains a single Container instance, or a bunch of further QSplitters. */ while (siblingSplitter->count() > 0) { //reparent contents into parent splitter QWidget *siblingWidget = siblingSplitter->widget(0); siblingWidget->setParent(parentSplitter); parentSplitter->addWidget(siblingWidget); } m_indexSplitters.remove(sibling); delete siblingSplitter; parentSplitter->setSizes(sizes); } qCDebug(SUBLIME) << "after deleation " << parent << " has " << parentSplitter->count() << " elements"; //find the container somewhere to activate Container *containerToActivate = parentSplitter->findChild(); //activate the current view there if (containerToActivate) { m_mainWindow->setActiveView(containerToActivate->viewForWidget(containerToActivate->currentWidget())); setTabBarLeftCornerWidget(m_leftTabbarCornerWidget.data()); return; } } } setTabBarLeftCornerWidget(m_leftTabbarCornerWidget.data()); if ( wasActive ) { m_mainWindow->setActiveView(nullptr); } } void MainWindowPrivate::toolViewAdded(Sublime::View* /*toolView*/, Sublime::Position position) { IdealToolViewCreator toolViewCreator(this); area->walkToolViews(toolViewCreator, position); } void MainWindowPrivate::aboutToRemoveToolView(Sublime::View *toolView, Sublime::Position /*position*/) { if (!docks.contains(toolView)) return; idealController->removeView(toolView); // TODO are Views unique? docks.removeAll(toolView); } void MainWindowPrivate::toolViewMoved( Sublime::View *toolView, Sublime::Position position) { if (!docks.contains(toolView)) return; idealController->moveView(toolView, positionToDockArea(position)); } Qt::DockWidgetArea MainWindowPrivate::positionToDockArea(Position position) { switch (position) { case Sublime::Left: return Qt::LeftDockWidgetArea; case Sublime::Right: return Qt::RightDockWidgetArea; case Sublime::Bottom: return Qt::BottomDockWidgetArea; case Sublime::Top: return Qt::TopDockWidgetArea; default: return Qt::LeftDockWidgetArea; } } void MainWindowPrivate::switchToArea(QAction *action) { qCDebug(SUBLIME) << "for" << action; controller->showArea(m_actionAreas.value(action), m_mainWindow); } void MainWindowPrivate::updateAreaSwitcher(Sublime::Area *area) { QAction* action = m_areaActions.value(area); if (action) action->setChecked(true); } void MainWindowPrivate::activateFirstVisibleView() { QList views = area->views(); if (views.count() > 0) m_mainWindow->activateView(views.first()); } void MainWindowPrivate::widgetResized(Qt::DockWidgetArea /*dockArea*/, int /*thickness*/) { //TODO: adymo: remove all thickness business } void MainWindowPrivate::widgetCloseRequest(QWidget* widget) { if (View *view = widgetToView.value(widget)) { area->closeView(view); } } void MainWindowPrivate::setTabBarLeftCornerWidget(QWidget* widget) { if(widget != m_leftTabbarCornerWidget.data()) { delete m_leftTabbarCornerWidget.data(); m_leftTabbarCornerWidget.clear(); } m_leftTabbarCornerWidget = widget; if(!widget || !area || viewContainers.isEmpty()) return; AreaIndex* putToIndex = area->rootIndex(); QSplitter* splitter = m_indexSplitters[putToIndex]; while(putToIndex->isSplit()) { putToIndex = putToIndex->first(); splitter = m_indexSplitters[putToIndex]; } // Q_ASSERT(splitter || putToIndex == area->rootIndex()); Container* c = nullptr; if(splitter) { c = qobject_cast(splitter->widget(0)); }else{ c = *viewContainers.constBegin(); } Q_ASSERT(c); c->setLeftCornerWidget(widget); } } #include "mainwindow_p.moc" #include "moc_mainwindow_p.cpp" diff --git a/sublime/sublimedebug.h b/sublime/sublimedebug.h deleted file mode 100644 index c44a22e66..000000000 --- a/sublime/sublimedebug.h +++ /dev/null @@ -1,26 +0,0 @@ -/* - * This file is part of KDevelop - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#ifndef KDEVPLATFORM_SUBLIMEDEBUG_H -#define KDEVPLATFORM_SUBLIMEDEBUG_H - -#include -Q_DECLARE_LOGGING_CATEGORY(SUBLIME) - -#endif diff --git a/util/CMakeLists.txt b/util/CMakeLists.txt index c784ddfaa..8bc2a217b 100644 --- a/util/CMakeLists.txt +++ b/util/CMakeLists.txt @@ -1,95 +1,100 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kdevplatform\") ########### next target ############### set(KDevPlatformUtil_LIB_SRCS autoorientedsplitter.cpp foregroundlock.cpp formattinghelpers.cpp zoomcontroller.cpp kdevstringhandler.cpp focusedtreeview.cpp processlinemaker.cpp commandexecutor.cpp environmentselectionwidget.cpp environmentselectionmodel.cpp environmentprofilelist.cpp jobstatus.cpp activetooltip.cpp executecompositejob.cpp shellutils.cpp multilevellistview.cpp objectlist.cpp placeholderitemproxymodel.cpp projecttestjob.cpp widgetcolorizer.cpp path.cpp - debug.cpp texteditorhelpers.cpp stack.cpp ) set (KDevPlatformUtil_LIB_UI runoptions.ui ) if(NOT WIN32) add_subdirectory(dbus_socket_transformer) endif() add_subdirectory(duchainify) add_subdirectory(tests) +ecm_qt_declare_logging_category(KDevPlatformUtil_LIB_SRCS + HEADER debug.h + IDENTIFIER UTIL + CATEGORY_NAME "kdevplatform.util" +) + ki18n_wrap_ui(KDevPlatformUtil_LIB_SRCS ${KDevPlatformUtil_LIB_US}) kdevplatform_add_library(KDevPlatformUtil SOURCES ${KDevPlatformUtil_LIB_SRCS}) target_link_libraries(KDevPlatformUtil LINK_PUBLIC KDev::Interfaces KF5::ItemModels LINK_PRIVATE KF5::ConfigWidgets KF5::TextEditor KF5::GuiAddons ) install( FILES kdevplatform_shell_environment.sh DESTINATION bin PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ GROUP_EXECUTE GROUP_READ WORLD_EXECUTE WORLD_READ) add_executable(kdev_format_source kdevformatsource.cpp kdevformatfile.cpp) ecm_mark_nongui_executable(kdev_format_source) target_link_libraries(kdev_format_source Qt5::Core) install(TARGETS kdev_format_source DESTINATION ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) install(FILES .zshrc PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ GROUP_EXECUTE GROUP_READ WORLD_EXECUTE WORLD_READ DESTINATION ${SHARE_INSTALL_PREFIX}/kdevplatform/shellutils/) ########### install files ############### install( FILES autoorientedsplitter.h foregroundlock.h formattinghelpers.h zoomcontroller.h kdevstringhandler.h ksharedobject.h focusedtreeview.h activetooltip.h processlinemaker.h commandexecutor.h environmentselectionwidget.h environmentprofilelist.h jobstatus.h pushvalue.h kdevvarlengtharray.h embeddedfreetree.h executecompositejob.h convenientfreelist.h multilevellistview.h objectlist.h placeholderitemproxymodel.h projecttestjob.h widgetcolorizer.h path.h stack.h texteditorhelpers.h ${CMAKE_CURRENT_BINARY_DIR}/utilexport.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/kdevplatform/util COMPONENT Devel) diff --git a/util/debug.cpp b/util/debug.cpp deleted file mode 100644 index a0e19fbf0..000000000 --- a/util/debug.cpp +++ /dev/null @@ -1,22 +0,0 @@ -/* - * This file is part of KDevelop - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#include "debug.h" - -Q_LOGGING_CATEGORY(UTIL, "kdevplatform.util") diff --git a/util/debug.h b/util/debug.h deleted file mode 100644 index 2482192f0..000000000 --- a/util/debug.h +++ /dev/null @@ -1,26 +0,0 @@ -/* - * This file is part of KDevelop - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#ifndef KDEVPLATFORM_UTIL_DEBUG_H -#define KDEVPLATFORM_UTIL_DEBUG_H - -#include -Q_DECLARE_LOGGING_CATEGORY(UTIL) - -#endif diff --git a/util/formattinghelpers.cpp b/util/formattinghelpers.cpp index 104380126..2bf7b2a65 100644 --- a/util/formattinghelpers.cpp +++ b/util/formattinghelpers.cpp @@ -1,194 +1,193 @@ /* This file is part of KDevelop * Copyright 2011 David Nolden This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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 "formattinghelpers.h" #include "debug.h" #include -#include namespace KDevelop { ///Matches the given prefix to the given text, ignoring all whitespace ///Returns -1 if mismatched, else the position in @p text where the @p prefix match ends int matchPrefixIgnoringWhitespace(QString text, QString prefix, QString fuzzyCharacters) { int prefixPos = 0; int textPos = 0; while (prefixPos < prefix.length() && textPos < text.length()) { skipWhiteSpace: while (prefixPos < prefix.length() && prefix[prefixPos].isSpace()) ++prefixPos; while (textPos < text.length() && text[textPos].isSpace()) ++textPos; if(prefixPos == prefix.length() || textPos == text.length()) break; if(prefix[prefixPos] != text[textPos]) { bool skippedFuzzy = false; while( prefixPos < prefix.length() && fuzzyCharacters.indexOf(prefix[prefixPos]) != -1 ) { ++prefixPos; skippedFuzzy = true; } while( textPos < text.length() && fuzzyCharacters.indexOf(text[textPos]) != -1 ) { ++textPos; skippedFuzzy = true; } if( skippedFuzzy ) goto skipWhiteSpace; return -1; } ++prefixPos; ++textPos; } return textPos; } static QString reverse( const QString& str ) { QString ret; for(int a = str.length()-1; a >= 0; --a) ret.append(str[a]); return ret; } // Returns the text start position with all whitespace that is redundant in the given context skipped int skipRedundantWhiteSpace( QString context, QString text, int tabWidth ) { if( context.isEmpty() || !context[context.size()-1].isSpace() || text.isEmpty() || !text[0].isSpace() ) return 0; int textPosition = 0; // Extract trailing whitespace in the context int contextPosition = context.size()-1; while( contextPosition > 0 && context[contextPosition-1].isSpace() ) --contextPosition; int textWhitespaceEnd = 0; while(textWhitespaceEnd < text.size() && text[textWhitespaceEnd].isSpace()) ++textWhitespaceEnd; QString contextWhiteSpace = context.mid(contextPosition); contextPosition = 0; QString textWhiteSpace = text.left(textWhitespaceEnd); // Step 1: Remove redundant newlines while(contextWhiteSpace.contains('\n') && textWhiteSpace.contains('\n')) { int contextOffset = contextWhiteSpace.indexOf('\n')+1; int textOffset = textWhiteSpace.indexOf('\n')+1; contextPosition += contextOffset; contextWhiteSpace.remove(0, contextOffset); textPosition += textOffset; textWhiteSpace.remove(0, textOffset); } int contextOffset = 0; int textOffset = 0; // Skip redundant ordinary whitespace while( contextOffset < contextWhiteSpace.size() && textOffset < textWhiteSpace.size() && contextWhiteSpace[contextOffset].isSpace() && contextWhiteSpace[contextOffset] != '\n' && textWhiteSpace[textOffset].isSpace() && textWhiteSpace[textOffset] != '\n' ) { bool contextWasTab = contextWhiteSpace[contextOffset] == ' '; bool textWasTab = textWhiteSpace[contextOffset] == ' '; ++contextOffset; ++textOffset; if( contextWasTab != textWasTab ) { // Problem: We have a mismatch of tabs and/or ordinary whitespaces if( contextWasTab ) { for( int s = 1; s < tabWidth; ++s ) if( textOffset < textWhiteSpace.size() && textWhiteSpace[textOffset] == ' ' ) ++textOffset; }else if( textWasTab ) { for( int s = 1; s < tabWidth; ++s ) if( contextOffset < contextWhiteSpace.size() && contextWhiteSpace[contextOffset] == ' ' ) ++contextOffset; } } } return textPosition+textOffset; } QString extractFormattedTextFromContext( const QString& _formattedMergedText, const QString& text, const QString& leftContext, const QString& rightContext, int tabWidth, const QString& fuzzyCharacters) { QString formattedMergedText = _formattedMergedText; //Now remove "leftContext" and "rightContext" from the sides if(!leftContext.isEmpty()) { int endOfLeftContext = matchPrefixIgnoringWhitespace( formattedMergedText, leftContext, QString() ); if(endOfLeftContext == -1) { // Try 2: Ignore the fuzzy characters while matching endOfLeftContext = matchPrefixIgnoringWhitespace( formattedMergedText, leftContext, fuzzyCharacters ); if(endOfLeftContext == -1) { qCWarning(UTIL) << "problem matching the left context"; return text; } } int startOfWhiteSpace = endOfLeftContext; // Include all leading whitespace while(startOfWhiteSpace > 0 && formattedMergedText[startOfWhiteSpace-1].isSpace()) --startOfWhiteSpace; formattedMergedText = formattedMergedText.mid(startOfWhiteSpace); int skip = skipRedundantWhiteSpace( leftContext, formattedMergedText, tabWidth ); formattedMergedText = formattedMergedText.mid(skip); } if(!rightContext.isEmpty()) { //Add a whitespace behind the text for matching, so that we definitely capture all trailing whitespace int endOfText = matchPrefixIgnoringWhitespace( formattedMergedText, text+' ', QString() ); if(endOfText == -1) { // Try 2: Ignore the fuzzy characters while matching endOfText = matchPrefixIgnoringWhitespace( formattedMergedText, text+' ', fuzzyCharacters ); if(endOfText == -1) { qCWarning(UTIL) << "problem matching the text while formatting"; return text; } } formattedMergedText = formattedMergedText.left(endOfText); int skip = skipRedundantWhiteSpace( reverse(rightContext), reverse(formattedMergedText), tabWidth ); formattedMergedText = formattedMergedText.left(formattedMergedText.size() - skip); } return formattedMergedText; } } diff --git a/util/placeholderitemproxymodel.cpp b/util/placeholderitemproxymodel.cpp index 8493a1730..93bd42918 100644 --- a/util/placeholderitemproxymodel.cpp +++ b/util/placeholderitemproxymodel.cpp @@ -1,212 +1,211 @@ /* * Copyright 2013 Kevin Funk * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "placeholderitemproxymodel.h" #include -#include using namespace KDevelop; struct PlaceholderItemProxyModel::Private { explicit Private(PlaceholderItemProxyModel* qq) : q(qq) {} inline int sourceRowCount() { return q->sourceModel() ? q->sourceModel()->rowCount() : 0; } inline bool isPlaceholderRow(const QModelIndex& index) const { if (!q->sourceModel()) { return false; } return index.row() == q->sourceModel()->rowCount(); } PlaceholderItemProxyModel* const q; /// column -> hint mapping QMap m_columnHints; }; PlaceholderItemProxyModel::PlaceholderItemProxyModel(QObject* parent) : QIdentityProxyModel(parent) , d(new Private(this)) {} PlaceholderItemProxyModel::~PlaceholderItemProxyModel() { } QVariant PlaceholderItemProxyModel::columnHint(int column) const { return d->m_columnHints.value(column); } void PlaceholderItemProxyModel::setColumnHint(int column, const QVariant& hint) { if (column < 0) { return; } d->m_columnHints[column] = hint; const int row = d->sourceRowCount(); emit dataChanged(index(row, 0), index(row, columnCount())); } Qt::ItemFlags PlaceholderItemProxyModel::flags(const QModelIndex& index) const { if (d->isPlaceholderRow(index)) { Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled; const int column = index.column(); // if the column doesn't provide a hint we assume that we can't edit this field if (d->m_columnHints.contains(column)) { flags |= Qt::ItemIsEditable; } return flags; } return QIdentityProxyModel::flags(index); } void PlaceholderItemProxyModel::setSourceModel(QAbstractItemModel* sourceModel) { QIdentityProxyModel::setSourceModel(sourceModel); // TODO: Listen to layoutDataChanged signals? } int PlaceholderItemProxyModel::rowCount(const QModelIndex& parent) const { if (!sourceModel()) return 0; // only flat models supported for now, assert early in case that's not true Q_ASSERT(!parent.isValid()); Q_UNUSED(parent); return sourceModel()->rowCount() + 1; } bool KDevelop::PlaceholderItemProxyModel::hasChildren(const QModelIndex& parent) const { if ( !parent.isValid() ) { return true; } return QIdentityProxyModel::hasChildren(parent); } QVariant PlaceholderItemProxyModel::data(const QModelIndex& proxyIndex, int role) const { const int column = proxyIndex.column(); if (d->isPlaceholderRow(proxyIndex)) { switch (role) { case Qt::DisplayRole: return columnHint(column); case Qt::ForegroundRole: { const KColorScheme scheme(QPalette::Normal); return scheme.foreground(KColorScheme::InactiveText); } default: return QVariant(); } } return QIdentityProxyModel::data(proxyIndex, role); } QModelIndex PlaceholderItemProxyModel::parent(const QModelIndex& child) const { if (d->isPlaceholderRow(child)) { return QModelIndex(); } return QIdentityProxyModel::parent(child); } QModelIndex PlaceholderItemProxyModel::buddy(const QModelIndex& index) const { if (d->isPlaceholderRow(index)) { return index; } return QIdentityProxyModel::buddy(index); } QModelIndex PlaceholderItemProxyModel::sibling(int row, int column, const QModelIndex& idx) const { const bool isPlaceHolderRow = (sourceModel() ? row == sourceModel()->rowCount() : false); if (isPlaceHolderRow) { return index(row, column, QModelIndex()); } return QIdentityProxyModel::sibling(row, column, idx); } QModelIndex PlaceholderItemProxyModel::mapToSource(const QModelIndex& proxyIndex) const { if (d->isPlaceholderRow(proxyIndex)) { return QModelIndex(); } return QIdentityProxyModel::mapToSource(proxyIndex); } bool PlaceholderItemProxyModel::setData(const QModelIndex& index, const QVariant& value, int role) { const int column = index.column(); if (d->isPlaceholderRow(index) && role == Qt::EditRole && d->m_columnHints.contains(column)) { const bool accept = validateRow(index, value); // if validation fails, clear the complete line if (!accept) { emit dataChanged(index, index); return false; } // update view emit dataChanged(index, index); // notify observers emit dataInserted(column, value); return true; } return QIdentityProxyModel::setData(index, value, role); } QModelIndex PlaceholderItemProxyModel::index(int row, int column, const QModelIndex& parent) const { Q_ASSERT(!parent.isValid()); Q_UNUSED(parent); const bool isPlaceHolderRow = (sourceModel() ? row == sourceModel()->rowCount() : false); if (isPlaceHolderRow) { return createIndex(row, column); } return QIdentityProxyModel::index(row, column, parent); } bool PlaceholderItemProxyModel::validateRow(const QModelIndex& index, const QVariant& value) const { Q_UNUSED(index); return !value.toString().isEmpty(); } #include "moc_placeholderitemproxymodel.cpp" diff --git a/vcs/CMakeLists.txt b/vcs/CMakeLists.txt index 89867da5e..f9162a469 100644 --- a/vcs/CMakeLists.txt +++ b/vcs/CMakeLists.txt @@ -1,111 +1,117 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kdevplatform\") add_subdirectory(dvcs/tests) add_subdirectory(models/tests) set(KDevPlatformVcs_UIS widgets/vcscommitdialog.ui widgets/vcseventwidget.ui widgets/vcsdiffwidget.ui dvcs/ui/dvcsimportmetadatawidget.ui dvcs/ui/branchmanager.ui ) set(KDevPlatformVcs_LIB_SRCS vcsjob.cpp vcsrevision.cpp vcsannotation.cpp vcspluginhelper.cpp vcslocation.cpp vcsdiff.cpp vcsevent.cpp vcsstatusinfo.cpp - debug.cpp widgets/vcsimportmetadatawidget.cpp widgets/vcseventwidget.cpp widgets/vcsdiffwidget.cpp widgets/vcscommitdialog.cpp widgets/vcsdiffpatchsources.cpp widgets/vcslocationwidget.cpp widgets/standardvcslocationwidget.cpp models/vcsannotationmodel.cpp models/vcseventmodel.cpp models/vcsfilechangesmodel.cpp models/vcsitemeventmodel.cpp models/brancheslistmodel.cpp dvcs/dvcsjob.cpp dvcs/dvcsplugin.cpp dvcs/ui/dvcsimportmetadatawidget.cpp dvcs/ui/branchmanager.cpp interfaces/ibasicversioncontrol.cpp interfaces/icontentawareversioncontrol.cpp interfaces/ipatchdocument.cpp interfaces/ipatchsource.cpp ) + +ecm_qt_declare_logging_category(KDevPlatformVcs_LIB_SRCS + HEADER debug.h + IDENTIFIER VCS + CATEGORY_NAME "kdevplatform.vcs" +) + ki18n_wrap_ui(KDevPlatformVcs_LIB_SRCS ${KDevPlatformVcs_UIS}) kdevplatform_add_library(KDevPlatformVcs SOURCES ${KDevPlatformVcs_LIB_SRCS}) target_link_libraries(KDevPlatformVcs LINK_PUBLIC KDev::OutputView KDev::Interfaces LINK_PRIVATE KF5::KIOWidgets KF5::Parts KDev::Util ) install(FILES vcsjob.h vcsrevision.h vcsannotation.h vcsdiff.h vcspluginhelper.h vcsevent.h vcsstatusinfo.h vcslocation.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/kdevplatform/vcs COMPONENT Devel ) install(FILES widgets/vcsimportmetadatawidget.h widgets/vcseventwidget.h widgets/vcsdiffwidget.h widgets/vcscommitdialog.h widgets/vcslocationwidget.h widgets/standardvcslocationwidget.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/kdevplatform/vcs/widgets COMPONENT Devel ) install(FILES models/vcsannotationmodel.h models/vcseventmodel.h models/vcsfilechangesmodel.h models/vcsitemeventmodel.h models/brancheslistmodel.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/kdevplatform/vcs/models COMPONENT Devel ) install(FILES interfaces/ibasicversioncontrol.h interfaces/icentralizedversioncontrol.h interfaces/idistributedversioncontrol.h interfaces/ibranchingversioncontrol.h interfaces/ibrowsableversioncontrol.h interfaces/irepositoryversioncontrol.h interfaces/ipatchdocument.h interfaces/ipatchsource.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/kdevplatform/vcs/interfaces COMPONENT Devel ) install(FILES dvcs/dvcsjob.h dvcs/dvcsplugin.h dvcs/dvcsevent.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/kdevplatform/vcs/dvcs COMPONENT Devel ) install(FILES dvcs/ui/dvcsimportmetadatawidget.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/kdevplatform/vcs/dvcs/ui COMPONENT Devel ) diff --git a/vcs/debug.cpp b/vcs/debug.cpp deleted file mode 100644 index 575e7cd02..000000000 --- a/vcs/debug.cpp +++ /dev/null @@ -1,22 +0,0 @@ -/* - * This file is part of KDevelop - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#include "debug.h" - -Q_LOGGING_CATEGORY(VCS, "kdevplatform.vcs") diff --git a/vcs/debug.h b/vcs/debug.h deleted file mode 100644 index c0ee7a6f7..000000000 --- a/vcs/debug.h +++ /dev/null @@ -1,26 +0,0 @@ -/* - * This file is part of KDevelop - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#ifndef KDEVPLATFORM_VCS_DEBUG_H -#define KDEVPLATFORM_VCS_DEBUG_H - -#include -Q_DECLARE_LOGGING_CATEGORY(VCS) - -#endif diff --git a/vcs/dvcs/dvcsjob.cpp b/vcs/dvcs/dvcsjob.cpp index 67d6a12a2..58e7a28fa 100644 --- a/vcs/dvcs/dvcsjob.cpp +++ b/vcs/dvcs/dvcsjob.cpp @@ -1,331 +1,331 @@ /*************************************************************************** * This file was partly taken from KDevelop's cvs plugin * * Copyright 2002-2003 Christian Loose * * Copyright 2007 Robert Gruber * * * * Adapted for DVCS * * Copyright 2008 Evgeniy Ivanov * * Copyright Aleix Pol Gonzalez * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "dvcsjob.h" -#include "../debug.h" +#include "debug.h" #include #include #include #include #include #include #include #include #include using namespace KDevelop; struct DVcsJobPrivate { DVcsJobPrivate() : childproc(new KProcess), vcsplugin(nullptr), ignoreError(false) {} ~DVcsJobPrivate() { delete childproc; } KProcess* childproc; VcsJob::JobStatus status; QByteArray output; QByteArray errorOutput; IPlugin* vcsplugin; QVariant results; OutputModel* model; bool ignoreError; }; DVcsJob::DVcsJob(const QDir& workingDir, IPlugin* parent, OutputJob::OutputJobVerbosity verbosity) : VcsJob(parent, verbosity), d(new DVcsJobPrivate) { Q_ASSERT(workingDir.exists()); d->status = JobNotStarted; d->vcsplugin = parent; d->childproc->setWorkingDirectory(workingDir.absolutePath()); d->model = new OutputModel; d->ignoreError = false; setModel(d->model); setCapabilities(Killable); connect(d->childproc, static_cast(&KProcess::finished), this, &DVcsJob::slotProcessExited); connect(d->childproc, static_cast(&KProcess::error), this, &DVcsJob::slotProcessError); connect(d->childproc, &KProcess::readyReadStandardOutput, this, &DVcsJob::slotReceivedStdout); } DVcsJob::~DVcsJob() { delete d; } QDir DVcsJob::directory() const { return QDir(d->childproc->workingDirectory()); } DVcsJob& DVcsJob::operator<<(const QString& arg) { *d->childproc << arg; return *this; } DVcsJob& DVcsJob::operator<<(const char* arg) { *d->childproc << arg; return *this; } DVcsJob& DVcsJob::operator<<(const QStringList& args) { *d->childproc << args; return *this; } QStringList DVcsJob::dvcsCommand() const { return d->childproc->program(); } QString DVcsJob::output() const { QByteArray stdoutbuf = rawOutput(); int endpos = stdoutbuf.size(); if (d->status==JobRunning) { // We may have received only part of a code-point. apol: ASSERT? endpos = stdoutbuf.lastIndexOf('\n')+1; // Include the final newline or become 0, when there is no newline } return QString::fromLocal8Bit(stdoutbuf, endpos); } QByteArray DVcsJob::rawOutput() const { return d->output; } QByteArray DVcsJob::errorOutput() const { return d->errorOutput; } void DVcsJob::setIgnoreError(bool ignore) { d->ignoreError = ignore; } void DVcsJob::setResults(const QVariant &res) { d->results = res; } QVariant DVcsJob::fetchResults() { return d->results; } void DVcsJob::start() { Q_ASSERT_X(d->status != JobRunning, "DVCSjob::start", "Another proccess was started using this job class"); const QDir& workingdir = directory(); if( !workingdir.exists() ) { QString error = i18n( "Working Directory does not exist: %1", d->childproc->workingDirectory() ); d->model->appendLine(error); setError( 255 ); setErrorText(error); d->status = JobFailed; emitResult(); return; } if( !workingdir.isAbsolute() ) { QString error = i18n( "Working Directory is not absolute: %1", d->childproc->workingDirectory() ); d->model->appendLine(error); setError( 255 ); setErrorText(error); d->status = JobFailed; emitResult(); return; } QString commandDisplay = KShell::joinArgs(dvcsCommand()); qCDebug(VCS) << "Execute dvcs command:" << commandDisplay; QString service; if(d->vcsplugin) service = d->vcsplugin->objectName(); setObjectName(service+": "+commandDisplay); d->status = JobRunning; d->childproc->setOutputChannelMode(KProcess::SeparateChannels); //the started() and error() signals may be delayed! It causes crash with deferred deletion!!! d->childproc->start(); d->model->appendLine(directory().path() + "> " + commandDisplay); } void DVcsJob::setCommunicationMode(KProcess::OutputChannelMode comm) { d->childproc->setOutputChannelMode(comm); } void DVcsJob::cancel() { d->childproc->kill(); } void DVcsJob::slotProcessError( QProcess::ProcessError err ) { d->status = JobFailed; setError(OutputJob::FailedShownError); //we don't want to trigger a message box d->errorOutput = d->childproc->readAllStandardError(); QString displayCommand = KShell::joinArgs(dvcsCommand()); QString completeErrorText = i18n("Process '%1' exited with status %2\n%3", displayCommand, d->childproc->exitCode(), QString::fromLocal8Bit(d->errorOutput) ); setErrorText( completeErrorText ); QString errorValue; //if trolls add Q_ENUMS for QProcess, then we can use better solution than switch: //QMetaObject::indexOfEnumerator(char*), QQStringLiteral(QMetaEnum::valueToKey())... switch (err) { case QProcess::FailedToStart: errorValue = QStringLiteral("FailedToStart"); break; case QProcess::Crashed: errorValue = QStringLiteral("Crashed"); break; case QProcess::Timedout: errorValue = QStringLiteral("Timedout"); break; case QProcess::WriteError: errorValue = QStringLiteral("WriteError"); break; case QProcess::ReadError: errorValue = QStringLiteral("ReadError"); break; case QProcess::UnknownError: errorValue = QStringLiteral("UnknownError"); break; } qCDebug(VCS) << "Found an error while running" << displayCommand << ":" << errorValue << "Exit code is:" << d->childproc->exitCode(); qCDebug(VCS) << "Error:" << completeErrorText; displayOutput(QString::fromLocal8Bit(d->errorOutput)); d->model->appendLine(i18n("Command finished with error %1.", errorValue)); if(verbosity()==Silent) { setVerbosity(Verbose); startOutput(); } emitResult(); } void DVcsJob::slotProcessExited(int exitCode, QProcess::ExitStatus exitStatus) { d->status = JobSucceeded; d->model->appendLine(i18n("Command exited with value %1.", exitCode)); if (exitStatus == QProcess::CrashExit) slotProcessError(QProcess::Crashed); else if (exitCode != 0 && !d->ignoreError) slotProcessError(QProcess::UnknownError); else jobIsReady(); } void DVcsJob::displayOutput(const QString& data) { d->model->appendLines(data.split('\n')); } void DVcsJob::slotReceivedStdout() { QByteArray output = d->childproc->readAllStandardOutput(); // accumulate output d->output.append(output); displayOutput(QString::fromLocal8Bit(output)); } VcsJob::JobStatus DVcsJob::status() const { return d->status; } IPlugin* DVcsJob::vcsPlugin() const { return d->vcsplugin; } DVcsJob& DVcsJob::operator<<(const QUrl& url) { *d->childproc << url.toLocalFile(); return *this; } DVcsJob& DVcsJob::operator<<(const QList< QUrl >& urls) { foreach(const QUrl &url, urls) operator<<(url); return *this; } bool DVcsJob::doKill() { if (d->childproc->state() == QProcess::NotRunning) { return true; } static const int terminateKillTimeout = 1000; // ms d->childproc->terminate(); bool terminated = d->childproc->waitForFinished( terminateKillTimeout ); if( !terminated ) { d->childproc->kill(); terminated = d->childproc->waitForFinished( terminateKillTimeout ); } return terminated; } void DVcsJob::jobIsReady() { emit readyForParsing(this); //let parsers to set status emitResult(); //KJob emit resultsReady(this); //VcsJob } KProcess* DVcsJob::process() {return d->childproc;} diff --git a/vcs/dvcs/ui/branchmanager.cpp b/vcs/dvcs/ui/branchmanager.cpp index 67bd87bbb..b6651a072 100644 --- a/vcs/dvcs/ui/branchmanager.cpp +++ b/vcs/dvcs/ui/branchmanager.cpp @@ -1,238 +1,238 @@ /*************************************************************************** * Copyright 2008 Evgeniy Ivanov * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "branchmanager.h" #include #include #include #include "../dvcsplugin.h" #include #include #include "ui_branchmanager.h" -#include "../../debug.h" +#include "debug.h" #include "widgets/vcsdiffpatchsources.h" #include #include #include #include #include #include #include using namespace KDevelop; BranchManager::BranchManager(const QString& repository, KDevelop::DistributedVersionControlPlugin* executor, QWidget *parent) : QDialog(parent) , m_repository(repository) , m_dvcPlugin(executor) { setWindowTitle(i18n("Branch Manager")); QWidget *mainWidget = new QWidget(this); QVBoxLayout *mainLayout = new QVBoxLayout(this); mainLayout->addWidget(mainWidget); m_ui = new Ui::BranchDialogBase; QWidget* w = new QWidget(this); m_ui->setupUi(w); mainLayout->addWidget(w); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close); connect(buttonBox, &QDialogButtonBox::accepted, this, &BranchManager::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &BranchManager::reject); mainLayout->addWidget(buttonBox); m_model = new BranchesListModel(this); m_model->initialize(m_dvcPlugin, QUrl::fromLocalFile(repository)); m_ui->branchView->setModel(m_model); QString branchName = m_model->currentBranch(); // apply initial selection QList< QStandardItem* > items = m_model->findItems(branchName); if (!items.isEmpty()) { m_ui->branchView->setCurrentIndex(items.first()->index()); } m_ui->newButton->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); connect(m_ui->newButton, &QPushButton::clicked, this, &BranchManager::createBranch); m_ui->deleteButton->setIcon(QIcon::fromTheme(QStringLiteral("list-remove"))); connect(m_ui->deleteButton, &QPushButton::clicked, this, &BranchManager::deleteBranch); m_ui->renameButton->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename"))); connect(m_ui->renameButton, &QPushButton::clicked, this, &BranchManager::renameBranch); m_ui->checkoutButton->setIcon(QIcon::fromTheme(QStringLiteral("dialog-ok-apply"))); connect(m_ui->checkoutButton, &QPushButton::clicked, this, &BranchManager::checkoutBranch); // checkout branch on double-click connect(m_ui->branchView, &QListView::doubleClicked, this, &BranchManager::checkoutBranch); m_ui->mergeButton->setIcon(QIcon::fromTheme(QStringLiteral("merge"))); connect(m_ui->mergeButton, &QPushButton::clicked, this, &BranchManager::mergeBranch); m_ui->diffButton->setIcon(QIcon::fromTheme(QStringLiteral("text-x-patch"))); connect(m_ui->diffButton, &QPushButton::clicked, this, &BranchManager::diffFromBranch); } BranchManager::~BranchManager() { delete m_ui; } void BranchManager::createBranch() { const QModelIndex currentBranchIdx = m_ui->branchView->currentIndex(); if (!currentBranchIdx.isValid()) { KMessageBox::messageBox(this, KMessageBox::Error, i18n("You must select a base branch from the list before creating a new branch.")); return; } QString baseBranch = currentBranchIdx.data().toString(); bool branchNameEntered = false; QString newBranch = QInputDialog::getText(this, i18n("New branch"), i18n("Name of the new branch:"), QLineEdit::Normal, QString(), &branchNameEntered); if (!branchNameEntered) return; if (!m_model->findItems(newBranch).isEmpty()) { KMessageBox::messageBox(this, KMessageBox::Sorry, i18n("Branch \"%1\" already exists.\n" "Please, choose another name.", newBranch)); } else m_model->createBranch(baseBranch, newBranch); } void BranchManager::deleteBranch() { QString baseBranch = m_ui->branchView->selectionModel()->selection().indexes().first().data().toString(); if (baseBranch == m_model->currentBranch()) { KMessageBox::messageBox(this, KMessageBox::Sorry, i18n("Currently at the branch \"%1\".\n" "To remove it, please change to another branch.", baseBranch)); return; } int ret = KMessageBox::messageBox(this, KMessageBox::WarningYesNo, i18n("Are you sure you want to irreversibly remove the branch '%1'?", baseBranch)); if (ret == KMessageBox::Yes) m_model->removeBranch(baseBranch); } void BranchManager::renameBranch() { QModelIndex currentIndex = m_ui->branchView->currentIndex(); if (!currentIndex.isValid()) return; m_ui->branchView->edit(currentIndex); } void BranchManager::checkoutBranch() { QString branch = m_ui->branchView->currentIndex().data().toString(); if (branch == m_model->currentBranch()) { KMessageBox::messageBox(this, KMessageBox::Sorry, i18n("Already on branch \"%1\"\n", branch)); return; } qCDebug(VCS) << "Switching to" << branch << "in" << m_repository; KDevelop::VcsJob *branchJob = m_dvcPlugin->switchBranch(QUrl::fromLocalFile(m_repository), branch); // connect(branchJob, SIGNAL(finished(KJob*)), m_model, SIGNAL(resetCurrent())); ICore::self()->runController()->registerJob(branchJob); close(); } void BranchManager::mergeBranch() { const QModelIndex branchToMergeIdx = m_ui->branchView->currentIndex(); if (branchToMergeIdx.isValid()) { QString branchToMerge = branchToMergeIdx.data().toString(); if (m_model->findItems(branchToMerge).isEmpty()) { KMessageBox::messageBox(this, KMessageBox::Sorry, i18n("Branch \"%1\" doesn't exists.\n" "Please, choose another name.", branchToMerge)); } else { KDevelop::VcsJob* branchJob = m_dvcPlugin->mergeBranch(QUrl::fromLocalFile(m_repository), branchToMerge); ICore::self()->runController()->registerJob(branchJob); close(); } } else { KMessageBox::messageBox(this, KMessageBox::Error, i18n("You must select a branch to merge into current one from the list.")); } } void BranchManager::diffFromBranch() { const auto dest = m_model->currentBranch(); const auto src = m_ui->branchView->currentIndex().data().toString(); if (src == dest) { KMessageBox::messageBox(this, KMessageBox::Information, i18n("Already on branch \"%1\"\n", src)); return; } VcsRevision srcRev; srcRev.setRevisionValue(src, KDevelop::VcsRevision::GlobalNumber); // We have two options here: // * create a regular VcsRevision to represent the last commit on the current branch or // * create a special branch to reflect the staging area. I chose this one. // If the staging area is clean it automatically defaults to the first option. const auto destRev = VcsRevision::createSpecialRevision(KDevelop::VcsRevision::Working); const auto job = m_dvcPlugin->diff(QUrl::fromLocalFile(m_repository), srcRev, destRev); connect(job, &VcsJob::finished, this, &BranchManager::diffJobFinished); m_dvcPlugin->core()->runController()->registerJob(job); } void BranchManager::diffJobFinished(KJob* job) { auto vcsjob = qobject_cast(job); Q_ASSERT(vcsjob); if (vcsjob->status() != KDevelop::VcsJob::JobSucceeded) { KMessageBox::error(ICore::self()->uiController()->activeMainWindow(), vcsjob->errorString(), i18n("Unable to retrieve diff.")); return; } auto diff = vcsjob->fetchResults().value(); if(diff.isEmpty()){ KMessageBox::information(ICore::self()->uiController()->activeMainWindow(), i18n("There are no committed differences."), i18n("VCS support")); return; } auto patch = new VCSDiffPatchSource(diff); showVcsDiff(patch); close(); } diff --git a/vcs/models/brancheslistmodel.cpp b/vcs/models/brancheslistmodel.cpp index 37f00d062..58ef1c670 100644 --- a/vcs/models/brancheslistmodel.cpp +++ b/vcs/models/brancheslistmodel.cpp @@ -1,211 +1,211 @@ /*************************************************************************** * Copyright 2008 Evgeniy Ivanov * * Copyright 2012 Aleix Pol Gonzalez * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "brancheslistmodel.h" -#include "../debug.h" +#include "debug.h" #include #include #include #include #include #include #include #include #include "util/path.h" #include #include #include using namespace std; using namespace KDevelop; class KDevelop::BranchesListModelPrivate { public: BranchesListModelPrivate() { } IBranchingVersionControl * dvcsplugin; QUrl repo; }; class BranchItem : public QStandardItem { public: explicit BranchItem(const QString& name, bool current=false) : QStandardItem(name) { setEditable(true); setCurrent(current); } void setCurrent(bool current) { setData(current, BranchesListModel::CurrentRole); setIcon(QIcon::fromTheme( current ? QStringLiteral("arrow-right") : QStringLiteral(""))); } void setData(const QVariant& value, int role = Qt::UserRole + 1) override { if(role==Qt::EditRole && value.toString()!=text()) { QString newBranch = value.toString(); BranchesListModel* bmodel = qobject_cast(model()); if(!bmodel->findItems(newBranch).isEmpty()) { KMessageBox::messageBox(nullptr, KMessageBox::Sorry, i18n("Branch \"%1\" already exists.", newBranch)); return; } int ret = KMessageBox::messageBox(nullptr, KMessageBox::WarningYesNo, i18n("Are you sure you want to rename \"%1\" to \"%2\"?", text(), newBranch)); if (ret == KMessageBox::No) { return; // ignore event } KDevelop::VcsJob *branchJob = bmodel->interface()->renameBranch(bmodel->repository(), newBranch, text()); ret = branchJob->exec(); qCDebug(VCS) << "Renaming " << text() << " to " << newBranch << ':' << ret; if (!ret) { return; // ignore event } } QStandardItem::setData(value, role); } }; static QVariant runSynchronously(KDevelop::VcsJob* job) { job->setVerbosity(KDevelop::OutputJob::Silent); QVariant ret; if(job->exec() && job->status()==KDevelop::VcsJob::JobSucceeded) { ret = job->fetchResults(); } delete job; return ret; } BranchesListModel::BranchesListModel(QObject* parent) : QStandardItemModel(parent), d(new BranchesListModelPrivate()) { } BranchesListModel::~BranchesListModel() { } QHash BranchesListModel::roleNames() const { auto roles = QAbstractItemModel::roleNames(); roles.insert(CurrentRole, "isCurrent"); return roles; } void BranchesListModel::createBranch(const QString& baseBranch, const QString& newBranch) { qCDebug(VCS) << "Creating " << baseBranch << " based on " << newBranch; KDevelop::VcsRevision rev; rev.setRevisionValue(baseBranch, KDevelop::VcsRevision::GlobalNumber); KDevelop::VcsJob* branchJob = d->dvcsplugin->branch(d->repo, rev, newBranch); qCDebug(VCS) << "Adding new branch"; if (branchJob->exec()) appendRow(new BranchItem(newBranch)); } void BranchesListModel::removeBranch(const QString& branch) { KDevelop::VcsJob *branchJob = d->dvcsplugin->deleteBranch(d->repo, branch); qCDebug(VCS) << "Removing branch:" << branch; if (branchJob->exec()) { QList< QStandardItem* > items = findItems(branch); foreach(QStandardItem* item, items) removeRow(item->row()); } } QUrl BranchesListModel::repository() const { return d->repo; } KDevelop::IBranchingVersionControl* BranchesListModel::interface() { return d->dvcsplugin; } void BranchesListModel::initialize(KDevelop::IBranchingVersionControl* branching, const QUrl& r) { d->dvcsplugin = branching; d->repo = r; refresh(); } void BranchesListModel::refresh() { QStringList branches = runSynchronously(d->dvcsplugin->branches(d->repo)).toStringList(); QString curBranch = runSynchronously(d->dvcsplugin->currentBranch(d->repo)).toString(); foreach(const QString& branch, branches) appendRow(new BranchItem(branch, branch == curBranch)); } void BranchesListModel::resetCurrent() { refresh(); emit currentBranchChanged(); } QString BranchesListModel::currentBranch() const { return runSynchronously(d->dvcsplugin->currentBranch(d->repo)).toString(); } KDevelop::IProject* BranchesListModel::project() const { return KDevelop::ICore::self()->projectController()->findProjectForUrl(d->repo); } void BranchesListModel::setProject(KDevelop::IProject* p) { if(!p || !p->versionControlPlugin()) { qCDebug(VCS) << "null or invalid project" << p; return; } KDevelop::IBranchingVersionControl* branching = p->versionControlPlugin()->extension(); if(branching) { initialize(branching, p->path().toUrl()); } else qCDebug(VCS) << "not a branching vcs project" << p->name(); } void BranchesListModel::setCurrentBranch(const QString& branch) { KDevelop::VcsJob* job = d->dvcsplugin->switchBranch(d->repo, branch); connect(job, &VcsJob::finished, this, &BranchesListModel::currentBranchChanged); KDevelop::ICore::self()->runController()->registerJob(job); } diff --git a/vcs/widgets/vcsdiffpatchsources.cpp b/vcs/widgets/vcsdiffpatchsources.cpp index ae180b79e..4f56c7f60 100644 --- a/vcs/widgets/vcsdiffpatchsources.cpp +++ b/vcs/widgets/vcsdiffpatchsources.cpp @@ -1,320 +1,320 @@ /* Copyright 2009 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "vcsdiffpatchsources.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "vcsdiff.h" #include "vcsjob.h" -#include "../debug.h" +#include "debug.h" using namespace KDevelop; VCSCommitDiffPatchSource::VCSCommitDiffPatchSource(VCSDiffUpdater* updater) : VCSDiffPatchSource(updater), m_vcs(updater->vcs()) { Q_ASSERT(m_vcs); m_commitMessageWidget = new QWidget; QVBoxLayout* layout = new QVBoxLayout(m_commitMessageWidget.data()); layout->setMargin(0); m_commitMessageEdit = new KTextEdit; m_commitMessageEdit.data()->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); m_commitMessageEdit.data()->setLineWrapMode(QTextEdit::NoWrap); m_vcs->setupCommitMessageEditor(updater->url(), m_commitMessageEdit.data()); QHBoxLayout* titleLayout = new QHBoxLayout; titleLayout->addWidget(new QLabel(i18n("Commit Message:"))); m_oldMessages = new KComboBox(m_commitMessageWidget.data()); m_oldMessages->addItem(i18n("Old Messages")); foreach(const QString& message, oldMessages()) m_oldMessages->addItem(message, message); m_oldMessages->setMaximumWidth(200); connect(m_oldMessages, static_cast(&KComboBox::currentIndexChanged), this, &VCSCommitDiffPatchSource::oldMessageChanged); titleLayout->addWidget(m_oldMessages); layout->addLayout(titleLayout); layout->addWidget(m_commitMessageEdit.data()); connect(this, &VCSCommitDiffPatchSource::reviewCancelled, this, &VCSCommitDiffPatchSource::addMessageToHistory); connect(this, &VCSCommitDiffPatchSource::reviewFinished, this, &VCSCommitDiffPatchSource::addMessageToHistory); } QStringList VCSCommitDiffPatchSource::oldMessages() const { KConfigGroup vcsGroup(ICore::self()->activeSession()->config(), "VCS"); return vcsGroup.readEntry("OldCommitMessages", QStringList()); } void VCSCommitDiffPatchSource::addMessageToHistory(const QString& message) { if(ICore::self()->shuttingDown()) return; KConfigGroup vcsGroup(ICore::self()->activeSession()->config(), "VCS"); const int maxMessages = 10; QStringList oldMessages = vcsGroup.readEntry("OldCommitMessages", QStringList()); oldMessages.removeAll(message); oldMessages.push_front(message); oldMessages = oldMessages.mid(0, maxMessages); vcsGroup.writeEntry("OldCommitMessages", oldMessages); } void VCSCommitDiffPatchSource::oldMessageChanged(QString text) { if(m_oldMessages->currentIndex() != 0) { m_oldMessages->setCurrentIndex(0); m_commitMessageEdit.data()->setText(text); } } void VCSCommitDiffPatchSource::jobFinished(KJob *job) { if (!job || job->error() != 0 ) { QString details = job ? job->errorText() : QString(); if (details.isEmpty()) { //errorText may be empty details = i18n("For more detailed information please see the Version Control toolview"); } KMessageBox::detailedError(nullptr, i18n("Unable to commit"), details, i18n("Commit unsuccessful")); } deleteLater(); } VCSDiffPatchSource::VCSDiffPatchSource(VCSDiffUpdater* updater) : m_updater(updater) { update(); KDevelop::IBasicVersionControl* vcs = m_updater->vcs(); QUrl url = m_updater->url(); QScopedPointer statusJob(vcs->status(QList() << url)); QVariant varlist; if( statusJob->exec() && statusJob->status() == VcsJob::JobSucceeded ) { varlist = statusJob->fetchResults(); foreach( const QVariant &var, varlist.toList() ) { VcsStatusInfo info = var.value(); m_infos += info; if(info.state()!=VcsStatusInfo::ItemUpToDate) m_selectable[info.url()] = info.state(); } } else qCDebug(VCS) << "Couldn't get status for urls: " << url; } VCSDiffPatchSource::VCSDiffPatchSource(const KDevelop::VcsDiff& diff) : m_updater(nullptr) { updateFromDiff(diff); } VCSDiffPatchSource::~VCSDiffPatchSource() { QFile::remove(m_file.toLocalFile()); delete m_updater; } QUrl VCSDiffPatchSource::baseDir() const { return m_base; } QUrl VCSDiffPatchSource::file() const { return m_file; } QString VCSDiffPatchSource::name() const { return m_name; } uint VCSDiffPatchSource::depth() const { return m_depth; } void VCSDiffPatchSource::updateFromDiff(VcsDiff vcsdiff) { if(!m_file.isValid()) { QTemporaryFile temp2(QDir::tempPath() + QLatin1String("/kdevelop_XXXXXX.patch")); temp2.setAutoRemove(false); temp2.open(); QTextStream t2(&temp2); t2 << vcsdiff.diff(); qCDebug(VCS) << "filename:" << temp2.fileName(); m_file = QUrl::fromLocalFile(temp2.fileName()); temp2.close(); }else{ QFile file(m_file.path()); file.open(QIODevice::WriteOnly); QTextStream t2(&file); t2 << vcsdiff.diff(); } qCDebug(VCS) << "using file" << m_file << vcsdiff.diff() << "base" << vcsdiff.baseDiff(); m_name = QStringLiteral("VCS Diff"); m_base = vcsdiff.baseDiff(); m_depth = vcsdiff.depth(); emit patchChanged(); } void VCSDiffPatchSource::update() { if(!m_updater) return; updateFromDiff(m_updater->update()); } VCSCommitDiffPatchSource::~VCSCommitDiffPatchSource() { delete m_commitMessageWidget.data(); } bool VCSCommitDiffPatchSource::canSelectFiles() const { return true; } QMap< QUrl, KDevelop::VcsStatusInfo::State> VCSDiffPatchSource::additionalSelectableFiles() const { return m_selectable; } QWidget* VCSCommitDiffPatchSource::customWidget() const { return m_commitMessageWidget.data(); } QString VCSCommitDiffPatchSource::finishReviewCustomText() const { return i18nc("@action:button To make a commit", "Commit"); } bool VCSCommitDiffPatchSource::canCancel() const { return true; } void VCSCommitDiffPatchSource::cancelReview() { QString message; if (m_commitMessageEdit) message = m_commitMessageEdit.data()->toPlainText(); emit reviewCancelled(message); deleteLater(); } bool VCSCommitDiffPatchSource::finishReview(QList< QUrl > selection) { QString message; if (m_commitMessageEdit) message = m_commitMessageEdit.data()->toPlainText(); qCDebug(VCS) << "Finishing with selection" << selection; QString files; foreach(const QUrl& url, selection) files += "
  • "+ICore::self()->projectController()->prettyFileName(url, KDevelop::IProjectController::FormatPlain) + "
  • "; QString text = i18n("Files will be committed:\n
      %1
    \nWith message:\n
    %2
    ", files, message); int res = KMessageBox::warningContinueCancel(nullptr, text, i18n("About to commit to repository"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QStringLiteral("ShouldAskConfirmCommit")); if (res != KMessageBox::Continue) { return false; } emit reviewFinished(message, selection); VcsJob* job = m_vcs->commit(message, selection, KDevelop::IBasicVersionControl::NonRecursive); if (!job) { return false; } connect (job, &VcsJob::finished, this, &VCSCommitDiffPatchSource::jobFinished); ICore::self()->runController()->registerJob(job); return true; } bool showVcsDiff(IPatchSource* vcsDiff) { KDevelop::IPatchReview* patchReview = ICore::self()->pluginController()->extensionForPlugin(QStringLiteral("org.kdevelop.IPatchReview")); if( patchReview ) { patchReview->startReview(vcsDiff); return true; } else { qCWarning(VCS) << "Patch review plugin not found"; return false; } } VcsDiff VCSStandardDiffUpdater::update() const { QScopedPointer diffJob(m_vcs->diff(m_url, KDevelop::VcsRevision::createSpecialRevision(KDevelop::VcsRevision::Base), KDevelop::VcsRevision::createSpecialRevision(KDevelop::VcsRevision::Working))); const bool success = diffJob ? diffJob->exec() : false; if (!success) { KMessageBox::error(nullptr, i18n("Could not create a patch for the current version.")); return {}; } return diffJob->fetchResults().value(); } VCSStandardDiffUpdater::VCSStandardDiffUpdater(IBasicVersionControl* vcs, QUrl url) : m_vcs(vcs), m_url(url) { } VCSStandardDiffUpdater::~VCSStandardDiffUpdater() { } VCSDiffUpdater::~VCSDiffUpdater() { } diff --git a/vcs/widgets/vcsdiffwidget.cpp b/vcs/widgets/vcsdiffwidget.cpp index 82c79b3d1..73cd00e0d 100644 --- a/vcs/widgets/vcsdiffwidget.cpp +++ b/vcs/widgets/vcsdiffwidget.cpp @@ -1,104 +1,104 @@ /*************************************************************************** * 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 "vcsdiffwidget.h" #include #include #include #include "../vcsjob.h" #include "../vcsrevision.h" #include "../vcsdiff.h" -#include "../debug.h" +#include "debug.h" #include "ui_vcsdiffwidget.h" #include "vcsdiffpatchsources.h" namespace KDevelop { class VcsDiffWidgetPrivate { public: Ui::VcsDiffWidget* m_ui; VcsJob* m_job; VcsDiffWidget* q; explicit VcsDiffWidgetPrivate(VcsDiffWidget* _q) : q(_q) { } void diffReady( KDevelop::VcsJob* job ) { if( job != m_job ) return; KDevelop::VcsDiff diff = m_job->fetchResults().value(); // Try using the patch-review plugin if possible VCSDiffPatchSource* patch = new VCSDiffPatchSource(diff); if(showVcsDiff(patch)) { q->deleteLater(); return; }else{ delete patch; } qCDebug(VCS) << "diff:" << diff.leftTexts().count(); qCDebug(VCS) << "diff:" << diff.diff(); qCDebug(VCS) << "diff:" << diff.type(); qCDebug(VCS) << "diff:" << diff.contentType(); m_ui->diffDisplay->setPlainText( diff.diff() ); m_ui->diffDisplay->setReadOnly( true ); } }; VcsDiffWidget::VcsDiffWidget( KDevelop::VcsJob* job, QWidget* parent ) : QWidget( parent ), d(new VcsDiffWidgetPrivate(this)) { d->m_job = job; d->m_ui = new Ui::VcsDiffWidget(); d->m_ui->setupUi( this ); connect( d->m_job, &VcsJob::resultsReady, this, [&] (VcsJob* job) { d->diffReady(job); } ); ICore::self()->runController()->registerJob( d->m_job ); } VcsDiffWidget::~VcsDiffWidget() { delete d->m_ui; delete d; } void VcsDiffWidget::setRevisions( const KDevelop::VcsRevision& first, const KDevelop::VcsRevision& second ) { d->m_ui->revLabel->setText( i18n("Difference between revision %1 and %2:", first.prettyValue(), second.prettyValue() ) ); } } #include "moc_vcsdiffwidget.cpp" diff --git a/vcs/widgets/vcseventwidget.cpp b/vcs/widgets/vcseventwidget.cpp index 0fb5dd049..e63f2e52a 100644 --- a/vcs/widgets/vcseventwidget.cpp +++ b/vcs/widgets/vcseventwidget.cpp @@ -1,232 +1,232 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2007 Dukju Ahn * * 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 "vcseventwidget.h" #include #include #include #include #include #include #include #include #include #include #include "ui_vcseventwidget.h" #include "vcsdiffwidget.h" #include "../interfaces/ibasicversioncontrol.h" #include "../models/vcseventmodel.h" #include "../models/vcsitemeventmodel.h" -#include "../debug.h" #include "../vcsevent.h" #include "../vcsjob.h" #include "../vcsrevision.h" +#include "debug.h" namespace KDevelop { class VcsEventWidgetPrivate { public: explicit VcsEventWidgetPrivate( VcsEventWidget* w ) : q( w ) { m_copyAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("Copy revision number"), q); m_copyAction->setShortcut(Qt::ControlModifier+Qt::Key_C); QObject::connect(m_copyAction, &QAction::triggered, q, [&] { copyRevision(); }); } Ui::VcsEventWidget* m_ui; VcsItemEventModel* m_detailModel; VcsEventLogModel *m_logModel; QUrl m_url; QModelIndex m_contextIndex; VcsEventWidget* q; QAction* m_copyAction; IBasicVersionControl* m_iface; void eventViewCustomContextMenuRequested( const QPoint &point ); void eventViewClicked( const QModelIndex &index ); void jobReceivedResults( KDevelop::VcsJob* job ); void copyRevision(); void diffToPrevious(); void diffRevisions(); void currentRowChanged(const QModelIndex& start, const QModelIndex& end); }; void VcsEventWidgetPrivate::eventViewCustomContextMenuRequested( const QPoint &point ) { m_contextIndex = m_ui->eventView->indexAt( point ); if( !m_contextIndex.isValid() ){ qCDebug(VCS) << "contextMenu is not in TreeView"; return; } QMenu menu( m_ui->eventView ); menu.addAction(m_copyAction); menu.addAction(i18n("Diff to previous revision"), q, SLOT(diffToPrevious())); QAction* action = menu.addAction(i18n("Diff between revisions"), q, SLOT(diffRevisions())); action->setEnabled(m_ui->eventView->selectionModel()->selectedRows().size()>=2); menu.exec( m_ui->eventView->viewport()->mapToGlobal(point) ); } void VcsEventWidgetPrivate::currentRowChanged(const QModelIndex& start, const QModelIndex& end) { Q_UNUSED(end); if(start.isValid()) eventViewClicked(start); } void VcsEventWidgetPrivate::eventViewClicked( const QModelIndex &index ) { KDevelop::VcsEvent ev = m_logModel->eventForIndex( index ); m_detailModel->removeRows(0, m_detailModel->rowCount()); if( ev.revision().revisionType() != KDevelop::VcsRevision::Invalid ) { m_ui->itemEventView->setEnabled(true); m_ui->message->setEnabled(true); m_ui->message->setPlainText( ev.message() ); m_detailModel->addItemEvents( ev.items() ); }else { m_ui->itemEventView->setEnabled(false); m_ui->message->setEnabled(false); m_ui->message->clear(); } QHeaderView* header = m_ui->itemEventView->header(); header->setSectionResizeMode(QHeaderView::ResizeToContents); header->setStretchLastSection(true); } void VcsEventWidgetPrivate::copyRevision() { qApp->clipboard()->setText(m_contextIndex.sibling(m_contextIndex.row(), 0).data().toString()); } void VcsEventWidgetPrivate::diffToPrevious() { KDevelop::VcsEvent ev = m_logModel->eventForIndex( m_contextIndex ); KDevelop::VcsRevision prev = KDevelop::VcsRevision::createSpecialRevision(KDevelop::VcsRevision::Previous); KDevelop::VcsJob* job = m_iface->diff( m_url, prev, ev.revision() ); VcsDiffWidget* widget = new VcsDiffWidget( job ); widget->setRevisions( prev, ev.revision() ); QDialog* dlg = new QDialog( q ); widget->connect(widget, &VcsDiffWidget::destroyed, dlg, &QDialog::deleteLater); dlg->setWindowTitle( i18n("Difference To Previous") ); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok); auto mainWidget = new QWidget; QVBoxLayout *mainLayout = new QVBoxLayout; dlg->setLayout(mainLayout); mainLayout->addWidget(mainWidget); QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); dlg->connect(buttonBox, &QDialogButtonBox::accepted, dlg, &QDialog::accept); dlg->connect(buttonBox, &QDialogButtonBox::rejected, dlg, &QDialog::reject); mainLayout->addWidget(widget); mainLayout->addWidget(buttonBox); dlg->show(); } void VcsEventWidgetPrivate::diffRevisions() { QModelIndexList l = m_ui->eventView->selectionModel()->selectedRows(); KDevelop::VcsEvent ev1 = m_logModel->eventForIndex( l.first() ); KDevelop::VcsEvent ev2 = m_logModel->eventForIndex( l.last() ); KDevelop::VcsJob* job = m_iface->diff( m_url, ev1.revision(), ev2.revision() ); VcsDiffWidget* widget = new VcsDiffWidget( job ); widget->setRevisions( ev1.revision(), ev2.revision() ); auto dlg = new QDialog( q ); dlg->setWindowTitle( i18n("Difference between Revisions") ); widget->connect(widget, &VcsDiffWidget::destroyed, dlg, &QDialog::deleteLater); auto mainLayout = new QVBoxLayout(dlg); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok); auto okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); dlg->connect(buttonBox, &QDialogButtonBox::accepted, dlg, &QDialog::accept); dlg->connect(buttonBox, &QDialogButtonBox::rejected, dlg, &QDialog::reject); mainLayout->addWidget(buttonBox); mainLayout->addWidget(widget); dlg->show(); } VcsEventWidget::VcsEventWidget( const QUrl& url, const VcsRevision& rev, KDevelop::IBasicVersionControl* iface, QWidget* parent ) : QWidget(parent), d(new VcsEventWidgetPrivate(this) ) { d->m_iface = iface; d->m_url = url; d->m_ui = new Ui::VcsEventWidget(); d->m_ui->setupUi(this); d->m_logModel = new VcsEventLogModel(iface, rev, url, this); d->m_ui->eventView->setModel( d->m_logModel ); d->m_ui->eventView->sortByColumn(0, Qt::DescendingOrder); d->m_ui->eventView->setContextMenuPolicy( Qt::CustomContextMenu ); QHeaderView* header = d->m_ui->eventView->header(); header->setSectionResizeMode( 0, QHeaderView::ResizeToContents ); header->setSectionResizeMode( 1, QHeaderView::Stretch ); header->setSectionResizeMode( 2, QHeaderView::ResizeToContents ); header->setSectionResizeMode( 3, QHeaderView::ResizeToContents ); // Select first row as soon as the model got populated connect(d->m_logModel, &QAbstractItemModel::rowsInserted, this, [this]() { auto view = d->m_ui->eventView; view->setCurrentIndex(view->model()->index(0, 0)); }); d->m_detailModel = new VcsItemEventModel(this); d->m_ui->itemEventView->setModel( d->m_detailModel ); connect( d->m_ui->eventView, &QTreeView::clicked, this, [&] (const QModelIndex& index) { d->eventViewClicked(index); } ); connect( d->m_ui->eventView->selectionModel(), &QItemSelectionModel::currentRowChanged, this, [&] (const QModelIndex& start, const QModelIndex& end) { d->currentRowChanged(start, end); }); connect( d->m_ui->eventView, &QTreeView::customContextMenuRequested, this, [&] (const QPoint& point) { d->eventViewCustomContextMenuRequested(point); } ); } VcsEventWidget::~VcsEventWidget() { delete d->m_ui; delete d; } } #include "moc_vcseventwidget.cpp"