diff --git a/CMakeLists.txt b/CMakeLists.txt index 319d21ce06..e604bcdc37 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,167 +1,173 @@ # kdevplatform version set(KDEVPLATFORM_VERSION_MAJOR 1) set(KDEVPLATFORM_VERSION_MINOR 90) set(KDEVPLATFORM_VERSION_PATCH 60) # plugin versions listed in the .desktop files set(KDEV_PLUGIN_VERSION 19) # library version / SO version set(KDEVPLATFORM_LIB_VERSION 8.0.0) set(KDEVPLATFORM_LIB_SOVERSION 8) ################################################################################ cmake_minimum_required(VERSION 2.8.12) project(KDevPlatform) # we need some parts of the ECM CMake helpers find_package (ECM 0.0.9 REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${KDevPlatform_SOURCE_DIR}/cmake/modules ${ECM_MODULE_PATH}) include(ECMOptionalAddSubdirectory) include(ECMInstallIcons) include(ECMSetupVersion) include(ECMMarkAsTest) include(ECMMarkNonGuiExecutable) include(ECMGenerateHeaders) include(CMakePackageConfigHelpers) include(FeatureSummary) include(WriteBasicConfigVersionFile) include(CheckFunctionExists) include(KDEInstallDirs) include(KDECMakeSettings) include(KDECompilerSettings) include(KDevPlatformMacros) set(QT_MIN_VERSION "5.2.0") find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED Core Declarative DBus Widgets Script WebKitWidgets Concurrent Test) find_package(KF5 CONFIG REQUIRED COMPONENTS Archive Config DocTools GuiAddons I18n Init ItemModels JobWidgets KCMUtils KIO NewStuff Parts PrintUtils Sonnet TextEditor ThreadWeaver WindowSystem XmlGui TextEditor NotifyConfig ThreadWeaver ItemModels Service Emoticons KCMUtils ItemViews # TODO KF5: Port away from this KDE4Support ) #TODO KF5: Re-enable Grantlee (currently GrantleeConfig.cmake calls find_package(Qt4) internall #find_package(Grantlee 0.1.7) # set_package_properties(Grantlee PROPERTIES # PURPOSE "Grantlee templating library, needed for file templates" # URL "http://www.grantlee.org/" # TYPE REQUIRED) add_definitions(-DQT_USE_FAST_CONCATENATION -DQT_USE_FAST_OPERATOR_PLUS -DQURL_NO_CAST_FROM_QSTRING) if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "Intel") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") endif() +# 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 +if (CMAKE_COMPILER_IS_GNUCXX) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-missing-field-initializers") +endif() + if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") # TODO: kf5 remove # in Qt 4, clang support is lacking... add_definitions("-DQ_COMPILER_INITIALIZER_LISTS=1") 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}) # include_directories(${Grantlee_INCLUDE_DIRS}) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/util/) # Now set the usual KDEVPLATFORM_XXX_LIBRARIES variable so we can more easily move plugins around set(KDEVPLATFORM_SUBLIME_LIBRARIES sublime) set(KDEVPLATFORM_INTERFACES_LIBRARIES kdevplatforminterfaces) set(KDEVPLATFORM_LANGUAGE_LIBRARIES kdevplatformlanguage) set(KDEVPLATFORM_PROJECT_LIBRARIES kdevplatformproject) set(KDEVPLATFORM_UTIL_LIBRARIES kdevplatformutil) set(KDEVPLATFORM_OUTPUTVIEW_LIBRARIES kdevplatformoutputview) set(KDEVPLATFORM_VCS_LIBRARIES kdevplatformvcs) set(KDEVPLATFORM_SHELL_LIBRARIES kdevplatformshell) set(KDEVPLATFORM_TESTS_LIBRARIES kdevplatformtests) set(KDEVPLATFORM_JSONTESTS_LIBRARIES kdevplatformjsontests) set(KDEVPLATFORM_DEBUGGER_LIBRARIES kdevplatformdebugger) set(KDEVPLATFORM_DOCUMENTATION_LIBRARIES kdevplatformdocumentation) 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) #ecm_optional_add_subdirectory(doc) add_subdirectory(debugger) add_subdirectory(documentation) # if (Grantlee_FOUND) # add_subdirectory(template) # endif() add_subdirectory(tests) add_subdirectory(plugins) write_basic_config_version_file(${KDevPlatform_BINARY_DIR}/KDevPlatformConfigVersion.cmake VERSION ${KDEVPLATFORM_VERSION_MAJOR}.${KDEVPLATFORM_VERSION_MINOR}.${KDEVPLATFORM_VERSION_PATCH} COMPATIBILITY AnyNewerVersion) configure_file( "${KDevPlatform_SOURCE_DIR}/KDevPlatformConfig.cmake.in" "${KDevPlatform_BINARY_DIR}/KDevPlatformConfig.cmake" @ONLY ) configure_file( "${KDevPlatform_SOURCE_DIR}/kdevplatformversion.h.cmake" "${KDevPlatform_BINARY_DIR}/kdevplatformversion.h" @ONLY ) set(CMAKECONFIG_INSTALL_DIR "${CMAKECONFIG_INSTALL_PREFIX}/KDevPlatform") configure_package_config_file("${CMAKE_CURRENT_SOURCE_DIR}/KDevPlatformConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/KDevPlatformConfig.cmake" INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR} ) install( FILES "${KDevPlatform_BINARY_DIR}/kdevplatformversion.h" DESTINATION "${INCLUDE_INSTALL_DIR}/kdevplatform" ) install( FILES "${KDevPlatform_BINARY_DIR}/config-kdevplatform.h" DESTINATION "${INCLUDE_INSTALL_DIR}/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 KDevPlatformImport__ 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/ChangeLog b/ChangeLog deleted file mode 100644 index b1da2d64c6..0000000000 --- a/ChangeLog +++ /dev/null @@ -1 +0,0 @@ -Please read the changelog at http://www.kdevelop.org/index.html?filename=HEAD/ChangeLog.html instead. diff --git a/NEWS b/NEWS deleted file mode 100644 index e8ffdb30da..0000000000 --- a/NEWS +++ /dev/null @@ -1 +0,0 @@ -News are announced on KDevelop website: http://www.kdevelop.org/index.html diff --git a/TODO b/TODO deleted file mode 100644 index b82272724c..0000000000 --- a/TODO +++ /dev/null @@ -1,4 +0,0 @@ -There is nothing here yet, but that does not mean that that there is nothing -to do. Please email kdevelop-devel@kdevelop.org or visit our IRC-channel -#kdevelop@irc.freenode.org if you are interested in contributing to the KDevelop platform. - diff --git a/debugger/breakpoint/breakpoint.cpp b/debugger/breakpoint/breakpoint.cpp index 0d0bd88620..ff5962b75b 100644 --- a/debugger/breakpoint/breakpoint.cpp +++ b/debugger/breakpoint/breakpoint.cpp @@ -1,375 +1,382 @@ /* 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 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 "breakpoint.h" #include #include #include #include "breakpointmodel.h" #include "../../interfaces/icore.h" #include "../../interfaces/idebugcontroller.h" #include "../interfaces/idebugsession.h" #include "../interfaces/ibreakpointcontroller.h" using namespace KDevelop; static const char* BREAKPOINT_KINDS[Breakpoint::LastBreakpointKind] = { "Code", "Write", "Read", "Access" }; static Breakpoint::BreakpointKind stringToKind(const QString& kindString) { for (int i = 0; i < Breakpoint::LastBreakpointKind; ++i) { if (BREAKPOINT_KINDS[i] == kindString) { return (Breakpoint::BreakpointKind)i; } } return Breakpoint::CodeBreakpoint; } Breakpoint::Breakpoint(BreakpointModel *model, BreakpointKind kind) : m_model(model), m_enabled(true) , m_deleted(false), m_kind(kind) , m_line(-1) , m_movingCursor(0), m_ignoreHits(0) { if (model) { model->registerBreakpoint(this); } } Breakpoint::Breakpoint(BreakpointModel *model, const KConfigGroup& config) : m_model(model), m_enabled(true) , m_deleted(false) , m_line(-1) , m_movingCursor(0), m_ignoreHits(0) { if (model) { model->registerBreakpoint(this); } m_kind = stringToKind(config.readEntry("kind", "")); m_enabled = config.readEntry("enabled", false); m_url = config.readEntry("url", QUrl()); m_line = config.readEntry("line", -1); m_expression = config.readEntry("expression", QString()); setCondition(config.readEntry("condition", "")); setIgnoreHits(config.readEntry("ignoreHits", 0)); } BreakpointModel *Breakpoint::breakpointModel() { return m_model; } bool Breakpoint::setData(int index, const QVariant& value) { if (index == EnableColumn) { m_enabled = static_cast(value.toInt()) == Qt::Checked; } if (index == LocationColumn || index == ConditionColumn) { QString s = value.toString(); if (index == LocationColumn) { QRegExp rx("^(.+):([0-9]+)$"); int idx = rx.indexIn(s); if (m_kind == CodeBreakpoint && idx != -1) { m_url = QUrl(rx.cap(1)); m_line = rx.cap(2).toInt() - 1; m_expression.clear(); } else { m_expression = s; m_url.clear(); m_line = -1; } } else { m_condition = s; } } reportChange(static_cast(index)); return true; } QVariant Breakpoint::data(int column, int role) const { if (column == EnableColumn) { if (role == Qt::CheckStateRole) return m_enabled ? Qt::Checked : Qt::Unchecked; else if (role == Qt::DisplayRole) return QVariant(); else return QVariant(); } if (column == StateColumn) { if (role == Qt::DecorationRole) { if (!errors().isEmpty()) { return QIcon::fromTheme("dialog-warning"); } switch (state()) { case NotStartedState: return QVariant(); case DirtyState: return QIcon::fromTheme("system-switch-user"); case PendingState: return QIcon::fromTheme("help-contents"); case CleanState: return QIcon::fromTheme("dialog-ok-apply"); } } else if (role == Qt::ToolTipRole) { if (!errors().isEmpty()) { return i18nc("@info:tooltip", "Error"); } switch (state()) { case NotStartedState: return QString(); case DirtyState: return i18nc("@info:tooltip", "Dirty"); case PendingState: return i18nc("@info:tooltip", "Pending"); case CleanState: return i18nc("@info:tooltip", "Clean"); } } else if (role == Qt::DisplayRole) { return QVariant(); } return QVariant(); } if (column == TypeColumn && role == Qt::DisplayRole) { return BREAKPOINT_KINDS[m_kind]; } if (role == Qt::DecorationRole) { if ((column == LocationColumn && errors().contains(LocationColumn)) || (column == ConditionColumn && errors().contains(ConditionColumn))) { /* FIXME: does this leak? Is this efficient? */ return QIcon::fromTheme("dialog-warning"); } return QVariant(); } if (column == ConditionColumn && (role == Qt::DisplayRole || role == Qt::EditRole)) { return m_condition; } if (column == LocationColumn) { if (role == LocationRole || role == Qt::EditRole || role == Qt::ToolTipRole || role == Qt::DisplayRole) { QString ret; if (m_kind == CodeBreakpoint && m_line != -1) { if (role == Qt::DisplayRole) { ret = m_url.fileName(); } else { ret = m_url.toDisplayString(QUrl::PreferLocalFile | QUrl::StripTrailingSlash); } ret += ':' + QString::number(m_line+1); } else { ret = m_expression; } //FIXME: there should be proper columns for function name and address. if (!m_address.isEmpty() && role == Qt::DisplayRole) { ret = QString("%1 (%2)").arg(ret).arg(m_address); } return ret; } } return QVariant(); } void Breakpoint::setDeleted() { m_deleted = true; BreakpointModel* m = breakpointModel(); + if (!m) + return; // already removed + if (m->breakpointIndex(this, 0).isValid()) { m->removeRow(m->breakpointIndex(this, 0).row()); } + m_model = 0; // invalidate } int Breakpoint::line() const { return m_line; } void Breakpoint::setLine(int line) { Q_ASSERT(m_kind == CodeBreakpoint); m_line = line; reportChange(LocationColumn); } void Breakpoint::setUrl(const KUrl& url) { Q_ASSERT(m_kind == CodeBreakpoint); m_url = url; reportChange(LocationColumn); } KUrl Breakpoint::url() const { return m_url; } void Breakpoint::setLocation(const KUrl& url, int line) { Q_ASSERT(m_kind == CodeBreakpoint); m_url = url; m_line = line; reportChange(LocationColumn); } QString KDevelop::Breakpoint::location() { return data(LocationColumn, LocationRole).toString(); } void Breakpoint::save(KConfigGroup& config) { config.writeEntry("kind", BREAKPOINT_KINDS[m_kind]); config.writeEntry("enabled", m_enabled); config.writeEntry("url", m_url); config.writeEntry("line", m_line); config.writeEntry("expression", m_expression); config.writeEntry("condition", m_condition); config.writeEntry("ignoreHits", m_ignoreHits); } Breakpoint::BreakpointKind Breakpoint::kind() const { return m_kind; } void Breakpoint::setAddress(const QString& address) { m_address = address; //reportChange(); } QString Breakpoint::address() const { return m_address; } int Breakpoint::hitCount() const { IDebugSession* session = ICore::self()->debugController()->currentSession(); if (session) { return session->breakpointController()->breakpointHitCount(this); } else { return -1; } } bool Breakpoint::deleted() const { return m_deleted; } bool Breakpoint::enabled() const { return data(EnableColumn, Qt::CheckStateRole).toBool(); } void KDevelop::Breakpoint::setMovingCursor(KTextEditor::MovingCursor* cursor) { m_movingCursor = cursor; } KTextEditor::MovingCursor* KDevelop::Breakpoint::movingCursor() const { return m_movingCursor; } void Breakpoint::setIgnoreHits(int c) { if (m_ignoreHits != c) { m_ignoreHits = c; reportChange(IgnoreHitsColumn); } } int Breakpoint::ignoreHits() const { return m_ignoreHits; } void Breakpoint::setCondition(const QString& c) { m_condition = c; reportChange(ConditionColumn); } QString Breakpoint::condition() const { return m_condition; } void Breakpoint::setExpression(const QString& e) { m_expression = e; reportChange(LocationColumn); } QString Breakpoint::expression() const { return m_expression; } Breakpoint::BreakpointState Breakpoint::state() const { IDebugSession* session = ICore::self()->debugController()->currentSession(); if (session) { return session->breakpointController()->breakpointState(this); } else { return NotStartedState; } } QSet Breakpoint::errors() const { IDebugSession* session = ICore::self()->debugController()->currentSession(); if (session) { return session->breakpointController()->breakpointErrors(this); } else { return QSet(); } } QString Breakpoint::errorText() const { IDebugSession* session = ICore::self()->debugController()->currentSession(); if (session) { return session->breakpointController()->breakpointErrorText(this); } else { return QString(); } } void KDevelop::Breakpoint::reportChange(Column c) { + if (!breakpointModel()) + return; + breakpointModel()->reportChange(this, c); } diff --git a/debugger/breakpoint/breakpoint.h b/debugger/breakpoint/breakpoint.h index 54c71f0b1b..7be3a8db08 100644 --- a/debugger/breakpoint/breakpoint.h +++ b/debugger/breakpoint/breakpoint.h @@ -1,137 +1,142 @@ /* This file is part of the KDE project Copyright (C) 2002 Matthias Hoelzer-Kluepfel Copyright (C) 2002 John Firebaugh Copyright (C) 2007 Hamish Rodda This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License 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 . */ #ifndef KDEVPLATFORM_BREAKPOINT_H #define KDEVPLATFORM_BREAKPOINT_H #include #include #include "../util/treeitem.h" class KConfigGroup; namespace KTextEditor { class MovingCursor; } namespace KDevelop { class BreakpointModel; class KDEVPLATFORMDEBUGGER_EXPORT Breakpoint { public: enum BreakpointKind { CodeBreakpoint = 0, WriteBreakpoint, ReadBreakpoint, AccessBreakpoint, LastBreakpointKind }; enum BreakpointState { NotStartedState, DirtyState, PendingState, CleanState }; ///Custom roles for retrieving data from breakpoint model. enum BreakpointRole{ LocationRole = Qt::UserRole + 1 ///< Retrieves breakpoint's full path unlike Qt::DisplayRole. Note: it's only appliable to LocationColumn. }; Breakpoint(BreakpointModel *model, BreakpointKind kind); Breakpoint(BreakpointModel *model, const KConfigGroup& config); ///Note: EnableColumn has 3, not 2(true and false) values: Qt::Unchecked, Qt:PartiallyChecked and Qt::Checked bool setData(int index, const QVariant& value); void setDeleted(); ///Note: to retrieve the full path use LocationRole, Qt::DisplayRole return only a file's name QVariant data(int column, int role) const; void save(KConfigGroup& config); enum Column { EnableColumn, StateColumn, TypeColumn, LocationColumn, ConditionColumn, HitCountColumn, IgnoreHitsColumn }; void setUrl(const KUrl &url); KUrl url() const; void setLine(int line); int line() const; void setLocation(const KUrl& url, int line); QString location(); BreakpointKind kind() const; void setAddress(const QString& address); QString address() const; int hitCount() const; bool deleted() const; bool enabled() const; void setMovingCursor(KTextEditor::MovingCursor *cursor); KTextEditor::MovingCursor *movingCursor() const; void setIgnoreHits(int c); int ignoreHits() const; void setCondition(const QString &c); QString condition() const; void setExpression(const QString &c); QString expression() const; BreakpointState state() const; QString errorText() const; QSet errors() const; protected: friend class IBreakpointController; + /** + * Return the model this breakpoint is attached to + * + * @note This might be null, e.g. after the breakpoint has been marked as deleted + */ BreakpointModel *breakpointModel(); BreakpointModel *m_model; bool m_enabled; bool m_deleted; BreakpointKind m_kind; /* For watchpoints, the address it is set at. */ QString m_address; QUrl m_url; int m_line; QString m_condition; KTextEditor::MovingCursor *m_movingCursor; int m_ignoreHits; QString m_expression; void reportChange(Column c); }; } #endif diff --git a/debugger/breakpoint/breakpointmodel.cpp b/debugger/breakpoint/breakpointmodel.cpp index 7af22b8c8d..daccb3d331 100644 --- a/debugger/breakpoint/breakpointmodel.cpp +++ b/debugger/breakpoint/breakpointmodel.cpp @@ -1,554 +1,553 @@ /* 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 #include "../interfaces/icore.h" #include "../interfaces/idocumentcontroller.h" #include "../interfaces/idocument.h" #include "../interfaces/ipartcontroller.h" #include "breakpoint.h" #include #include #include #include #define IF_DEBUG(x) using namespace KDevelop; using namespace KTextEditor; BreakpointModel::BreakpointModel(QObject* parent) : QAbstractTableModel(parent), m_dontUpdateMarks(false) { connect(this, SIGNAL(dataChanged(QModelIndex,QModelIndex)), SLOT(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(), SIGNAL(partAdded(KParts::Part*)), this, SLOT(slotPartAdded(KParts::Part*))); } connect (KDevelop::ICore::self()->documentController(), SIGNAL(textDocumentCreated(KDevelop::IDocument*)), this, SLOT(textDocumentCreated(KDevelop::IDocument*))); connect (KDevelop::ICore::self()->documentController(), SIGNAL(documentSaved(KDevelop::IDocument*)), SLOT(documentSaved(KDevelop::IDocument*))); load(); } BreakpointModel::~BreakpointModel() { save(); qDeleteAll(m_breakpoints); } void BreakpointModel::slotPartAdded(KParts::Part* part) { if (KTextEditor::Document* doc = dynamic_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 ) { 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; kDebug() << type; /* Is this a breakpoint mark, to begin with? */ if (!(type & AllBreakpointMarks)) return; Breakpoint *b = breakpoint(document->url(), mark.line); Q_ASSERT(b); if (!b) return; QMenu menu; QAction deleteAction(QIcon::fromTheme("edit-delete"), i18n("&Delete Breakpoint"), 0); QAction disableAction(QIcon::fromTheme("dialog-cancel"), i18n("&Disable Breakpoint"), 0); QAction enableAction(QIcon::fromTheme("dialog-ok-apply"), i18n("&Enable Breakpoint"), 0); 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("dialog-ok-apply"); else if (section == 1) return QIcon::fromTheme("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 0; 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 (row + count > m_breakpoints.count()) { - count = m_breakpoints.count() - row; - if (count <= 0) return false; - } + if (count < 1 || (row < 0) || (row + count) > rowCount(parent)) + return false; + beginRemoveRows(parent, row, row+count-1); for (int i=0; i < count; ++i) { Breakpoint *b = m_breakpoints.at(row); m_breakpoints.removeAt(row); IF_DEBUG ( kDebug() << m_breakpoints; ) - if (!b->deleted()) b->setDeleted(); + b->setDeleted(); emit breakpointDeleted(b); } endRemoveRows(); updateMarks(); 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::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)); connect(document, SIGNAL(aboutToDeleteMovingInterfaceContent(KTextEditor::Document*)), this, SLOT(aboutToDeleteMovingInterfaceContent(KTextEditor::Document*))); 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("script-error").pixmap(QSize(22,22), QIcon::Active, QIcon::Off); return &pixmap; } const QPixmap* BreakpointModel::pendingBreakpointPixmap() { static QPixmap pixmap=QIcon::fromTheme("script-error").pixmap(QSize(22,22), QIcon::Normal, QIcon::Off); return &pixmap; } const QPixmap* BreakpointModel::reachedBreakpointPixmap() { static QPixmap pixmap=QIcon::fromTheme("script-error").pixmap(QSize(22,22), QIcon::Selected, QIcon::Off); return &pixmap; } const QPixmap* BreakpointModel::disabledBreakpointPixmap() { static QPixmap pixmap=QIcon::fromTheme("script-error").pixmap(QSize(22,22), QIcon::Disabled, QIcon::Off); return &pixmap; } void BreakpointModel::toggleBreakpoint(const KUrl& 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); } emit breakpointChanged(breakpoint, column); } 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( kDebug() << type << breakpoint->url() << mark->mark(breakpoint->line()); ) doc->textDocument()->blockSignals(true); 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); } doc->textDocument()->blockSignals(false); } //remove marks foreach (IDocument *doc, ICore::self()->documentController()->openDocuments()) { KTextEditor::MarkInterface *mark = qobject_cast(doc->textDocument()); if (!mark) continue; doc->textDocument()->blockSignals(true); foreach (KTextEditor::Mark *m, mark->marks()) { if (!(m->type & AllBreakpointMarks)) continue; IF_DEBUG( kDebug() << 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:; } doc->textDocument()->blockSignals(false); } } void BreakpointModel::documentSaved(KDevelop::IDocument* doc) { IF_DEBUG( kDebug(); ) 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(0); } } } void BreakpointModel::load() { KConfigGroup breakpoints = KSharedConfig::openConfig()->group("breakpoints"); int count = breakpoints.readEntry("number", 0); if (count == 0) return; beginInsertRows(QModelIndex(), 0, count); 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() { KConfigGroup breakpoints = KSharedConfig::openConfig()->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(); } QList KDevelop::BreakpointModel::breakpoints() const { return m_breakpoints; } Breakpoint* BreakpointModel::breakpoint(int row) { if (row >= m_breakpoints.count()) return 0; 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 KUrl& 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)); m_breakpoints << breakpoint; } Breakpoint* BreakpointModel::breakpoint(const KUrl& url, int line) { foreach (Breakpoint *b, m_breakpoints) { if (b->url() == url && b->line() == line) { return b; } } return 0; } #include "breakpointmodel.moc" diff --git a/debugger/variable/variablecollection.cpp b/debugger/variable/variablecollection.cpp index 13badcca54..b713e74e99 100644 --- a/debugger/variable/variablecollection.cpp +++ b/debugger/variable/variablecollection.cpp @@ -1,513 +1,524 @@ /* * 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 "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), inScope_(true), topLevel_(true), changed_(false), showError_(false), m_format(Natural) { expression_ = expression; // FIXME: should not duplicate the data, instead overload 'data' // and return expression_ directly. if (display.isEmpty()) - setData(QVector() << expression << QString()); + setData(QVector() << expression << QString() << QString()); else - setData(QVector() << display << QString()); + setData(QVector() << display << QString() << QString()); } QString Variable::expression() const { return expression_; } bool Variable::inScope() const { return inScope_; } void Variable::setValue(const QString& v) { - itemData[1] = v; + itemData[VariableCollection::ValueColumn] = v; reportChange(); } QString Variable::value() const { - return itemData[1].toString(); + 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) { topLevel_ = v; } void Variable::setInScope(bool v) { inScope_ = v; for (int i=0; i < childCount(); ++i) { if (Variable *var = dynamic_cast(child(i))) { var->setInScope(v); } } reportChange(); } void Variable::setShowError (bool v) { showError_ = v; reportChange(); } bool Variable::showError() { return showError_; } Variable::~Variable() { } void Variable::die() { removeSelf(); deleteLater(); } void Variable::setChanged(bool c) { 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=="Binary" || str=="binary") return Binary; if(str=="Octal" || str=="octal") return Octal; if(str=="Decimal" || str=="decimal") return Decimal; if(str=="Hexadecimal" || str=="hexadecimal")return Hexadecimal; return Natural; // maybe most reasonable default } QString Variable::format2str(format_t format) { switch(format) { case Natural: return "natural"; case Binary: return "binary"; case Octal: return "octal"; case Decimal: return "decimal"; case Hexadecimal: return "hexadecimal"; default: return ""; } } void Variable::setFormat(Variable::format_t format) { if (m_format != format) { m_format = format; formatChanged(); } } void Variable::formatChanged() { } QVariant Variable::data(int column, int role) const { if (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::ForegroundRole) { // FIXME: returning hardcoded gray is bad, // but we don't have access to any widget, or pallette // thereof, at this point. if(!inScope_) return QColor(128, 128, 128); if(changed_) return QColor(255, 0, 0); } 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_(0) { setData(QVector() << i18n("Auto") << QString()); } Variable* Watches::add(const QString& expression) { if (!hasStartedSession()) return 0; 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, "$ret"); appendChild(finishResult_); finishResult_->attachMaybe(); if (childCount() == 1 && !isExpanded()) { setExpanded(true); } return finishResult_; } void Watches::removeFinishResult() { if (finishResult_) { finishResult_->die(); finishResult_ = 0; } } 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(dynamic_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(dynamic_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) { watches_ = new Watches(model, this); appendChild(watches_, true); } Locals* VariablesRoot::locals(const QString& name) { if (!locals_.contains(name)) { locals_[name] = new Locals(model(), this, name); appendChild(locals_[name]); } return locals_[name]; } QHash VariablesRoot::allLocals() const { return locals_; } void VariablesRoot::resetChanged() { watches_->resetChanged(); foreach (Locals *l, locals_) { l->resetChanged(); } } VariableCollection::VariableCollection(IDebugController* controller) -: TreeModel(QVector() << i18n( "Name" ) << i18n( "Value" ), controller), m_widgetVisible(false) +: TreeModel(QVector() << i18n( "Name" ) << i18n( "Value" ) << i18n("Type"), controller), m_widgetVisible(false) { universe_ = new VariablesRoot(this); setRootItem(universe_); //new ModelTest(this); connect (ICore::self()->documentController(), SIGNAL(textDocumentCreated(KDevelop::IDocument*)), this, SLOT(textDocumentCreated(KDevelop::IDocument*)) ); connect(controller, SIGNAL(currentSessionChanged(KDevelop::IDebugSession*)), SLOT(updateAutoUpdate(KDevelop::IDebugSession*))); connect(locals(), SIGNAL(expanded()), SLOT(updateAutoUpdate())); connect(locals(), SIGNAL(collapsed()), SLOT(updateAutoUpdate())); connect(watches(), SIGNAL(expanded()), SLOT(updateAutoUpdate())); connect(watches(), SIGNAL(collapsed()), SLOT(updateAutoUpdate())); } void VariableCollection::variableWidgetHidden() { m_widgetVisible = false; updateAutoUpdate(); } void VariableCollection::variableWidgetShown() { m_widgetVisible = true; updateAutoUpdate(); } void VariableCollection::updateAutoUpdate(IDebugSession* session) { if (!session) session = currentSession(); kDebug() << 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(), SIGNAL(viewCreated(KTextEditor::Document*,KTextEditor::View*)), this, SLOT(viewCreated(KTextEditor::Document*,KTextEditor::View*)) ); 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(new VariableProvider(this)); } VariableProvider::VariableProvider(VariableCollection* collection) : KTextEditor::TextHintProvider() , m_collection(collection) { } QString VariableProvider::textHint(KTextEditor::View* view, const KTextEditor::Cursor& cursor) { // Don't do anything if there's already an open tooltip. if (m_collection->activeTooltip_) return QString(); if (!hasStartedSession()) return QString(); if (ICore::self()->uiController()->activeArea()->objectName() != "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(); QString expression = currentSession()->variableController()->expressionUnderCursor(doc, cursor); 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->activeTooltip_ = new VariableToolTip(w, global+QPoint(30,30), expression); return QString(); } } #include "variablecollection.moc" diff --git a/debugger/variable/variablecollection.h b/debugger/variable/variablecollection.h index 1f6fd54d0d..e620f0e8d4 100644 --- a/debugger/variable/variablecollection.h +++ b/debugger/variable/variablecollection.h @@ -1,242 +1,250 @@ /* * 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. */ #ifndef KDEVPLATFORM_VARIABLECOLLECTION_H #define KDEVPLATFORM_VARIABLECOLLECTION_H #include #include #include #include #include #include #include "../util/treemodel.h" #include "../util/treeitem.h" #include "../../interfaces/idocument.h" #include "../debuggerexport.h" #include "../interfaces/idebugsession.h" #include "../../interfaces/idebugcontroller.h" namespace GDBDebugger { class GdbTest; } namespace KDevelop { class VariableToolTip; class KDEVPLATFORMDEBUGGER_EXPORT Variable : public TreeItem { Q_OBJECT friend class GDBDebugger::GdbTest; public: protected: Variable(TreeModel* model, TreeItem* parent, const QString& expression, const QString& display = ""); public: enum format_t { Natural, Binary, Octal, Decimal, Hexadecimal }; static format_t str2format(const QString& str); static QString format2str(format_t format); QString expression() const; bool inScope() const; void setInScope(bool v); void setValue(const QString &v); QString value() const; + void setType(const QString& type); + QString type() const; void setTopLevel(bool v); void setShowError(bool v); bool showError(); using TreeItem::setHasMore; using TreeItem::setHasMoreInitial; using TreeItem::appendChild; using TreeItem::deleteChildren; using TreeItem::isExpanded; using TreeItem::parent; using TreeItem::model; ~Variable(); /* Connects this variable to debugger, fetching the current value and otherwise preparing this variable to be displayed everywhere. The attempt may fail, for example if the expression is invalid. Calls slot 'callbackMethod' in 'callback' to notify of the result. The slot should be taking 'bool ok' parameter. */ virtual void attachMaybe(QObject *callback = 0, const char *callbackMethod = 0) = 0; virtual bool canSetFormat() const { return false; } void setFormat(format_t format); format_t format() const { return m_format; } virtual void formatChanged(); // get/set 'changed' state, if the variable changed it will be highlighted bool isChanged() const { return changed_; } void setChanged(bool c); void resetChanged(); public slots: void die(); protected: bool topLevel() const { return topLevel_; } private: // TreeItem overrides QVariant data(int column, int role) const; private: QString expression_; bool inScope_; bool topLevel_; bool changed_; bool showError_; format_t m_format; }; class KDEVPLATFORMDEBUGGER_EXPORT TooltipRoot : public TreeItem { public: TooltipRoot(TreeModel* model) : TreeItem(model) {} void init(Variable *var) { appendChild(var); } void fetchMoreChildren() {} }; class KDEVPLATFORMDEBUGGER_EXPORT Watches : public TreeItem { friend class GDBDebugger::GdbTest; public: Watches(TreeModel* model, TreeItem* parent); Variable* add(const QString& expression); void reinstall(); Variable *addFinishResult(const QString& convenienceVarible); void removeFinishResult(); void resetChanged(); using TreeItem::childCount; friend class VariableCollection; friend class IVariableController; private: QVariant data(int column, int role) const; void fetchMoreChildren() {} Variable* finishResult_; }; class KDEVPLATFORMDEBUGGER_EXPORT Locals : public TreeItem { public: Locals(TreeModel* model, TreeItem* parent, const QString &name); QList updateLocals(QStringList locals); void resetChanged(); using TreeItem::deleteChildren; using TreeItem::setHasMore; friend class VariableCollection; friend class IVariableController; private: void fetchMoreChildren() {} }; class KDEVPLATFORMDEBUGGER_EXPORT VariablesRoot : public TreeItem { public: VariablesRoot(TreeModel* model); Watches *watches() const { return watches_; } Locals *locals(const QString &name = "Locals"); QHash allLocals() const; void fetchMoreChildren() {} void resetChanged(); private: Watches *watches_; QHash locals_; }; class VariableProvider : public KTextEditor::TextHintProvider { public: VariableProvider(VariableCollection* collection); virtual QString textHint(KTextEditor::View* view, const KTextEditor::Cursor& position) Q_DECL_OVERRIDE; private: VariableCollection* m_collection; }; class KDEVPLATFORMDEBUGGER_EXPORT VariableCollection : public TreeModel { Q_OBJECT public: + enum Column { + NameColumn, + ValueColumn, + TypeColumn + }; + VariableCollection(IDebugController* parent); virtual ~VariableCollection(); VariablesRoot* root() const { return universe_; } Watches* watches() const { return universe_->watches(); } Locals* locals(const QString &name = i18n("Locals")) const { return universe_->locals(name); } QHash allLocals() const { return universe_->allLocals(); } public Q_SLOTS: void variableWidgetShown(); void variableWidgetHidden(); private Q_SLOTS: void updateAutoUpdate(KDevelop::IDebugSession* session = 0); void textDocumentCreated( KDevelop::IDocument*); void viewCreated(KTextEditor::Document*, KTextEditor::View*); private: VariablesRoot* universe_; QWeakPointer activeTooltip_; bool m_widgetVisible; friend class VariableProvider; }; } #endif diff --git a/debugger/variable/variabletooltip.cpp b/debugger/variable/variabletooltip.cpp index a8d223f43f..1457c54528 100644 --- a/debugger/variable/variabletooltip.cpp +++ b/debugger/variable/variabletooltip.cpp @@ -1,210 +1,210 @@ /* * KDevelop Debugger Support * * 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 "variabletooltip.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "variablecollection.h" #include "../breakpoint/breakpointmodel.h" #include "../interfaces/ivariablecontroller.h" #include "../../util/activetooltip.h" #include "../../interfaces/icore.h" #include "../../interfaces/idebugcontroller.h" namespace KDevelop { class SizeGrip : public QWidget { public: SizeGrip(QWidget* parent) : QWidget(parent) { m_parent = parent; } protected: virtual void paintEvent(QPaintEvent *) { QPainter painter(this); QStyleOptionSizeGrip opt; opt.init(this); opt.corner = Qt::BottomRightCorner; style()->drawControl(QStyle::CE_SizeGrip, &opt, &painter, this); } virtual void mousePressEvent(QMouseEvent* e) { if (e->button() == Qt::LeftButton) { m_pos = e->globalPos(); m_startSize = m_parent->size(); e->ignore(); } } virtual void mouseReleaseEvent(QMouseEvent*) { m_pos = QPoint(); } virtual void mouseMoveEvent(QMouseEvent* e) { if (!m_pos.isNull()) { m_parent->resize( m_startSize.width() + (e->globalPos().x() - m_pos.x()), m_startSize.height() + (e->globalPos().y() - m_pos.y()) ); } } private: QWidget *m_parent; QSize m_startSize; QPoint m_pos; }; VariableToolTip::VariableToolTip(QWidget* parent, QPoint position, const QString& identifier) : ActiveToolTip(parent, position) { setPalette( QApplication::palette() ); - model_ = new TreeModel(QVector() << i18n("Name") << i18n("Value"), + model_ = new TreeModel(QVector() << i18n("Name") << i18n("Value") << i18n("Type"), this); TooltipRoot* tr = new TooltipRoot(model_); model_->setRootItem(tr); var_ = ICore::self()->debugController()->currentSession()-> variableController()->createVariable( model_, tr, identifier); tr->init(var_); var_->attachMaybe(this, "variableCreated"); QVBoxLayout* l = new QVBoxLayout(this); l->setContentsMargins(0, 0, 0, 0); view_ = new AsyncTreeView(model_, this); view_->header()->resizeSection(0, 150); view_->header()->resizeSection(1, 90); view_->setSelectionBehavior(QAbstractItemView::SelectRows); view_->setSelectionMode(QAbstractItemView::SingleSelection); view_->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); view_->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Expanding); l->addWidget(view_); itemHeight_ = view_->indexRowSizeHint(model_->indexForItem(var_, 0)); connect(view_->verticalScrollBar(), SIGNAL(rangeChanged(int,int)), this, SLOT(slotRangeChanged(int,int))); selection_ = view_->selectionModel(); selection_->select(model_->indexForItem(var_, 0), QItemSelectionModel::Rows | QItemSelectionModel::ClearAndSelect); QHBoxLayout* labelL = new QHBoxLayout(); labelL->setContentsMargins(11, 0, 11, 6); QLabel* label = new QLabel(i18n("Watch this"), this); labelL->addWidget(label); - QLabel* label2 = new QLabel(i18n("Stop on change"), + QLabel* label2 = new QLabel(i18n("Stop on change"), this); labelL->addWidget(label2); connect(label, SIGNAL(linkActivated(QString)), this, SLOT(slotLinkActivated(QString))); connect(label2, SIGNAL(linkActivated(QString)), this, SLOT(slotLinkActivated(QString))); QHBoxLayout* inner = new QHBoxLayout(); l->addLayout(inner); inner->setContentsMargins(0, 0, 0, 0); inner->addLayout(labelL); inner->addStretch(); SizeGrip* g = new SizeGrip(this); g->setFixedSize(16, 16); inner->addWidget(g, 0, (Qt::Alignment)(Qt::AlignRight | Qt::AlignBottom)); move(position); } void VariableToolTip::variableCreated(bool hasValue) { view_->resizeColumns(); if (hasValue) { ActiveToolTip::showToolTip(this, 0.0); } else { close(); } } void VariableToolTip::slotLinkActivated(const QString& link) { Variable* v = var_; QItemSelection s = selection_->selection(); if (!s.empty()) { QModelIndex index = s.front().topLeft(); TreeItem *item = model_->itemForIndex(index); if (item) { Variable* v2 = dynamic_cast(item); if (v2) v = v2; } } IDebugSession *session = ICore::self()->debugController()->currentSession(); if (session && session->state() != IDebugSession::NotStartedState && session->state() != IDebugSession::EndedState) { if (link == "add_watch") { session->variableController()->addWatch(v); } else if (link == "watchpoint") { session->variableController()->addWatchpoint(v); } } close(); } void VariableToolTip::slotRangeChanged(int min, int max) { Q_ASSERT(min == 0); Q_UNUSED(min); QRect rect = QApplication::desktop()->screenGeometry(this); if (pos().y() + height() + max*itemHeight_ < rect.bottom()) resize(width(), height() + max*itemHeight_); else { // Oh, well, I'm sorry, but here's the scrollbar you was // longing to see view_->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); } } } #include "variabletooltip.moc" diff --git a/debugger/variable/variablewidget.cpp b/debugger/variable/variablewidget.cpp index 6b0b7b1570..825e33b45b 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 #include #include #include #include #include #include #include #include #include #include #include "../../interfaces/icore.h" #include #include "../interfaces/ivariablecontroller.h" #include "variablecollection.h" /** 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), variablesRoot_(controller->variableCollection()->root()) { //setWindowIcon(QIcon::fromTheme("math_brace")); setWindowIcon(QIcon::fromTheme("debugger")); setWindowTitle(i18n("Debugger Variables")); varTree_ = new VariableTree(controller, this); setFocusProxy(varTree_); watchVarEditor_ = new KHistoryComboBox( this ); QVBoxLayout *topLayout = new QVBoxLayout(this); topLayout->addWidget(varTree_, 10); topLayout->addWidget(watchVarEditor_); topLayout->setMargin(0); connect(watchVarEditor_, SIGNAL(returnPressed(QString)), this, SLOT(slotAddWatch(QString))); //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.
")); watchVarEditor_->setWhatsThis( i18n("Expression entry" "Type in expression to watch.")); } void VariableWidget::slotAddWatch(const QString &expression) { if (!expression.isEmpty()) { watchVarEditor_->addToHistory(expression); kDebug(9012) << "Trying to add watch\n"; Variable* v = variablesRoot_->watches()->add(expression); if (v) { QModelIndex index = variableCollection()->indexForItem(v, 0); /* 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. */ //varTree_->setExpanded(index, true); } 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) : AsyncTreeView(controller->variableCollection(), parent) #if 0 , activePopup_(0), toggleWatch_(0) #endif { setRootIsDecorated(true); setAllColumnsShowFocus(true); 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(model())); 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, SIGNAL(triggered()), m_signalMapper, SLOT(map())); addAction(act); } connect(m_signalMapper, SIGNAL(mapped(int)), SLOT(changeVariableFormat(int))); m_watchDelete = new QAction( QIcon::fromTheme("edit-delete"), i18n( "Remove Watch Variable" ), this); m_watchDelete->setShortcut(Qt::Key_Delete); m_watchDelete->setShortcutContext(Qt::WidgetWithChildrenShortcut); addAction(m_watchDelete); connect(m_watchDelete, SIGNAL(triggered(bool)), SLOT(watchDelete())); m_copyVariableValue = new QAction(i18n("&Copy Value"), this); m_copyVariableValue->setShortcutContext(Qt::WidgetWithChildrenShortcut); m_copyVariableValue->setShortcut(QKeySequence::Copy); connect(m_copyVariableValue, SIGNAL(triggered(bool)), SLOT(copyVariableValue())); - m_stopOnChange = new QAction(i18n("&Stop on change"), this); + m_stopOnChange = new QAction(i18n("&Stop on Change"), this); connect(m_stopOnChange, SIGNAL(triggered(bool)), SLOT(stopOnChange())); } Variable* VariableTree::selectedVariable() const { if (selectionModel()->selectedRows().isEmpty()) return 0; TreeItem* item = collection()->itemForIndex(selectionModel()->selectedRows().first()); if (!item) return 0; return dynamic_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 (dynamic_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 (!dynamic_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)) { KMenu 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 KMenu(this); KMenu 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, QString("&%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 // ************************************************************************** // ************************************************************************** // ************************************************************************** } #include "variablewidget.moc" diff --git a/interfaces/isourceformatter.cpp b/interfaces/isourceformatter.cpp index fc4a63614e..53946110a7 100644 --- a/interfaces/isourceformatter.cpp +++ b/interfaces/isourceformatter.cpp @@ -1,73 +1,195 @@ /* This file is part of KDevelop Copyright (C) 2008 Cédric Pasteur 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 "isourceformatter.h" #include namespace KDevelop { SettingsWidget::SettingsWidget(QWidget *parent) : QWidget(parent) { } SettingsWidget::~SettingsWidget() { } ISourceFormatter::~ISourceFormatter() { } +SourceFormatterStyle::SourceFormatterStyle() + : m_usePreview(false) +{ +}; + +SourceFormatterStyle::SourceFormatterStyle(const QString &name) + : m_name(name) +{ +} + +void SourceFormatterStyle::setContent(const QString &content) +{ + m_content = content; +} + +void SourceFormatterStyle::setCaption(const QString &caption) +{ + m_caption = caption; +} + +QString SourceFormatterStyle::content() const +{ + return m_content; +} + +QString SourceFormatterStyle::caption() const +{ + return m_caption; +} + +QString SourceFormatterStyle::name() const +{ + return m_name; +} + +QString SourceFormatterStyle::description() const +{ + return m_description; +} + +void SourceFormatterStyle::setDescription(const QString &desc) +{ + m_description = desc; +} + +bool SourceFormatterStyle::usePreview() const +{ + return m_usePreview; +} + +void SourceFormatterStyle::setUsePreview(bool use) +{ + m_usePreview = use; +} + +void SourceFormatterStyle::setMimeTypes(const SourceFormatterStyle::MimeList& types) +{ + m_mimeTypes = types; +} + +void SourceFormatterStyle::setMimeTypes(const QStringList& types) +{ + for ( auto t: types ) { + auto items = t.split('|'); + if ( items.size() != 2 ) { + continue; + } + m_mimeTypes << MimeHighlightPair{items.at(0), items.at(1)}; + } +} + +void SourceFormatterStyle::setOverrideSample(const QString &sample) +{ + m_overrideSample = sample; +} + +QString SourceFormatterStyle::overrideSample() const +{ + return m_overrideSample; +} + +SourceFormatterStyle::MimeList SourceFormatterStyle::mimeTypes() const +{ + return m_mimeTypes; +} + +QVariant SourceFormatterStyle::mimeTypesVariant() const +{ + QStringList result; + for ( const auto& item: m_mimeTypes ) { + result << item.mimeType + "|" + item.highlightMode; + } + return QVariant::fromValue(result); +} + +bool SourceFormatterStyle::supportsLanguage(const QString &language) const +{ + for ( const auto& item: m_mimeTypes ) { + if ( item.highlightMode == language ) { + return true; + } + } + return false; +} + +QString SourceFormatterStyle::modeForMimetype(const KMimeType::Ptr& mime) const +{ + for ( const auto& item: mimeTypes() ) { + if ( mime->is(item.mimeType) ) { + return item.highlightMode; + } + } + return QString(); +} + +void SourceFormatterStyle::copyDataFrom(SourceFormatterStyle *other) +{ + m_content = other->content(); + m_mimeTypes = other->mimeTypes(); + m_overrideSample = other->overrideSample(); +} + QString ISourceFormatter::optionMapToString(const QMap &map) { QString options; QMap::const_iterator it = map.constBegin(); for (; it != map.constEnd(); ++it) { options += it.key(); options += '='; options += it.value().toString(); options += ','; } return options; } QMap ISourceFormatter::stringToOptionMap(const QString &options) { QMap map; QStringList pairs = options.split(',', QString::SkipEmptyParts); QStringList::const_iterator it; for (it = pairs.constBegin(); it != pairs.constEnd(); ++it) { QStringList bits = (*it).split('='); map[bits[0]] = bits[1]; } return map; } QString ISourceFormatter::missingExecutableMessage(const QString &name) { return i18n("The executable %1 cannot be found. Please make sure" " it is installed and can be executed.
" "The plugin will not work until you fix this problem.", "" + name + ""); } } // kate: indent-mode cstyle; space-indent off; tab-width 4; diff --git a/interfaces/isourceformatter.h b/interfaces/isourceformatter.h index 79c616d03b..6709f7d738 100644 --- a/interfaces/isourceformatter.h +++ b/interfaces/isourceformatter.h @@ -1,195 +1,231 @@ /* This file is part of KDevelop Copyright (C) 2008 Cédric Pasteur 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. */ #ifndef KDEVPLATFORM_ISOURCEFORMATTER_H #define KDEVPLATFORM_ISOURCEFORMATTER_H #include #include #include #include "interfacesexport.h" class KUrl; namespace KDevelop { class KDEVPLATFORMINTERFACES_EXPORT SourceFormatterStyle { public: - SourceFormatterStyle() : m_usePreview(false) {}; - SourceFormatterStyle( const QString& name ) : m_name(name) {} - void setContent( const QString& content ) { m_content = content; } - void setCaption( const QString& caption ) { m_caption = caption; } - QString content() const { return m_content; } - QString caption() const { return m_caption; } - QString name() const { return m_name; } - QString description() const { return m_description; } - void setDescription( const QString& desc ) { m_description = desc; } - bool usePreview() const { return m_usePreview; } - void setUsePreview(bool use) { m_usePreview = use; } + struct MimeHighlightPair { + QString mimeType; + QString highlightMode; + }; + typedef QVector MimeList; + + SourceFormatterStyle(); + SourceFormatterStyle( const QString& name ); + void setContent( const QString& content ); + void setCaption( const QString& caption ); + QString content() const; + QString caption() const; + QString name() const; + QString description() const; + void setDescription( const QString& desc ); + bool usePreview() const; + void setUsePreview(bool use); + + void setMimeTypes( const MimeList& types ); + void setMimeTypes( const QStringList& types ); + + /// Provides the possibility to the item to return a better-suited + /// code sample. If empty, the default is used. + QString overrideSample() const; + void setOverrideSample( const QString& sample ); + + MimeList mimeTypes() const; + /// mime types as a QVariantList, type and mode separated by | in strings + QVariant mimeTypesVariant() const; + bool supportsLanguage(const QString& language) const; + + /// get the language / highlight mode for a given @p mime + QString modeForMimetype(const KMimeType::Ptr& mime) const; + + /// Copy content, mime types and code sample from @p other. + void copyDataFrom(SourceFormatterStyle *other); + private: bool m_usePreview; QString m_name; QString m_caption; QString m_content; QString m_description; + QString m_overrideSample; + MimeList m_mimeTypes; }; +/** + * @brief An object describing a style associated with a plugin + * which can deal with this style. + */ +struct SourceFormatterStyleItem { + QString engine; + SourceFormatterStyle style; +}; + +typedef QVector SourceFormatterItemList; + /** * @short A widget to edit a style * A plugin should inherit this widget to create a widget to * edit a style. * @author Cédric Pasteur */ class KDEVPLATFORMINTERFACES_EXPORT SettingsWidget : public QWidget { Q_OBJECT public: SettingsWidget(QWidget *parent = 0); virtual ~SettingsWidget(); /** This function is called after the creation of the dialog. * it should initialise the widgets with the values corresponding to * the predefined style \arg name if it's not empty, or * to the string \arg content. */ virtual void load(const SourceFormatterStyle&) = 0; /** \return A string representing the state of the config. */ virtual QString save() = 0; Q_SIGNALS: /** Emits this signal when a setting was changed and the preview * needs to be updated. \arg text is the text that will be shown in the * editor. One might want to show different text * according to the different pages shown in the widget. * Text should already be formatted. */ void previewTextChanged(const QString &text); }; /** * @short An interface for a source beautifier * An example of a plugin using an external executable to do * the formatting can be found in kdevelop/plugins/formatters/indent_plugin.cpp. * @author Cédric Pasteur */ class KDEVPLATFORMINTERFACES_EXPORT ISourceFormatter { public: virtual ~ISourceFormatter(); /** \return The name of the plugin. This should contain only * ASCII chars and no spaces. This will be used internally to identify * the plugin. */ virtual QString name() = 0; /** \return A caption describing the plugin. */ virtual QString caption() = 0; /** \return A more complete description of the plugin. * The string should be written in Rich text. It can eg contain * a link to the project homepage. */ virtual QString description() = 0; - /** \return The highlighting mode for Kate corresponding to this mime. - */ - virtual QString highlightModeForMime(const KMimeType::Ptr &mime) = 0; - /** Formats using the current style. * @param text The text to format * @param url The URL to which the text belongs (its contents must not be changed). * @param leftContext The context at the left side of the text. If it is in another line, it must end with a newline. * @param rightContext The context at the right side of the text. If it is in the next line, it must start with a newline. * * If the source-formatter cannot work correctly with the context, it will just return the input text. */ virtual QString formatSource(const QString &text, const KUrl& url, const KMimeType::Ptr &mime, const QString& leftContext = QString(), const QString& rightContext = QString()) = 0; /** * Format with the given style, this is mostly for the kcm to format the preview text * Its a bit of a hassle that this needs to be public API, but I can't find a better way * to do this. */ virtual QString formatSourceWithStyle( SourceFormatterStyle, const QString& text, const KUrl& url, const KMimeType::Ptr &mime, const QString& leftContext = QString(), const QString& rightContext = QString() ) = 0; /** \return A map of predefined styles (a key and a caption for each type) */ virtual QList predefinedStyles() = 0; /** \return The widget to edit a style. */ virtual SettingsWidget* editStyleWidget(const KMimeType::Ptr &mime) = 0; /** \return The text used in the config dialog to preview the current style. */ - virtual QString previewText(const KMimeType::Ptr &mime) = 0; + virtual QString previewText(const SourceFormatterStyle* style, const KMimeType::Ptr &mime) = 0; struct Indentation { Indentation() : indentationTabWidth(0), indentWidth(0) { } // If this indentation is really valid bool isValid() const { return indentationTabWidth != 0 || indentWidth != 0; } // The length of one tab used for indentation. // Zero if unknown, -1 if tabs should not be used for indentation int indentationTabWidth; // The number of columns that equal one indentation level. // If this is zero, the default should be used. int indentWidth; }; /** \return The indentation of the style applicable for the given url. */ virtual Indentation indentation(const KUrl& url) = 0; /** \return A string representing the map. Values are written in the form * key=value and separated with ','. */ static QString optionMapToString(const QMap &map); /** \return A map corresponding to the string, that was created with * \ref optionMapToString. */ static QMap stringToOptionMap(const QString &option); /** \return A message to display when an executable needed by a * plugin is missing. This should be returned as description * if a needed executable is not found. */ static QString missingExecutableMessage(const QString &name); }; } Q_DECLARE_INTERFACE(KDevelop::ISourceFormatter, "org.kdevelop.ISourceFormatter") +Q_DECLARE_TYPEINFO(KDevelop::SourceFormatterStyle::MimeHighlightPair, Q_MOVABLE_TYPE); #endif // KDEVPLATFORM_ISOURCEFORMATTER_H // kate: indent-mode cstyle; space-indent off; tab-width 4; diff --git a/language/CMakeLists.txt b/language/CMakeLists.txt index c07fce2af5..24bd214599 100644 --- a/language/CMakeLists.txt +++ b/language/CMakeLists.txt @@ -1,358 +1,363 @@ add_definitions( -DKDE_DEFAULT_DEBUG_AREA=9505 ) add_subdirectory(highlighting/tests) add_subdirectory(duchain/tests) add_subdirectory(backgroundparser/tests) add_subdirectory(codegen/tests) set(kdevplatformlanguage_LIB_SRCS 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/referencecounting.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/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/dumpchain.cpp duchain/indexedstring.cpp duchain/duchainregister.cpp duchain/persistentsymboltable.cpp duchain/instantiationinformation.cpp duchain/problem.cpp duchain/types/typesystem.cpp duchain/types/typeregister.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/repositories/abstractitemrepository.cpp duchain/repositories/itemrepositoryregistry.cpp duchain/repositories/typerepository.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/templatesmodel.cpp # codegen/templateclassgenerator.cpp codegen/codedescription.cpp # codegen/sourcefiletemplate.cpp # codegen/templaterenderer.cpp # codegen/templateengine.cpp # codegen/archivetemplateloader.cpp codegen/basicrefactoring.cpp + codegen/progressdialogs/refactoringdialog.cpp util/setrepository.cpp util/includeitem.cpp util/navigationtooltip.cpp highlighting/colorcache.cpp highlighting/configurablecolors.cpp highlighting/codehighlighting.cpp checks/dataaccessrepository.cpp checks/dataaccess.cpp checks/controlflowgraph.cpp checks/controlflownode.cpp ) +kde4_add_ui_files(kdevplatformlanguage_LIB_SRCS + codegen/basicrefactoring.ui + codegen/progressdialogs/refactoringdialog.ui) + add_library(kdevplatformlanguage SHARED ${kdevplatformlanguage_LIB_SRCS}) target_link_libraries(kdevplatformlanguage LINK_PUBLIC KF5::ThreadWeaver kdevplatforminterfaces LINK_PRIVATE KF5::TextEditor KF5::Parts KF5::NewStuff KF5::Archive KF5::KDE4Support kdevplatformutil # ${Grantlee_CORE_LIBRARIES} ) set_target_properties(kdevplatformlanguage PROPERTIES VERSION ${KDEVPLATFORM_LIB_VERSION} SOVERSION ${KDEVPLATFORM_LIB_SOVERSION}) install(TARGETS kdevplatformlanguage EXPORT KDevPlatformTargets ${INSTALL_TARGETS_DEFAULT_ARGS} ) ########### install files ############### install(FILES languageexport.h DESTINATION ${INCLUDE_INSTALL_DIR}/kdevplatform/language 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 ${INCLUDE_INSTALL_DIR}/kdevplatform/language/interfaces COMPONENT Devel ) install(FILES editor/persistentmovingrange.h editor/documentrange.h editor/documentcursor.h editor/simplecursor.h editor/simplerange.h editor/cursorinrevision.h editor/rangeinrevision.h editor/modificationrevision.h editor/modificationrevisionset.h DESTINATION ${INCLUDE_INSTALL_DIR}/kdevplatform/language/editor COMPONENT Devel ) install(FILES backgroundparser/backgroundparser.h backgroundparser/parsejob.h backgroundparser/parseprojectjob.h backgroundparser/urlparselock.h backgroundparser/documentchangetracker.h DESTINATION ${INCLUDE_INSTALL_DIR}/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 ${INCLUDE_INSTALL_DIR}/kdevplatform/language/util COMPONENT Devel ) install(FILES duchain/referencecounting.h 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/dumpchain.h duchain/indexedstring.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 ${INCLUDE_INSTALL_DIR}/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/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 DESTINATION ${INCLUDE_INSTALL_DIR}/kdevplatform/language/duchain/types COMPONENT Devel ) install(FILES duchain/builders/abstractcontextbuilder.h duchain/builders/abstractdeclarationbuilder.h duchain/builders/abstracttypebuilder.h duchain/builders/abstractusebuilder.h DESTINATION ${INCLUDE_INSTALL_DIR}/kdevplatform/language/duchain/builders COMPONENT Devel ) install(FILES duchain/repositories/itemrepositoryexampleitem.h duchain/repositories/abstractitemrepository.h duchain/repositories/itemrepositoryregistry.h duchain/repositories/repositorymanager.h duchain/repositories/itemrepository.h duchain/repositories/typerepository.h DESTINATION ${INCLUDE_INSTALL_DIR}/kdevplatform/language/duchain/repositories 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 ${INCLUDE_INSTALL_DIR}/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/templaterenderer.h codegen/templateengine.h codegen/sourcefiletemplate.h codegen/templateclassgenerator.h codegen/codedescription.h codegen/basicrefactoring.h DESTINATION ${INCLUDE_INSTALL_DIR}/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 ${INCLUDE_INSTALL_DIR}/kdevplatform/language/duchain/navigation COMPONENT Devel ) install(FILES highlighting/codehighlighting.h highlighting/colorcache.h highlighting/configurablecolors.h DESTINATION ${INCLUDE_INSTALL_DIR}/kdevplatform/language/highlighting COMPONENT Devel ) install(FILES checks/dataaccess.h checks/dataaccessrepository.h checks/controlflowgraph.h checks/controlflownode.h DESTINATION ${INCLUDE_INSTALL_DIR}/kdevplatform/language/checks COMPONENT Devel ) diff --git a/language/codegen/basicrefactoring.cpp b/language/codegen/basicrefactoring.cpp index be33e2948d..498afbdb14 100644 --- a/language/codegen/basicrefactoring.cpp +++ b/language/codegen/basicrefactoring.cpp @@ -1,289 +1,298 @@ /* 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 -#include -#include -#include -#include -#include // KDE / KDevelop #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include "progressdialogs/refactoringdialog.h" + +#include "ui_basicrefactoring.h" 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()), 0); action->setData(QVariant::fromValue(IndexedDeclaration(declaration))); action->setIcon(QIcon::fromTheme("edit-rename")); connect(action, SIGNAL(triggered(bool)), this, SLOT(executeRenameAction())); extension.addAction(ContextMenuExtension::RefactorGroup, action); } } } 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(true); 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()) { kDebug() << "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(true); } 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()) kDebug() << "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(true); } KDevelop::IndexedDeclaration BasicRefactoring::declarationUnderCursor(bool allowUse) { KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView(); Q_ASSERT(view); KTextEditor::Document* doc = view->document(); DUChainReadLocker lock; if (allowUse) return DUChainUtils::itemUnderCursor(doc->url(), SimpleCursor(view->cursorPosition())); else return DUChainUtils::declarationInLine(SimpleCursor(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(); - QString replacementName; + lock.unlock(); - BasicRefactoringCollector *collector = new BasicRefactoringCollector(decl); - QPointer dialog = new QDialog(); - QTabWidget tabWidget; - UsesWidget uses(declaration, collector); + NameAndCollector nc = newNameForDeclaration(DeclarationPointer(declaration)); - //So the context-links work - QWidget *navigationWidget = declaration->context()->createNavigationWidget(declaration); - AbstractNavigationWidget* abstractNavigationWidget = dynamic_cast(navigationWidget); - if (abstractNavigationWidget) - connect(&uses, SIGNAL(navigateDeclaration(KDevelop::IndexedDeclaration)), abstractNavigationWidget, SLOT(navigateDeclaration(KDevelop::IndexedDeclaration))); + if (nc.newName == originalName || nc.newName.isEmpty()) + return; - QVBoxLayout verticalLayout; - QHBoxLayout actionsLayout; - dialog->setLayout(&verticalLayout); - dialog->setWindowTitle(i18n("Rename %1", declaration->toString())); + renameCollectedDeclarations(nc.collector.data(), nc.newName, originalName); +} - QLabel newNameLabel(i18n("New name:")); - actionsLayout.addWidget(&newNameLabel); +bool BasicRefactoring::acceptForContextMenu(const Declaration *decl) +{ + // Default implementation. Some language plugins might override it to + // handle some cases. + Q_UNUSED(decl); + return true; +} - QLineEdit edit(declaration->identifier().identifier().str()); - newNameLabel.setBuddy(&edit); +void BasicRefactoring::executeRenameAction() +{ + QAction *action = qobject_cast(sender()); + if (action) { + IndexedDeclaration decl = action->data().value(); + if(!decl.isValid()) + decl = declarationUnderCursor(); + startInteractiveRename(decl); + } +} - actionsLayout.addWidget(&edit); - edit.setText(originalName); - edit.setFocus(); - edit.selectAll(); - QPushButton goButton(i18n("Rename")); - goButton.setToolTip(i18n("Note: All overloaded functions, overloads, forward-declarations, etc. will be renamed too")); - actionsLayout.addWidget(&goButton); - connect(&goButton, SIGNAL(clicked(bool)), dialog, SLOT(accept())); +BasicRefactoring::NameAndCollector BasicRefactoring::newNameForDeclaration(const KDevelop::DeclarationPointer& declaration) +{ + DUChainReadLocker lock; + if (!declaration) { + return {}; + } - QPushButton cancelButton(i18n("Cancel")); - actionsLayout.addWidget(&cancelButton); - verticalLayout.addLayout(&actionsLayout); + QSharedPointer collector(new BasicRefactoringCollector(declaration.data())); - tabWidget.addTab(&uses, i18n("Uses")); - if (navigationWidget) - tabWidget.addTab(navigationWidget, i18n("Declaration Info")); + Ui::RenameDialog renameDialog; + QDialog dialog; + renameDialog.setupUi(&dialog); + + UsesWidget uses(declaration.data(), collector); - verticalLayout.addWidget(&tabWidget); + //So the context-links work + QWidget *navigationWidget = declaration->context()->createNavigationWidget(declaration.data()); + AbstractNavigationWidget* abstractNavigationWidget = dynamic_cast(navigationWidget); + if (abstractNavigationWidget) + connect(&uses, SIGNAL(navigateDeclaration(KDevelop::IndexedDeclaration)), abstractNavigationWidget, SLOT(navigateDeclaration(KDevelop::IndexedDeclaration))); - connect(&cancelButton, SIGNAL(clicked(bool)), dialog, SLOT(reject())); + 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(); - dialog->resize(750, 550); - if (dialog->exec() != QDialog::Accepted) - return; - replacementName = edit.text(); - if (replacementName == originalName || replacementName.isEmpty()) - return; + if (dialog.exec() != QDialog::Accepted) + return {}; + RefactoringProgressDialog refactoringProgress(i18n("Renaming \"%1\" to \"%2\"", declarationName, renameDialog.edit->text()), collector.data()); + if (!collector->isReady()) { + refactoringProgress.exec(); + if (refactoringProgress.result() != QDialog::Accepted) { + return {}; + } + } + + //TODO: input validation + return {renameDialog.edit->text(),collector}; +} + +DocumentChangeSet BasicRefactoring::renameCollectedDeclarations(KDevelop::BasicRefactoringCollector* collector, const QString& replacementName, const QString& originalName, bool apply) +{ DocumentChangeSet changes; - lock.lock(); - foreach (const KDevelop::IndexedTopDUContext &collected, collector->allUsingContexts()) { + DUChainReadLocker lock; + + foreach (const KDevelop::IndexedTopDUContext& collected, collector->allUsingContexts()) { QSet hadIndices; - foreach (const IndexedDeclaration &decl, collector->declarations()) { + 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(0, i18n("Applying changes failed: %1", result.m_failureReason)); - return; + return {}; } } } DocumentChangeSet::ChangeResult result = applyChangesToDeclarations(originalName, replacementName, changes, collector->declarations()); - if(!result) { + if (!result) { KMessageBox::error(0, i18n("Applying changes failed: %1", result.m_failureReason)); - return; + 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) { + if (!result) { KMessageBox::error(0, i18n("Applying changes failed: %1", result.m_failureReason)); - return; } -} -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(); - startInteractiveRename(decl); - } + return {}; } //END: BasicRefactoring } // End of namespace KDevelop #include "basicrefactoring.moc" diff --git a/language/codegen/basicrefactoring.h b/language/codegen/basicrefactoring.h index 06606a5b87..8c7f782bdc 100644 --- a/language/codegen/basicrefactoring.h +++ b/language/codegen/basicrefactoring.h @@ -1,112 +1,134 @@ /* 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. */ #ifndef BASICREFACTORING_H_ #define BASICREFACTORING_H_ #include -#include +#include #include #include #include namespace KDevelop { class ContextMenuExtension; class IndexedDeclaration; class Context; class Declaration; class DUContext; - /** * A widget that show the uses that it has collected for * the given declaration. */ class KDEVPLATFORMLANGUAGE_EXPORT BasicRefactoringCollector : public UsesWidget::UsesWidgetCollector { public: BasicRefactoringCollector(const IndexedDeclaration &decl); QVector allUsingContexts() const; protected: /// Process the uses for the given TopDUContext. virtual void processUses(KDevelop::ReferencedTopDUContext topContext) override; private: QVector m_allUsingContexts; }; + /// The base class for Refactoring classes from Language plugins. class KDEVPLATFORMLANGUAGE_EXPORT BasicRefactoring : public QObject { Q_OBJECT public: explicit BasicRefactoring(QObject *parent = NULL); /// Update the context menu with the "Rename" action. virtual void fillContextMenu(KDevelop::ContextMenuExtension &extension, KDevelop::Context *context); + struct NameAndCollector { + QString newName; + QSharedPointer collector; + }; + protected: /** * Apply the changes to the uses that can be found inside the given * context and its children. * NOTE: the DUChain must be locked. */ virtual DocumentChangeSet::ChangeResult applyChanges(const QString &oldName, const QString &newName, DocumentChangeSet &changes, DUContext *context, int usedDeclarationIndex); /** * Apply the changes to the given declarations. * NOTE: the DUChain must be locked. */ virtual DocumentChangeSet::ChangeResult applyChangesToDeclarations(const QString &oldName, const QString &newName, DocumentChangeSet &changes, const QList &declarations); /** * Get the declaration under the current position of the cursor. * * @param allowUse Set to false if the declarations to be returned * cannot come from uses. */ virtual IndexedDeclaration declarationUnderCursor(bool allowUse = true); - /// Start the renaming of a declaration. + /** + * Start the renaming of a declaration. + * This function retrieves the new name for declaration @p decl and if approved renames all instances of it. + */ virtual void startInteractiveRename(const KDevelop::IndexedDeclaration &decl); + /** + * Asks user to input a new name for @p declaration + * @return new name or empty string if user changed his mind or new name contains inappropriate symbols (e.g. spaces, points, braces e.t.c) and the collector used for collecting information about @p declaration. + * NOTE: unlock the DUChain before calling this. + */ + virtual BasicRefactoring::NameAndCollector newNameForDeclaration(const KDevelop::DeclarationPointer& declaration); + + /** + * Renames all declarations collected by @p collector from @p oldName to @p newName + * @param apply - if changes should be applied immediately + * @return all changes if @p apply is false and empty set otherwise. + */ + DocumentChangeSet renameCollectedDeclarations(KDevelop::BasicRefactoringCollector* collector, const QString& replacementName, const QString& originalName, bool apply = true); + /** * @returns true if we can show the interactive rename widget for the * given declaration. The default implementation just returns true. */ virtual bool acceptForContextMenu(const Declaration *decl); private slots: void executeRenameAction(); }; } // End of namespace KDevelop #endif /* BASICREFACTORING_H_ */ diff --git a/language/codegen/basicrefactoring.ui b/language/codegen/basicrefactoring.ui new file mode 100644 index 0000000000..e353d1e8d5 --- /dev/null +++ b/language/codegen/basicrefactoring.ui @@ -0,0 +1,95 @@ + + + RenameDialog + + + + 0 + 0 + 750 + 550 + + + + Rename + + + + + + + + &New name: + + + edit + + + + + + + + + + <html><head/><body><p>&quot;Note: All overloaded functions, overloads, forward-declarations, etc. will be renamed too&quot;</p></body></html> + + + &Rename + + + + + + + &Cancel + + + + + + + + + -1 + + + + + + + + + cancelButton + clicked() + RenameDialog + reject() + + + 702 + 22 + + + 374 + 274 + + + + + goButton + clicked() + RenameDialog + accept() + + + 617 + 22 + + + 374 + 274 + + + + + diff --git a/language/codegen/codedescriptionmetatypes.h b/language/codegen/codedescriptionmetatypes.h index 3877224555..ff4bfe447e 100644 --- a/language/codegen/codedescriptionmetatypes.h +++ b/language/codegen/codedescriptionmetatypes.h @@ -1,67 +1,70 @@ /* 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. */ #ifndef KDEVPLATFORM_CODEDESCRIPTIONMETATYPES_H #define KDEVPLATFORM_CODEDESCRIPTIONMETATYPES_H #include "codedescription.h" #include #define GRANTLEE_LOOKUP_PROPERTY(name) \ if (property == #name) return QVariant::fromValue(object.name); #define GRANTLEE_LOOKUP_LIST_PROPERTY(name) \ if (property == #name) return QVariant::fromValue(KDevelop::CodeDescription::toVariantList(object.name)); GRANTLEE_BEGIN_LOOKUP(KDevelop::VariableDescription) GRANTLEE_LOOKUP_PROPERTY(name) GRANTLEE_LOOKUP_PROPERTY(type) GRANTLEE_LOOKUP_PROPERTY(access) GRANTLEE_LOOKUP_PROPERTY(value) GRANTLEE_END_LOOKUP GRANTLEE_BEGIN_LOOKUP(KDevelop::FunctionDescription) GRANTLEE_LOOKUP_PROPERTY(name) GRANTLEE_LOOKUP_PROPERTY(access) GRANTLEE_LOOKUP_LIST_PROPERTY(arguments) GRANTLEE_LOOKUP_LIST_PROPERTY(returnArguments) GRANTLEE_LOOKUP_PROPERTY(isConstructor) GRANTLEE_LOOKUP_PROPERTY(isDestructor) GRANTLEE_LOOKUP_PROPERTY(isVirtual) GRANTLEE_LOOKUP_PROPERTY(isStatic) + GRANTLEE_LOOKUP_PROPERTY(isConst) + GRANTLEE_LOOKUP_PROPERTY(isSignal) + GRANTLEE_LOOKUP_PROPERTY(isSlot) if (property == "returnType") { return object.returnType(); } GRANTLEE_END_LOOKUP GRANTLEE_BEGIN_LOOKUP(KDevelop::InheritanceDescription) GRANTLEE_LOOKUP_PROPERTY(inheritanceMode) GRANTLEE_LOOKUP_PROPERTY(baseType) GRANTLEE_END_LOOKUP GRANTLEE_BEGIN_LOOKUP(KDevelop::ClassDescription) GRANTLEE_LOOKUP_PROPERTY(name) GRANTLEE_LOOKUP_LIST_PROPERTY(baseClasses) GRANTLEE_LOOKUP_LIST_PROPERTY(members) GRANTLEE_LOOKUP_LIST_PROPERTY(methods) GRANTLEE_END_LOOKUP #endif // KDEVPLATFORM_CODEDESCRIPTIONMETATYPES_H diff --git a/language/codegen/progressdialogs/refactoringdialog.cpp b/language/codegen/progressdialogs/refactoringdialog.cpp new file mode 100644 index 0000000000..1e9ffc88b0 --- /dev/null +++ b/language/codegen/progressdialogs/refactoringdialog.cpp @@ -0,0 +1,66 @@ +/*************************************************************************** + * This file is part of KDevelop * + * Copyright 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 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 "refactoringdialog.h" + +#include +#include +#include + +namespace KDevelop +{ + +RefactoringProgressDialog::RefactoringProgressDialog(const QString& action, KDevelop::UsesCollector* collector) + : m_collector(collector) +{ + if (!collector) { + return; + } + + m_rd.setupUi(this); + + m_rd.progressBar->setMinimum(0); + m_rd.progressBar->setMaximum(0); + m_rd.renameLabel->setText(action); + connect(m_collector, SIGNAL(processUsesSignal(KDevelop::ReferencedTopDUContext)), this, SLOT(processUses(KDevelop::ReferencedTopDUContext))); + connect(m_collector, SIGNAL(progressSignal(uint, uint)), this, SLOT(progress(uint, uint))); + connect(m_collector, SIGNAL(maximumProgressSignal(uint)), this, SLOT(maximumProgress(uint))); +} + +void RefactoringProgressDialog::progress(uint done, uint max) +{ + if (done == max) + accept(); +} + +void RefactoringProgressDialog::maximumProgress(uint max) +{ + if (max == 0) + accept(); +} + +void RefactoringProgressDialog::processUses(const KDevelop::ReferencedTopDUContext& context) +{ + DUChainReadLocker lock; + if (context.data()) { + m_rd.fileLabel->setText(context->url().str()); + } +} +} diff --git a/tests/autotestshell.cpp b/language/codegen/progressdialogs/refactoringdialog.h similarity index 64% copy from tests/autotestshell.cpp copy to language/codegen/progressdialogs/refactoringdialog.h index d3381133b9..52c3380007 100644 --- a/tests/autotestshell.cpp +++ b/language/codegen/progressdialogs/refactoringdialog.h @@ -1,30 +1,50 @@ /*************************************************************************** - * Copyright 2013 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 "autotestshell.h" +#ifndef PROGRESSDIALOGS_H +#define PROGRESSDIALOGS_H -KDevelop::AutoTestShell::AutoTestShell(const QStringList& plugins) - : m_plugins(plugins) +#include +#include + +#include "ui_refactoringdialog.h" + +namespace KDevelop { -} +class UsesCollector; -void KDevelop::AutoTestShell::init(const QStringList& plugins) +class RefactoringProgressDialog : public QDialog { - s_instance = new AutoTestShell(plugins); + Q_OBJECT +public: + RefactoringProgressDialog(const QString& action, UsesCollector* collector); + +private slots: + void progress(uint done, uint max); + void maximumProgress(uint max); + void processUses(const KDevelop::ReferencedTopDUContext& context); + +private: + UsesCollector* m_collector; + Ui::RefactoringDialog m_rd; +}; + } +#endif diff --git a/language/codegen/progressdialogs/refactoringdialog.ui b/language/codegen/progressdialogs/refactoringdialog.ui new file mode 100644 index 0000000000..59a20bef23 --- /dev/null +++ b/language/codegen/progressdialogs/refactoringdialog.ui @@ -0,0 +1,66 @@ + + + RefactoringDialog + + + + 0 + 0 + 536 + 99 + + + + Refactoring + + + + + + 5 + + + + + + + &Abort + + + + + + + + + + + + + + + + + + + + + + + abortButton + clicked() + RefactoringDialog + reject() + + + 489 + 77 + + + 267 + 49 + + + + + diff --git a/language/codegen/templateclassgenerator.cpp b/language/codegen/templateclassgenerator.cpp index 32ccd20f0c..f2e2ba0014 100644 --- a/language/codegen/templateclassgenerator.cpp +++ b/language/codegen/templateclassgenerator.cpp @@ -1,315 +1,338 @@ /* 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 "interfaces/icore.h" #include #include #include "language/codegen/documentchangeset.h" #include "codedescription.h" #include "templaterenderer.h" #include "sourcefiletemplate.h" #include "templateengine.h" #include #include #include #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(" "); + + InheritanceDescription desc; + desc.baseType = identifier; + desc.inheritanceMode = inheritanceMode; + return desc; +} + class KDevelop::TemplateClassGeneratorPrivate { public: SourceFileTemplate fileTemplate; KUrl 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 KUrl& baseUrl) : d(new TemplateClassGeneratorPrivate) { 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()) { KUrl url(d->baseUrl); QString outputName = d->renderer.render(outputFile.outputName, outputFile.identifier); url.addPath(outputName); d->fileUrls.insert(outputFile.identifier, url); } } return d->fileUrls; } KUrl TemplateClassGenerator::baseUrl() const { return d->baseUrl; } KUrl TemplateClassGenerator::fileUrl(const QString& outputFile) const { return fileUrls().value(outputFile); } void TemplateClassGenerator::setFileUrl(const QString& outputFile, const KUrl& url) { d->fileUrls.insert(outputFile, url); d->renderer.addVariable("output_file_" + outputFile.toLower(), KUrl::relativeUrl(d->baseUrl, url)); d->renderer.addVariable("output_file_" + outputFile.toLower() + "_absolute", url.toLocalFile()); } SimpleCursor TemplateClassGenerator::filePosition(const QString& outputFile) const { return d->filePositions.value(outputFile); } void TemplateClassGenerator::setFilePosition(const QString& outputFile, const SimpleCursor& 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("name", newName); } QString TemplateClassGenerator::identifier() const { return name(); } void TemplateClassGenerator::setIdentifier(const QString& identifier) { d->renderer.addVariable("identifier", identifier);; QStringList separators; separators << "::" << "." << ":" << "\\" << "/"; 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("namespaces", namespaces); } /// Specify license for this class void TemplateClassGenerator::setLicense(const QString& license) { kDebug() << "New Class: " << d->name << "Set license: " << d->license; d->license = license; d->renderer.addVariable("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["description"] = QVariant::fromValue(description); variables["members"] = CodeDescription::toVariantList(description.members); variables["functions"] = CodeDescription::toVariantList(description.methods); variables["base_classes"] = CodeDescription::toVariantList(description.baseClasses); d->renderer.addVariables(variables); } ClassDescription TemplateClassGenerator::description() const { return d->description; } void TemplateClassGenerator::addBaseClass(const QString& base) { - QStringList splitBase = base.split(' '); - QString identifier = splitBase.takeLast(); - QString inheritanceMode = splitBase.join(" "); - - InheritanceDescription desc; - desc.baseType = identifier; - desc.inheritanceMode = inheritanceMode; + const InheritanceDescription desc = descriptionFromString(base); ClassDescription cd = description(); cd.baseClasses << desc; setDescription(cd); DUChainReadLocker lock; - PersistentSymbolTable::Declarations decl = PersistentSymbolTable::self().getDeclarations(IndexedQualifiedIdentifier(QualifiedIdentifier(identifier))); + 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/templateclassgenerator.h b/language/codegen/templateclassgenerator.h index 7b13f53e29..76a08b79d8 100644 --- a/language/codegen/templateclassgenerator.h +++ b/language/codegen/templateclassgenerator.h @@ -1,178 +1,179 @@ /* 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. */ #ifndef KDEVPLATFORM_TEMPLATECLASSGENERATOR_H #define KDEVPLATFORM_TEMPLATECLASSGENERATOR_H #include #include #include "../languageexport.h" #include class KUrl; namespace KDevelop { class TemplateEngine; class SimpleCursor; struct ClassDescription; class TemplateRenderer; class SourceFileTemplate; class DocumentChangeSet; /** * Generates new classes from templates * * * @section Variables Variables Passed to Templates * * TemplateClassGenerator makes use of the ClassDescription returned by ClassGenerator::description(). * From this description, it constructs the following variables: * @li @c description (ClassDescription) - the class description * @li @c name (QString) - the class name, same as @c description.name * @li @c namespaces (QStringList) - the list of nested namespaces in which the class will be declared * @li @c identifier (QString) - the full class identifier, composed of namespaces and name * @li @c members (VariableDescriptionList) - data members, same as @c description.members * @li @c functions (FunctionDescriptionList) - function members, same as @c description.methods * @li @c base_classes (InheritanceDescriptionList) - directly inherited classes, same as @c description.baseClasses * @li @c license (QString) - the license for this class, including author copyright, without comment characters or indentation. It is recommended to use the "lines_prepend" filters from library "kdev_filters" to format it. * * For each output file, TemplateRenderer add two variables named @c output_file_x * and @c output_file_x_absolute, where @c x is replaced * with the file name specified in the template description file. * See TemplateRenderer::renderFileTemplate() for details. * * If the templates uses custom options, these options are added to the template variables. Their names match the * names specified in the options file, and their values to the values entered by the user. * * Subclasses can override templateVariables() and insert additional variables. * **/ class KDEVPLATFORMLANGUAGE_EXPORT TemplateClassGenerator { public: typedef QHash UrlHash; /** * Creates a new generator. * * You should call setTemplateDescription() before any other template-related functions. * * @param baseUrl the folder where new files will be created **/ explicit TemplateClassGenerator(const KUrl& baseUrl); virtual ~TemplateClassGenerator(); /** * @brief Selects the template to be used * * This function must be called before using any other functions. * * The passed @p templateDescription should be an absolute path to a template description (.desktop) file. * TemplateClassGenerator will attempt to find a template archive with a matching name. * * @param templateDescription the template description file **/ void setTemplateDescription(const SourceFileTemplate& templateDescription); /** * Set the name (without namespace) for this class */ void setName(const QString&); /** * \return The name of the class to generate (excluding namespaces) */ QString name() const; /** * \param identifier The Qualified identifier that the class will have */ virtual void setIdentifier(const QString& identifier); /** * \return The Identifier of the class to generate (including all used namespaces) */ virtual QString identifier() const; /** * \param namespaces The list of nested namespaces in which this class is to be declared */ virtual void setNamespaces(const QStringList& namespaces) const; /** * \return The list of nested namespace in which this class will be declared */ virtual QStringList namespaces() const; void addBaseClass(const QString& base); + void setBaseClasses(const QList& bases); QList directBaseClasses() const; QList allBaseClasses() const; void setLicense(const QString& license); QString license() const; void setDescription(const ClassDescription& description); ClassDescription description() const; virtual DocumentChangeSet generate(); QHash fileLabels() const; KUrl baseUrl() const; UrlHash fileUrls() const; void setFileUrl(const QString& outputFile, const KUrl& url); KUrl fileUrl(const QString& outputFile) const; void setFilePosition(const QString& outputFile, const SimpleCursor& position); SimpleCursor filePosition(const QString& outputFile) const; SourceFileTemplate sourceFileTemplate() const; /** * Adds variables @p variables to the context passed to all template files. * * The variable values must be of a type registered with Grantlee::registerMetaType() * * @param variables additional variables to be passed to all templates **/ void addVariables(const QVariantHash& variables); /** * Convenience function to render a string @p text as a Grantlee template **/ QString renderString(const QString& text) const; /** * The template renderer used to render all the templates for this class. * * This function is useful if you want a rendeder with all current template variables. */ TemplateRenderer* renderer() const; private: class TemplateClassGeneratorPrivate* const d; }; } #endif // KDEVPLATFORM_TEMPLATECLASSGENERATOR_H diff --git a/language/codegen/tests/data/kdevcodegentest/templates/test_yaml/test_yaml.desktop b/language/codegen/tests/data/kdevcodegentest/templates/test_yaml/test_yaml.desktop index 575a52c43f..4b9be00d43 100644 --- a/language/codegen/tests/data/kdevcodegentest/templates/test_yaml/test_yaml.desktop +++ b/language/codegen/tests/data/kdevcodegentest/templates/test_yaml/test_yaml.desktop @@ -1,98 +1,98 @@ [General] Name=Testing YAML template Name[bs]=Testiranje YAML predloška Name[ca]=Plantilla YAML de prova Name[ca@valencia]=Plantilla YAML de prova Name[da]=Tester YAML-skabelon Name[de]=Testvorlage für YAML Name[el]=Δοκιμή προτύπου YAML Name[es]=Prueba de plantilla YAML Name[et]=YAML malli testimine Name[fi]=Testi-YAML-malli Name[fr]=Modèle YAML pour tester Name[gl]=Modelo de YAML de probas Name[hu]=YAML sablon tesztelése Name[it]=Test del modello YAML Name[kk]=Сынақ YAML үлгісі Name[nb]=Mal for YAML-testing Name[nl]=YAML-sjabloon testen -Name[pl]=Szablon testowy YAML +Name[pl]=Próbny szablon YAML Name[pt]=A testar o modelo em YAML Name[pt_BR]=Testando o modelo em YAML Name[ru]=Тестовый шаблон YAML Name[sk]=Testovanie YAML šablóny Name[sl]=Preizkusna predloga YAML Name[sv]=YAML-mall för test Name[tr]=YAML şablonu testi Name[uk]=Тестовий шаблон YAML Name[x-test]=xxTesting YAML templatexx Name[zh_CN]=YAML 测试模板 Name[zh_TW]=測試 YAML 樣本 Comment=Describes a class using YAML syntax Comment[bs]=Opisuje klasu koristeći YAML sintaksu Comment[ca]=Descriu una classe utilitzant la sintaxi YAML Comment[ca@valencia]=Descriu una classe utilitzant la sintaxi YAML Comment[da]=Beskriver en klasse ved brug af YAML-syntaks Comment[de]=Beschreibt ein Klasse, die YAML-Syntax verwendet Comment[el]=Περιγράφει μια class με χρήση σύνταξης YAML Comment[es]=Describe una clase usando la sintaxis YAML Comment[et]=Klassi kirjeldamine YAML süntaksiga Comment[fi]=Kuvaa luokan YAML-syntaksilla Comment[fr]=Décrit une classe utilisant la syntaxe de YAML Comment[gl]=Describe unha clase que emprega a sintaxe de YAML Comment[hu]=Leír egy osztályt a YAML szintaxis használatával Comment[it]=Descrive una classe usando la sintassi YAML Comment[kk]=YAML синтаксисін қолданатын класс сипаттамасы Comment[nb]=Beskriver en klasse ved bruk av YAML-syntaks Comment[nl]=Beschrijft een klasse met gebruik van YAML-syntaxis Comment[pl]=Opisuje klasę wykorzystującą składnię YAML Comment[pt]=Descreve uma classe com uma sintaxe em YAML Comment[pt_BR]=Descreve uma classe que usa a sintaxe em YAML Comment[ru]=Описывает класс на языке YAML Comment[sk]=Popisuje triedu pomocou syntaxe YAML Comment[sl]=Opiše razred z uporabo skladnje YAML Comment[sv]=Beskriver en klass med YAML-syntax Comment[tr]=YAML sözdizimi kullanarak bir sınıf tanımlar Comment[uk]=Описує клас з використанням синтаксису YAML Comment[x-test]=xxDescribes a class using YAML syntaxxx Comment[zh_CN]=使用 YAML 语法描述一个类 Comment[zh_TW]=描述使用 YAML 語法的類別 Category=Testing/YAML Type=Class Files=Description OptionsFile=options.kcfg [Description] Name=Description Name[bs]=Opis Name[ca]=Descripció Name[ca@valencia]=Descripció Name[da]=Beskrivelse Name[de]=Beschreibung Name[el]=Περιγραφή Name[es]=Descripción Name[et]=Kirjeldus Name[fi]=Kuvaus Name[fr]=Description Name[gl]=Descrición Name[hu]=Leírás Name[it]=Descrizione Name[kk]=Сипаттамасы Name[mr]=वर्णन Name[nb]=Beskrivelse Name[nl]=Beschrijving Name[pl]=Opis Name[pt]=Descrição Name[pt_BR]=Descrição Name[ru]=Описание Name[sk]=Popis Name[sl]=Opis Name[sv]=Beskrivning Name[tr]=Tanımlama Name[ug]=چۈشەندۈرۈش Name[uk]=Опис Name[x-test]=xxDescriptionxx Name[zh_CN]=描述 Name[zh_TW]=描述 File=class.yaml OutputFile={{ name }}.yaml diff --git a/language/duchain/declarationid.h b/language/duchain/declarationid.h index ba9c814111..bbd18f1c7d 100644 --- a/language/duchain/declarationid.h +++ b/language/duchain/declarationid.h @@ -1,201 +1,199 @@ /* This file is part of KDevelop Copyright 2008 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_DECLARATION_ID_H #define KDEVPLATFORM_DECLARATION_ID_H -#include "../editor/cursorinrevision.h" - #include "indexeddeclaration.h" #include "identifier.h" #include "instantiationinformation.h" #include //krazy:excludeall=dpointer class QString; namespace KDevelop { class Declaration; class TopDUContext; /** * \short Allows clearly identifying a Declaration. * * DeclarationId is needed to uniquely address Declarations that are in another top-context, * because there may be multiple parsed versions of a file. * * There are two forms of DeclarationId, one indirect and one direct. The direct form * holds a reference to the Declaration instance, whereas the indirect form stores the qualified * identifier and an additional index to disambiguate instances of multiple declarations with the same * identifier. * * Both forms also have a specialization index. It can be used in a language-specific way to pick other * versions of the declaration. When the declaration is found, Declaration::specialize() is called on * the found declaration with this value, and the returned value is the actually found declaration. * * \note This only works when the Declaration is in the symbol table. * */ class KDEVPLATFORMLANGUAGE_EXPORT DeclarationId { public: /** * Constructor for indirect access to a declaration. The resulting DeclarationId will not * have a direct reference to the Declaration, but will look it up as needed. * * \param id Identifier for this declaration id. * \param additionalId Additional index to disambiguate * \param specialization Specialization index (see class documentation). */ explicit DeclarationId(const IndexedQualifiedIdentifier& id = IndexedQualifiedIdentifier(), uint additionalId = 0, const IndexedInstantiationInformation& specialization = IndexedInstantiationInformation()); /** * Constructor for direct access to a declaration. The resulting DeclarationId will * directly reference the Declaration * * \param decl Declaration to reference. * \param specialization Specialization index (see class documentation). */ explicit DeclarationId(const IndexedDeclaration& decl, const IndexedInstantiationInformation& specialization = IndexedInstantiationInformation()); /** * Equality operator. * * \param rhs declaration identifier to compare. * \returns true if equal, otherwise false. */ bool operator==(const DeclarationId& rhs) const { if(m_direct != rhs.m_direct) return false; if(!m_direct) return indirect.m_identifier == rhs.indirect.m_identifier && indirect.m_additionalIdentity == rhs.indirect.m_additionalIdentity && m_specialization == rhs.m_specialization; else return direct == rhs.direct && m_specialization == rhs.m_specialization; } /** * Not equal operator. * * \param rhs declaration identifier to compare. * \returns true if not equal, otherwise false. */ bool operator!=(const DeclarationId& rhs) const { return !operator==(rhs); } /** * Determine whether this declaration identifier references a valid declaration. */ bool isValid() const { return (m_direct && direct.isValid()) || indirect.m_identifier.isValid(); } /** * Hash function for this declaration identifier. * * \warning This may return different hashes for the same declaration, * depending on whether the id is direct or indirect, * and thus you cannot compare hashes for declaration equality (use operator==() instead) */ uint hash() const { if(m_direct) return KDevHash() << direct.hash() << m_specialization.index(); else return KDevHash() << indirect.m_identifier.getIndex() << indirect.m_additionalIdentity << m_specialization.index(); } /** * Retrieve the declaration, from the perspective of \a context. * In order to be retrievable, the declaration must be in the symbol table. * * \param context Context in which to search for the Declaration. * \param instantiateIfRequired Whether the declaration should be instantiated if required * \returns the referenced Declaration, or null if none was found. * */ Declaration* getDeclaration(const TopDUContext* context, bool instantiateIfRequired = true) const; /** * Same as getDeclaration(..), but returns all matching declarations if there are multiple. * This also returns found forward-declarations. */ KDevVarLengthArray getDeclarations(const TopDUContext* context) const; /** * Set the specialization index (see class documentation). * * \param specializationIndex the new specialization index. */ void setSpecialization(const IndexedInstantiationInformation& spec); /** * Retrieve the specialization index (see class documentation). * * \returns the specialization index. */ IndexedInstantiationInformation specialization() const; /** * Determine whether this DeclarationId directly references a Declaration by indices, * or if it uses identifiers and other data to reference the Declaration. * * \returns true if direct, false if indirect. */ bool isDirect() const; /** * Return the qualified identifier for this declaration. * * \warning This is relatively expensive, and not 100% correct in all cases(actually a top-context would be needed to resolve this correctly), * so avoid using this, except for debugging purposes. */ QualifiedIdentifier qualifiedIdentifier() const; private: struct Indirect { IndexedQualifiedIdentifier m_identifier; // Hash from signature, or similar. // Used to disambiguate multiple declarations of the same name. uint m_additionalIdentity; } ; //union { //An indirect reference to the declaration, which uses the symbol-table for lookup. Should be preferred for all //declarations that are in the symbol-table Indirect indirect; IndexedDeclaration direct; //}; bool m_direct; // Can be used in a language-specific way to pick other versions of the declaration. // When the declaration is found, pickSpecialization is called on the found declaration // with this value, and the returned value is the actually found declaration. IndexedInstantiationInformation m_specialization; }; inline uint qHash(const KDevelop::DeclarationId& id) { return id.hash(); } } Q_DECLARE_TYPEINFO(KDevelop::DeclarationId, Q_MOVABLE_TYPE); #endif diff --git a/language/duchain/duchain.cpp b/language/duchain/duchain.cpp index 72c0cd9904..5223e3059d 100644 --- a/language/duchain/duchain.cpp +++ b/language/duchain/duchain.cpp @@ -1,1718 +1,1718 @@ /* 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 #include #include #include #include #include #include "../interfaces/ilanguagesupport.h" #include "../interfaces/icodehighlighting.h" #include "../backgroundparser/backgroundparser.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 "repositories/itemrepository.h" #include "waitforupdate.h" #include "referencecounting.h" #include "importers.h" 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 SpinLockData 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(0), 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(0) { } ///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 = 0; class DUChainPrivate { class CleanupThread : public QThread { public: 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() { 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); if(m_stopRunning) break; } } bool m_stopRunning; QWaitCondition m_wait; QMutex m_waitMutex; DUChainPrivate* m_data; }; public: DUChainPrivate() : m_chainsMutex(QMutex::Recursive), instance(0), m_cleanupDisabled(false), m_destroyed(false), m_environmentListInfo("Environment Lists"), m_environmentInfo("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) { kDebug() << "reading parsing-environment static data"; //Read f.read((char*)ParsingEnvironmentFile::m_staticData, sizeof(StaticParsingEnvironmentData)); }else{ kDebug() << "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()/sizeof(uint)); f.read((char*)m_availableTopContextIndices.data(), f.size()); } } } ~DUChainPrivate() { kDebug() << "Destroying"; DUChain::m_deleted = true; m_cleanup->stopThread(); delete m_cleanup; delete instance; } void clear() { if(!m_cleanupDisabled) doMoreCleanup(); QMutexLocker l(&m_chainsMutex); DUChainWriteLocker writeLock(DUChain::lock()); foreach(TopDUContext* top, m_chainsByUrl.values()) 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 kDebug() << "removed a top-context that was reference-counted:" << context->url().str() << context->ownIndex(); m_referenceCounts.remove(context); } } uint index = context->ownIndex(); // kDebug(9505) << "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)); SpinLock<> lock(DUChain::chainsByIndexLock); DUChain::chainsByIndex[index] = 0; } ///Must be locked before accessing content of this class. ///Should be released during expensive disk-operations and such. QMutex m_chainsMutex; 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_ARRAY(uint topContextIndex, topContextIndices) { KSharedPtr< ParsingEnvironmentFile > p = ParsingEnvironmentFilePointer(loadInformation(topContextIndex)); if(p) { ret << p; }else{ kDebug() << "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(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) { SpinLock<> 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) { SpinLock<> lock(DUChain::chainsByIndexLock); if(DUChain::chainsByIndex.size() > index) return DUChain::chainsByIndex[index]; else return 0; } ///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(); kDebug() << "waiting for another thread to load index" << index; usleep(50000); l.relock(); } loaded.insert(index); return; } m_loading.insert(index); loaded.insert(index); l.unlock(); kDebug() << "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); kDebug(9505) << "Did not find stored item for" << url.str() << "count:" << m_fileEnvironmentInformations.values(url); } if(!atomic) { locker.unlock(); locker.lock(); } } } QMutex& cleanupMutex() { static QMutex mutex(QMutex::Recursive); return mutex; } ///@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, bool needLockRepository = true) { 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()); PersistentSymbolTable::self().clearCache(); //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 lockedParseMutexes; QList locked; if(needLockRepository) { if (ICore* core = ICore::self()) if (ILanguageController* lc = core->languageController()) lockedParseMutexes = lc->loadedLanguages(); writeLock.unlock(); //Here we wait for all parsing-threads to stop their processing foreach(ILanguage* language, lockedParseMutexes) { language->parseLock()->lockForWrite(); locked << language->parseLock(); } writeLock.lock(); globalItemRepositoryRegistry().lockForWriting(); kDebug(9505) << "starting cleanup"; } QTime startTime = QTime::currentTime(); 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); foreach(TopDUContext* top, m_chainsByUrl.values()) { 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 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 foreach(TopDUContext* context, m_referenceCounts.keys()) { 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 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. kDebug(9505) << "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, false); writeLock.lock(); } if(needLockRepository) { globalItemRepositoryRegistry().unlockForWriting(); int elapsedSeconds = startTime.secsTo(QTime::currentTime()); kDebug(9505) << "seconds spent doing cleanup: " << elapsedSeconds << "top-contexts still open:" << m_chainsByUrl.size(); } if(!retries) { int elapesedMilliSeconds = startTime.msecsTo(QTime::currentTime()); kDebug(9505) << "milliseconds spent doing cleanup with locked duchain: " << elapesedMilliSeconds; } foreach(QReadWriteLock* lock, locked) lock->unlock(); } ///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 0; } ///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 0; } 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() ); kDebug() << "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()) { kDebug() << "removing top-context for" << top.data()->url().str() << "because it is out of date"; instance->removeDocumentChain(top.data()); } } kDebug() << "check ready"; } private: void addContextsForRemoval(QSet& topContexts, IndexedTopDUContext top) { if(topContexts.contains(top.index())) return; KSharedPtr 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< KSharedPtr > importers = info->importers(); QSet< KSharedPtr > checkNext; //Do breadth first search, so less imports/importers have to be loaded, and a lower depth is reached for(QList< KSharedPtr >::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< KSharedPtr >::const_iterator it = checkNext.begin(); it != checkNext.end(); ++it) { topContexts.remove((*it)->indexedTopContext().index()); //Enable full check again addContextsForRemoval(topContexts, (*it)->indexedTopContext()); } } } template bool listContains(const Entry entry, const Entry* list, uint listSize) { for(uint a = 0; a < listSize; ++a) if(list[a] == entry) return true; return false; } ///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-informations. 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; }; K_GLOBAL_STATIC(DUChainPrivate, sdDUChainPrivate) DUChain::DUChain() { Q_ASSERT(ICore::self()); connect(ICore::self()->documentController(), SIGNAL(documentLoadedPrepare(KDevelop::IDocument*)), this, SLOT(documentLoadedPrepare(KDevelop::IDocument*))); connect(ICore::self()->documentController(), SIGNAL(documentUrlChanged(KDevelop::IDocument*)), this, SLOT(documentRenamed(KDevelop::IDocument*))); connect(ICore::self()->documentController(), SIGNAL(documentActivated(KDevelop::IDocument*)), this, SLOT(documentActivated(KDevelop::IDocument*))); connect(ICore::self()->documentController(), SIGNAL(documentClosed(KDevelop::IDocument*)), this, SLOT(documentClosed(KDevelop::IDocument*))); } 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(); extern void initReferenceCounting(); 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); // kDebug(9505) << "duchain: adding document" << chain->url().str() << " " << chain; Q_ASSERT(chain); Q_ASSERT(!sdDUChainPrivate->hasChainForIndex(chain->ownIndex())); { SpinLock<> lock(DUChain::chainsByIndexLock); if(DUChain::chainsByIndex.size() <= chain->ownIndex()) DUChain::chainsByIndex.resize(chain->ownIndex() + 100, 0); 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); if(ICore::self() && 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 KUrl& document, bool proxyContext) const { return chainForDocument(IndexedString(document.pathOrUrl()), 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); { SpinLock<> lock(chainsByIndexLock); if(chainsByIndex.size() > index) { TopDUContext* top = chainsByIndex[index]; if(top) return top; } } return 0; } TopDUContext* DUChain::chainForDocument(const KDevelop::IndexedString& document, bool proxyContext) const { ENSURE_CHAIN_READ_LOCKED; if(sdDUChainPrivate->m_destroyed) return 0; QMutexLocker l(&sdDUChainPrivate->m_chainsMutex); /* { int count = 0; QMap::Iterator it = sdDUChainPrivate->m_chains.lowerBound(document); for( ; it != sdDUChainPrivate->m_chains.end() && it.key().url() == document.url(); ++it ) ++count; if( count > 1 ) kDebug(9505) << "found " << count << " chains for " << document.url().str(); }*/ //Eventually load an existing chain from disk l.unlock(); 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 0; } QList DUChain::chainsForDocument(const KUrl& 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 (QMultiMap::Iterator 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 KUrl& 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); // kDebug() << document.str() << ": matching" << list.size() << (onlyProxyContexts ? "proxy-contexts" : (noProxyContexts ? "content-contexts" : "contexts")); QList< ParsingEnvironmentFilePointer>::const_iterator 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 0; ParsingEnvironmentFilePointer envFile = environmentFileForDocument(document, environment, proxyContext); if(envFile) { return envFile->topContext(); }else{ return 0; } } QList DUChain::documents() const { QMutexLocker l(&sdDUChainPrivate->m_chainsMutex); QList ret; foreach(TopDUContext* top, sdDUChainPrivate->m_chainsByUrl.values()) { ret << top->url().toUrl(); } return ret; } QList DUChain::indexedDocuments() const { QMutexLocker l(&sdDUChainPrivate->m_chainsMutex); QList ret; foreach(TopDUContext* top, sdDUChainPrivate->m_chainsByUrl.values()) { ret << top->url(); } return ret; } void DUChain::documentActivated(KDevelop::IDocument* doc) { if(sdDUChainPrivate->m_destroyed) return; //Check whether the document has an attached environment-manager, and whether that one thinks the document needs to be updated. //If yes, update it. DUChainReadLocker lock( DUChain::lock() ); QMutexLocker l(&sdDUChainPrivate->m_chainsMutex); TopDUContext* ctx = DUChainUtils::standardContextForUrl(doc->url(), true); if(ctx && ctx->parsingEnvironmentFile()) if(ctx->parsingEnvironmentFile()->needsUpdate()) ICore::self()->languageController()->backgroundParser()->addDocument(IndexedString(doc->url())); } 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); QList 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( KDevelop::ILanguage* language, languages) if(language->languageSupport() && language->languageSupport()->codeHighlighting()) language->languageSupport()->codeHighlighting()->highlightDUChain(standardContext); kDebug() << "highlighted" << doc->url() << "in foreground"; return; } }else{ kDebug() << "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? kWarning() << "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; } 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()); 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 { //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(); } sdDUChainPrivate->doMoreCleanup(); sdDUChainPrivate->m_openDocumentContexts.clear(); sdDUChainPrivate->m_destroyed = true; sdDUChainPrivate->clear(); 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)) { kWarning() << "Problem in the management of availalbe top-context indices"; return newTopContextIndex(); } return ret; } } static QAtomicInt& currentId( globalItemRepositoryRegistry().getCustomCounter("Top-Context Counter", 1) ); return currentId.fetchAndAddRelaxed(1); } void DUChain::refCountUp(TopDUContext* top) { QMutexLocker l(&sdDUChainPrivate->m_referenceCountsMutex); if(!sdDUChainPrivate->m_referenceCounts.contains(top)) sdDUChainPrivate->m_referenceCounts.insert(top, 1); else ++sdDUChainPrivate->m_referenceCounts[top]; } bool DUChain::deleted() { return m_deleted; } void DUChain::refCountDown(TopDUContext* top) { QMutexLocker l(&sdDUChainPrivate->m_referenceCountsMutex); if(!sdDUChainPrivate->m_referenceCounts.contains(top)) { //kWarning() << "tried to decrease reference-count for" << top->url().str() << "but this top-context is not referenced"; return; } --sdDUChainPrivate->m_referenceCounts[top]; if(!sdDUChainPrivate->m_referenceCounts[top]) sdDUChainPrivate->m_referenceCounts.remove(top); } void DUChain::emitDeclarationSelected(const DeclarationPointer& decl) { emit declarationSelected(decl); } KDevelop::ReferencedTopDUContext DUChain::waitForUpdate(const KDevelop::IndexedString& document, KDevelop::TopDUContext::Features minFeatures, bool proxyContext) { Q_ASSERT(!lock()->currentThreadHasReadLock() && !lock()->currentThreadHasWriteLock()); WaitForUpdate waiter; waiter.m_dataMutex.lock(); 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 0; } QMetaObject::invokeMethod(ICore::self()->languageController()->backgroundParser(), "parseDocuments"); QApplication::processEvents(); 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() { - sdDUChainPrivate->m_cleanupDisabled = true; +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; } void DUChain::finalCleanup() { DUChainWriteLocker writeLock(DUChain::lock()); kDebug() << "doing final cleanup"; int cleaned = 0; while((cleaned = globalItemRepositoryRegistry().finalCleanup())) { kDebug() << "cleaned" << cleaned << "B"; if(cleaned < 1000) { kDebug() << "cleaned enough"; break; } } kDebug() << "final cleanup ready"; } bool DUChain::compareToDisk() { DUChainWriteLocker writeLock(DUChain::lock()); ///Step 1: Compare the repositories return true; } } #include "duchain.moc" // kate: space-indent on; indent-width 2; tab-width 4; replace-tabs on; auto-insert-doxygen on diff --git a/language/duchain/duchain.h b/language/duchain/duchain.h index cb61a582a0..dd84bf7281 100644 --- a/language/duchain/duchain.h +++ b/language/duchain/duchain.h @@ -1,311 +1,311 @@ /* This file 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. */ #ifndef KDEVPLATFORM_DUCHAIN_H #define KDEVPLATFORM_DUCHAIN_H #include #include "topducontext.h" #include "parsingenvironment.h" class KUrl; namespace KDevelop { class IDocument; class TopDUContext; class DUChainLock; class ParsingEnvironmentManager; class ParsingEnvironment; class ParsingEnvironmentFile; typedef KSharedPtr ParsingEnvironmentFilePointer; class Definitions; class Uses; /** * \short Holds references to all top level source file contexts. * * The DUChain is a global static class which manages the definition-use * chains. It performs the following functions: * \li registers chains with addDocumentChain() and deregisters with removeDocumentChain() * \li allows querying for existing chains * \li watches text editors, registering and deregistering them with the BackgroundParser when files * are opened and closed. */ class KDEVPLATFORMLANGUAGE_EXPORT DUChain : public QObject { Q_OBJECT public: /** * Initializes common static item repositories. * Must be called once for multi threaded applications to work reliably. */ static void initialize(); /** * Return a list of all chains available */ Q_SCRIPTABLE QList allChains() const; /** * Makes sure the standard-context for the given url is up-to-date. * This may trigger a parsing in background, so a QObject can be given that will be notified * asyonchronously once the update is ready. * If the context is already up to date, the given QObject is notified directly. * * @param document Document to update * @param features The requested features. If you want to force a full update of the context, give TopDUContext::ForceUpdate. * If you want to force an update including all imports, use TopDUContext::ForceUpdateRecursive. * @param notifyReady An optional pointer to a QObject that should contain a slot * "void updateReady(KDevelop::IndexedString url, KDevelop::ReferencedTopDUContext topContext)". * The notification is guaranteed to be called once for each call to updateContextForUrl. The given top-context * may be invalid if the update failed. A queued connection is used if a re-parse has to be done. The duchain * will _not_ be locked when updateReady is called. * @param priority An optional priority for the job. The lower the value, the higher it's priority. * @note The duchain must _not_ be locked when this is called! */ Q_SCRIPTABLE void updateContextForUrl(const IndexedString& document, TopDUContext::Features minFeatures, QObject* notifyReady = 0, int priority = 1) const; /** * Convenience-function similar to updateContextForUrl that blocks this thread until the update of the given document is ready, * and returns the top-context. * @param document The document to update * @param features The requested features. If you want to force a full update of the context, give TopDUContext::ForceUpdate. * If you want to force an update including all imports, use TopDUContext::ForceUpdateRecursive. * @return The up-to-date top-context, or zero if the update failed * * @note The duchain must _not_ be locked when this is called! * */ KDevelop::ReferencedTopDUContext waitForUpdate(const KDevelop::IndexedString& document, KDevelop::TopDUContext::Features minFeatures, bool proxyContext = false); /** * Return any chain for the given document * If available, the version accepting IndexedString should be used instead of this, for performance reasons. * When no fitting chain is in memory, one may be loaded from disk. * * @note The duchain must be at least read-locked locked when this is called! * */ Q_SCRIPTABLE TopDUContext* chainForDocument(const KUrl& document, bool proxyContext = false) const; Q_SCRIPTABLE TopDUContext* chainForDocument(const IndexedString& document, bool proxyContext = false) const; /** * Return all chains for the given document that are currently in memory. * This does not load any chains from disk. * */ Q_SCRIPTABLE QList chainsForDocument(const KUrl& document) const; /** * Return all chains for the given document that are currently in memory. * This does not load any chains from disk. * Should be preferred over the KUrl version. * */ Q_SCRIPTABLE QList chainsForDocument(const IndexedString& document) const; /** * Find a chain that fits into the given environment. If no fitting chain is found, 0 is returned. * When no fitting chain is in memory, one may be loaded from disk. * @param proxyContext If this is true, only contexts are found that have an ParsingEnvironmentFile that has the proxy-flag set. Else, only content-contexts will be returned. * * @note The duchain must be at least read-locked locked when this is called! * */ Q_SCRIPTABLE TopDUContext* chainForDocument(const KUrl& document, const ParsingEnvironment* environment, bool proxyContext = false) const; /** * Find a chain that fits into the given environment. If no fitting chain is found, 0 is returned. * When no fitting chain is in memory, one may be loaded from disk. * @param proxyContext If this is true, only contexts are found that have an ParsingEnvironmentFile that has the proxy-flag set. Else, only content-contexts will be returned. * * Prefer this over the KUrl version. * * @note The duchain must be at least read-locked locked when this is called! * */ Q_SCRIPTABLE TopDUContext* chainForDocument(const IndexedString& document, const ParsingEnvironment* environment, bool proxyContext = false) const; /** * Find the environment-file of a chain that fits into the given environment. If no fitting chain is found, 0 is returned. * When no fitting chain is in memory, one may be loaded from disk. * * This should be preferred over chainForDocument when only the environment-info is needed, because the TopDUContext is not loaded in this function. * ** @param proxyContext If this is true, only contexts are found that have an ParsingEnvironmentFile that has the proxy-flag set. Else, only content-contexts will be returned. * * Prefer this over the KUrl version. * * @note The duchain must be at least read-locked locked when this is called! * */ Q_SCRIPTABLE ParsingEnvironmentFilePointer environmentFileForDocument(const IndexedString& document, const ParsingEnvironment* environment, bool proxyContext = false) const; Q_SCRIPTABLE ParsingEnvironmentFilePointer environmentFileForDocument(IndexedTopDUContext topContext) const; /** * Returns the list of the environment-infos of all versions of the given document. */ Q_SCRIPTABLE QList allEnvironmentFiles(const IndexedString& document); ///Returns the top-context that has the given index assigned, or zero if it doesn't exist. @see TopDUContext::ownIndex ///The duchain must be read-locked when this is called ///This function is inlined because it is called in a very high frequency Q_SCRIPTABLE inline TopDUContext* chainForIndex(uint index) { if(m_deleted) return 0; { SpinLock<> lock(chainsByIndexLock); if(chainsByIndex.size() > index) { TopDUContext* top = chainsByIndex[index]; if(top) return top; } } //Load the top-context return loadChain(index); } ///Returns the url for the given top-context index if available. This does have some cost, so avoid it when possible. Q_SCRIPTABLE IndexedString urlForIndex(uint index) const; /// Only used for debugging at the moment Q_SCRIPTABLE QList documents() const; /// Only used for debugging at the moment /// Prefer that over the KUrl version for performance reasons Q_SCRIPTABLE QList indexedDocuments() const; /** * Registers a new definition-use \a chain for the given \a document. */ Q_SCRIPTABLE void addDocumentChain(TopDUContext* chain); /// Returns true if the global duchain instance has already been deleted Q_SCRIPTABLE static bool deleted(); /// Returns the global static instance. Q_SCRIPTABLE static DUChain* self(); /// Returns the structure that manages mapping between definitions and declarations Q_SCRIPTABLE static Definitions* definitions(); /// Returns the structure that manages mapping between declarations, and which top level contexts contain uses of them. static Uses* uses(); /** * Retrieve the read write lock for the entire definition-use chain. * To call non-const methods, you must be holding a write lock. * * Evaluations made prior to holding a lock (including which objects * exist) must be verified once the lock is held, as they may have changed * or been deleted. * * \threadsafe */ Q_SCRIPTABLE static DUChainLock* lock(); /// Returns whether the top-context with the given index is currently loaded in memory Q_SCRIPTABLE bool isInMemory(uint topContextIndex) const; /** * Changes the environment attached to the given top-level context, and updates the management-structures to reflect that * */ Q_SCRIPTABLE void updateContextEnvironment( TopDUContext* context, ParsingEnvironmentFile* file ); ///Allocates a new identity for a new top-context, no lock needed. The returned value is never zero static uint newTopContextIndex(); ///If you call this, the persistent disk-storage structure will stay unaffected, and no duchain cleanup will be done. ///Call this from within tests. - void disablePersistentStorage(); + void disablePersistentStorage(bool disable = true); ///Stores the whole duchain and all its repositories in the current state to disk ///The duchain must not be locked in any way void storeToDisk(); ///Compares the whole duchain and all its repositories in the current state to disk ///When the comparison fails, debug-output will show why ///The duchain must not be locked when calling this ///@return true If the current memory state equals the disk state, else false bool compareToDisk(); ///Does a big cleanup of the repositories, removing all items that are not needed any more for disk-persistency ///The duchain must not be locked when calling this ///@warning Only do this right after startup, or after shutdown, in a moment where indexed /// repository items are not needed any more, since the reference-counting mechanisms only respect disk persistency. /// All items that are not disk-referenced are invalidated during this step, which will lead to inconsistency, crashes, /// etc. when done in the wrong moment. void finalCleanup(); Q_SIGNALS: ///Is emitted when the declaration has been selected somewhere in the user-interface, for example in the completion-list void declarationSelected(const KDevelop::DeclarationPointer& decl); public Q_SLOTS: ///Removes the given top-context from the duchain, and deletes it. void removeDocumentChain(KDevelop::TopDUContext* document); ///Emits the declarationSelected signal, so other parties can notice it. void emitDeclarationSelected(const KDevelop::DeclarationPointer& decl); /** * Shutdown and cleanup the DUChain. */ void shutdown(); private Q_SLOTS: void documentActivated(KDevelop::IDocument* doc); void documentLoadedPrepare(KDevelop::IDocument* document); void documentRenamed(KDevelop::IDocument* document); void documentClosed(KDevelop::IDocument*); private: TopDUContext* loadChain(uint index); //These two are exported here so that the extremely frequently called chainForIndex(..) can be inlined static bool m_deleted; static std::vector chainsByIndex; static SpinLockData chainsByIndexLock; /// Increases the reference-count for the given top-context. The result: It will not be unloaded. /// Do this to prevent KDevelop from unloading a top-context that you plan to use. Don't forget calling unReferenceToContext again, /// else the top-context will stay in memory for ever. void refCountUp(TopDUContext* top); /// Decreases the reference-count for the given top-context. When it reaches zero, KDevelop is free to unload it at any time, /// also invalidating all the contained declarations and contexts. void refCountDown(TopDUContext* top); void addToEnvironmentManager( TopDUContext * chain ); void removeFromEnvironmentManager( TopDUContext * chain ); DUChain(); ~DUChain(); friend class DUChainPrivate; friend class ReferencedTopDUContext; }; } #endif // KDEVPLATFORM_DUCHAIN_H // kate: space-indent on; indent-width 2; tab-width 4; replace-tabs on; auto-insert-doxygen on diff --git a/language/duchain/navigation/useswidget.cpp b/language/duchain/navigation/useswidget.cpp index 42913c1f20..78158d3149 100644 --- a/language/duchain/navigation/useswidget.cpp +++ b/language/duchain/navigation/useswidget.cpp @@ -1,633 +1,651 @@ /* 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; 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, uint cutOff, SimpleRange range) { uint leftCutRoom = range.start.column; uint 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 uint leftCut = 0; uint 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.start.column -= leftCut; range.end.column -= 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 background(251, 250, 150); const QColor foreground(0, 0, 0); return "" + Qt::escape(line.left(range.start.column)) + "" + Qt::escape(line.mid(range.start.column, range.end.column - range.start.column)) + "" + Qt::escape(line.mid(range.end.column, line.length() - range.end.column)) + ""; } OneUseWidget::OneUseWidget(IndexedDeclaration declaration, IndexedString document, SimpleRange 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); setLayout(m_layout); m_label = new QLabel(this); m_icon = new QLabel(this); m_icon->setPixmap(QIcon::fromTheme("code-function").pixmap(16)); connect(m_label, SIGNAL(linkActivated(QString)), this, SLOT(jumpTo())); DUChainReadLocker lock(DUChain::lock()); QString text = "" + i18nc("refers to a line in source code", "Line %1:", range.start.line) + QString(""); 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 = Qt::escape(code.line(a)); if (m_range->range().start.line <= a && m_range->range().end.line >= a) { lineText = QString("") + lineText + QString(""); } if(!lineText.trimmed().isEmpty()) { toolTipText += lineText + "
"; } } if ( toolTipText.endsWith("
") ) { toolTipText.remove(toolTipText.length() - 4, 4); } setToolTip(QString("
") + toolTipText + QString("
")); } m_label->setText(text); m_layout->addWidget(m_icon); m_layout->addWidget(m_label); m_layout->setAlignment(Qt::AlignLeft); } void OneUseWidget::jumpTo() { //This is used to execute the slot delayed in the event-loop, so crashes are avoided ICore::self()->documentController()->openDocument(m_document.toUrl(), m_range->range().start.textCursor()); } OneUseWidget::~OneUseWidget() { } void OneUseWidget::resizeEvent ( QResizeEvent * event ) { ///Adapt the content QSize size = event->size(); SimpleRange 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(QString("") + i18nc("Refers to a line in source code", "Line %1", range.start.line+1) + QString(" %2").arg(highlightAndEscapeUseText(m_sourceLine, cutOff, range))); while(sizeHint().width() > size.width() && cutOff < maxCutOff) { //We've got to save space m_label->setText(QString("") + i18nc("Refers to a line in source code", "Line %1", range.start.line+1) + QString(" %2").arg(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(0); } 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->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) { m_previousButton = new QToolButton(); m_previousButton->setIcon(QIcon::fromTheme("go-previous")); m_nextButton = new QToolButton(); m_nextButton->setIcon(QIcon::fromTheme("go-next")); m_headerLayout->addWidget(m_previousButton); m_headerLayout->addWidget(m_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 += "(...)"; } 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", "" + Qt::escape(headerText) + ": ")); addHeaderItem(headerLabel); setUpdatesEnabled(true); connect(headerLabel, SIGNAL(linkActivated(QString)), this, SLOT(linkWasActivated(QString))); } void ContextUsesWidget::linkWasActivated(QString link) { if ( link == "navigateToFunction" ) { DUChainReadLocker lock(DUChain::lock()); DUContext* context = m_context.context(); if(context) { CursorInRevision contextStart = context->range().start; KTextEditor::Cursor cursor(contextStart.line, contextStart.column); KUrl 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; 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("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, SIGNAL(linkActivated(QString)), this, SLOT(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() { - delete m_collector; +UsesWidget::~UsesWidget() +{ + if (m_collector) { + m_collector->setWidget(0); + } } -UsesWidget::UsesWidget(IndexedDeclaration declaration, UsesWidgetCollector* customCollector) : NavigatableWidgetList(true) { +UsesWidget::UsesWidget(const IndexedDeclaration& declaration, QSharedPointer customCollector) + : NavigatableWidgetList(true) +{ DUChainReadLocker lock(DUChain::lock()); setUpdatesEnabled(false); m_headerLine = new QLabel; redrawHeaderLine(); connect(m_headerLine, SIGNAL(linkActivated(QString)), this, SLOT(headerLinkActivated(QString))); 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 = new UsesWidgetCollector(declaration); - }else{ - m_collector = customCollector; + if (!customCollector) { + m_collector = QSharedPointer(new 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 == "expandAll") { setAllExpanded(true); } else if(linkName == "collapseAll") { setAllExpanded(false); } } UsesWidget::UsesWidgetCollector::UsesWidgetCollector(IndexedDeclaration decl) : UsesCollector(decl), m_widget(0) { } 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{ kWarning() << "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 = 0; m_widget->setShowHeader(false); m_widget->setUpdatesEnabled(true); } }else{ kWarning() << "progress() called too often"; } } void UsesWidget::UsesWidgetCollector::processUses( KDevelop::ReferencedTopDUContext topContext ) { - DUChainReadLocker lock(DUChain::lock()); + if (!m_widget) { + return; + } + + DUChainReadLocker lock; + kDebug() << "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().equals(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; } #include "useswidget.moc" diff --git a/language/duchain/navigation/useswidget.h b/language/duchain/navigation/useswidget.h index f52d8688a8..a2bf006000 100644 --- a/language/duchain/navigation/useswidget.h +++ b/language/duchain/navigation/useswidget.h @@ -1,163 +1,162 @@ /* Copyright 2008 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_USESWIDGET_H #define KDEVPLATFORM_USESWIDGET_H #include #include +#include #include #include #include "../../languageexport.h" #include "usescollector.h" #include class QLabel; class QToolButton; class QVBoxLayout; class QHBoxLayout; class QBoxLayout; class QPushButton; class QProgressBar; namespace KDevelop { class CodeRepresentation; class IndexedDeclaration; ///A widget representing one use of a Declaration in a speicific context class KDEVPLATFORMLANGUAGE_EXPORT OneUseWidget : public QWidget { Q_OBJECT public: OneUseWidget(IndexedDeclaration declaration, IndexedString document, SimpleRange range, const CodeRepresentation& code); ~OneUseWidget(); private slots: void jumpTo(); private: virtual void resizeEvent ( QResizeEvent * event ); PersistentMovingRange::Ptr m_range; IndexedDeclaration m_declaration; IndexedString m_document; QString m_sourceLine; QLabel* m_label; QLabel* m_icon; QHBoxLayout* m_layout; }; class KDEVPLATFORMLANGUAGE_EXPORT NavigatableWidgetList : public QScrollArea { Q_OBJECT public: NavigatableWidgetList(bool allowScrolling = false, uint maxHeight = 0, bool vertical = true); ~NavigatableWidgetList(); void addItem(QWidget* widget, int pos = -1); void addHeaderItem(QWidget* widget, Qt::Alignment alignment = 0); ///Whether items were added to this list using addItem(..) bool hasItems() const; ///Deletes all items that were added using addItem void deleteItems(); QList items() const; void setShowHeader(bool show); protected: QBoxLayout* m_itemLayout; QVBoxLayout* m_layout; private: QHBoxLayout* m_headerLayout; QToolButton *m_previousButton, *m_nextButton; bool m_allowScrolling, m_useArrows; }; class KDEVPLATFORMLANGUAGE_EXPORT ContextUsesWidget : public NavigatableWidgetList { Q_OBJECT public: ContextUsesWidget(const CodeRepresentation& code, QList usedDeclaration, IndexedDUContext context); Q_SIGNALS: void navigateDeclaration(KDevelop::IndexedDeclaration); private Q_SLOTS: void linkWasActivated(QString); private: IndexedDUContext m_context; }; class KDEVPLATFORMLANGUAGE_EXPORT DeclarationWidget : public NavigatableWidgetList { Q_OBJECT public: DeclarationWidget(const KDevelop::CodeRepresentation& code, const KDevelop::IndexedDeclaration& declaration); }; /** * Represents the uses of a declaration within one top-context */ class KDEVPLATFORMLANGUAGE_EXPORT TopContextUsesWidget : public NavigatableWidgetList { Q_OBJECT public: TopContextUsesWidget(IndexedDeclaration declaration, QList localDeclarations, IndexedTopDUContext topContext); void setExpanded(bool); int usesCount() const; private slots: void labelClicked(); private: IndexedTopDUContext m_topContext; IndexedDeclaration m_declaration; QLabel* m_icon; QLabel* m_toggleButton; QList m_allDeclarations; int m_usesCount; }; /** * A widget that allows browsing through all the uses of a declaration, and also through all declarations of it. */ class KDEVPLATFORMLANGUAGE_EXPORT UsesWidget : public NavigatableWidgetList { Q_OBJECT public: ///This class can be overridden to do additional processing while the uses-widget shows the uses. struct KDEVPLATFORMLANGUAGE_EXPORT UsesWidgetCollector : public UsesCollector { public: void setWidget(UsesWidget* widget ); UsesWidgetCollector(IndexedDeclaration decl); virtual void processUses(KDevelop::ReferencedTopDUContext topContext); virtual void maximumProgress(uint max); virtual void progress(uint processed, uint total); UsesWidget* m_widget; }; virtual QSize sizeHint () const; - ///@param customCollector allows specifying an own subclass of UsesWidgetCollector. The object will be owned - ///@param showDeclarations whether all declarations used for the search should be shown as well in the list - ///by this widget, and will be deleted on destruction. - UsesWidget(IndexedDeclaration declaration, UsesWidgetCollector* customCollector = 0); + ///@param customCollector allows specifying an own subclass of UsesWidgetCollector. + UsesWidget(const IndexedDeclaration& declaration, QSharedPointer customCollector = {}); ~UsesWidget(); void setAllExpanded(bool expanded); unsigned int countAllUses() const; Q_SIGNALS: void navigateDeclaration(KDevelop::IndexedDeclaration); private: const QString headerLineText() const; QLabel* m_headerLine; - UsesWidgetCollector* m_collector; + QSharedPointer m_collector; QProgressBar* m_progressBar; public slots: void headerLinkActivated(QString linkName); void redrawHeaderLine(); }; } #endif diff --git a/language/duchain/problem.cpp b/language/duchain/problem.cpp index 25a67df855..dd36643844 100644 --- a/language/duchain/problem.cpp +++ b/language/duchain/problem.cpp @@ -1,281 +1,257 @@ /* This file is part of KDevelop Copyright 2007 Hamish Rodda This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License 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 "problem.h" #include "duchainregister.h" #include "topducontextdynamicdata.h" #include "topducontext.h" #include "topducontextdata.h" +#include "duchain.h" +#include "duchainlock.h" #include #include namespace KDevelop { REGISTER_DUCHAIN_ITEM(Problem); DEFINE_LIST_MEMBER_HASH(ProblemData, diagnostics, LocalIndexedProblem) } using namespace KDevelop; -LocalIndexedProblem::LocalIndexedProblem(const Problem* problem) - : m_index(problem ? problem->m_indexInTopContext : 0) +LocalIndexedProblem::LocalIndexedProblem(const ProblemPointer& problem, const TopDUContext* top) + : m_index(problem->m_indexInTopContext) { + ENSURE_CHAIN_READ_LOCKED + // ensure child problems are properly serialized before we serialize the parent problem + if (static_cast(problem->m_diagnostics.size()) != problem->d_func()->diagnosticsSize()) { + // see below, the diagnostic size is kept in sync by the mutable API of Problem + Q_ASSERT(!problem->diagnostics().isEmpty()); + // the const cast is ugly but we don't really "change" the state as observed from the outside + auto& serialized = const_cast(problem.data())->d_func_dynamic()->diagnosticsList(); + Q_ASSERT(serialized.isEmpty()); + foreach(const ProblemPointer& child, problem->m_diagnostics) { + serialized << LocalIndexedProblem(child, top); + } + } + + if (!m_index) { + m_index = top->m_dynamicData->allocateProblemIndex(problem); + } } -Problem* LocalIndexedProblem::data(const TopDUContext* top) const +ProblemPointer LocalIndexedProblem::data(const TopDUContext* top) const { if (!m_index) { - return nullptr; + return {}; } return top->m_dynamicData->getProblemForIndex(m_index); } -bool LocalIndexedProblem::isLoaded(TopDUContext* top) const -{ - return m_index && top->m_dynamicData->isProblemForIndexLoaded(m_index); -} - Problem::Problem() : DUChainBase(*new ProblemData) , m_topContext(nullptr) , m_indexInTopContext(0) { d_func_dynamic()->setClassId(this); } Problem::Problem(ProblemData& data) : DUChainBase(data) , m_topContext(nullptr) , m_indexInTopContext(0) { } Problem::~Problem() { - if (m_topContext) { - m_topContext->m_dynamicData->clearProblemIndex(this); - } -} - -void Problem::setContext(TopDUContext* context) -{ - Q_ASSERT(!m_topContext); - Q_ASSERT(!m_indexInTopContext); - Q_ASSERT(context); - m_topContext = context; - m_indexInTopContext = m_topContext->m_dynamicData->allocateProblemIndex(this); } TopDUContext* Problem::topContext() const { - return m_topContext; + return m_topContext.data(); } IndexedString Problem::url() const { return d_func()->url; } DocumentRange Problem::finalLocation() const { return DocumentRange(d_func()->url, rangeInCurrentRevision()); } void Problem::setFinalLocation(const DocumentRange& location) { setRange(transformToLocalRevision(location)); d_func_dynamic()->url = location.document; } QList Problem::diagnostics() const { - if (!m_topContext || !m_diagnostics.isEmpty()) { - // child data already deserialized - return m_diagnostics; - } - - const auto data = d_func(); - m_diagnostics.reserve(data->diagnosticsSize()); - for (uint i = 0; i < data->diagnosticsSize(); ++i) { - m_diagnostics << ProblemPointer(data->diagnostics()[i].data(m_topContext)); - } return m_diagnostics; } void Problem::setDiagnostics(const QList& diagnostics) { - clearDiagnostics(); - m_diagnostics = diagnostics; + // keep serialization in sync, see also LocalIndexedProblem ctor above + d_func_dynamic()->diagnosticsList().clear(); } void Problem::addDiagnostic(const ProblemPointer& diagnostic) { m_diagnostics << diagnostic; } void Problem::clearDiagnostics() { m_diagnostics.clear(); + // keep serialization in sync, see also LocalIndexedProblem ctor above + d_func_dynamic()->diagnosticsList().clear(); } QString Problem::description() const { return d_func()->description.str(); } void Problem::setDescription(const QString& description) { d_func_dynamic()->description = IndexedString(description); } QString Problem::explanation() const { return d_func()->explanation.str(); } void Problem::setExplanation(const QString& explanation) { d_func_dynamic()->explanation = IndexedString(explanation); } ProblemData::Source Problem::source() const { return d_func()->source; } void Problem::setSource(ProblemData::Source source) { d_func_dynamic()->source = source; } KSharedPtr Problem::solutionAssistant() const { return {}; } ProblemData::Severity Problem::severity() const { return d_func()->severity; } void Problem::setSeverity(ProblemData::Severity severity) { d_func_dynamic()->severity = severity; } QString Problem::severityString() const { switch(severity()) { case ProblemData::Error: return i18n("Error"); case ProblemData::Warning: return i18n("Warning"); case ProblemData::Hint: return i18n("Hint"); } return QString(); } QString Problem::sourceString() const { switch (source()) { case ProblemData::Disk: return i18n("Disk"); case ProblemData::Preprocessor: return i18n("Preprocessor"); case ProblemData::Lexer: return i18n("Lexer"); case ProblemData::Parser: return i18n("Parser"); case ProblemData::DUChainBuilder: return i18n("Definition-Use Chain"); case ProblemData::SemanticAnalysis: return i18n("Semantic Analysis"); case ProblemData::ToDo: return i18n("TODO"); case ProblemData::Unknown: default: return i18n("Unknown"); } } QString Problem::toString() const { return QString("%1: %2 in %3:[(%4,%5),(%6,%7)]: %8 (found by %9)") .arg(severityString()) .arg(description()) .arg(url().str()) .arg(range().start.line) .arg(range().start.column) .arg(range().end.line) .arg(range().end.column) .arg((explanation().isEmpty() ? QString("") : explanation())) .arg(sourceString()); } void Problem::rebuildDynamicData(DUContext* parent, uint ownIndex) { - m_topContext = dynamic_cast(parent); - Q_ASSERT(m_topContext); - m_indexInTopContext = ownIndex; - - DUChainBase::rebuildDynamicData(parent, ownIndex); -} + auto top = dynamic_cast(parent); + Q_ASSERT(top); -ProblemPointer Problem::prepareStorage(TopDUContext* context) -{ - if (!m_topContext) { - this->setContext(context); - } else if (m_topContext != context) { - // clone problem from another topcontext so we can store its data - // NOTE: this is ugly and only required since ProblemPointer was used as a shared ptr - // but the current DUChain serialization mechanism cannot cope with that as it will crash when you - // try to delete mmapped data e.g. Copying the problem workarounds this limitation nicely and is - // not bad from a performance POV as only a few problems exist per context usually. - auto data = DUChainItemSystem::self().cloneData(*d_func()); - Q_ASSERT_X(data, Q_FUNC_INFO, "Failed to clone problem data."); - ProblemPointer ret(dynamic_cast(DUChainItemSystem::self().create(data))); - Q_ASSERT_X(ret, Q_FUNC_INFO, "Failed to clone problem."); - return ret->prepareStorage(context); - } + m_topContext = top; + m_indexInTopContext = ownIndex; - auto data = d_func_dynamic(); - // prepare child diagnostics for storage - data->diagnosticsList().clear(); - for (auto& child : m_diagnostics) { - child = child->prepareStorage(context); - data->diagnosticsList().append(LocalIndexedProblem(child.data())); + // deserialize child diagnostics here, as the top-context might get unloaded + // but we still want to keep the child-diagnostics in-tact, as one would assume + // a shared-ptr works. + const auto data = d_func(); + m_diagnostics.reserve(data->diagnosticsSize()); + for (uint i = 0; i < data->diagnosticsSize(); ++i) { + m_diagnostics << ProblemPointer(data->diagnostics()[i].data(top)); } - return ProblemPointer(this); + DUChainBase::rebuildDynamicData(parent, ownIndex); } QDebug operator<<(QDebug s, const Problem& problem) { s.nospace() << problem.toString(); return s.space(); } QDebug operator<<(QDebug s, const ProblemPointer& problem) { s.nospace() << problem->toString(); return s.space(); } diff --git a/language/duchain/problem.h b/language/duchain/problem.h index f431691604..41acc79b5c 100644 --- a/language/duchain/problem.h +++ b/language/duchain/problem.h @@ -1,281 +1,262 @@ /* This file is part of KDevelop Copyright 2007 Hamish Rodda This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License 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. */ #ifndef KDEVPLATFORM_I_PROBLEM_H #define KDEVPLATFORM_I_PROBLEM_H #include #include #include "../editor/documentrange.h" #include "../languageexport.h" #include "duchainbase.h" #include "indexedstring.h" #include "indexedtopducontext.h" namespace KDevelop { class IAssistant; class Problem; +using ProblemPointer = KSharedPtr; + /** * Represents a problem only by its index within the top-context * * Fixme: share code with the other LocalIndexed* classes */ class KDEVPLATFORMLANGUAGE_EXPORT LocalIndexedProblem { public: - LocalIndexedProblem(const Problem* problem = 0); - LocalIndexedProblem(uint index) + LocalIndexedProblem(const ProblemPointer& problem, const TopDUContext* top); + LocalIndexedProblem(uint index = 0) : m_index(index) {} /** * \note Duchain must be read locked */ - Problem* data(const TopDUContext* top) const; + ProblemPointer data(const TopDUContext* top) const; bool operator==(const LocalIndexedProblem& rhs) const { return m_index == rhs.m_index; } - uint hash() const - { - return m_index; - } - bool isValid() const { return m_index; } - bool operator<(const LocalIndexedProblem& rhs) const - { - return m_index < rhs.m_index; - } - /** * Index of the Declaration within the top context */ uint localIndex() const { return m_index; } - bool isLoaded(TopDUContext* top) const; - private: uint m_index; }; KDEVPLATFORMLANGUAGE_EXPORT DECLARE_LIST_MEMBER_HASH(ProblemData, diagnostics, LocalIndexedProblem) class KDEVPLATFORMLANGUAGE_EXPORT ProblemData : public DUChainBaseData { public: enum Source { Unknown /**< Unknown problem */, Disk /**< problem reading from disk */, Preprocessor /**< problem during pre-processing */, Lexer /**< problem while lexing the file */, Parser /**< problem while parsing the file */, DUChainBuilder /**< problem while building the duchain */, SemanticAnalysis /**< problem during semantic analysis */, ToDo /**< TODO item in a comment */ }; enum Severity { Error, Warning, Hint //For implementation-helpers and such stuff. Is not highlighted if the user disables the "highlight semantic problems" option }; ProblemData() : source(Unknown) , severity(Error) { initializeAppendedLists(); } ProblemData(const ProblemData& rhs) : DUChainBaseData(rhs) , source(rhs.source) , severity(rhs.severity) , url(rhs.url) , description(rhs.description) , explanation(rhs.explanation) { initializeAppendedLists(); copyListsFrom(rhs); } ~ProblemData() { freeAppendedLists(); } Source source; Severity severity; IndexedString url; IndexedString description; IndexedString explanation; START_APPENDED_LISTS_BASE(ProblemData, DUChainBaseData); APPENDED_LIST_FIRST(ProblemData, LocalIndexedProblem, diagnostics); END_APPENDED_LISTS(ProblemData, diagnostics); }; /** * An object representing a problem in preprocessing, parsing, definition-use chain compilation, etc. * * You should always use ProblemPointer, because Problem may be subclassed. * The subclass would be lost while copying. * * @warning Access to problems must be serialized through DUChainLock. */ class KDEVPLATFORMLANGUAGE_EXPORT Problem : public DUChainBase, public KShared { public: using Ptr = KSharedPtr; Problem(); Problem(ProblemData& data); ~Problem(); ProblemData::Source source() const; void setSource(ProblemData::Source source); /** * Returns a string version of the problem source */ QString sourceString() const; - void setContext(TopDUContext* context); TopDUContext* topContext() const override; KDevelop::IndexedString url() const override; /** * Location where this problem occurred * @warning Must only be called from the foreground * */ DocumentRange finalLocation() const; void setFinalLocation(const DocumentRange& location); /** * Returns child diagnostics of this particular problem * * Example: * @code * void foo(unsigned int); * void foo(const char*); * int main() { foo(0); } * @endcode * * => foo(0) is ambigous. This will give us a ProblemPointer pointing to 'foo(0)'. * * Additionally, @p diagnostics may return the two locations to the ambiguous overloads, * with descriptions such as 'test.cpp:1: candidate : ...' */ QList diagnostics() const; void setDiagnostics(const QList& diagnostics); void addDiagnostic(const Ptr& diagnostic); void clearDiagnostics(); /** * A brief description of the problem. */ QString description() const; void setDescription(const QString& description); /** * A (detailed) explanation of why the problem occurred. */ QString explanation() const; void setExplanation(const QString& explanation); /** * Get the severity of this problem. * This is used for example to decide for a highlighting color. * * @see setSeverity() */ ProblemData::Severity severity() const; /** * Set the severity of this problem. */ void setSeverity(ProblemData::Severity severity); /** * Returns a string representation of the severity. */ QString severityString() const; /** * If this problem can be solved, this may return an assistant for the solution. */ virtual KSharedPtr solutionAssistant() const; enum { Identity = 15 }; /** * Returns a string representation of this problem, useful for debugging. */ virtual QString toString() const; private: void rebuildDynamicData(DUContext* parent, uint ownIndex) override; - /** - * Return a problem equivalent to this one for serialization in @p context. - * - * If this problem is not yet associated with a context, we set the context - * of it to @p context. If this problem is associated with a different context, - * the problem is cloned and a copy is returned which can be serialized. - */ - Ptr prepareStorage(TopDUContext* context); Q_DISABLE_COPY(Problem); DUCHAIN_DECLARE_DATA(Problem) friend class TopDUContext; friend class TopDUContextDynamicData; friend class LocalIndexedProblem; //BEGIN dynamic data - TopDUContext* m_topContext; + TopDUContextPointer m_topContext; mutable QList m_diagnostics; uint m_indexInTopContext; //END dynamic data }; -using ProblemPointer = KSharedPtr; - } +Q_DECLARE_TYPEINFO(KDevelop::LocalIndexedProblem, Q_MOVABLE_TYPE); + KDEVPLATFORMLANGUAGE_EXPORT QDebug operator<<(QDebug s, const KDevelop::Problem& problem); KDEVPLATFORMLANGUAGE_EXPORT QDebug operator<<(QDebug s, const KDevelop::ProblemPointer& problem); #endif // KDEVPLATFORM_I_PROBLEM_H diff --git a/language/duchain/repositories/itemrepositoryregistry.cpp b/language/duchain/repositories/itemrepositoryregistry.cpp index 06ed84ea21..c37b70fe4f 100644 --- a/language/duchain/repositories/itemrepositoryregistry.cpp +++ b/language/duchain/repositories/itemrepositoryregistry.cpp @@ -1,396 +1,402 @@ /* 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 #include "abstractitemrepository.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 xdgCacheDir = QProcessEnvironment::systemEnvironment().value("XDG_CACHE_HOME", QDir::homePath() + "/.cache") + "/kdevduchain"; QString baseDir = QProcessEnvironment::systemEnvironment().value("KDEV_DUCHAIN_DIR", xdgCacheDir); baseDir += QString("/%1-%2").arg(qAppName()).arg(session->id()); - KStandardDirs::makeDir(baseDir); return baseDir; } +bool shouldClear(const QString& path) +{ + QDir dir(path); + + if (!dir.exists()) { + return false; + } + + if (getenv("CLEAR_DUCHAIN_DIR")) { + kDebug() << "clearing duchain directory because CLEAR_DUCHAIN_DIR is set"; + return true; + } + + if (dir.exists("is_writing")) { + kWarning() << "repository" << path << "was write-locked, it probably is inconsistent"; + return true; + } + + if (!dir.exists(QString("version_%1").arg(staticItemRepositoryVersion()))) { + kWarning() << "version-hint not found, seems to be an old version"; + return true; + } + + QFile crashesFile(dir.filePath(QString("crash_counter"))); + if (crashesFile.open(QIODevice::ReadOnly)) { + int count; + QDataStream stream(&crashesFile); + stream >> count; + + kDebug() << "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) { + kDebug() << "User chose to clean repository"; + return true; + } else { + setCrashCounter(crashesFile, 1); + kDebug() << "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; ItemRepositoryRegistryPrivate(ItemRepositoryRegistry* owner) : m_owner(owner) , m_shallDelete(false) , m_mutex(QMutex::Recursive) { } void lockForWriting(); void unlockForWriting(); void deleteDataDirectory(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 = 0; 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. removeDirectory(repositoryPath); } } 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(); kError() << "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(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 = removeDirectory(m_path); 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) { KStandardDirs::makeDir(m_path); } } bool ItemRepositoryRegistryPrivate::open(const QString& path) { QMutexLocker mlock(&m_mutex); if(m_path == path) { return true; } m_path = path; // Check if the repository shall be cleared - // do-while is here for breaks - bool clear = false; - do { - if(QFile::exists(m_path + "/is_writing")) { - kWarning() << "repository" << m_path << "was write-locked, it probably is inconsistent"; - clear = true; - break; - } - - if(!QFile::exists(m_path + QString("/version_%1").arg(staticItemRepositoryVersion()))) { - kWarning() << "version-hint not found, seems to be an old version"; - clear = true; - break; - } - - if(getenv("CLEAR_DUCHAIN_DIR")) { - kWarning() << "clearing duchain directory because CLEAR_DUCHAIN_DIR is set"; - clear = true; - break; - } - - QFile crashesFile(m_path + QString("/crash_counter")); - if(crashesFile.open(QIODevice::ReadOnly)) { - int count; - QDataStream stream(&crashesFile); - stream >> count; - - kDebug() << "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) { - clear = true; - kDebug() << "User chose to clean repository"; - break; - } else { - setCrashCounter(crashesFile, 1); - kDebug() << "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); - } - } while(false); - - if(clear) { + if (shouldClear(path)) { kWarning() << QString("The data-repository at %1 has to be cleared.").arg(m_path); deleteDataDirectory(); } + KStandardDirs::makeDir(path); + foreach(AbstractItemRepository* repository, m_repositories.keys()) { if(!repository->open(path)) { deleteDataDirectory(); kError() << "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; } } return true; } void ItemRepositoryRegistry::store() { QMutexLocker lock(&d->m_mutex); foreach(AbstractItemRepository* repository, d->m_repositories.keys()) { repository->store(); } QFile versionFile(d->m_path + QString("/version_%1").arg(staticItemRepositoryVersion())); if(versionFile.open(QIODevice::WriteOnly)) { versionFile.close(); } else { kWarning() << "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 { kWarning() << "Could not open counter file for writing"; } } void ItemRepositoryRegistry::printAllStatistics() const { QMutexLocker lock(&d->m_mutex); foreach(AbstractItemRepository* repository, d->m_repositories.keys()) { kDebug() << "statistics in" << repository->repositoryName() << ":"; kDebug() << 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; kDebug() << "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() { if(d->m_shallDelete) { d->deleteDataDirectory(false); } else { QFile::remove(d->m_path + QString("/crash_counter")); } } } diff --git a/language/duchain/tests/test_duchain.cpp b/language/duchain/tests/test_duchain.cpp index 85ce3316bc..a167d6eb47 100644 --- a/language/duchain/tests/test_duchain.cpp +++ b/language/duchain/tests/test_duchain.cpp @@ -1,862 +1,941 @@ /* * This file is part of KDevelop * * Copyright 2011-2013 Milian Wolff * Copyright 2006 Hamish Rodda * Copyright 2007-2009 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 "test_duchain.h" #include #include #include #include #include #include #include #include #include #include #include #include #include +#include +#include #include #include #include // #include #include #include #include // needed for std::insert_iterator on windows #include //Extremely slow // #define TEST_NORMAL_IMPORTS QTEST_MAIN(TestDUChain) using namespace KDevelop; using namespace Utils; typedef BasicSetRepository::Index Index; struct Timer { Timer() { m_timer.start(); } qint64 elapsed() { return m_timer.nsecsElapsed(); } QElapsedTimer m_timer; }; void TestDUChain::initTestCase() { AutoTestShell::init(); TestCore::initialize(Core::NoUi); DUChain::self()->disablePersistentStorage(); CodeRepresentation::setDiskChangesForbidden(true); } void TestDUChain::cleanupTestCase() { TestCore::shutdown(); } void TestDUChain::testStringSets() { const unsigned int setCount = 8; const unsigned int choiceCount = 40; const unsigned int itemCount = 120; BasicSetRepository rep("test repository"); // kDebug() << "Start repository-layout: \n" << rep.dumpDotGraph(); qint64 repositoryTime = 0; //Time spent on repository-operations qint64 genericTime = 0; //Time spend on equivalent operations with generic sets qint64 repositoryIntersectionTime = 0; //Time spent on repository-operations qint64 genericIntersectionTime = 0; //Time spend on equivalent operations with generic sets qint64 qsetIntersectionTime = 0; //Time spend on equivalent operations with generic sets qint64 repositoryUnionTime = 0; //Time spent on repository-operations qint64 genericUnionTime = 0; //Time spend on equivalent operations with generic sets qint64 repositoryDifferenceTime = 0; //Time spent on repository-operations qint64 genericDifferenceTime = 0; //Time spend on equivalent operations with generic sets Set sets[setCount]; std::set realSets[setCount]; for(unsigned int a = 0; a < setCount; a++) { std::set chosenIndices; unsigned int thisCount = rand() % choiceCount; if(thisCount == 0) thisCount = 1; for(unsigned int b = 0; b < thisCount; b++) { Index choose = (rand() % itemCount) + 1; while(chosenIndices.find(choose) != chosenIndices.end()) { choose = (rand() % itemCount) + 1; } Timer t; chosenIndices.insert(chosenIndices.end(), choose); genericTime += t.elapsed(); } { Timer t; sets[a] = rep.createSet(chosenIndices); repositoryTime += t.elapsed(); } realSets[a] = chosenIndices; std::set tempSet = sets[a].stdSet(); if(tempSet != realSets[a]) { QString dbg = "created set: "; for(std::set::const_iterator it = realSets[a].begin(); it != realSets[a].end(); ++it) dbg += QString("%1 ").arg(*it); qDebug() << dbg; dbg = "repo. set: "; for(std::set::const_iterator it = tempSet.begin(); it != tempSet.end(); ++it) dbg += QString("%1 ").arg(*it); qDebug() << dbg; qDebug() << "DOT-Graph:\n\n" << sets[a].dumpDotGraph() << "\n\n"; QFAIL("sets are not the same!"); } } for(int cycle = 0; cycle < 100; ++cycle) { if(cycle % 10 == 0) qDebug() << "cycle" << cycle; for(unsigned int a = 0; a < setCount; a++) { for(unsigned int b = 0; b < setCount; b++) { /// ----- SUBTRACTION/DIFFERENCE std::set _realDifference; { Timer t; std::set_difference(realSets[a].begin(), realSets[a].end(), realSets[b].begin(), realSets[b].end(), std::insert_iterator >(_realDifference, _realDifference.begin())); genericDifferenceTime += t.elapsed(); } Set _difference; { Timer t; _difference = sets[a] - sets[b]; repositoryDifferenceTime += t.elapsed(); } if(_difference.stdSet() != _realDifference) { { qDebug() << "SET a:"; QString dbg = ""; for(std::set::const_iterator it = realSets[a].begin(); it != realSets[a].end(); ++it) dbg += QString("%1 ").arg(*it); qDebug() << dbg; qDebug() << "DOT-Graph:\n\n" << sets[a].dumpDotGraph() << "\n\n"; } { qDebug() << "SET b:"; QString dbg = ""; for(std::set::const_iterator it = realSets[b].begin(); it != realSets[b].end(); ++it) dbg += QString("%1 ").arg(*it); qDebug() << dbg; qDebug() << "DOT-Graph:\n\n" << sets[b].dumpDotGraph() << "\n\n"; } { std::set tempSet = _difference.stdSet(); qDebug() << "SET difference:"; QString dbg = "real set: "; for(std::set::const_iterator it = _realDifference.begin(); it != _realDifference.end(); ++it) dbg += QString("%1 ").arg(*it); qDebug() << dbg; dbg = "repo. set: "; for(std::set::const_iterator it = tempSet.begin(); it != tempSet.end(); ++it) dbg += QString("%1 ").arg(*it); qDebug() << dbg; qDebug() << "DOT-Graph:\n\n" << _difference.dumpDotGraph() << "\n\n"; } QFAIL("difference sets are not the same!"); } /// ------ UNION std::set _realUnion; { Timer t; std::set_union(realSets[a].begin(), realSets[a].end(), realSets[b].begin(), realSets[b].end(), std::insert_iterator >(_realUnion, _realUnion.begin())); genericUnionTime += t.elapsed(); } Set _union; { Timer t; _union = sets[a] + sets[b]; repositoryUnionTime += t.elapsed(); } if(_union.stdSet() != _realUnion) { { qDebug() << "SET a:"; QString dbg = ""; for(std::set::const_iterator it = realSets[a].begin(); it != realSets[a].end(); ++it) dbg += QString("%1 ").arg(*it); qDebug() << dbg; qDebug() << "DOT-Graph:\n\n" << sets[a].dumpDotGraph() << "\n\n"; } { qDebug() << "SET b:"; QString dbg = ""; for(std::set::const_iterator it = realSets[b].begin(); it != realSets[b].end(); ++it) dbg += QString("%1 ").arg(*it); qDebug() << dbg; qDebug() << "DOT-Graph:\n\n" << sets[b].dumpDotGraph() << "\n\n"; } { std::set tempSet = _union.stdSet(); qDebug() << "SET union:"; QString dbg = "real set: "; for(std::set::const_iterator it = _realUnion.begin(); it != _realUnion.end(); ++it) dbg += QString("%1 ").arg(*it); qDebug() << dbg; dbg = "repo. set: "; for(std::set::const_iterator it = tempSet.begin(); it != tempSet.end(); ++it) dbg += QString("%1 ").arg(*it); qDebug() << dbg; qDebug() << "DOT-Graph:\n\n" << _union.dumpDotGraph() << "\n\n"; } QFAIL("union sets are not the same"); } std::set _realIntersection; /// -------- INTERSECTION { Timer t; std::set_intersection(realSets[a].begin(), realSets[a].end(), realSets[b].begin(), realSets[b].end(), std::insert_iterator >(_realIntersection, _realIntersection.begin())); genericIntersectionTime += t.elapsed(); } //Just for fun: Test how fast QSet intersections are QSet first, second; for(std::set::const_iterator it = realSets[a].begin(); it != realSets[a].end(); ++it) { first.insert(*it); } for(std::set::const_iterator it = realSets[b].begin(); it != realSets[b].end(); ++it) { second.insert(*it); } { Timer t; QSet i = first.intersect(second); qsetIntersectionTime += t.elapsed(); } Set _intersection; { Timer t; _intersection = sets[a] & sets[b]; repositoryIntersectionTime += t.elapsed(); } if(_intersection.stdSet() != _realIntersection) { { qDebug() << "SET a:"; QString dbg = ""; for(std::set::const_iterator it = realSets[a].begin(); it != realSets[a].end(); ++it) dbg += QString("%1 ").arg(*it); qDebug() << dbg; qDebug() << "DOT-Graph:\n\n" << sets[a].dumpDotGraph() << "\n\n"; } { qDebug() << "SET b:"; QString dbg = ""; for(std::set::const_iterator it = realSets[b].begin(); it != realSets[b].end(); ++it) dbg += QString("%1 ").arg(*it); qDebug() << dbg; qDebug() << "DOT-Graph:\n\n" << sets[b].dumpDotGraph() << "\n\n"; } { std::set tempSet = _intersection.stdSet(); qDebug() << "SET intersection:"; QString dbg = "real set: "; for(std::set::const_iterator it = _realIntersection.begin(); it != _realIntersection.end(); ++it) dbg += QString("%1 ").arg(*it); qDebug() << dbg; dbg = "repo. set: "; for(std::set::const_iterator it = tempSet.begin(); it != tempSet.end(); ++it) dbg += QString("%1 ").arg(*it); qDebug() << dbg; qDebug() << "DOT-Graph:\n\n" << _intersection.dumpDotGraph() << "\n\n"; } QFAIL("intersection sets are not the same"); } } } qDebug() << "cycle " << cycle; qDebug() << "ns needed for set-building: repository-set: " << float(repositoryTime) << " generic-set: " << float(genericTime); qDebug() << "ns needed for intersection: repository-sets: " << float(repositoryIntersectionTime) << " generic-set: " << float(genericIntersectionTime) << " QSet: " << float(qsetIntersectionTime); qDebug() << "ns needed for union: repository-sets: " << float(repositoryUnionTime) << " generic-set: " << float(genericUnionTime); qDebug() << "ns needed for difference: repository-sets: " << float(repositoryDifferenceTime) << " generic-set: " << float(genericDifferenceTime); } } void TestDUChain::testSymbolTableValid() { DUChainReadLocker lock(DUChain::lock()); PersistentSymbolTable::self().selfAnalysis(); } void TestDUChain::testIndexedStrings() { int testCount = 600000; QHash knownIndices; int a = 0; for(a = 0; a < testCount; ++a) { QString testString; int length = rand() % 10; for(int b = 0; b < length; ++b) testString.append((char)(rand() % 6) + 'a'); QByteArray array = testString.toUtf8(); //kDebug() << "checking with" << testString; //kDebug() << "checking" << a; IndexedString indexed(array.constData(), array.size(), IndexedString::hashString(array.constData(), array.size())); QCOMPARE(indexed.str(), testString); if(knownIndices.contains(testString)) { QCOMPARE(indexed.index(), knownIndices[testString].index()); } else { knownIndices[testString] = indexed; } if(a % (testCount/10) == 0) kDebug() << a << "of" << testCount; } kDebug() << a << "successful tests"; } struct TestContext { TestContext() { static int number = 0; ++number; DUChainWriteLocker lock(DUChain::lock()); m_context = new TopDUContext(IndexedString(QString("/test1/%1").arg(number)), RangeInRevision()); m_normalContext = new DUContext(RangeInRevision(), m_context); DUChain::self()->addDocumentChain(m_context); Q_ASSERT(IndexedDUContext(m_context).context() == m_context); } ~TestContext() { foreach(TestContext* importer, importers) importer->unImport(QList() << this); unImport(imports); DUChainWriteLocker lock(DUChain::lock()); TopDUContextPointer tp(m_context); DUChain::self()->removeDocumentChain(static_cast(m_context)); Q_ASSERT(!tp); } void verify(QList allContexts) { { DUChainReadLocker lock(DUChain::lock()); QCOMPARE(m_context->importedParentContexts().count(), imports.count()); } //Compute a closure of all children, and verify that they are imported. QSet collected; collectImports(collected); collected.remove(this); DUChainReadLocker lock(DUChain::lock()); foreach(TestContext* context, collected) { QVERIFY(m_context->imports(context->m_context, CursorInRevision::invalid())); #ifdef TEST_NORMAL_IMPORTS QVERIFY(m_normalContext->imports(context->m_normalContext)); #endif } //Verify that no other contexts are imported foreach(TestContext* context, allContexts) if(context != this) { QVERIFY(collected.contains(context) || !m_context->imports(context->m_context, CursorInRevision::invalid())); #ifdef TEST_NORMAL_IMPORTS QVERIFY(collected.contains(context) || !m_normalContext->imports(context->m_normalContext, CursorInRevision::invalid())); #endif } } void collectImports(QSet& collected) { if(collected.contains(this)) return; collected.insert(this); foreach(TestContext* context, imports) context->collectImports(collected); } void import(TestContext* ctx) { if(imports.contains(ctx) || ctx == this) return; imports << ctx; ctx->importers << this; DUChainWriteLocker lock(DUChain::lock()); m_context->addImportedParentContext(ctx->m_context); #ifdef TEST_NORMAL_IMPORTS m_normalContext->addImportedParentContext(ctx->m_normalContext); #endif } void unImport(QList ctxList) { QList list; QList normalList; foreach(TestContext* ctx, ctxList) { if(!imports.contains(ctx)) continue; list << ctx->m_context; normalList << ctx->m_normalContext; imports.removeAll(ctx); ctx->importers.removeAll(this); } DUChainWriteLocker lock(DUChain::lock()); m_context->removeImportedParentContexts(list); #ifdef TEST_NORMAL_IMPORTS foreach(DUContext* ctx, normalList) m_normalContext->removeImportedParentContext(ctx); #endif } void clearImports() { { DUChainWriteLocker lock(DUChain::lock()); m_context->clearImportedParentContexts(); m_normalContext->clearImportedParentContexts(); } foreach(TestContext* ctx, imports) { imports.removeAll(ctx); ctx->importers.removeAll(this); } } QList imports; private: TopDUContext* m_context; DUContext* m_normalContext; QList importers; }; void collectReachableNodes(QSet& reachableNodes, uint currentNode) { if(!currentNode) return; reachableNodes.insert(currentNode); const Utils::SetNodeData* node = KDevelop::RecursiveImportRepository::repository()->nodeFromIndex(currentNode); Q_ASSERT(node); collectReachableNodes(reachableNodes, node->leftNode()); collectReachableNodes(reachableNodes, node->rightNode()); } uint collectNaiveNodeCount(uint currentNode) { if(!currentNode) return 0; uint ret = 1; const Utils::SetNodeData* node = KDevelop::RecursiveImportRepository::repository()->nodeFromIndex(currentNode); Q_ASSERT(node); ret += collectNaiveNodeCount(node->leftNode()); ret += collectNaiveNodeCount(node->rightNode()); return ret; } void TestDUChain::testImportStructure() { Timer total; kDebug() << "before: " << KDevelop::RecursiveImportRepository::repository()->getDataRepository().statistics().print(); ///Maintains a naive import-structure along with a real top-context import structure, and allows comparing both. int cycles = 5; //int cycles = 100; //srand(time(NULL)); for(int t = 0; t < cycles; ++t) { QList allContexts; //Create a random structure int contextCount = 50; int verifyOnceIn = contextCount/*((contextCount*contextCount)/20)+1*/; //Verify once in every chances(not in all cases, because else the import-structure isn't built on-demand!) int clearOnceIn = contextCount; for(int a = 0; a < contextCount; a++) allContexts << new TestContext(); for(int c = 0; c < cycles; ++c) { //kDebug() << "main-cycle" << t << "sub-cycle" << c; //Add random imports and compare for(int a = 0; a < contextCount; a++) { //Import up to 5 random other contexts into each context int importCount = rand() % 5; //kDebug() << "cnt> " << importCount; for(int i = 0; i < importCount; ++i) { //int importNr = rand() % contextCount; //kDebug() << "nmr > " << importNr; //allContexts[a]->import(allContexts[importNr]); allContexts[a]->import(allContexts[rand() % contextCount]); } for(int b = 0; b < contextCount; b++) if(rand() % verifyOnceIn == 0) allContexts[b]->verify(allContexts); } //Remove random imports and compare for(int a = 0; a < contextCount; a++) { //Import up to 5 random other contexts into each context int removeCount = rand() % 3; QSet removeImports; for(int i = 0; i < removeCount; ++i) if(allContexts[a]->imports.count()) removeImports.insert(allContexts[a]->imports[rand() % allContexts[a]->imports.count()]); allContexts[a]->unImport(removeImports.toList()); for(int b = 0; b < contextCount; b++) if(rand() % verifyOnceIn == 0) allContexts[b]->verify(allContexts); } for(int a = 0; a < contextCount; a++) { if(rand() % clearOnceIn == 0) { allContexts[a]->clearImports(); allContexts[a]->verify(allContexts); } } } kDebug() << "after: " << KDevelop::RecursiveImportRepository::repository()->getDataRepository().statistics().print(); for(int a = 0; a < contextCount; ++a) delete allContexts[a]; allContexts.clear(); kDebug() << "after cleanup: " << KDevelop::RecursiveImportRepository::repository()->getDataRepository().statistics().print(); } qDebug() << "total ns needed for import-structure test:" << float(total.elapsed()); } class TestWorker : public QObject { Q_OBJECT public slots: void lockForWrite() { for(int i = 0; i < 10000; ++i) { DUChainWriteLocker lock; } } void lockForRead() { for(int i = 0; i < 10000; ++i) { DUChainReadLocker lock; } } void lockForReadWrite() { for(int i = 0; i < 10000; ++i) { { DUChainReadLocker lock; } { DUChainWriteLocker lock; } } } static QSharedPointer createWorkerThread(const char* workerSlot) { QThread* thread = new QThread; TestWorker* worker = new TestWorker; connect(thread, SIGNAL(started()), worker, workerSlot); connect(thread, SIGNAL(finished()), worker, SLOT(deleteLater())); worker->moveToThread(thread); return QSharedPointer(thread); } }; class ThreadList : public QVector< QSharedPointer > { public: bool join(int timeout) { foreach(const QSharedPointer& thread, *this) { // quit event loop Q_ASSERT(thread->isRunning()); thread->quit(); // wait for finish if (!thread->wait(timeout)) { return false; } Q_ASSERT(thread->isFinished()); } return true; } void start() { foreach(const QSharedPointer& thread, *this) { thread->start(); } } }; void TestDUChain::testLockForWrite() { ThreadList threads; for(int i = 0; i < 10; ++i) { threads << TestWorker::createWorkerThread(SLOT(lockForWrite())); } threads.start(); QBENCHMARK { { DUChainWriteLocker lock; } { DUChainReadLocker lock; } } QVERIFY(threads.join(1000)); } void TestDUChain::testLockForRead() { ThreadList threads; for(int i = 0; i < 10; ++i) { threads << TestWorker::createWorkerThread(SLOT(lockForRead())); } threads.start(); QBENCHMARK { DUChainReadLocker lock; } QVERIFY(threads.join(1000)); } void TestDUChain::testLockForReadWrite() { ThreadList threads; for(int i = 0; i < 10; ++i) { threads << TestWorker::createWorkerThread(SLOT(lockForReadWrite())); } threads.start(); QBENCHMARK { DUChainWriteLocker lock; } QVERIFY(threads.join(1000)); } +void TestDUChain::testProblemSerialization() +{ + DUChain::self()->disablePersistentStorage(false); + + auto parent = ProblemPointer{new Problem}; + parent->setDescription("parent"); + + auto child = ProblemPointer{new Problem}; + child->setDescription("child"); + parent->addDiagnostic(child); + + const IndexedString url("/my/test/file"); + + TopDUContextPointer smartTop; + + { // serialize + DUChainWriteLocker lock; + auto file = new ParsingEnvironmentFile(url); + auto top = new TopDUContext(url, {}, file); + + top->addProblem(parent); + QCOMPARE(top->problems().size(), 1); + auto p = top->problems().first(); + QCOMPARE(p->description(), QString("parent")); + QCOMPARE(p->diagnostics().size(), 1); + auto c = p->diagnostics().first(); + QCOMPARE(c->description(), QString("child")); + + DUChain::self()->addDocumentChain(top); + QVERIFY(DUChain::self()->chainForDocument(url)); + smartTop = top; + } + + DUChain::self()->storeToDisk(); + + ProblemPointer parent_deserialized; + ProblemPointer child_deserialized; + + { // deserialize + DUChainWriteLocker lock; + QVERIFY(!smartTop); + auto top = DUChain::self()->chainForDocument(url); + QVERIFY(top); + smartTop = top; + QCOMPARE(top->problems().size(), 1); + parent_deserialized = top->problems().first(); + QCOMPARE(parent_deserialized->diagnostics().size(), 1); + child_deserialized = parent_deserialized->diagnostics().first(); + + QCOMPARE(parent_deserialized->description(), QString("parent")); + QCOMPARE(child_deserialized->description(), QString("child")); + + top->clearProblems(); + QVERIFY(top->problems().isEmpty()); + + QCOMPARE(parent_deserialized->description(), QString("parent")); + QCOMPARE(child_deserialized->description(), QString("child")); + + DUChain::self()->removeDocumentChain(top); + + QCOMPARE(parent_deserialized->description(), QString("parent")); + QCOMPARE(child_deserialized->description(), QString("child")); + + QVERIFY(!smartTop); + } + + DUChain::self()->disablePersistentStorage(true); + + QCOMPARE(parent->description(), QString("parent")); + QCOMPARE(child->description(), QString("child")); + QCOMPARE(parent_deserialized->description(), QString("parent")); + QCOMPARE(child_deserialized->description(), QString("child")); + + parent->clearDiagnostics(); + QVERIFY(parent->diagnostics().isEmpty()); +} + #if 0 ///NOTE: the "unit tests" below are not automated, they - so far - require /// human interpretation which is not useful for a unit test! /// someone should investigate what the expected output should be /// and add proper QCOMPARE/QVERIFY checks accordingly ///FIXME: this needs to be rewritten in order to remove dependencies on formerly run unit tests void TestDUChain::testImportCache() { KDevelop::globalItemRepositoryRegistry().printAllStatistics(); KDevelop::RecursiveImportRepository::repository()->printStatistics(); //Analyze the whole existing import-cache //This is very expensive, since it involves loading all existing top-contexts uint topContextCount = DUChain::self()->newTopContextIndex(); uint analyzedCount = 0; uint totalImportCount = 0; uint naiveNodeCount = 0; QSet reachableNodes; DUChainReadLocker lock(DUChain::lock()); for(uint a = 0; a < topContextCount; ++a) { if(a % qMax(1u, topContextCount / 100) == 0) { kDebug() << "progress:" << (a * 100) / topContextCount; } TopDUContext* context = DUChain::self()->chainForIndex(a); if(context) { TopDUContext::IndexedRecursiveImports imports = context->recursiveImportIndices(); ++analyzedCount; totalImportCount += imports.set().count(); collectReachableNodes(reachableNodes, imports.setIndex()); naiveNodeCount += collectNaiveNodeCount(imports.setIndex()); } } QVERIFY(analyzedCount); kDebug() << "average total count of imports:" << totalImportCount / analyzedCount; kDebug() << "count of reachable nodes:" << reachableNodes.size(); kDebug() << "naive node-count:" << naiveNodeCount << "sharing compression factor:" << ((float)reachableNodes.size()) / ((float)naiveNodeCount); } #endif void TestDUChain::benchCodeModel() { const IndexedString file("testFile"); QVERIFY(!QTypeInfo< KDevelop::CodeModelItem >::isStatic); int i = 0; QBENCHMARK { CodeModel::self().addItem(file, QualifiedIdentifier("testQID" + QString::number(i++)), KDevelop::CodeModelItem::Class); } } void TestDUChain::benchTypeRegistry() { IntegralTypeData data; data.m_dataType = IntegralType::TypeInt; data.typeClassId = IntegralType::Identity; data.inRepository = false; data.m_modifiers = 42; data.m_dynamic = false; data.refCount = 1; IntegralTypeData to; QFETCH(int, func); QBENCHMARK { switch(func) { case 0: TypeSystem::self().dataClassSize(data); break; case 1: TypeSystem::self().dynamicSize(data); break; case 2: TypeSystem::self().create(&data); break; case 3: TypeSystem::self().isFactoryLoaded(data); break; case 4: TypeSystem::self().copy(data, to, !data.m_dynamic); break; case 5: TypeSystem::self().copy(data, to, data.m_dynamic); break; case 6: TypeSystem::self().callDestructor(&data); break; } } } void TestDUChain::benchTypeRegistry_data() { QTest::addColumn("func"); QTest::newRow("dataClassSize") << 0; QTest::newRow("dynamicSize") << 1; QTest::newRow("create") << 2; QTest::newRow("isFactoryLoaded") << 3; QTest::newRow("copy") << 4; QTest::newRow("copyNonDynamic") << 5; QTest::newRow("callDestructor") << 6; } void TestDUChain::benchDuchainReadLocker() { QBENCHMARK { DUChainReadLocker lock; } } void TestDUChain::benchDuchainWriteLocker() { QBENCHMARK { DUChainWriteLocker lock; } } void TestDUChain::benchDUChainItemFactory_copy() { DUChainItemFactory factory; DeclarationData from, to; from.classId = Declaration::Identity; QFETCH(int, constant); bool c = constant; QBENCHMARK { factory.copy(from, to, c); if (constant == 2) { c = !c; } } } void TestDUChain::benchDUChainItemFactory_copy_data() { QTest::addColumn("constant"); QTest::newRow("non-const") << 0; QTest::newRow("const") << 1; QTest::newRow("flip") << 2; } #include "test_duchain.moc" #include "moc_test_duchain.cpp" diff --git a/language/duchain/tests/test_duchain.h b/language/duchain/tests/test_duchain.h index 85ed83249a..d6986b3483 100644 --- a/language/duchain/tests/test_duchain.h +++ b/language/duchain/tests/test_duchain.h @@ -1,56 +1,57 @@ /* * This file is part of KDevelop * * Copyright 2011-2013 Milian Wolff * Copyright 2006 Hamish Rodda * Copyright 2007-2009 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_TEST_DUCHAIN_H #define KDEVPLATFORM_TEST_DUCHAIN_H #include class TestDUChain : public QObject { Q_OBJECT private slots: void initTestCase(); void cleanupTestCase(); void testStringSets(); void testSymbolTableValid(); void testIndexedStrings(); void testImportStructure(); void testLockForWrite(); void testLockForRead(); void testLockForReadWrite(); + void testProblemSerialization(); ///NOTE: these are not "automated"! // void testImportCache(); void benchCodeModel(); void benchTypeRegistry(); void benchTypeRegistry_data(); void benchDuchainWriteLocker(); void benchDuchainReadLocker(); void benchDUChainItemFactory_copy(); void benchDUChainItemFactory_copy_data(); }; #endif // KDEVPLATFORM_TEST_DUCHAIN_H diff --git a/language/duchain/topducontext.cpp b/language/duchain/topducontext.cpp index 3bd2ea8f40..e5407e3acb 100644 --- a/language/duchain/topducontext.cpp +++ b/language/duchain/topducontext.cpp @@ -1,1244 +1,1219 @@ /* 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 #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 "abstractfunctiondeclaration.h" #include "uses.h" #include "topducontextdata.h" #include "duchainregister.h" #include "topducontextdynamicdata.h" // #define DEBUG_SEARCH const uint maxApplyAliasesRecursion = 100; namespace KDevelop { Utils::BasicSetRepository* RecursiveImportRepository::repository() { static Utils::BasicSetRepository recursiveImportRepositoryObject("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(0))) dynamic_cast(import.context(0))->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(0)); TopDUContext* top = import.context(0)->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; - // used to keep problems alive while the topducontext is alive - QList m_problems; ParsingEnvironmentFilePointer m_file; KSharedPtr 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(0)); 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); } } }; ///Takes a set of conditions in the constructors, and checks with each call to operator() whether these conditions are fulfilled on the given declaration. ///The import-structure needs to be constructed and locked when this is used TopDUContext::DeclarationChecker::DeclarationChecker(const TopDUContext* _top, const CursorInRevision& _position, const AbstractType::Ptr& _dataType, DUContext::SearchFlags _flags, KDevVarLengthArray* _createVisibleCache) : createVisibleCache(_createVisibleCache) , top(_top) , topDFunc(_top->d_func()) , position(_position) , dataType(_dataType) , flags(_flags) { } bool TopDUContext::DeclarationChecker::operator()(const Declaration* decl) const { if(!decl) return false; if (top != decl->topContext()) { if((flags & DUContext::OnlyFunctions) && !dynamic_cast(decl)) return false; if (dataType && decl->abstractType()->indexed() != dataType->indexed()) // The declaration doesn't match the type filter we are applying return false; } else { if((flags & DUContext::OnlyFunctions) && !dynamic_cast(decl)) return false; if (dataType && decl->abstractType() != dataType) // The declaration doesn't match the type filter we are applying return false; if (decl->range().start >= position) if(!decl->context() || decl->context()->type() != DUContext::Class) return false; // The declaration is behind the position we're searching from, therefore not accessible } // Success, this declaration is accessible return true; } 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()) { kDebug() << "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()) { kDebug() << "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(0))); //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(0))); //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 == 0 && 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)) { 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); } KSharedPtr 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; - // ugly hack: if we don't clear the problems here we'll end up with double deletions as the shared ptrs - // will potentially try to delete mmapped, serialized data :-/ - m_local->m_problems.clear(); 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(KSharedPtr 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 = KSharedPtr(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 kDebug() << "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( decl->kind() == Declaration::Alias ) { //Apply alias declarations AliasDeclaration* alias = static_cast(decl); if(alias->aliasedDeclaration().isValid()) { decl = alias->aliasedDeclaration().declaration(); } else { kDebug() << "lost aliased declaration"; } } target.append(decl); } check.createVisibleCache = 0; 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 FOREACH_ARRAY(const SearchItem::Ptr& idTree, identifiers) foreach(const QualifiedIdentifier &id, idTree->toList()) kDebug() << "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 = 0; } //May also be called when this is zero. 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(); kDebug() << "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 kDebug() << "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, 0); //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()) { kDebug() << "found empty import"; continue; } if(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_ARRAY(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_ARRAY(const SearchItem::Ptr& next, identifier->next) if(!applyAliases(id, next, accept, position, canBeNamespace, 0, 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 // kDebug() << "checking imports in" << (backPointer ? id.toString() : QString("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, 0); 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 kDebug() << "found import of" << alias->importIdentifier().toString(); #endif QualifiedIdentifier importIdentifier = alias->importIdentifier(); if(importIdentifier.isEmpty()) { kDebug() << "found empty import"; continue; } if(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; FOREACH_ARRAY(const SearchItem::Ptr& item, identifiers) applyAliases(emptyId, item, acceptor, position, canBeNamespace, 0, 0); } TopDUContext * TopDUContext::topContext() const { return const_cast(this); } bool TopDUContext::deleting() const { ///@todo remove d_func()->m_deleting, not used any more return m_dynamicData->m_deleting; } QList TopDUContext::problems() const { ENSURE_CAN_READ const auto data = d_func(); - if (m_local->m_problems.isEmpty() && data->m_problemsSize()) { - // deserialize problems into shared ptrs when not done so already - m_local->m_problems.reserve(data->m_problemsSize()); - for (uint i = 0; i < data->m_problemsSize(); ++i) { - m_local->m_problems << ProblemPointer(data->m_problems()[i].data(this)); - } + 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 m_local->m_problems; + 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(); - - // ensure we deserialized problems already to keep m_local->m_problems in sync - problems(); - - auto serializableProblem = ProblemPointer(problem)->prepareStorage(this); - // store for indexing - LocalIndexedProblem indexedProblem(serializableProblem.constData()); + LocalIndexedProblem indexedProblem(problem, this); Q_ASSERT(indexedProblem.isValid()); data->m_problemsList().append(indexedProblem); - Q_ASSERT(indexedProblem.data(this) == serializableProblem.data()); - - // and also for dynamic, shared retrieval during the lifetime of this topcontext - m_local->m_problems << serializableProblem; -} - -void TopDUContext::removeProblem(const ProblemPointer& problem) -{ - ENSURE_CAN_WRITE - Q_ASSERT(problem); - Q_ASSERT(problem->m_topContext == this); - m_local->m_problems.removeOne(problem); - d_func_dynamic()->m_problemsList().removeOne({problem.data()}); + Q_ASSERT(indexedProblem.data(this)); } void TopDUContext::clearProblems() { ENSURE_CAN_WRITE - m_local->m_problems.clear(); 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. kDebug() << "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; } TopDUContext::Flags TopDUContext::flags() const { return d_func()->m_flags; } void TopDUContext::setFlags(Flags f) { d_func_dynamic()->m_flags = f; } 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 &= (0xffffffff - (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 0; } 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. } DeclarationId id(declaration->id()); 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); } KSharedPtr TopDUContext::ast() const { return m_local->m_ast; } void TopDUContext::clearAst() { setAst(KSharedPtr(0)); } IndexedString TopDUContext::url() const { return d_func()->m_url; } } // kate: space-indent on; indent-width 2; tab-width 4; replace-tabs on; auto-insert-doxygen on diff --git a/language/duchain/topducontext.h b/language/duchain/topducontext.h index b8a1af88a7..8ad013080a 100644 --- a/language/duchain/topducontext.h +++ b/language/duchain/topducontext.h @@ -1,399 +1,392 @@ /* This file 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. */ #ifndef KDEVPLATFORM_TOPDUCONTEXT_H #define KDEVPLATFORM_TOPDUCONTEXT_H #include "ducontext.h" #include #include template< class T > class KSharedPtr; namespace KDevelop { class IAstContainer; class QualifiedIdentifier; class DUChain; class ParsingEnvironmentFile; class TopDUContextData; class TopDUContextLocalPrivate; class IndexedTopDUContext; // class TopDUContextDynamicData; class Problem; class DeclarationChecker; class TopDUContext; struct KDEVPLATFORMLANGUAGE_EXPORT RecursiveImportRepository { static Utils::BasicSetRepository* repository(); }; ///Maps an imported top-context to a pair: ///1. The distance to the top-context, and 2. The next step towards the top-context ///in the chain. typedef QHash > RecursiveImports; typedef DUChainPointer TopDUContextPointer; typedef KSharedPtr ProblemPointer; ///KDevelop can unload unused top-context at any time. To prevent unloading, ///keep a ReferencedTopDUContext. class KDEVPLATFORMLANGUAGE_EXPORT ReferencedTopDUContext { public: ReferencedTopDUContext(TopDUContext* context = 0); ReferencedTopDUContext(const ReferencedTopDUContext& rhs); ~ReferencedTopDUContext(); ReferencedTopDUContext& operator=(const ReferencedTopDUContext& rhs); inline TopDUContext* data() const { return m_topContext; } inline operator TopDUContext*() const { return m_topContext; } inline bool operator==(const ReferencedTopDUContext& rhs) const { return m_topContext == rhs.m_topContext; } inline bool operator!=(const ReferencedTopDUContext& rhs) const { return m_topContext != rhs.m_topContext; } inline TopDUContext* operator->() const { return m_topContext; } inline uint hash() const { return (uint)(((quint64)m_topContext) * 37); } private: TopDUContext* m_topContext; }; /** * The top context in a definition-use chain for one source file. * * Implements SymbolTable lookups and locking for the chain. * * Contexts and Classes can only be found through TopDUContext if they are in the symbol table. * @see DUContext::setInSymbolTable, Declaration::setInSymbolTable * * \todo move the registration with DUChain here * * @warning Do not delete top-contexts directly, use DUChain::removeDocumentChain instead. */ class KDEVPLATFORMLANGUAGE_EXPORT TopDUContext : public DUContext { public: explicit TopDUContext(const IndexedString& url, const RangeInRevision& range, ParsingEnvironmentFile* file = 0); explicit TopDUContext(TopDUContextData& data); TopDUContext* topContext() const override; ///Returns an indexed representation of this top-context. Indexed representations stay valid even if the top-context is unloaded. IndexedTopDUContext indexed() const; uint ownIndex() const; IndexedString url() const; /** * @see ParsingEnvironmentFile * May return zero if no file was set. * */ KSharedPtr parsingEnvironmentFile() const; /// Returns true if this object is being deleted, otherwise false. bool deleting() const; /// Returns true if this object is registered in the du-chain. If it is not, all sub-objects(context, declarations, etc.) can be changed virtual bool inDUChain() const; /// This flag is only used by DUChain, never change it from outside. void setInDuChain(bool); /// Whether this top-context has a stored version on disk bool isOnDisk() const; /** * Returns a list of all problems encountered while parsing this top-context. * Does not include the problems of imported contexts. * */ QList problems() const; /** * Add a parsing-problem to this context. * * \note you must be holding a write lock when you access this function. * */ void addProblem(const ProblemPointer& problem); - /** - * Remove a parsing-problem from this context. - * - * \note you must be holding a write lock when you access this function. - * */ - void removeProblem(const ProblemPointer& problem); - /** * Clear the list of problems * * \note you must be holding a write lock when you access this function. */ void clearProblems(); /** * Set the list of problems, replacing all existing ones. * * \note you must be holding a write lock when you access this function. */ void setProblems(const QList& pointers); /** * Determine if this chain imports another chain recursively. * * This uses the imports-cache for speedup if it is available, thus it is not necessarily 100% correct * if the cache is not up-to-date. * * \note you must be holding a read but not a write chain lock when you access this function. */ virtual bool imports(const DUContext* origin, const CursorInRevision& position) const; enum { Identity = 4 }; enum Flags { NoFlags = 0, ///Can be used by language parts to mark contexts they currently update(for their internal usage) UpdatingContext = 1, ///You can define own language-dependent flags behind this flag LastFlag = 2 }; enum Features { ///Top-context features standard that can be requested from the duchain, and that are stored in the features() member. Empty = 0, //Only the top-context structure (imports etc.) is built, but no declarations and no contexts SimplifiedVisibleDeclarationsAndContexts = 2, //The top-context should only contain publically simplified accessible declarations and contexts, without doing type look-up, //without extended information like function-argument declarations, etc., imported contexts can be parsed with 'Empty' features //This flag essentially leads to a ctags-like processing level. VisibleDeclarationsAndContexts = SimplifiedVisibleDeclarationsAndContexts + 4, //Default: The top-context should only contain publically accessible declarations and contexts AllDeclarationsAndContexts = VisibleDeclarationsAndContexts + 8, //The top-context should also contain non-public declarations and contexts, but no uses AllDeclarationsContextsAndUses = 16 + AllDeclarationsAndContexts, //The top-context should contain uses and all declarations + contexts AST = 32, //Signalizes that the ast() should be filled AllDeclarationsContextsUsesAndAST = AST | AllDeclarationsContextsAndUses, //Convenience flag, combining AST and AllDeclarationsContextsAndUses ///Additional update-flags that have a special meaning during updating, but are not set stored into a top-context Recursive = 64, //Request the given features on all recursively imported contexts. Only the features are applied recursively (including AST) ForceUpdate = 128, //Enforce updating the top-context ForceUpdateRecursive = ForceUpdate | 256, //Enforce updating the top-context and all its imports ///You can define own language-dependent features behind this flag LastFeature = 512 }; ///Returns the currently active features of this top-context. The features will include AST if ast() is valid. Features features() const; ///Set the features of this top-context. These features are ignored: AST, ForceUpdate, and ForceUpdateRecursive. void setFeatures(Features); /** * Retrieves or creates a local index that is to be used for referencing the given @param declaration * in local uses. Also registers this context as a user of the declaration. * @param create If this is false, only already registered indices will be returned. * If the declaration is not registered, std::numeric_limits::max() is returned * * The duchain must be write-locked if create is true, else it must at least be read-locked. * */ int indexForUsedDeclaration(Declaration* declaration, bool create = true); /** * Tries to retrieve the used declaration @param declarationIndex * @param context must be the context where the use happened * */ Declaration* usedDeclarationForIndex(unsigned int declarationIndex) const; /** * You can use this before you rebuild all uses. This does not affect any uses directly, * it only invalidates the mapping of declarationIndices to Declarations. * * usedDeclarationForIndex(..) must not be called until the use has gotten a new index through * indexForUsedDeclaration(..). * */ void clearUsedDeclarationIndices(); /** * Recursively deletes all contained uses, declaration-indices, etc. */ virtual void deleteUsesRecursively(); /** * Use flags to mark top-contexts for special behavior. Any flags above LastFlag may be used for language-specific stuff. * */ Flags flags() const; void setFlags(Flags f); /** * Returns the AST Container, that contains the AST created during parsing. * This is only created if you request the AST feature for parsing. * It may be discarded at any time. Every update without the AST feature will discard it. * The actual contents is language-specific. * * @todo Figure out logic to get rid of AST when it is not needed/useful */ KSharedPtr ast() const; /** * Sets the AST Container. */ void setAst(KSharedPtr ast); /** * Utility function to clear the AST Container */ void clearAst(); ///@param temporary If this is true, importers of this context will not be notified of the new imports. This greatly increases performance while removing the context, ///but creates in inconsistent import-structure. Therefore it is only suitable for temporary imports. These imports will not be visible from contexts that import this one. ///When this top-context does not own its private data, the import is added locally only to this context, not into the shared data. virtual void addImportedParentContext(DUContext* context, const CursorInRevision& position = CursorInRevision(), bool anonymous=false, bool temporary=false); ///Use this for mass-adding of imported contexts, it is faster than adding them individually. ///@param temporary If this is true, importers of this context will not be notified of the new imports. This greatly increases performance while removing the context, ///but creates in inconsistent import-structure. Therefore it is only suitable for temporary imports. These imports will not be visible from contexts that import this one. ///When this top-context does not own its private data, the import is added locally only to this context, not into the shared data. virtual void addImportedParentContexts(const QList >& contexts, bool temporary=false); ///When this top-context does not own its private data, the import is removed locally only from this context, not from the shared data. virtual void removeImportedParentContext(DUContext* context); ///Use this for mass-removing of imported contexts, it is faster than removing them individually. ///When this top-context does not own its private data, the import is removed locally only from this context, not from the shared data. virtual void removeImportedParentContexts(const QList& contexts); ///When this top-context does not own its private data, only the local imports of this context are removed, not those from the shared data. virtual void clearImportedParentContexts(); typedef Utils::StorableSet IndexedRecursiveImports; virtual QVector importedParentContexts() const; virtual QVector importers() const; ///Returns all currently loade importers virtual QList loadedImporters() const; virtual CursorInRevision importPosition(const DUContext* target) const; ///Returns the set of all recursively imported top-contexts. If import-caching is used, this returns the cached set. ///The list also contains this context itself. This set is used to determine declaration-visibility from within this top-context. const IndexedRecursiveImports& recursiveImportIndices() const; /** * Updates the cache of recursive imports. When you call this, from that moment on the set returned by recursiveImportIndices() is fixed, until * you call it again to update them. If your language has a very complex often-changing import-structure, * like for example in the case of C++, it is recommended to call this during while parsing, instead of using * the expensive builtin implicit mechanism. * Note that if you use caching, you _must_ call this before you see any visibility-effect after adding imports. * * Using import-caching has another big advantage: A top-context can be loaded without loading all its imports. * * Note: This is relatively expensive since it requires loading all imported contexts. * * When this is called, the top-context must already be registered in the duchain. */ void updateImportsCache(); bool usingImportsCache() const; virtual bool findDeclarationsInternal(const SearchItem::PtrList& identifiers, const CursorInRevision& position, const AbstractType::Ptr& dataType, DeclarationList& ret, const TopDUContext* source, SearchFlags flags, uint depth) const; protected: void setParsingEnvironmentFile(ParsingEnvironmentFile*); /** * Does the same as DUContext::updateAliases, except that it uses the symbol-store, and processes the whole identifier. * @param canBeNamespace whether the searched identifier may be a namespace. * If this is true, namespace-aliasing is applied to the last elements of the identifiers. * */ template void applyAliases( const SearchItem::PtrList& identifiers, Acceptor& accept, const CursorInRevision& position, bool canBeNamespace ) const; protected: virtual ~TopDUContext(); void clearFeaturesSatisfied(); void rebuildDynamicData(DUContext* parent, uint ownIndex); //Must be called after all imported top-contexts were loaded into the du-chain void rebuildDynamicImportStructure(); struct AliasChainElement; struct FindDeclarationsAcceptor; struct DeclarationChecker; struct ApplyAliasesBuddyInfo; template bool applyAliases( const QualifiedIdentifier& previous, const SearchItem::Ptr& identifier, Acceptor& acceptor, const CursorInRevision& position, bool canBeNamespace, ApplyAliasesBuddyInfo* buddy, uint recursionDepth ) const; //Same as imports, without the slow access-check, for internal usage bool importsPrivate(const DUContext * origin, const CursorInRevision& position) const; DUCHAIN_DECLARE_DATA(TopDUContext) ///Called by DUChain::removeDocumentChain to destroy this top-context. void deleteSelf(); //Most of these classes need access to m_dynamicData friend class DUChain; friend class DUChainPrivate; friend class TopDUContextData; friend class TopDUContextLocalPrivate; friend class TopDUContextDynamicData; friend class Declaration; friend class DUContext; friend class Problem; friend class IndexedDeclaration; friend class IndexedDUContext; friend class LocalIndexedDeclaration; friend class LocalIndexedDUContext; friend class LocalIndexedProblem; friend class DeclarationId; friend class ParsingEnvironmentFile; TopDUContextLocalPrivate* m_local; class TopDUContextDynamicData* m_dynamicData; }; /** * Returns all uses of the given declaration within this top-context and all sub-contexts * */ KDEVPLATFORMLANGUAGE_EXPORT QList allUses(TopDUContext* context, Declaration* declaration, bool noEmptyRanges = false); inline uint qHash(const ReferencedTopDUContext& ctx) { return ctx.hash(); } } Q_DECLARE_METATYPE(KDevelop::ReferencedTopDUContext); #endif // KDEVPLATFORM_TOPDUCONTEXT_H // kate: space-indent on; indent-width 2; tab-width 4; replace-tabs on; auto-insert-doxygen on diff --git a/language/duchain/topducontextdynamicdata.cpp b/language/duchain/topducontextdynamicdata.cpp index c3822b0cdc..8a09731919 100644 --- a/language/duchain/topducontextdynamicdata.cpp +++ b/language/duchain/topducontextdynamicdata.cpp @@ -1,814 +1,847 @@ /* 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 #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 "repositories/itemrepository.h" #include "problem.h" //#define DEBUG_DATA_INFO //This might be problematic on some systems, because really many mmaps are created #define USE_MMAP using namespace KDevelop; -static QMutex s_temporaryDataMutex(QMutex::Recursive); - namespace { -void saveDUChainItem(QList& data, DUChainBase& item, uint& totalDataOffset) { - +/** + * 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(QList& 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. kError() << QString("no class-id set for data attached to a declaration of type %1").arg(typeid(item).name()); Q_ASSERT(0); } int size = DUChainItemSystem::self().dynamicSize(*item.d_func()); if(data.back().first.size() - int(data.back().second) < size) //Create a new data item data.append( qMakePair(QByteArray(size > 10000 ? size : 10000, 0), 0u) ); uint pos = data.back().second; data.back().second += size; totalDataOffset += size; DUChainBaseData& target(*(reinterpret_cast(data.back().first.data() + pos))); if(item.d_func()->isDynamic()) { //Change from dynamic data to constant data enableDUChainReferenceCounting(data.back().first.data(), data.back().first.size()); DUChainItemSystem::self().copy(*item.d_func(), target, true); Q_ASSERT(!target.isDynamic()); - item.setData(&target); + if (!isSharedDataItem) { + item.setData(&target); + } disableDUChainReferenceCounting(data.back().first.data()); }else{ //Just copy the data into another place, expensive copy constructors are not needed memcpy(&target, item.d_func(), size); - item.setData(&target, false); + if (!isSharedDataItem) { + item.setData(&target, false); + } } - Q_ASSERT(item.d_func() == &target); - Q_ASSERT(!item.d_func()->isDynamic()); + 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(Problem* /*problem*/) +uint indexForParentContext(const ProblemPointer& /*problem*/) { // always stored in the top context return 0; } -void validateItem(const DUChainBase* const item, const uchar* const mappedData, const size_t mappedDataSize) +void validateItem(const DUChainBaseData* const data, const uchar* const mappedData, const size_t mappedDataSize) { - Q_ASSERT(!item->d_func()->isDynamic()); + Q_ASSERT(!data->isDynamic()); if (mappedData) { - Q_ASSERT(((size_t)item->d_func()) < ((size_t)mappedData) - || ((size_t)item->d_func()) > ((size_t)mappedData) + mappedDataSize); + Q_ASSERT(((size_t)data) < ((size_t)mappedData) + || ((size_t)data) > ((size_t)mappedData) + mappedDataSize); } } const char* pointerInData(const QList& data, uint totalOffset) { for(int a = 0; a < data.size(); ++a) { if(totalOffset < data[a].second) return data[a].first.constData() + totalOffset; totalOffset -= data[a].second; } Q_ASSERT_X(false, Q_FUNC_INFO, "Offset doesn't exist in the data."); return 0; } void verifyDataInfo(const TopDUContextDynamicData::ItemDataInfo& info, const QList& 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); +} + +template +void loadPartialData(const uint topContextIndex, F callback) +{ + QFile file(pathForTopContext(topContextIndex)); + if (file.open(QIODevice::ReadOnly)) { + uint readValue; + file.read((char*)&readValue, sizeof(uint)); + // now readValue is filled with the top-context data size + + // We only read the most needed stuff, not the whole top-context data + QByteArray data = file.read(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 +constexpr bool isSharedDataItem() +{ + return false; +} + +template<> +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); - //NOTE: not clearing, is called oly from the dtor anyways + items.clear(); +} + +namespace KDevelop { +template<> +void TopDUContextDynamicData::DUChainItemStorage::clearItems() +{ + // don't delete anything - the problem is shared + items.clear(); +} } template -void TopDUContextDynamicData::DUChainItemStorage::clearItemIndex(Item* item, const uint index) +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 { - QMutexLocker lock(&s_temporaryDataMutex); 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; } } } template void TopDUContextDynamicData::DUChainItemStorage::storeData(uint& currentDataOffset, const QList& 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); - verifyDataInfo(offsets.back(), data->m_data); - - validateItem(item, data->m_mappedData, data->m_mappedDataSize); + saveDUChainItem(data->m_data, *item, currentDataOffset, isSharedDataItem()); } } #ifndef QT_NO_DEBUG - for (auto item : items) { - if (item) { - validateItem(item, data->m_mappedData, data->m_mappedDataSize); + 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_ptr->m_dynamic) { return true; } } return false; } template -uint TopDUContextDynamicData::DUChainItemStorage::allocateItemIndex(Item* item, const bool temporary) +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 { - QMutexLocker lock(&s_temporaryDataMutex); 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 +Item TopDUContextDynamicData::DUChainItemStorage::getItemForIndex(uint index) const { if (index >= (0x0fffffff/2)) { - QMutexLocker lock(&s_temporaryDataMutex); index = 0x0fffffff - index; //We always keep the highest bit at zero if(index == 0 || index > uint(temporaryItems.size())) - return 0; + return {}; else return temporaryItems[index-1]; } if (index == 0 || index > static_cast(items.size())) { kWarning() << "item index out of bounds:" << index << "count:" << items.size(); - return nullptr; + return {}; } const uint realIndex = index - 1; - Item*& item = items[realIndex]; + Item& item = items[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)) ); - item = dynamic_cast(DUChainItemSystem::self().create(itemData)); + 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. kError() << "Failed to load item with identity" << itemData->classId; } + 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 { kWarning() << "invalid item for index" << index << offsets.size() << offsets.value(realIndex).dataOffset; } return item; } template void TopDUContextDynamicData::DUChainItemStorage::deleteOnDisk() { - for (Item* item : items) { + 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* KDevelop::TopDUContextDynamicData::pointerInData(uint totalOffset) const { Q_ASSERT(!m_mappedData || m_data.isEmpty()); if(m_mappedData && m_mappedDataSize) return (char*)m_mappedData + totalOffset; for(int a = 0; a < m_data.size(); ++a) { if(totalOffset < m_data[a].second) return m_data[a].first.constData() + totalOffset; totalOffset -= m_data[a].second; } Q_ASSERT(0); //Offset doesn't exist in the data return 0; } 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(0) , m_mappedData(0) , 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 = 0; m_mappedData = 0; m_mappedDataSize = 0; } +bool TopDUContextDynamicData::fileExists(uint topContextIndex) +{ + return QFile::exists(pathForTopContext(topContextIndex)); +} + QList TopDUContextDynamicData::loadImporters(uint topContextIndex) { QList ret; - - QString baseDir = globalItemRepositoryRegistry().path() + "/topcontexts"; - QString fileName = baseDir + '/' + QString("%1").arg(topContextIndex); - QFile file(fileName); - if(file.open(QIODevice::ReadOnly)) { - uint readValue; - file.read((char*)&readValue, sizeof(uint)); - //now readValue is filled with the top-context data size - - //We only read the most needed stuff, not the whole top-context data - QByteArray data = file.read(readValue); - const TopDUContextData* topData = reinterpret_cast(data.constData()); - FOREACH_FUNCTION(const IndexedDUContext& importer, topData->m_importers) + loadPartialData(topContextIndex, [&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; - - QString baseDir = globalItemRepositoryRegistry().path() + "/topcontexts"; - QString fileName = baseDir + '/' + QString("%1").arg(topContextIndex); - QFile file(fileName); - if(file.open(QIODevice::ReadOnly)) { - uint readValue; - file.read((char*)&readValue, sizeof(uint)); - //now readValue is filled with the top-context data size - - //We only read the most needed stuff, not the whole top-context data - QByteArray data = file.read(readValue); - const TopDUContextData* topData = reinterpret_cast(data.constData()); - FOREACH_FUNCTION(const DUContext::Import& import, topData->m_importedContexts) + loadPartialData(topContextIndex, [&ret] (const TopDUContextData* topData) { + ret.reserve(topData->m_importedContextsSize()); + FOREACH_FUNCTION(const DUContext::Import& import, topData->m_importedContexts) ret << import.indexedContext(); - } - + }); return ret; } -bool TopDUContextDynamicData::fileExists(uint topContextIndex) -{ - QString baseDir = globalItemRepositoryRegistry().path() + "/topcontexts"; - QString fileName = baseDir + '/' + QString("%1").arg(topContextIndex); - QFile file(fileName); - return file.exists(); -} - IndexedString TopDUContextDynamicData::loadUrl(uint topContextIndex) { - - QString baseDir = globalItemRepositoryRegistry().path() + "/topcontexts"; - QString fileName = baseDir + '/' + QString("%1").arg(topContextIndex); - QFile file(fileName); - if(file.open(QIODevice::ReadOnly)) { - uint readValue; - file.read((char*)&readValue, sizeof(uint)); - //now readValue is filled with the top-context data size - - //We only read the most needed stuff, not the whole top-context data - QByteArray data = file.read(sizeof(TopDUContextData)); - const TopDUContextData* topData = reinterpret_cast(data.constData()); - Q_ASSERT(topData->m_url.isEmpty() || topData->m_url.index() >> 16); - return topData->m_url; - } - - return IndexedString(); + IndexedString url; + loadPartialData(topContextIndex, [&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()); - QString baseDir = globalItemRepositoryRegistry().path() + "/topcontexts"; - KStandardDirs::makeDir(baseDir); - - QString fileName = baseDir + '/' + QString("%1").arg(m_topContext->ownIndex()); - QFile* file = new QFile(fileName); + 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{ - kDebug() << "Failed to map" << fileName; + kDebug() << "Failed to map" << file->fileName(); } #endif if(!m_mappedFile) { QByteArray data = file->readAll(); m_data.append(qMakePair(data, (uint)data.size())); delete file; } m_dataLoaded = true; } TopDUContext* TopDUContextDynamicData::load(uint topContextIndex) { - QString baseDir = globalItemRepositoryRegistry().path() + "/topcontexts"; - KStandardDirs::makeDir(baseDir); - - QString fileName = baseDir + '/' + QString("%1").arg(topContextIndex); - QFile file(fileName); + QFile file(pathForTopContext(topContextIndex)); if(file.open(QIODevice::ReadOnly)) { if(file.size() == 0) { - kWarning() << "Top-context file is empty" << fileName; + kWarning() << "Top-context file is empty" << file.fileName(); return 0; } 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()); /* IndexedString language = static_cast(topData)->m_language; if(!language.isEmpty()) {*/ ///@todo Load the language if it isn't loaded yet, problem: We're possibly not in the foreground thread! // } TopDUContext* ret = dynamic_cast(DUChainItemSystem::self().create(topData)); if(!ret) { kWarning() << "Cannot load a top-context, the requered language-support is probably not loaded"; return 0; } //Disable the updating flag on loading. Sometimes it may be set when the context was saved while it was updated ret->setFlags( (TopDUContext::Flags) (ret->flags() & (~TopDUContext::UpdatingContext)) ); TopDUContextDynamicData& target(*ret->m_dynamicData); // kDebug() << "loaded" << ret->url().str() << ret->ownIndex() << "import-count:" << ret->importedParentContexts().size() << ret->d_func()->m_importedContextsSize(); target.m_data.clear(); target.m_dataLoaded = false; target.m_onDisk = true; ret->rebuildDynamicData(0, topContextIndex); target.m_topContextData.append(qMakePair(topContextData, (uint)0)); // kDebug() << "loaded" << ret->url().str() << ret->ownIndex() << "import-count:" << ret->importedParentContexts().size() << ret->d_func()->m_importedContextsSize(); return ret; }else{ return 0; } } bool TopDUContextDynamicData::isOnDisk() const { return m_onDisk; } void TopDUContextDynamicData::deleteOnDisk() { if(!isOnDisk()) return; kDebug() << "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); kDebug() << "deletion ready"; } QString KDevelop::TopDUContextDynamicData::filePath() const { - QString baseDir = globalItemRepositoryRegistry().path() + "/topcontexts"; - KStandardDirs::makeDir(baseDir); - return baseDir + '/' + QString("%1").arg(m_topContext->ownIndex()); + return pathForTopContext(m_topContext->ownIndex()); } bool TopDUContextDynamicData::hasChanged() const { return !m_onDisk || m_topContext->d_ptr->m_dynamic || m_contexts.itemsHaveChanged() || m_declarations.itemsHaveChanged() || m_problems.itemsHaveChanged(); } void TopDUContextDynamicData::store() { // kDebug() << "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( qMakePair(QByteArray(DUChainItemSystem::self().dynamicSize(*m_topContext->d_func()), topContextDataSize), (uint)0) ); 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.second; if(newDataSize < 10000) newDataSize = 10000; //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( qMakePair(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); + 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(); - + + KStandardDirs::makeDir(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.first.constData(), pos.second); m_contexts.writeData(&file); m_declarations.writeData(&file); m_problems.writeData(&file); foreach(const ArrayWithPosition& pos, m_data) file.write(pos.first.constData(), pos.second); m_onDisk = true; if (file.size() == 0) { kWarning() << "Saving zero size top ducontext data"; } file.close(); } else { kWarning() << "Cannot open top-context for writing"; } // kDebug() << "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); int size = DUChainItemSystem::self().dynamicSize(*data); Q_ASSERT(size); if(m_data.back().first.size() - int(m_data.back().second) < size) //Create a new m_data item m_data.append( qMakePair(QByteArray(size > 10000 ? size : 10000, 0), 0u) ); ret.dataOffset = totalDataOffset; uint pos = m_data.back().second; m_data.back().second += size; totalDataOffset += size; DUChainBaseData& target(*reinterpret_cast(m_data.back().first.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(Problem* problem) +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::isProblemForIndexLoaded(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); } -Problem* TopDUContextDynamicData::getProblemForIndex(uint index) const +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::clearProblemIndex(Problem* problem) +void TopDUContextDynamicData::clearProblems() { - m_problems.clearItemIndex(problem, problem->m_indexInTopContext); + m_problems.clearItems(); } diff --git a/language/duchain/topducontextdynamicdata.h b/language/duchain/topducontextdynamicdata.h index e89d08d365..1bdc465b04 100644 --- a/language/duchain/topducontextdynamicdata.h +++ b/language/duchain/topducontextdynamicdata.h @@ -1,188 +1,187 @@ class QFile; /* This is part of KDevelop Copyright 2008 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_TOPDUCONTEXTDYNAMICDATA_H #define KDEVPLATFORM_TOPDUCONTEXTDYNAMICDATA_H #include #include #include #include #include +#include "problem.h" namespace KDevelop { class TopDUContext; class DUContext; class Declaration; class IndexedString; class IndexedDUContext; class DUChainBaseData; -class Problem; typedef QPair ArrayWithPosition; ///This class contains dynamic data of a top-context, and also the repository that contains all the data within this top-context. class TopDUContextDynamicData { public: TopDUContextDynamicData(TopDUContext* topContext); ~TopDUContextDynamicData(); void clear(); /** * Allocates an index for the given declaration in this top-context. * The returned index is never zero. * @param anonymous whether the declaration is temporary. If it is, it will be stored separately, not stored to disk, * and a duchain write-lock is not needed. Else, you need a write-lock when calling this. */ uint allocateDeclarationIndex(Declaration* decl, bool temporary); Declaration* getDeclarationForIndex(uint index) const; bool isDeclarationForIndexLoaded(uint index) const; void clearDeclarationIndex(Declaration* decl); /** * Allocates an index for the given context in this top-context. * The returned index is never zero. * @param anonymous whether the context is temporary. If it is, it will be stored separately, not stored to disk, * and a duchain write-lock is not needed. Else, you need a write-lock when calling this. */ uint allocateContextIndex(DUContext* ctx, bool temporary); DUContext* getContextForIndex(uint index) const; bool isContextForIndexLoaded(uint index) const; void clearContextIndex(DUContext* ctx); /** * Allocates an index for the given problem in this top-context. * The returned index is never zero. */ - uint allocateProblemIndex(Problem* problem); - Problem* getProblemForIndex(uint index) const; - bool isProblemForIndexLoaded(uint index) const; - void clearProblemIndex(Problem* problem); + uint allocateProblemIndex(ProblemPointer problem); + ProblemPointer getProblemForIndex(uint index) const; + void clearProblems(); ///Stores this top-context to disk void store(); ///Stores all remainings of this top-context that are on disk. The top-context will be fully dynamic after this. void deleteOnDisk(); ///Whether this top-context is on disk(Either has been loaded, or has been stored) bool isOnDisk() const; ///Loads the top-context from disk, or returns zero on failure. The top-context will not be registered anywhere, and will have no ParsingEnvironmentFile assigned. ///Also loads all imported contexts. The Declarations/Contexts will be correctly initialized, and put into the symbol tables if needed. static TopDUContext* load(uint topContextIndex); ///Loads only the url out of the data stored on disk for the top-context. static IndexedString loadUrl(uint topContextIndex); static bool fileExists(uint topContextIndex); ///Loads only the list of importers out of the data stored on disk for the top-context. static QList loadImporters(uint topContextIndex); static QList loadImports(uint topContextIndex); bool isTemporaryContextIndex(uint index) const; bool isTemporaryDeclarationIndex(uint index) const ; bool m_deleting; ///Flag used during destruction struct ItemDataInfo { //parentContext 0 means the global context ItemDataInfo(uint _dataOffset = 0, uint _parentContext = 0) : dataOffset(_dataOffset), parentContext(_parentContext) { } uint dataOffset; //Offset of the data uint parentContext; //Parent context of the data }; private: bool hasChanged() const; void unmap(); //Converts away from an mmap opened file to a data array QString filePath() const; void loadData() const; const char* pointerInData(uint offset) const; ItemDataInfo writeDataInfo(const ItemDataInfo& info, const DUChainBaseData* data, uint& totalDataOffset); TopDUContext* m_topContext; template struct DUChainItemStorage { DUChainItemStorage(TopDUContextDynamicData* data); ~DUChainItemStorage(); void clearItems(); bool itemsHaveChanged() const; void storeData(uint& currentDataOffset, const QList& oldData); - Item* getItemForIndex(uint index) const; + Item getItemForIndex(uint index) const; - void clearItemIndex(Item* item, const uint index); + void clearItemIndex(const Item& item, const uint index); - uint allocateItemIndex(Item* item, const bool temporary); + uint allocateItemIndex(const Item& item, const bool temporary); void deleteOnDisk(); bool isItemForIndexLoaded(uint index) const; void loadData(QFile* file) const; void writeData(QFile* file); //May contain zero items if they were deleted - mutable QVector items; + mutable QVector items; mutable QVector offsets; - QVector temporaryItems; + QVector temporaryItems; TopDUContextDynamicData* const data; }; - DUChainItemStorage m_contexts; - DUChainItemStorage m_declarations; - DUChainItemStorage m_problems; + DUChainItemStorage m_contexts; + DUChainItemStorage m_declarations; + DUChainItemStorage m_problems; //For temporary declarations that will not be stored to disk, like template instantiations mutable QList m_data; mutable QList m_topContextData; bool m_onDisk; mutable bool m_dataLoaded; mutable QFile* m_mappedFile; mutable uchar* m_mappedData; mutable size_t m_mappedDataSize; mutable bool m_itemRetrievalForbidden; }; } Q_DECLARE_TYPEINFO(KDevelop::TopDUContextDynamicData::ItemDataInfo, Q_MOVABLE_TYPE); #endif diff --git a/language/editor/simplerange.h b/language/editor/simplerange.h index 8ff62e82aa..2cc70dd34c 100644 --- a/language/editor/simplerange.h +++ b/language/editor/simplerange.h @@ -1,108 +1,110 @@ /* This file is part of KDevelop 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. */ #ifndef KDEVPLATFORM_SIMPLERANGE_H #define KDEVPLATFORM_SIMPLERANGE_H #include #include "../languageexport.h" #include "simplecursor.h" /** * Represents a range (start- and end-cursor) within a text document. Generally this is * a more efficient version of KTextEditor::Range. * In KDevelop, this object is used when referencing the most current document revision * (the document in its current version) + * + * TODO KF5: Just use KTextEditor::Range here? Cursors are now simple members just like here */ namespace KDevelop { class KDEVPLATFORMLANGUAGE_EXPORT SimpleRange { public: SimpleCursor start, end; SimpleRange(const SimpleCursor& _start, const SimpleCursor& _end) : start(_start), end(_end) { } SimpleRange(const SimpleCursor& _start, int length) : start(_start), end(_start.line, _start.column + length) { } SimpleRange() { } SimpleRange(const KTextEditor::Range& range) : start(range.start()), end(range.end()) { } SimpleRange(int sLine, int sCol, int eLine, int eCol) : start(sLine, sCol), end(eLine, eCol) { } static SimpleRange invalid() { return SimpleRange(-1, -1, -1, -1); } bool isValid() const { return start.column != -1 || start.line != -1 || end.column != -1 || end.line != -1; } bool isEmpty() const { return start == end; } bool contains(const SimpleCursor& position) const { return position >= start && position < end; } bool contains(const SimpleRange& range) const { return range.start >= start && range.end <= end; } bool operator ==( const SimpleRange& rhs ) const { return start == rhs.start && end == rhs.end; } bool operator !=( const SimpleRange& rhs ) const { return !(*this == rhs); } bool operator <( const SimpleRange& rhs ) const { return start < rhs.start; } KTextEditor::Range textRange() const { return KTextEditor::Range( KTextEditor::Cursor(start.line, start.column), KTextEditor::Cursor(end.line, end.column) ); } /** * kDebug() stream operator. Writes this range to the debug output in a nicely formatted way. */ inline friend QDebug operator<< (QDebug s, const SimpleRange& range) { s.nospace() << '[' << range.start << ", " << range.end << ']'; return s.space(); } }; inline uint qHash(const KDevelop::SimpleRange& range) { return qHash(range.start) + qHash(range.end)*41; } } // namespace KDevelop Q_DECLARE_TYPEINFO(KDevelop::SimpleRange, Q_MOVABLE_TYPE); #endif diff --git a/language/interfaces/abbreviations.cpp b/language/interfaces/abbreviations.cpp index b9b5ee00b2..8df0504f60 100644 --- a/language/interfaces/abbreviations.cpp +++ b/language/interfaces/abbreviations.cpp @@ -1,158 +1,152 @@ /* This file is part of KDevelop Copyright 2014 Sven Brauch 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 "abbreviations.h" bool matchesAbbreviationHelper(const QStringRef &word, const QString &typed, const QVarLengthArray< int, 32 > &offsets, int &depth, int atWord, int i) { int atLetter = 1; for ( ; i < typed.size(); i++ ) { const QChar c = typed.at(i).toLower(); bool haveNextWord = offsets.size() > atWord + 1; bool canCompare = atWord != -1 && word.size() > offsets.at(atWord) + atLetter; if ( canCompare && c == word.at(offsets.at(atWord) + atLetter).toLower() ) { // the typed letter matches a letter after the current word beginning if ( ! haveNextWord || c != word.at(offsets.at(atWord + 1)).toLower() ) { // good, simple case, no conflict atLetter += 1; continue; } // For maliciously crafted data, the code used here theoretically can have very high // complexity. Thus ensure we don't run into this case, by limiting the amount of branches // we walk through to 128. depth++; if ( depth > 128 ) { return false; } // the letter matches both the next word beginning and the next character in the word if ( haveNextWord && matchesAbbreviationHelper(word, typed, offsets, depth, atWord + 1, i + 1) ) { // resolving the conflict by taking the next word's first character worked, fine return true; } // otherwise, continue by taking the next letter in the current word. atLetter += 1; continue; } else if ( haveNextWord && c == word.at(offsets.at(atWord + 1)).toLower() ) { // the typed letter matches the next word beginning atWord++; atLetter = 1; continue; } // no match return false; } // all characters of the typed word were matched return true; } bool matchesAbbreviation(const QStringRef &word, const QString &typed) { // A mismatch is very likely for random even for the first letter, // thus this optimization makes sense. if ( word.at(0).toLower() != typed.at(0).toLower() ) { return false; } // First, check if all letters are contained in the word in the right order. int atLetter = 0; foreach ( const QChar c, typed ) { while ( c.toLower() != word.at(atLetter).toLower() ) { atLetter += 1; if ( atLetter >= word.size() ) { return false; } } } bool haveUnderscore = true; QVarLengthArray offsets; // We want to make "KComplM" match "KateCompletionModel"; this means we need // to allow parts of the typed text to be not part of the actual abbreviation, // which consists only of the uppercased / underscored letters (so "KCM" in this case). // However it might be ambigous whether a letter is part of such a word or part of // the following abbreviation, so we need to find all possible word offsets first, // then compare. for ( int i = 0; i < word.size(); i++ ) { const QChar c = word.at(i); if ( c == QLatin1Char('_') || c == QLatin1Char('-') ) { haveUnderscore = true; } else if ( haveUnderscore || c.isUpper() ) { offsets.append(i); haveUnderscore = false; } } int depth = 0; return matchesAbbreviationHelper(word, typed, offsets, depth); } -AbbreviationMatchQuality matchesPath(const QVector< QString > &path, const QString &typed) +bool matchesPath(const QString &path, const QString &typed) { int consumed = 0; - bool sequential = true; - for ( const QString& item: path ) { - int pos = 0; - // try to find all the characters in typed in the right order in the path; - // jumps are allowed everywhere - bool componentHasMatch = false; - while ( consumed < typed.size() && pos < item.size() ) { - if ( typed.at(consumed).toLower() == item.at(pos).toLower() ) { - consumed++; - componentHasMatch = true; - } - else if ( componentHasMatch ) { - sequential = false; - } - pos++; + int pos = 0; + // try to find all the characters in typed in the right order in the path; + // jumps are allowed everywhere + while ( consumed < typed.size() && pos < path.size() ) { + if ( typed.at(consumed).toLower() == path.at(pos).toLower() ) { + consumed++; } + pos++; } - return consumed == typed.size() ? (sequential ? MatchesSequentially : MatchesSomewhere) : NoMatch; + return consumed == typed.size(); } bool matchesAbbreviationMulti(const QString &word, const QStringList &typedFragments) { if ( word.size() == 0 ) { return true; } int lastSpace = 0; int matchedFragments = 0; for ( int i = 0; i < word.size(); i++ ) { const QChar& c = word.at(i); bool isDoubleColon = false; // if it's not a separation char, walk over it. if ( c != ' ' && c != '/' && i != word.size() - 1 ) { if ( c != ':' && i < word.size()-1 && word.at(i+1) != ':' ) { continue; } isDoubleColon = true; i++; } // if it's '/', ' ' or '::', split the word here and check the next sub-word. const QStringRef wordFragment = word.midRef(lastSpace, i-lastSpace); const QString& typedFragment = typedFragments.at(matchedFragments); if ( wordFragment.size() > 0 && matchesAbbreviation(wordFragment, typedFragment) ) { matchedFragments += 1; if ( matchedFragments == typedFragments.size() ) { break; } } lastSpace = isDoubleColon ? i : i+1; } return matchedFragments == typedFragments.size(); } + +// kate: space-indent on; indent-width 2 diff --git a/language/interfaces/abbreviations.h b/language/interfaces/abbreviations.h index 3ffca310b4..6adddb7074 100644 --- a/language/interfaces/abbreviations.h +++ b/language/interfaces/abbreviations.h @@ -1,58 +1,52 @@ /* This file is part of KDevelop Copyright 2014 Sven Brauch 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. */ #ifndef KDEVPLATFORM_ABBREVIATIONS_H #define KDEVPLATFORM_ABBREVIATIONS_H #include #include #include #include #include // Taken and adapted for kdevelop from katecompletionmodel.cpp KDEVPLATFORMLANGUAGE_EXPORT bool matchesAbbreviationHelper(const QStringRef& word, const QString& typed, const QVarLengthArray& offsets, int& depth, int atWord = -1, int i = 0); KDEVPLATFORMLANGUAGE_EXPORT bool matchesAbbreviation(const QStringRef& word, const QString& typed); -enum AbbreviationMatchQuality { - NoMatch, - MatchesSomewhere, - MatchesSequentially -}; - -KDEVPLATFORMLANGUAGE_EXPORT AbbreviationMatchQuality matchesPath(const QVector& path, const QString& typed); +KDEVPLATFORMLANGUAGE_EXPORT bool matchesPath(const QString& path, const QString& typed); /** * @brief Matches a word against a list of search fragments. * The word will be split at separation characters (space, / and ::) and * the resulting fragments will be matched one-by-one against the typed fragments. * If all typed fragments can be matched against a fragment in word in the right order * (skipping is allowed), true will be returned. * @param word the word to search in * @param typedFragments the fragments which were typed * @return bool true if match, else false */ KDEVPLATFORMLANGUAGE_EXPORT bool matchesAbbreviationMulti(const QString& word, const QStringList& typedFragments); #endif // kate: space-indent on; indent-width 2 diff --git a/language/interfaces/codecontext.cpp b/language/interfaces/codecontext.cpp index 1b1b03a913..4a4297f974 100644 --- a/language/interfaces/codecontext.cpp +++ b/language/interfaces/codecontext.cpp @@ -1,136 +1,131 @@ /* This file is part of KDevelop Copyright 2001-2002 Matthias Hoelzer-Kluepfel Copyright 2001-2002 Bernd Gehrmann Copyright 2001 Sandy Meier Copyright 2002 Daniel Engelschalt Copyright 2002 Simon Hausmann Copyright 2002-2003 Roberto Raggi Copyright 2003 Mario Scalas Copyright 2003 Harald Fernengel Copyright 2003,2006,2008 Hamish Rodda Copyright 2004 Alexander Dymo Copyright 2006 Adam Treat 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 "codecontext.h" #include #include #include #include #include #include #include namespace KDevelop { class DUContextContext::Private { public: Private( const IndexedDUContext& item ) : m_item( item ) {} IndexedDUContext m_item; }; DUContextContext::DUContextContext( const IndexedDUContext& item ) : Context(), d( new Private( item ) ) {} DUContextContext::~DUContextContext() { delete d; } int DUContextContext::type() const { return Context::CodeContext; } IndexedDUContext DUContextContext::context() const { return d->m_item; } void DUContextContext::setContext(IndexedDUContext context) { d->m_item = context; } class DeclarationContext::Private { public: Private( const IndexedDeclaration& declaration, const DocumentRange& use ) : m_declaration( declaration ), m_use(use) {} IndexedDeclaration m_declaration; DocumentRange m_use; }; DeclarationContext::DeclarationContext( const IndexedDeclaration& declaration, const DocumentRange& use, const IndexedDUContext& context ) : DUContextContext(context), d( new Private( declaration, use ) ) {} DeclarationContext::DeclarationContext(KTextEditor::View* view, KTextEditor::Cursor position) : DUContextContext(IndexedDUContext()) { - DUChainReadLocker lock(DUChain::lock()); - DocumentRange useRange = DocumentRange::invalid(); - IndexedDeclaration declaration; + const KUrl& url = view->document()->url(); + const SimpleCursor pos = SimpleCursor(position); + DUChainReadLocker lock; + DocumentRange useRange = DocumentRange(IndexedString(url), DUChainUtils::itemRangeUnderCursor(url, pos)); + Declaration* declaration = DUChainUtils::itemUnderCursor(url, pos); + IndexedDeclaration indexed; + if ( declaration ) { + indexed = IndexedDeclaration(declaration); + } IndexedDUContext context; TopDUContext* topContext = DUChainUtils::standardContextForUrl(view->document()->url()); if(topContext) { - CursorInRevision localRevisionCursor = topContext->transformToLocalRevision(SimpleCursor(position)); - - DUContext* specific = topContext->findContextAt(localRevisionCursor); - context = IndexedDUContext(specific); - if(specific) { - int use = specific->findUseAt(localRevisionCursor); - if(use != -1) { - //Found a use under the cursor: - useRange = DocumentRange(IndexedString(specific->url().str()), topContext->transformFromLocalRevision(specific->uses()[use].m_range)); - declaration = IndexedDeclaration(specific->topContext()->usedDeclarationForIndex( specific->uses()[use].m_declarationIndex )); - }else{ - declaration = IndexedDeclaration(specific->findDeclarationAt(localRevisionCursor)); - } - } + DUContext* specific = topContext->findContextAt(CursorInRevision(pos.line, pos.column)); + if(specific) + context = IndexedDUContext(specific); } d = new Private(declaration, useRange); setContext(context); } DeclarationContext::~DeclarationContext() { delete d; } int DeclarationContext::type() const { return Context::CodeContext; } IndexedDeclaration DeclarationContext::declaration() const { return d->m_declaration; } DocumentRange DeclarationContext::use() const { return d->m_use; } } diff --git a/language/interfaces/ilanguagesupport.cpp b/language/interfaces/ilanguagesupport.cpp index 1d50d9495d..5797511f19 100644 --- a/language/interfaces/ilanguagesupport.cpp +++ b/language/interfaces/ilanguagesupport.cpp @@ -1,81 +1,86 @@ /*************************************************************************** * Copyright 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 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 "ilanguagesupport.h" #include "../duchain/duchain.h" #include "../editor/simplerange.h" #include #include #include namespace KDevelop { TopDUContext* ILanguageSupport::standardContext(const KUrl& url, bool proxyContext) { Q_UNUSED(proxyContext) return DUChain::self()->chainForDocument(url); } SimpleRange ILanguageSupport::specialLanguageObjectRange(const KUrl& url, const SimpleCursor& position) { Q_UNUSED(url) Q_UNUSED(position) return SimpleRange::invalid(); } QPair ILanguageSupport::specialLanguageObjectJumpCursor(const KUrl& url, const SimpleCursor& position) { Q_UNUSED(url) Q_UNUSED(position) return QPair(KUrl(), SimpleCursor::invalid()); } QWidget* ILanguageSupport::specialLanguageObjectNavigationWidget(const KUrl& url, const SimpleCursor& position) { Q_UNUSED(url) Q_UNUSED(position) return 0; } ICodeHighlighting* ILanguageSupport::codeHighlighting() const { return 0; } ICreateClassHelper* ILanguageSupport::createClassHelper() const { return 0; } ILanguage* ILanguageSupport::language() { return ICore::self()->languageController()->language(name()); } DocumentChangeTracker* ILanguageSupport::createChangeTrackerForDocument ( KTextEditor::Document* document ) const { return new DocumentChangeTracker(document); } ILanguageSupport::WhitespaceSensitivity ILanguageSupport::whitespaceSensititivy() const { return ILanguageSupport::Insensitive; } +SourceFormatterItemList ILanguageSupport::sourceFormatterItems() const +{ + return SourceFormatterItemList(); +} + QString ILanguageSupport::indentationSample() const { return ""; } } diff --git a/language/interfaces/ilanguagesupport.h b/language/interfaces/ilanguagesupport.h index 74892d5aa9..5c8747b0cf 100644 --- a/language/interfaces/ilanguagesupport.h +++ b/language/interfaces/ilanguagesupport.h @@ -1,134 +1,147 @@ /*************************************************************************** * 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. * ***************************************************************************/ #ifndef KDEVPLATFORM_ILANGUAGESUPPORT_H #define KDEVPLATFORM_ILANGUAGESUPPORT_H #include #include "../editor/simplerange.h" #include "../languageexport.h" +#include "interfaces/isourceformatter.h" + namespace KDevelop { class IndexedString; class ParseJob; class ILanguage; class TopDUContext; class DocumentRange; class SimpleCursor; class SimpleRange; class ICodeHighlighting; class DocumentChangeTracker; class ICreateClassHelper; class KDEVPLATFORMLANGUAGE_EXPORT ILanguageSupport { public: virtual ~ILanguageSupport() {} /** @return the name of the language.*/ virtual QString name() const = 0; /** @return the parse job that is used by background parser to parse given @p url.*/ virtual ParseJob *createParseJob(const IndexedString &url) = 0; /** @return the language for this support.*/ virtual ILanguage *language(); /** * Only important for languages that can parse multiple different versions of a file, like C++ due to the preprocessor. * The default-implementation for other languages is "return DUChain::chainForDocument(url);" * * @param proxyContext Whether the returned context should be a proxy-contexts. In C++, a proxy-contexts has no direct content. * It mainly just imports an actual content-context, and it holds all the imports. It can also represent * multiple different versions of the same content in the eyes of the preprocessor. Also, a proxy-context may contain the problem- * descriptions of preprocessor problems. * The proxy-context should be preferred whenever the problem-list is required, or for deciding whether a document needs to be updated * (only the proxy-context knows about all the dependencies, since it contains most of the imports) * * @warning The DUChain must be locked before calling this, @see KDevelop::DUChainReadLocker * * @return the standard context used by this language for the given @param url. * */ virtual TopDUContext *standardContext(const KUrl& url, bool proxyContext = false); /** * Should return a code-highlighting instance for this language, or zero. */ virtual ICodeHighlighting* codeHighlighting() const; /** * Should return a document change-tracker for this language that tracks the changes in the given document * */ virtual DocumentChangeTracker* createChangeTrackerForDocument(KTextEditor::Document* document) const; /** * Should return a class creating helper for this language, or zero. * * If zero is returned, a default class helper will be created. * Reimplementing this method is therefore not necessary to have classes created in this language. * */ virtual ICreateClassHelper* createClassHelper() const; /** * The following functions are used to allow navigation-features, tooltips, etc. for non-duchain language objects. * In C++, they are used to allow highlighting and navigation of macro-uses. * */ /**Should return the local range within the given url that belongs to the *special language-object that contains @param position, or (KUrl(), SimpleRange:invalid()) */ virtual SimpleRange specialLanguageObjectRange(const KUrl& url, const SimpleCursor& position); /**Should return the source-range and source-document that the *special language-object that contains @param position refers to, or SimpleRange:invalid(). */ virtual QPair specialLanguageObjectJumpCursor(const KUrl& url, const SimpleCursor& position); /**Should return a navigation-widget for the *special language-object that contains @param position refers, or 0. *If you setProperty("DoNotCloseOnCursorMove", true) on the widget returned, *then the widget will not close when the cursor moves in the document, which *enables you to change the document contents from the widget without immediately closing the widget.*/ virtual QWidget* specialLanguageObjectNavigationWidget(const KUrl& url, const SimpleCursor& position); /**Should return a tiny piece of code which makes it possible for KDevelop to derive the indentation *settings from an automatic source formatter. Example for C++: "class C{\n class D {\n void c() {\n int m;\n }\n }\n};\n" *The sample must be completely unindented (no line must start with leading whitespace), *and it must contain at least 4 indentation levels! *The default implementation returns an empty string.*/ virtual QString indentationSample() const; + + /** + * Can return a list of source formatting items for this language. + * For example, if your language wants to use the CustomScript engine with + * a specific executable, return an item with "customscript" as the engine + * and a style describing your options as the style (in this case, especially + * the command to execute in the "content" member). + * Multiple items can be returned. Make sure to set the mime type(s) of your language + * on the returned items. + */ + virtual SourceFormatterItemList sourceFormatterItems() const; enum WhitespaceSensitivity { Insensitive = 0, IndentOnly = 1, Sensitive = 2 }; /**Specifies whether this language is sensitive to whitespace changes. * - The default "Insensitive" will only schedule a document for reparsing when * a change in a non-whitespace area happens (non-whitespace chars added or whitespace * added where it was surrounded by characters) * - "IndentOnly" will additionally schedule the document for reparsing if a whitespace * change occurs at the beginning of the line (more exactly, if all characters before the * changed ones are whitespace) * - "Sensitive" will always schedule the document for reparsing, no matter what was changed. */ virtual WhitespaceSensitivity whitespaceSensititivy() const; }; } Q_DECLARE_INTERFACE( KDevelop::ILanguageSupport, "org.kdevelop.ILanguageSupport") #endif diff --git a/language/interfaces/quickopenfilter.h b/language/interfaces/quickopenfilter.h index a27d9ecb2e..65eb9d7c79 100644 --- a/language/interfaces/quickopenfilter.h +++ b/language/interfaces/quickopenfilter.h @@ -1,274 +1,273 @@ /* * 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. */ #ifndef KDEVPLATFORM_QUICKOPEN_FILTER_H #define KDEVPLATFORM_QUICKOPEN_FILTER_H #include #include #include "abbreviations.h" #include namespace KDevelop { /** * This is a simple filter-implementation that helps you implementing own quickopen data-providers. * You should use it when possible, because that way additional features(like regexp filtering) can * be implemented in a central place. * * This implementation does incremental filtering while * typing text, so it quite efficient for the most common case. * * The simplest way of using this is by reimplementing your data-provider * based on QuickOpenDataProviderBase and KDevelop::Filter. * * YourType should be the type that holds all the information you need. * The filter will hold the data, and you can access it through "items()". * * What you need to do to use it: * * Reimplement itemText(..) to provide the text filtering * should be performend on(This must be efficient). * * Call setItems(..) when starting a new quickopen session, or when the content * changes, to initialize the filter with your data. * * Call setFilter(..) with the text that should be filtered for on user-input. * * Use filteredItems() to provide data to quickopen. * */ template class Filter { public: virtual ~Filter() { } ///Clears the filter, but not the data. void clearFilter() { m_filtered = m_items; m_oldFilterText.clear(); } ///Clears the filter and sets new data. The filter-text will be lost. void setItems( const QList& data ) { m_items = data; clearFilter(); } const QList& items() const { return m_items; } ///Returns the data that is left after the filtering const QList& filteredItems() const { return m_filtered; } ///Changes the filter-text and refilters the data void setFilter( const QString& text ) { if (m_oldFilterText == text) { return; } if (text.isEmpty()) { clearFilter(); return; } QList filterBase = m_filtered; if( !text.startsWith( m_oldFilterText ) ) { filterBase = m_items; //Start filtering based on the whole data } m_filtered.clear(); QStringList typedFragments = text.split("::", QString::SkipEmptyParts); if ( typedFragments.last().endsWith(':') ) { // remove the trailing colon if there's only one; otherwise, // this breaks incremental filtering typedFragments.last().chop(1); } foreach( const Item& data, filterBase ) { const QString& itemData = itemText( data ); if( itemData.contains(text, Qt::CaseInsensitive) || matchesAbbreviationMulti(itemData, typedFragments) ) { m_filtered << data; } } m_oldFilterText = text; } protected: ///Should return the text an item should be filtered by. virtual QString itemText( const Item& data ) const = 0; private: QString m_oldFilterText; QList m_filtered; QList m_items; }; } namespace KDevelop { template class PathFilter { public: ///Clears the filter, but not the data. void clearFilter() { m_filtered = m_items; m_oldFilterText.clear(); } ///Clears the filter and sets new data. The filter-text will be lost. void setItems( const QList& data ) { m_items = data; clearFilter(); } const QList& items() const { return m_items; } ///Returns the data that is left after the filtering const QList& filteredItems() const { return m_filtered; } ///Changes the filter-text and refilters the data void setFilter( const QStringList& text ) { if (m_oldFilterText == text) { return; } if (text.isEmpty()) { clearFilter(); return; } const QString joinedText = text.join(QString()); QList filterBase = m_filtered; if ( m_oldFilterText.isEmpty()) { filterBase = m_items; } else if (m_oldFilterText.mid(0, m_oldFilterText.count() - 1) == text.mid(0, text.count() - 1) && text.last().startsWith(m_oldFilterText.last())) { //Good, the prefix is the same, and the last item has been extended } else if (m_oldFilterText.size() == text.size() - 1 && m_oldFilterText == text.mid(0, text.size() - 1)) { //Good, an item has been added } else { //Start filtering based on the whole data, there was a big change to the filter filterBase = m_items; } // filterBase is correctly sorted, to keep it that way we add // exact matches to this list in sorted way and then prepend the whole list in one go. QList exactMatches; // similar for starting matches QList startMatches; // all other matches QList otherMatches; foreach( const Item& data, filterBase ) { const Path toFilter = static_cast(this)->itemPath(data); const QVector& segments = toFilter.segments(); if (text.count() > segments.count()) { // number of segments mismatches, thus item cannot match continue; } { bool allMatched = true; // try to put exact matches up front for(int i = segments.count() - 1, j = text.count() - 1; i >= 0 && j >= 0; --i, --j) { if (segments.at(i) != text.at(j)) { allMatched = false; break; } } if (allMatched) { exactMatches << data; continue; } } int searchIndex = 0; int pathIndex = 0; int lastMatchIndex = -1; // stop early if more search fragments remain than available after path index while (pathIndex < segments.size() && searchIndex < text.size() && (pathIndex + text.size() - searchIndex - 1) < segments.size() ) { const QString& segment = segments.at(pathIndex); const QString& typedSegment = text.at(searchIndex); lastMatchIndex = segment.indexOf(typedSegment, 0, Qt::CaseInsensitive); if (lastMatchIndex == -1 && !matchesAbbreviation(segment.midRef(0), typedSegment)) { // no match, try with next path segment ++pathIndex; continue; } // else we matched ++searchIndex; ++pathIndex; } - AbbreviationMatchQuality fuzzyMatch = NoMatch; if (searchIndex != text.size()) { - if ( (fuzzyMatch = matchesPath(segments, joinedText)) == NoMatch ) { + if ( ! matchesPath(segments.last(), joinedText) ) { continue; } } // prefer matches whose last element starts with the filter - if ((pathIndex == segments.size() && lastMatchIndex == 0) || fuzzyMatch == MatchesSequentially) { + if (pathIndex == segments.size() && lastMatchIndex == 0) { startMatches << data; } else { otherMatches << data; } } m_filtered = exactMatches + startMatches + otherMatches; m_oldFilterText = text; } private: QStringList m_oldFilterText; QList m_filtered; QList m_items; }; } #endif diff --git a/language/util/includeitem.cpp b/language/util/includeitem.cpp index 6c6a70e957..5a4f0a33ac 100644 --- a/language/util/includeitem.cpp +++ b/language/util/includeitem.cpp @@ -1,42 +1,47 @@ /* 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 "includeitem.h" -namespace KDevelop { +#include + +using namespace KDevelop; IncludeItem::IncludeItem() : pathNumber(0) , isDirectory(false) { } ///Constructs the url from basePath and name. KUrl IncludeItem::url() const { KUrl u; if( !basePath.isEmpty() ) { u = KUrl( basePath ); u.addPath( name ); }else{ u = KUrl( name ); } return u; } +QDebug operator<<(QDebug dbg, const IncludeItem& item) +{ + return dbg << item.url(); } diff --git a/language/util/includeitem.h b/language/util/includeitem.h index fc9719769e..9aa2d799da 100644 --- a/language/util/includeitem.h +++ b/language/util/includeitem.h @@ -1,48 +1,52 @@ /* 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. */ #ifndef KDEVPLATFORM_INCLUDEITEM_H #define KDEVPLATFORM_INCLUDEITEM_H #include #include "../languageexport.h" +class QDebug; + namespace KDevelop { class KDEVPLATFORMLANGUAGE_EXPORT IncludeItem { public: IncludeItem(); ///Constructs the url from basePath and name. KUrl url() const; ///The name of this include-item, starting behind basePath. QString name; ///basePath + name = Absolute path of file KUrl basePath; ///Which path in the include-path was used to find this item? int pathNumber; ///If this is true, this item represents a sub-directory. Else it represents a file. bool isDirectory; }; } +KDEVPLATFORMLANGUAGE_EXPORT QDebug operator<<(QDebug dbg, const KDevelop::IncludeItem& item); + #endif // KDEVPLATFORM_INCLUDEITEM_H diff --git a/outputview/outputfilteringstrategies.cpp b/outputview/outputfilteringstrategies.cpp index f2e65e1156..d916b9a176 100644 --- a/outputview/outputfilteringstrategies.cpp +++ b/outputview/outputfilteringstrategies.cpp @@ -1,416 +1,422 @@ /* This file is part of KDevelop Copyright (C) 2012 Morten Danielsen Volden mvolden2@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "outputfilteringstrategies.h" #include "outputformats.h" #include "filtereditem.h" #include #include #include #include namespace KDevelop { +FilteredItem FilteringStrategyUtils::match(const QList& errorFormats, const QString& line) +{ + FilteredItem item(line); + foreach( const ErrorFormat& curErrFilter, errorFormats ) { + QRegExp regEx = curErrFilter.expression; + if( regEx.indexIn( line ) != -1 ) + { + item.url = regEx.cap( curErrFilter.fileGroup ); + item.lineNo = regEx.cap( curErrFilter.lineGroup ).toInt() - 1; + if(curErrFilter.columnGroup >= 0) { + item.columnNo = regEx.cap( curErrFilter.columnGroup ).toInt() - 1; + } else { + item.columnNo = 0; + } + + QString txt = regEx.cap(curErrFilter.textGroup); + + item.type = FilteredItem::ErrorItem; + + // Make the item clickable if it comes with the necessary file & line number information + if (curErrFilter.fileGroup > 0 && curErrFilter.lineGroup > 0) { + item.isActivatable = true; + } + break; + } + } + return item; +} + /// --- No filter strategy --- NoFilterStrategy::NoFilterStrategy() { } FilteredItem NoFilterStrategy::actionInLine(const QString& line) { return FilteredItem( line ); } FilteredItem NoFilterStrategy::errorInLine(const QString& line) { return FilteredItem( line ); } /// --- Compiler error filter strategy --- /// Impl. of CompilerFilterStrategy. struct CompilerFilterStrategyPrivate { CompilerFilterStrategyPrivate(const KUrl& buildDir); KUrl urlForFile( const QString& ) const; bool isMultiLineCase(ErrorFormat curErrFilter) const; void putDirAtEnd(const QString& ); QVector m_currentDirs; KUrl m_buildDir; typedef QMap PositionMap; PositionMap m_positionInCurrentDirs; }; namespace { // All the possible string that indicate an error if we via Regex have been able to // extract file and linenumber from a given outputline typedef QPair Indicator; const QVector INDICATORS { // ld Indicator("undefined reference", FilteredItem::ErrorItem), Indicator("undefined symbol", FilteredItem::ErrorItem), Indicator("ld: cannot find", FilteredItem::ErrorItem), Indicator("no such file", FilteredItem::ErrorItem), // gcc Indicator("error", FilteredItem::ErrorItem), // generic Indicator("warning", FilteredItem::WarningItem), Indicator("info", FilteredItem::InformationItem), Indicator("note", FilteredItem::InformationItem), }; // A list of filters for possible compiler, linker, and make errors const QVector ERROR_FILTERS { // GCC - another case, eg. for #include "pixmap.xpm" which does not exists ErrorFormat( "^([^:\t]+):([0-9]+):([0-9]+):([^0-9]+)", 1, 2, 4, 3 ), // GCC ErrorFormat( "^([^:\t]+):([0-9]+):([^0-9]+)", 1, 2, 3 ), // GCC ErrorFormat( "^(In file included from |[ ]+from )([^: \\t]+):([0-9]+)(:|,)(|[0-9]+)", 2, 3, 5 ), // ICC ErrorFormat( "^([^: \\t]+)\\(([0-9]+)\\):([^0-9]+)", 1, 2, 3, "intel" ), //libtool link ErrorFormat( "^(libtool):( link):( warning): ", 0, 0, 0 ), // make ErrorFormat( "No rule to make target", 0, 0, 0 ), // cmake ErrorFormat( "^([^: \\t]+):([0-9]+):", 1, 2, 0, "cmake" ), // cmake ErrorFormat( "CMake (Error|Warning) (|\\([a-zA-Z]+\\) )(in|at) ([^:]+):($|[0-9]+)", 4, 5, 1, "cmake" ), // cmake/automoc // example: AUTOMOC: error: /home/krf/devel/src/foo/src/quick/quickpathitem.cpp The file includes (...), ErrorFormat( "^AUTOMOC: error: (.*) (The file includes .*)$", 1, 0, 0 ), // Fortran ErrorFormat( "\"(.*)\", line ([0-9]+):(.*)", 1, 2, 3 ), // GFortran ErrorFormat( "^(.*):([0-9]+)\\.([0-9]+):(.*)", 1, 2, 4, "gfortran", 3 ), // Jade ErrorFormat( "^[a-zA-Z]+:([^: \t]+):([0-9]+):[0-9]+:[a-zA-Z]:(.*)", 1, 2, 3 ), // ifort ErrorFormat( "^fortcom: (.*): (.*), line ([0-9]+):(.*)", 2, 3, 1, "intel" ), // PGI ErrorFormat( "PGF9(.*)-(.*)-(.*)-(.*) \\((.*): ([0-9]+)\\)", 5, 6, 4, "pgi" ), // PGI (2) ErrorFormat( "PGF9(.*)-(.*)-(.*)-Symbol, (.*) \\((.*)\\)", 5, 5, 4, "pgi" ), }; // A list of filters for possible compiler, linker, and make actions const QVector ACTION_FILTERS { ActionFormat( I18N_NOOP2_NOSTRIP("", "compiling"), 1, 2, "(?:^|[^=])\\b(gcc|CC|cc|distcc|c\\+\\+|" "g\\+\\+|clang|clang\\+\\+|mpicc|icc|icpc)\\s+.*-c.*[/ '\\\\]+(\\w+\\.(?:cpp|CPP|c|C|cxx|CXX|cs|" "java|hpf|f|F|f90|F90|f95|F95))"), //moc and uic ActionFormat( I18N_NOOP2_NOSTRIP("", "generating"), 1, 2, "/(moc|uic)\\b.*\\s-o\\s([^\\s;]+)"), //libtool linking ActionFormat( I18N_NOOP2_NOSTRIP("Linking object files into a library or executable", "linking"), "libtool", "/bin/sh\\s.*libtool.*--mode=link\\s.*\\s-o\\s([^\\s;]+)", 1 ), //unsermake ActionFormat( I18N_NOOP2_NOSTRIP("", "compiling"), 1, 1, "^compiling (.*)" ), ActionFormat( I18N_NOOP2_NOSTRIP("", "generating"), 1, 2, "^generating (.*)" ), ActionFormat( I18N_NOOP2_NOSTRIP("Linking object files into a library or executable", "linking"), 1, 2, "(gcc|cc|c\\+\\+|g\\+\\+|clang|clang\\+\\+|mpicc|icc|icpc)\\S* (?:\\S* )*-o ([^\\s;]+)"), ActionFormat( I18N_NOOP2_NOSTRIP("Linking object files into a library or executable", "linking"), 1, 2, "^linking (.*)" ), //cmake ActionFormat( I18N_NOOP2_NOSTRIP("", "built"), -1, 1, "\\[.+%\\] Built target (.*)" ), ActionFormat( I18N_NOOP2_NOSTRIP("", "compiling"), "cmake", "\\[.+%\\] Building .* object (.*)CMakeFiles/", 1 ), ActionFormat( I18N_NOOP2_NOSTRIP("", "generating"), -1, 1, "\\[.+%\\] Generating (.*)" ), ActionFormat( I18N_NOOP2_NOSTRIP("Linking object files into a library or executable", "linking"), -1, 1, "^Linking (.*)" ), ActionFormat( I18N_NOOP2_NOSTRIP("", "configuring"), "cmake", "(-- Configuring (done|incomplete)|-- Found|-- Adding|-- Enabling)", -1 ), ActionFormat( I18N_NOOP2_NOSTRIP("", "installing"), -1, 1, "-- Installing (.*)" ), //libtool install ActionFormat( I18N_NOOP2_NOSTRIP("", "creating"), "", "/(?:bin/sh\\s.*mkinstalldirs).*\\s([^\\s;]+)", 1 ), ActionFormat( I18N_NOOP2_NOSTRIP("", "installing"), "", "/(?:usr/bin/install|bin/sh\\s.*mkinstalldirs" "|bin/sh\\s.*libtool.*--mode=install).*\\s([^\\s;]+)", 1 ), //dcop ActionFormat( I18N_NOOP2_NOSTRIP("", "generating"), "dcopidl", "dcopidl .* > ([^\\s;]+)", 1 ), ActionFormat( I18N_NOOP2_NOSTRIP("", "compiling"), "dcopidl2cpp", "dcopidl2cpp (?:\\S* )*([^\\s;]+)", 1 ), // match against Entering directory to update current build dir ActionFormat( "", "cd", "", "make\\[\\d+\\]: Entering directory (\\`|\\')(.+)'", 2), + // waf and scons use the same basic convention as make + ActionFormat( "", "cd", "", "(Waf|scons): Entering directory (\\`|\\')(.+)'", 3) }; } CompilerFilterStrategyPrivate::CompilerFilterStrategyPrivate(const KUrl& buildDir) : m_buildDir(buildDir) { } KUrl CompilerFilterStrategyPrivate::urlForFile(const QString& filename) const { QFileInfo fi( filename ); KUrl currentUrl; if( fi.isRelative() ) { if( m_currentDirs.isEmpty() ) { currentUrl = m_buildDir; currentUrl.addPath( filename ); return currentUrl; } QVector::const_iterator it = m_currentDirs.constEnd() - 1; do { currentUrl = KUrl( *it ); currentUrl.addPath( filename ); } while( (it-- != m_currentDirs.constBegin()) && !QFileInfo(currentUrl.toLocalFile()).exists() ); return currentUrl; } else { currentUrl = KUrl( filename ); } return currentUrl; } bool CompilerFilterStrategyPrivate::isMultiLineCase(KDevelop::ErrorFormat curErrFilter) const { if(curErrFilter.compiler == "gfortran" || curErrFilter.compiler == "cmake") { return true; } return false; } void CompilerFilterStrategyPrivate::putDirAtEnd(const QString& dirNameToInsert) { CompilerFilterStrategyPrivate::PositionMap::iterator it = m_positionInCurrentDirs.find( dirNameToInsert ); // Encountered new build directory? if (it == m_positionInCurrentDirs.end()) { m_currentDirs.push_back( dirNameToInsert ); m_positionInCurrentDirs.insert( dirNameToInsert, m_currentDirs.size() - 1 ); } else { // Build dir already in currentDirs, but move it to back of currentDirs list // (this gives us most-recently-used semantics in urlForFile) std::rotate(m_currentDirs.begin() + it.value(), m_currentDirs.begin() + it.value() + 1, m_currentDirs.end() ); it.value() = m_currentDirs.size() - 1; } } CompilerFilterStrategy::CompilerFilterStrategy(const KUrl& buildDir) : d(new CompilerFilterStrategyPrivate( buildDir )) { } CompilerFilterStrategy::~CompilerFilterStrategy() { delete d; } QVector< QString > CompilerFilterStrategy::getCurrentDirs() { return d->m_currentDirs; } FilteredItem CompilerFilterStrategy::actionInLine(const QString& line) { const QByteArray cd = "cd"; const QByteArray compiling = "compiling"; FilteredItem item(line); foreach( const ActionFormat& curActFilter, ACTION_FILTERS ) { QRegExp regEx = curActFilter.expression; if( regEx.indexIn( line ) != -1 ) { item.type = FilteredItem::ActionItem; if( curActFilter.fileGroup != -1 && curActFilter.toolGroup != -1 ) { item.shortenedText = QString( "%1 %2 (%3)").arg(i18nc(curActFilter.context, curActFilter.action)).arg( regEx.cap( curActFilter.fileGroup ) ).arg( regEx.cap( curActFilter.toolGroup ) ); } if( curActFilter.action == cd ) { d->m_currentDirs.push_back( regEx.cap( curActFilter.fileGroup ) ); d->m_positionInCurrentDirs.insert( regEx.cap( curActFilter.fileGroup ) , d->m_currentDirs.size() - 1 ); } // Special case for cmake: we parse the "Compiling " expression // and use it to find out about the build paths encountered during a build. // They are later searched by urlForFile to find source files corresponding to // compiler errors. if ( curActFilter.action == compiling && curActFilter.tool == "cmake") { KUrl url = d->m_buildDir; url.addPath(regEx.cap( curActFilter.fileGroup )); d->putDirAtEnd(url.toLocalFile()); } break; } } return item; } FilteredItem CompilerFilterStrategy::errorInLine(const QString& line) { FilteredItem item(line); foreach( const ErrorFormat& curErrFilter, ERROR_FILTERS ) { QRegExp regEx = curErrFilter.expression; if( regEx.indexIn( line ) != -1 && !( line.contains( "Each undeclared identifier is reported only once" ) || line.contains( "for each function it appears in." ) ) ) { if(curErrFilter.fileGroup > 0) { if( curErrFilter.compiler == "cmake" ) { // Unfortunately we cannot know if an error or an action comes first in cmake, and therefore we need to do this if( d->m_currentDirs.empty() ) { d->putDirAtEnd( d->m_buildDir.upUrl().toLocalFile() ); } } item.url = d->urlForFile( regEx.cap( curErrFilter.fileGroup ) ); } item.lineNo = regEx.cap( curErrFilter.lineGroup ).toInt() - 1; if(curErrFilter.columnGroup >= 0) { item.columnNo = regEx.cap( curErrFilter.columnGroup ).toInt() - 1; } else { item.columnNo = 0; } QString txt = regEx.cap(curErrFilter.textGroup); // Find the indicator which happens most early. int earliestIndicatorIdx = txt.length(); foreach( const Indicator& curIndicator, INDICATORS ) { int curIndicatorIdx = txt.indexOf(curIndicator.first, 0, Qt::CaseInsensitive); if((curIndicatorIdx >= 0) && (earliestIndicatorIdx > curIndicatorIdx)) { earliestIndicatorIdx = curIndicatorIdx; item.type = curIndicator.second; } } // Make the item clickable if it comes with the necessary file information if (item.url.isValid()) { item.isActivatable = true; if(item.type == FilteredItem::InvalidItem) { // If there are no error indicators in the line // maybe this is a multiline case if(d->isMultiLineCase(curErrFilter)) { item.type = FilteredItem::ErrorItem; } else { // Okay so we couldn't find anything to indicate an error, but we have file and lineGroup // Lets keep this item clickable and indicate this to the user. item.type = FilteredItem::InformationItem; } } } break; } } return item; } /// --- Script error filter strategy --- // A list of filters for possible Python and PHP errors const QList SCRIPT_ERROR_FILTERS = QList() //QRegExp python("^ File \"(.*)\", line (\\d*), in(.*)$"); << ErrorFormat( "^ File \"(.*)\", line ([0-9]+)(.*$|, in(.*)$)", 1, 2, -1 ) //QRegExp phpstacktrace("^.*(/.*):(\\d*).*$"); << ErrorFormat( "^.*(/.*):([0-9]+).*$", 1, 2, -1 ) //QRegExp phperror("^.* in (/.*) on line (\\d*).*$"); << ErrorFormat( "^.* in (/.*) on line ([0-9]+).*$", 1, 2, -1 ); ScriptErrorFilterStrategy::ScriptErrorFilterStrategy() { } FilteredItem ScriptErrorFilterStrategy::actionInLine(const QString& line) { return FilteredItem(line); } FilteredItem ScriptErrorFilterStrategy::errorInLine(const QString& line) { - FilteredItem item(line); - foreach( const ErrorFormat& curErrFilter, SCRIPT_ERROR_FILTERS ) { - QRegExp regEx = curErrFilter.expression; - if( regEx.indexIn( line ) != -1 ) - { - item.url = regEx.cap( curErrFilter.fileGroup ); - item.lineNo = regEx.cap( curErrFilter.lineGroup ).toInt() - 1; - if(curErrFilter.columnGroup >= 0) { - item.columnNo = regEx.cap( curErrFilter.columnGroup ).toInt() - 1; - } else { - item.columnNo = 0; - } + return FilteringStrategyUtils::match(SCRIPT_ERROR_FILTERS, line); +} - QString txt = regEx.cap(curErrFilter.textGroup); +/// --- Native application error filter strategy --- - item.type = FilteredItem::ErrorItem; +const QList QT_APPLICATION_ERROR_FILTERS = QList() + // ASSERT: "errors().isEmpty()" in file /foo/bar.cpp, line 49 + << ErrorFormat("^ASSERT: \"(.*)\" in file (.*), line ([0-9]+)$", 2, 3, -1) + // FAIL! : Foo::testBar() Compared pointers are not the same + // Actual ... + // Expected ... + // Loc: [/foo/bar.cpp(33)] + << ErrorFormat("^ Loc: \\[(.*)\\(([0-9]+)\\)\\]$", 1, 2, -1); - // Make the item clickable if it comes with the necessary file & line number information - if (curErrFilter.fileGroup > 0 && curErrFilter.lineGroup > 0) - item.isActivatable = true; +NativeAppErrorFilterStrategy::NativeAppErrorFilterStrategy() +{ +} - break; - } - } - return item; +FilteredItem NativeAppErrorFilterStrategy::actionInLine(const QString& line) +{ + return FilteredItem(line); +} + +FilteredItem NativeAppErrorFilterStrategy::errorInLine(const QString& line) +{ + return FilteringStrategyUtils::match(QT_APPLICATION_ERROR_FILTERS, line); } /// --- Static Analysis filter strategy --- // A list of filters for static analysis tools (krazy2, cppcheck) const QList STATIC_ANALYSIS_FILTERS = QList() // CppCheck << ErrorFormat( "^\\[(.*):([0-9]+)\\]:(.*)", 1, 2, 3 ) // krazy2 << ErrorFormat( "^\\t([^:]+).*line#([0-9]+).*", 1, 2, -1 ) // krazy2 without line info << ErrorFormat( "^\\t(.*): missing license", 1, -1, -1 ); StaticAnalysisFilterStrategy::StaticAnalysisFilterStrategy() { } FilteredItem StaticAnalysisFilterStrategy::actionInLine(const QString& line) { return FilteredItem(line); } FilteredItem StaticAnalysisFilterStrategy::errorInLine(const QString& line) { - FilteredItem item(line); - foreach( const ErrorFormat& curErrFilter, STATIC_ANALYSIS_FILTERS ) { - QRegExp regEx = curErrFilter.expression; - if( regEx.indexIn( line ) != -1 ) - { - item.url = regEx.cap( curErrFilter.fileGroup ); - item.lineNo = regEx.cap( curErrFilter.lineGroup ).toInt() - 1; - if(curErrFilter.columnGroup >= 0) { - item.columnNo = regEx.cap( curErrFilter.columnGroup ).toInt() - 1; - } else { - item.columnNo = 0; - } - - QString txt = regEx.cap(curErrFilter.textGroup); - - item.type = FilteredItem::ErrorItem; - - // Make the item clickable if it comes with the necessary file & line number information - if (curErrFilter.fileGroup > 0 && curErrFilter.lineGroup > 0) { - item.isActivatable = true; - } - break; - } - } - return item; + return FilteringStrategyUtils::match(STATIC_ANALYSIS_FILTERS, line); } - -} // namespace KDevelop - +} diff --git a/outputview/outputfilteringstrategies.h b/outputview/outputfilteringstrategies.h index 43c63bbf43..0f63cd5326 100644 --- a/outputview/outputfilteringstrategies.h +++ b/outputview/outputfilteringstrategies.h @@ -1,110 +1,136 @@ /* This file is part of KDevelop Copyright (C) 2012 Morten Danielsen Volden mvolden2@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef KDEVPLATFORM_OUTPUTFILTERINGSTRATEGIES_H #define KDEVPLATFORM_OUTPUTFILTERINGSTRATEGIES_H /** * @file This file contains Concrete strategies for filtering output * in KDevelop output model. I.e. classes that inherit from ifilterstrategy. * New filtering strategies should be added here */ #include "ifilterstrategy.h" +#include "outputformats.h" #include - -#include - +#include #include +class KUrl; + namespace KDevelop { +namespace FilteringStrategyUtils +{ + /** + * Looks through @p errorFormats and matches each item against @p line + * + * @return A FilteredItem object for the first match encountered + */ + FilteredItem match(const QList& errorFormats, const QString& line); +} + struct CompilerFilterStrategyPrivate; /** * This filter strategy is for not applying any filtering at all. Implementation of the * interface methods are basically noops **/ class KDEVPLATFORMOUTPUTVIEW_EXPORT NoFilterStrategy : public IFilterStrategy { public: NoFilterStrategy(); virtual FilteredItem errorInLine(QString const& line); virtual FilteredItem actionInLine(QString const& line); }; /** * This filter stategy checks if a given line contains output * that is defined as an error (or an action) from a compiler. **/ class KDEVPLATFORMOUTPUTVIEW_EXPORT CompilerFilterStrategy : public IFilterStrategy { public: CompilerFilterStrategy(KUrl const& buildDir); virtual ~CompilerFilterStrategy(); virtual FilteredItem errorInLine(QString const& line); virtual FilteredItem actionInLine(QString const& line); QVector getCurrentDirs(); private: CompilerFilterStrategyPrivate* const d; }; /** * This filter stategy filters out errors (no actions) from Python and PHP scripts. **/ class KDEVPLATFORMOUTPUTVIEW_EXPORT ScriptErrorFilterStrategy : public IFilterStrategy { public: ScriptErrorFilterStrategy(); virtual FilteredItem errorInLine(QString const& line); virtual FilteredItem actionInLine(QString const& line); }; +/** + * This filter strategy filters out errors (no actions) from runtime debug output of native applications + * + * This is especially useful for runtime output of Qt applications, for example lines such as: + * "ASSERT: "errors().isEmpty()" in file /tmp/foo/bar.cpp", line 49" + */ +class KDEVPLATFORMOUTPUTVIEW_EXPORT NativeAppErrorFilterStrategy : public IFilterStrategy +{ +public: + NativeAppErrorFilterStrategy(); + + virtual FilteredItem errorInLine(const QString& line); + virtual FilteredItem actionInLine(const QString& line); +}; + /** * This filter stategy filters out errors (no actions) from Static code analysis tools (Cppcheck,) **/ class KDEVPLATFORMOUTPUTVIEW_EXPORT StaticAnalysisFilterStrategy : public IFilterStrategy { public: StaticAnalysisFilterStrategy(); virtual FilteredItem errorInLine(QString const& line); virtual FilteredItem actionInLine(QString const& line); }; } // namespace KDevelop #endif // KDEVPLATFORM_OUTPUTFILTERINGSTRATEGIES_H diff --git a/outputview/outputmodel.cpp b/outputview/outputmodel.cpp index 59da9eb2fb..04162cc320 100644 --- a/outputview/outputmodel.cpp +++ b/outputview/outputmodel.cpp @@ -1,392 +1,396 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2007 Andreas Pakulat * * Copyright 2010 Aleix Pol Gonzalez * * Copyright (C) 2012 Morten Danielsen Volden mvolden2@gmail.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "outputmodel.h" #include "filtereditem.h" #include "outputfilteringstrategies.h" #include #include #include #include #include #include #include #include #include #include Q_DECLARE_METATYPE(QVector) Q_DECLARE_METATYPE(KDevelop::IFilterStrategy*) namespace KDevelop { /** * Number of lines that are processed in one go before we notify the GUI thread * about the result. It is generally faster to add multiple items to a model * in one go compared to adding each item independently. */ static const int BATCH_SIZE = 50; /** * Time in ms that we wait in the parse worker for new incoming lines before * actually processing them. If we already have enough for one batch though * we process immediately. */ static const int BATCH_AGGREGATE_TIME_DELAY = 50; class ParseWorker : public QObject { Q_OBJECT public: ParseWorker() : QObject(0) , m_filter( new NoFilterStrategy ) , m_timer(new QTimer(this)) { m_timer->setInterval(BATCH_AGGREGATE_TIME_DELAY); m_timer->setSingleShot(true); connect(m_timer, SIGNAL(timeout()), SLOT(process())); } public slots: void changeFilterStrategy( KDevelop::IFilterStrategy* newFilterStrategy ) { m_filter = QSharedPointer( newFilterStrategy ); } void addLines( const QStringList& lines ) { m_cachedLines << lines; if (m_cachedLines.size() >= BATCH_SIZE) { // if enough lines were added, process immediately m_timer->stop(); process(); } else if (!m_timer->isActive()) { m_timer->start(); } } signals: void parsedBatch(const QVector& filteredItems); private slots: /** * Process *all* cached lines, emit parsedBatch for each batch */ void process() { QVector filteredItems; filteredItems.reserve(qMin(BATCH_SIZE, m_cachedLines.size())); foreach(const QString& line, m_cachedLines) { FilteredItem item = m_filter->errorInLine(line); if( item.type == FilteredItem::InvalidItem ) { item = m_filter->actionInLine(line); } filteredItems << item; if( filteredItems.size() == BATCH_SIZE ) { emit parsedBatch(filteredItems); filteredItems.clear(); filteredItems.reserve(qMin(BATCH_SIZE, m_cachedLines.size())); } } // Make sure to emit the rest as well if( !filteredItems.isEmpty() ) { emit parsedBatch(filteredItems); } m_cachedLines.clear(); } private: QSharedPointer m_filter; QStringList m_cachedLines; QTimer* m_timer; }; class ParsingThread { public: ParsingThread() { m_thread.setObjectName("OutputFilterThread"); } virtual ~ParsingThread() { if (m_thread.isRunning()) { m_thread.quit(); m_thread.wait(); } } void addWorker(ParseWorker* worker) { if (!m_thread.isRunning()) { m_thread.start(); } worker->moveToThread(&m_thread); } private: QThread m_thread; }; Q_GLOBAL_STATIC(ParsingThread, s_parsingThread); struct OutputModelPrivate { OutputModelPrivate( OutputModel* model, const KUrl& builddir = KUrl() ); ~OutputModelPrivate(); bool isValidIndex( const QModelIndex&, int currentRowCount ) const; OutputModel* model; ParseWorker* worker; QVector m_filteredItems; // We use std::set because that is ordered std::set m_errorItems; // Indices of all items that we want to move to using previous and next KUrl m_buildDir; void linesParsed(const QVector& items) { model->beginInsertRows( QModelIndex(), model->rowCount(), model->rowCount() + items.size() - 1); foreach( const FilteredItem& item, items ) { if( item.type == FilteredItem::ErrorItem ) { m_errorItems.insert(m_filteredItems.size()); } m_filteredItems << item; } model->endInsertRows(); } }; OutputModelPrivate::OutputModelPrivate( OutputModel* model_, const KUrl& builddir) : model(model_) , worker(new ParseWorker ) , m_buildDir( builddir ) { qRegisterMetaType >(); qRegisterMetaType(); s_parsingThread->addWorker(worker); model->connect(worker, SIGNAL(parsedBatch(QVector)), model, SLOT(linesParsed(QVector))); } bool OutputModelPrivate::isValidIndex( const QModelIndex& idx, int currentRowCount ) const { return ( idx.isValid() && idx.row() >= 0 && idx.row() < currentRowCount && idx.column() == 0 ); } OutputModelPrivate::~OutputModelPrivate() { worker->deleteLater(); } OutputModel::OutputModel( const KUrl& builddir, QObject* parent ) : QAbstractListModel(parent) , d( new OutputModelPrivate( this, builddir ) ) { } OutputModel::OutputModel( QObject* parent ) : QAbstractListModel(parent) , d( new OutputModelPrivate( this ) ) { } OutputModel::~OutputModel() { delete d; } QVariant OutputModel::data(const QModelIndex& idx , int role ) const { if( d->isValidIndex(idx, rowCount()) ) { switch( role ) { case Qt::DisplayRole: return d->m_filteredItems.at( idx.row() ).shortenedText; break; case OutputModel::OutputItemTypeRole: return static_cast(d->m_filteredItems.at( idx.row() ).type); break; case Qt::FontRole: return KGlobalSettings::fixedFont(); break; default: break; } } return QVariant(); } int OutputModel::rowCount( const QModelIndex& parent ) const { if( !parent.isValid() ) return d->m_filteredItems.count(); return 0; } QVariant OutputModel::headerData( int, Qt::Orientation, int ) const { return QVariant(); } void OutputModel::activate( const QModelIndex& index ) { if( index.model() != this || !d->isValidIndex(index, rowCount()) ) { return; } kDebug() << "Model activated" << index.row(); FilteredItem item = d->m_filteredItems.at( index.row() ); if( item.isActivatable ) { kDebug() << "activating:" << item.lineNo << item.url; KTextEditor::Cursor range( item.lineNo, item.columnNo ); KDevelop::IDocumentController *docCtrl = KDevelop::ICore::self()->documentController(); KUrl url = item.url; if(url.isRelative()) { url = KUrl(d->m_buildDir, url.path()); } docCtrl->openDocument( url, range ); } else { kDebug() << "not an activateable item"; } } QModelIndex OutputModel::nextHighlightIndex( const QModelIndex ¤tIdx ) { int startrow = d->isValidIndex(currentIdx, rowCount()) ? currentIdx.row() + 1 : 0; if( !d->m_errorItems.empty() ) { kDebug() << "searching next error"; // Jump to the next error item std::set< int >::const_iterator next = d->m_errorItems.lower_bound( startrow ); if( next == d->m_errorItems.end() ) next = d->m_errorItems.begin(); return index( *next, 0, QModelIndex() ); } for( int row = 0; row < rowCount(); ++row ) { int currow = (startrow + row) % rowCount(); if( d->m_filteredItems.at( currow ).isActivatable ) { return index( currow, 0, QModelIndex() ); } } return QModelIndex(); } QModelIndex OutputModel::previousHighlightIndex( const QModelIndex ¤tIdx ) { //We have to ensure that startrow is >= rowCount - 1 to get a positive value from the % operation. int startrow = rowCount() + (d->isValidIndex(currentIdx, rowCount()) ? currentIdx.row() : rowCount()) - 1; if(!d->m_errorItems.empty()) { kDebug() << "searching previous error"; // Jump to the previous error item std::set< int >::const_iterator previous = d->m_errorItems.lower_bound( currentIdx.row() ); if( previous == d->m_errorItems.begin() ) previous = d->m_errorItems.end(); --previous; return index( *previous, 0, QModelIndex() ); } for ( int row = 0; row < rowCount(); ++row ) { int currow = (startrow - row) % rowCount(); if( d->m_filteredItems.at( currow ).isActivatable ) { return index( currow, 0, QModelIndex() ); } } return QModelIndex(); } void OutputModel::setFilteringStrategy(const OutputFilterStrategy& currentStrategy) { + // TODO: Turn into factory, decouple from OutputModel IFilterStrategy* filter = 0; switch( currentStrategy ) { case NoFilter: filter = new NoFilterStrategy; break; case CompilerFilter: filter = new CompilerFilterStrategy( d->m_buildDir ); break; case ScriptErrorFilter: filter = new ScriptErrorFilterStrategy; break; + case NativeAppErrorFilter: + filter = new NativeAppErrorFilterStrategy; + break; case StaticAnalysisFilter: filter = new StaticAnalysisFilterStrategy; break; default: // assert(false); filter = new NoFilterStrategy; break; } Q_ASSERT(filter); QMetaObject::invokeMethod(d->worker, "changeFilterStrategy", Q_ARG(KDevelop::IFilterStrategy*, filter)); } void OutputModel::appendLines( const QStringList& lines ) { if( lines.isEmpty() ) return; QMetaObject::invokeMethod(d->worker, "addLines", Q_ARG(QStringList, lines)); } void OutputModel::appendLine( const QString& l ) { appendLines( QStringList() << l ); } } #include "outputmodel.moc" #include "moc_outputmodel.cpp" diff --git a/outputview/outputmodel.h b/outputview/outputmodel.h index dee7a911d8..8d249b43b0 100644 --- a/outputview/outputmodel.h +++ b/outputview/outputmodel.h @@ -1,86 +1,87 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2007 Andreas Pakulat * * Copyright (C) 2012 Morten Danielsen Volden mvolden2@gmail.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef KDEVPLATFORM_OUTPUTMODEL_H #define KDEVPLATFORM_OUTPUTMODEL_H #include "outputviewexport.h" #include "ioutputviewmodel.h" #include class KUrl; namespace KDevelop { struct FilteredItem; struct OutputModelPrivate; class KDEVPLATFORMOUTPUTVIEW_EXPORT OutputModel : public QAbstractListModel, public KDevelop::IOutputViewModel { Q_OBJECT public: enum CustomRoles { OutputItemTypeRole = Qt::UserRole + 1 }; enum OutputFilterStrategy { NoFilter, CompilerFilter, ScriptErrorFilter, + NativeAppErrorFilter, StaticAnalysisFilter }; explicit OutputModel( const KUrl& builddir , QObject* parent = 0 ); OutputModel( QObject* parent = 0 ); virtual ~OutputModel(); /// IOutputViewModel interfaces void activate( const QModelIndex& index ); QModelIndex nextHighlightIndex( const QModelIndex ¤t ); QModelIndex previousHighlightIndex( const QModelIndex ¤t ); /// QAbstractItemModel interfaces QVariant data( const QModelIndex&, int = Qt::DisplayRole ) const; int rowCount( const QModelIndex& = QModelIndex() ) const; QVariant headerData( int, Qt::Orientation, int = Qt::DisplayRole ) const; void setFilteringStrategy(const OutputFilterStrategy& currentStrategy); public Q_SLOTS: void appendLine( const QString& ); void appendLines( const QStringList& ); private: OutputModelPrivate* const d; friend struct OutputModelPrivate; Q_PRIVATE_SLOT(d, void linesParsed(const QVector& lines)); }; } Q_DECLARE_METATYPE( KDevelop::OutputModel::OutputFilterStrategy ) #endif diff --git a/outputview/tests/filteringstrategytest.cpp b/outputview/tests/filteringstrategytest.cpp index 87c2a63c2d..0ec430d646 100644 --- a/outputview/tests/filteringstrategytest.cpp +++ b/outputview/tests/filteringstrategytest.cpp @@ -1,420 +1,455 @@ /* This file is part of KDevelop Copyright 2012 Milian Wolff Copyright (C) 2012 Morten Danielsen Volden mvolden2@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "filteringstrategytest.h" #include "testlinebuilderfunctions.h" #include #include #include QTEST_GUILESS_MAIN(KDevelop::FilteringStrategyTest) namespace QTest { template<> inline char* toString(const KDevelop::FilteredItem::FilteredOutputItemType& type) { switch (type) { case KDevelop::FilteredItem::ActionItem: return qstrdup("ActionItem"); case KDevelop::FilteredItem::CustomItem: return qstrdup("CustomItem"); case KDevelop::FilteredItem::ErrorItem: return qstrdup("ErrorItem"); case KDevelop::FilteredItem::InformationItem: return qstrdup("InformationItem"); case KDevelop::FilteredItem::InvalidItem: return qstrdup("InvalidItem"); case KDevelop::FilteredItem::StandardItem: return qstrdup("StandardItem"); case KDevelop::FilteredItem::WarningItem: return qstrdup("WarningItem"); } return qstrdup("unknown"); } } namespace KDevelop { -void FilteringStrategyTest::testNoFilterstrategy_data() +void FilteringStrategyTest::testNoFilterStrategy_data() { QTest::addColumn("line"); QTest::addColumn("expected"); QTest::newRow("cppcheck-info-line") << buildCppCheckInformationLine() << FilteredItem::InvalidItem; QTest::newRow("cppcheck-error-line") << buildCppCheckErrorLine() << FilteredItem::InvalidItem; QTest::newRow("compiler-line") << buildCompilerLine() << FilteredItem::InvalidItem; QTest::newRow("compiler-error-line") << buildCompilerErrorLine() << FilteredItem::InvalidItem; QTest::newRow("compiler-action-line") << buildCompilerActionLine() << FilteredItem::InvalidItem; QTest::newRow("compiler-information-line") << buildCompilerInformationLine() << FilteredItem::InvalidItem; QTest::newRow("python-error-line") << buildPythonErrorLine() << FilteredItem::InvalidItem; } -void FilteringStrategyTest::testNoFilterstrategy() +void FilteringStrategyTest::testNoFilterStrategy() { QFETCH(QString, line); QFETCH(FilteredItem::FilteredOutputItemType, expected); NoFilterStrategy testee; FilteredItem item1 = testee.errorInLine(line); QCOMPARE(item1.type, expected); item1 = testee.actionInLine(line); QCOMPARE(item1.type, expected); } -void FilteringStrategyTest::testCompilerFilterstrategy_data() +void FilteringStrategyTest::testCompilerFilterStrategy_data() { QTest::addColumn("line"); QTest::addColumn("expectedError"); QTest::addColumn("expectedAction"); QTest::newRow("cppcheck-info-line") << buildCppCheckInformationLine() << FilteredItem::InvalidItem << FilteredItem::InvalidItem; QTest::newRow("cppcheck-error-line") << buildCppCheckErrorLine() << FilteredItem::InvalidItem << FilteredItem::InvalidItem; QTest::newRow("compiler-line") << buildCompilerLine() << FilteredItem::InvalidItem << FilteredItem::InvalidItem; QTest::newRow("compiler-error-line") << buildCompilerErrorLine() << FilteredItem::ErrorItem << FilteredItem::InvalidItem; QTest::newRow("compiler-information-line") << buildCompilerInformationLine() << FilteredItem::InformationItem << FilteredItem::InvalidItem; QTest::newRow("compiler-information-line2") << buildInfileIncludedFromFirstLine() << FilteredItem::InformationItem << FilteredItem::InvalidItem; QTest::newRow("compiler-information-line3") << buildInfileIncludedFromSecondLine() << FilteredItem::InformationItem << FilteredItem::InvalidItem; QTest::newRow("cmake-error-line1") << "CMake Error at CMakeLists.txt:2 (cmake_minimum_required):" << FilteredItem::ErrorItem << FilteredItem::InvalidItem; QTest::newRow("cmake-error-multiline1") << "CMake Error: Error in cmake code at" << FilteredItem::InvalidItem << FilteredItem::InvalidItem; QTest::newRow("cmake-error-multiline2") << buildCmakeConfigureMultiLine() << FilteredItem::ErrorItem << FilteredItem::InvalidItem; QTest::newRow("cmake-warning-line") << "CMake Warning (dev) in CMakeLists.txt:" << FilteredItem::WarningItem << FilteredItem::InvalidItem; QTest::newRow("cmake-automoc-error") << "AUTOMOC: error: /home/krf/devel/src/foo/src/quick/quickpathitem.cpp The file includes the moc file \"moc_QuickPathItem.cpp\"" << FilteredItem::ErrorItem << FilteredItem::InvalidItem; QTest::newRow("linker-action-line") << "linking testCustombuild (g++)" << FilteredItem::InvalidItem << FilteredItem::ActionItem; QTest::newRow("linker-error-line") << buildLinkerErrorLine() << FilteredItem::ErrorItem << FilteredItem::InvalidItem; QTest::newRow("python-error-line") << buildPythonErrorLine() << FilteredItem::InvalidItem << FilteredItem::InvalidItem; } -void FilteringStrategyTest::testCompilerFilterstrategy() +void FilteringStrategyTest::testCompilerFilterStrategy() { QFETCH(QString, line); QFETCH(FilteredItem::FilteredOutputItemType, expectedError); QFETCH(FilteredItem::FilteredOutputItemType, expectedAction); KUrl projecturl( PROJECTS_SOURCE_DIR"/onefileproject/" ); CompilerFilterStrategy testee(projecturl); FilteredItem item1 = testee.errorInLine(line); QCOMPARE(item1.type, expectedError); item1 = testee.actionInLine(line); QCOMPARE(item1.type, expectedAction); } void FilteringStrategyTest::testCompilerFilterstrategyMultipleKeywords_data() { QTest::addColumn("line"); QTest::addColumn("expectedError"); QTest::addColumn("expectedAction"); QTest::newRow("warning-containing-error-word") << "RingBuffer.cpp:64:6: warning: unused parameter ‘errorItem’ [-Wunused-parameter]" << FilteredItem::WarningItem << FilteredItem::InvalidItem; QTest::newRow("error-containing-info-word") << "NodeSet.hpp:89:27: error: ‘Info’ was not declared in this scope" << FilteredItem::ErrorItem << FilteredItem::InvalidItem; QTest::newRow("warning-in-filename-containing-error-word") << "ErrorHandling.cpp:100:56: warning: unused parameter ‘item’ [-Wunused-parameter]" << FilteredItem::WarningItem << FilteredItem::InvalidItem; QTest::newRow("error-in-filename-containing-warning-word") << "WarningHandling.cpp:100:56: error: ‘Item’ was not declared in this scope" << FilteredItem::ErrorItem << FilteredItem::InvalidItem; } void FilteringStrategyTest::testCompilerFilterstrategyMultipleKeywords() { QFETCH(QString, line); QFETCH(FilteredItem::FilteredOutputItemType, expectedError); QFETCH(FilteredItem::FilteredOutputItemType, expectedAction); KUrl projecturl( PROJECTS_SOURCE_DIR"/onefileproject/" ); CompilerFilterStrategy testee(projecturl); FilteredItem item1 = testee.errorInLine(line); QCOMPARE(item1.type, expectedError); item1 = testee.actionInLine(line); QCOMPARE(item1.type, expectedAction); } void FilteringStrategyTest::testCompilerFilterStrategyShortenedText_data() { QTest::addColumn("line"); QTest::addColumn("expectedShortenedText"); QTest::newRow("c++-compile") << "g++ -c main.cpp -o main.o" << "compiling main.cpp (g++)"; QTest::newRow("clang++-link") << "clang++ -c main.cpp -o main.o" << "compiling main.cpp (clang++)"; // see bug: https://bugs.kde.org/show_bug.cgi?id=240017 QTest::newRow("mpicc-link") << "/usr/bin/mpicc -c main.cpp -o main.o" << "compiling main.cpp (mpicc)"; QTest::newRow("c++-link") << "/usr/bin/g++ main.cpp -o main" << "linking main (g++)"; QTest::newRow("clang++-link") << "/usr/bin/clang++ main.cpp -o a.out" << "linking a.out (clang++)"; QTest::newRow("mpicc-link") << "mpicc main.cpp -o main" << "linking main (mpicc)"; } void FilteringStrategyTest::testCompilerFilterStrategyShortenedText() { QFETCH(QString, line); QFETCH(QString, expectedShortenedText); KUrl projecturl( PROJECTS_SOURCE_DIR"/onefileproject/" ); CompilerFilterStrategy testee(projecturl); FilteredItem item = testee.actionInLine(line); QCOMPARE(item.shortenedText, expectedShortenedText); } -void FilteringStrategyTest::testScriptErrorFilterstrategy_data() +void FilteringStrategyTest::testScriptErrorFilterStrategy_data() { QTest::addColumn("line"); QTest::addColumn("expectedError"); QTest::addColumn("expectedAction"); QTest::newRow("cppcheck-info-line") << buildCppCheckInformationLine() << FilteredItem::InvalidItem << FilteredItem::InvalidItem; QTest::newRow("cppcheck-error-line") << buildCppCheckErrorLine() << FilteredItem::ErrorItem << FilteredItem::InvalidItem; QTest::newRow("compiler-line") << buildCompilerLine() << FilteredItem::InvalidItem << FilteredItem::InvalidItem; QTest::newRow("compiler-error-line") << buildCompilerErrorLine() << FilteredItem::ErrorItem << FilteredItem::InvalidItem; QTest::newRow("compiler-action-line") << "linking testCustombuild (g++)" << FilteredItem::InvalidItem << FilteredItem::InvalidItem; QTest::newRow("python-error-line") << buildPythonErrorLine() << FilteredItem::InvalidItem << FilteredItem::InvalidItem; } -void FilteringStrategyTest::testScriptErrorFilterstrategy() +void FilteringStrategyTest::testScriptErrorFilterStrategy() { QFETCH(QString, line); QFETCH(FilteredItem::FilteredOutputItemType, expectedError); QFETCH(FilteredItem::FilteredOutputItemType, expectedAction); ScriptErrorFilterStrategy testee; FilteredItem item1 = testee.errorInLine(line); QCOMPARE(item1.type, expectedError); item1 = testee.actionInLine(line); QCOMPARE(item1.type, expectedAction); } +void FilteringStrategyTest::testNativeAppErrorFilterStrategy_data() +{ + QTest::addColumn("line"); + QTest::addColumn("file"); + QTest::addColumn("lineNo"); + QTest::addColumn("column"); + QTest::addColumn("itemtype"); + + QTest::newRow("qt-assert") + << "ASSERT: \"errors().isEmpty()\" in file /tmp/foo/bar.cpp, line 49" + << "/tmp/foo/bar.cpp" + << 48 << 0 << FilteredItem::ErrorItem; + QTest::newRow("qttest-loc") + << " Loc: [/foo/bar.cpp(33)]" + << "/foo/bar.cpp" + << 32 << 0 << FilteredItem::ErrorItem; +} + +void FilteringStrategyTest::testNativeAppErrorFilterStrategy() +{ + QFETCH(QString, line); + QFETCH(QString, file); + QFETCH(int, lineNo); + QFETCH(int, column); + QFETCH(FilteredItem::FilteredOutputItemType, itemtype); + NativeAppErrorFilterStrategy testee; + FilteredItem item = testee.errorInLine(line); + QCOMPARE(item.url.path(), file); + QCOMPARE(item.lineNo , lineNo); + QCOMPARE(item.columnNo , column); + QCOMPARE(item.type , itemtype); +} + void FilteringStrategyTest::testStaticAnalysisFilterStrategy_data() { QTest::addColumn("line"); QTest::addColumn("expectedError"); QTest::addColumn("expectedAction"); QTest::newRow("cppcheck-info-line") << buildCppCheckInformationLine() << FilteredItem::InvalidItem << FilteredItem::InvalidItem; QTest::newRow("cppcheck-error-line") << buildCppCheckErrorLine() << FilteredItem::ErrorItem << FilteredItem::InvalidItem; QTest::newRow("krazy2-error-line") << buildKrazyErrorLine() << FilteredItem::ErrorItem << FilteredItem::InvalidItem; QTest::newRow("krazy2-error-line-two-colons") << buildKrazyErrorLine2() << FilteredItem::ErrorItem << FilteredItem::InvalidItem; QTest::newRow("krazy2-error-line-error-description") << buildKrazyErrorLine3() << FilteredItem::ErrorItem << FilteredItem::InvalidItem; QTest::newRow("krazy2-error-line-wo-line-info") << buildKrazyErrorLineNoLineInfo() << FilteredItem::ErrorItem << FilteredItem::InvalidItem; QTest::newRow("compiler-line") << buildCompilerLine() << FilteredItem::InvalidItem << FilteredItem::InvalidItem; QTest::newRow("compiler-error-line") << buildCompilerErrorLine() << FilteredItem::InvalidItem << FilteredItem::InvalidItem; QTest::newRow("compiler-action-line") << "linking testCustombuild (g++)" << FilteredItem::InvalidItem << FilteredItem::InvalidItem; QTest::newRow("python-error-line") << buildPythonErrorLine() << FilteredItem::InvalidItem << FilteredItem::InvalidItem; } void FilteringStrategyTest::testStaticAnalysisFilterStrategy() { // Test that url's are extracted correctly as well QString referencePath( PROJECTS_SOURCE_DIR"/onefileproject/main.cpp" ); QFETCH(QString, line); QFETCH(FilteredItem::FilteredOutputItemType, expectedError); QFETCH(FilteredItem::FilteredOutputItemType, expectedAction); StaticAnalysisFilterStrategy testee; FilteredItem item1 = testee.errorInLine(line); QString extractedPath = item1.url.toLocalFile(); QVERIFY((item1.type != FilteredItem::ErrorItem) || ( extractedPath == referencePath)); QCOMPARE(item1.type, expectedError); item1 = testee.actionInLine(line); QCOMPARE(item1.type, expectedAction); } void FilteringStrategyTest::testCompilerFilterstrategyUrlFromAction_data() { QTest::addColumn("line"); QTest::addColumn("expectedLastDir"); QString basepath( PROJECTS_SOURCE_DIR"/onefileproject/" ); QTest::newRow("cmake-line1") << "[ 25%] Building CXX object /path/to/one/CMakeFiles/file.o" << QString( basepath + "path/to/one/" ); QTest::newRow("cmake-line2") << "[ 26%] Building CXX object /path/to/two/CMakeFiles/file.o" << QString( basepath + "path/to/two/"); QTest::newRow("cmake-line3") << "[ 26%] Building CXX object /path/to/three/CMakeFiles/file.o" << QString( basepath + "path/to/three/"); QTest::newRow("cmake-line4") << "[ 26%] Building CXX object /path/to/four/CMakeFiles/file.o" << QString( basepath + "path/to/four/"); QTest::newRow("cmake-line5") << "[ 26%] Building CXX object /path/to/two/CMakeFiles/file.o" << QString( basepath + "path/to/two/"); QTest::newRow("cd-line6") << QString("make[4]: Entering directory '" + basepath + "path/to/one/'") << QString( basepath + "path/to/one/"); + QTest::newRow("waf-cd") + << QString("Waf: Entering directory `" + basepath + "path/to/two/'") << QString( basepath + "path/to/two/"); QTest::newRow("cmake-line7") << QString("[ 50%] Building CXX object CMakeFiles/testdeque.dir/RingBuffer.cpp.o") << QString( basepath); } void FilteringStrategyTest::testCompilerFilterstrategyUrlFromAction() { QFETCH(QString, line); QFETCH(QString, expectedLastDir); KUrl projecturl( PROJECTS_SOURCE_DIR"/onefileproject/" ); static CompilerFilterStrategy testee(projecturl); FilteredItem item1 = testee.actionInLine(line); QCOMPARE(testee.getCurrentDirs().last(), expectedLastDir); } void FilteringStrategyTest::benchMarkCompilerFilterAction() { QString projecturl( PROJECTS_SOURCE_DIR"/onefileproject/" ); QStringList outputlines; const int numLines(10000); int j(0), k(0), l(0), m(0); do { ++j; ++k; ++l; QString tmp; if(m % 2 == 0) { tmp = QString( "[ 26%] Building CXX object /this/is/the/path/to/the/files/%1/%2/%3/CMakeFiles/file.o").arg( j ).arg( k ).arg( l ); } else { tmp = QString( "make[4]: Entering directory '" + projecturl + "/this/is/the/path/to/the/files/%1/%2/%3/").arg( j ).arg( k ).arg( l ); } outputlines << tmp; if(j % 6 == 0) { j = 0; ++m; } if(k % 9 == 0) { k = 0; ++m; } if(l % 13 == 0) { l = 0; ++m; } } while(outputlines.size() < numLines ); // gives us numLines (-ish) QElapsedTimer totalTime; totalTime.start(); static CompilerFilterStrategy testee(projecturl); FilteredItem item1("dummyline", FilteredItem::InvalidItem); QBENCHMARK { for(int i = 0; i < outputlines.size(); ++i) { item1 = testee.actionInLine(outputlines.at(i)); } } const qint64 elapsed = totalTime.elapsed(); qDebug() << "ms elapsed to add directories: " << elapsed; qDebug() << "total number of directories: " << outputlines.count(); const double avgDirectoryInsertion = double(elapsed) / outputlines.count(); qDebug() << "average ms spend pr. dir: " << avgDirectoryInsertion; QVERIFY(avgDirectoryInsertion < 2); } -void FilteringStrategyTest::testExtractionOfLineAndCulmn_data() +void FilteringStrategyTest::testExtractionOfLineAndColumn_data() { QTest::addColumn("line"); QTest::addColumn("file"); QTest::addColumn("lineNr"); QTest::addColumn("column"); QTest::addColumn("itemtype"); QTest::newRow("gcc-with-col") << "/path/to/file.cpp:123:45: fatal error: ..." << "/path/to/file.cpp" << 122 << 44 << FilteredItem::ErrorItem; QTest::newRow("gcc-no-col") << "/path/to/file.cpp:123: error ..." << "/path/to/file.cpp" << 122 << 0 << FilteredItem::ErrorItem; QTest::newRow("fortcom") << "fortcom: Error: Ogive8.f90, line 123: ..." << "./Ogive8.f90" << 122 << 0 << FilteredItem::ErrorItem; QTest::newRow("fortcomError") << "fortcom: Error: ./Ogive8.f90, line 123: ..." << "././Ogive8.f90" << 122 << 0 << FilteredItem::ErrorItem; QTest::newRow("fortcomWarning") << "fortcom: Warning: /path/Ogive8.f90, line 123: ..." << "/path/Ogive8.f90" << 122 << 0 << FilteredItem::WarningItem; QTest::newRow("fortcomInfo") << "fortcom: Info: Ogive8.f90, line 123: ..." << "./Ogive8.f90" << 122 << 0 << FilteredItem::InformationItem; QTest::newRow("libtool") << "libtool: link: warning: ..." << "" << -1 << 0 << FilteredItem::WarningItem; QTest::newRow("gfortranError1") << "/path/to/file.f90:123.456:Error: ...." << "/path/to/file.f90" << 122 << 455 << FilteredItem::ErrorItem; QTest::newRow("gfortranError2") << "/path/flib.f90:3567.22:" << "/path/flib.f90" << 3566 << 21 << FilteredItem::ErrorItem; } -void FilteringStrategyTest::testExtractionOfLineAndCulmn() +void FilteringStrategyTest::testExtractionOfLineAndColumn() { QFETCH(QString, line); QFETCH(QString, file); QFETCH(int, lineNr); QFETCH(int, column); QFETCH(FilteredItem::FilteredOutputItemType, itemtype); KUrl projecturl( "./" ); CompilerFilterStrategy testee(projecturl); FilteredItem item1 = testee.errorInLine(line); QCOMPARE(item1.type , itemtype); QCOMPARE(item1.url.path(), file); QCOMPARE(item1.lineNo , lineNr); QCOMPARE(item1.columnNo , column); } } diff --git a/outputview/tests/filteringstrategytest.h b/outputview/tests/filteringstrategytest.h index 38a4deae14..ef3f8fd5b5 100644 --- a/outputview/tests/filteringstrategytest.h +++ b/outputview/tests/filteringstrategytest.h @@ -1,52 +1,54 @@ /* This file is part of KDevelop Copyright (C) 2012 Morten Danielsen Volden mvolden2@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef KDEVPLATFORM_FILTERINGSTRATEGYTEST_H #define KDEVPLATFORM_FILTERINGSTRATEGYTEST_H #include namespace KDevelop { class FilteringStrategyTest : public QObject { Q_OBJECT private slots: - void testNoFilterstrategy_data(); - void testNoFilterstrategy(); - void testCompilerFilterstrategy_data(); - void testCompilerFilterstrategy(); + void testNoFilterStrategy_data(); + void testNoFilterStrategy(); + void testCompilerFilterStrategy_data(); + void testCompilerFilterStrategy(); void testCompilerFilterstrategyMultipleKeywords_data(); void testCompilerFilterstrategyMultipleKeywords(); void testCompilerFilterStrategyShortenedText(); void testCompilerFilterStrategyShortenedText_data(); void testCompilerFilterstrategyUrlFromAction_data(); void testCompilerFilterstrategyUrlFromAction(); - void testScriptErrorFilterstrategy_data(); - void testScriptErrorFilterstrategy(); + void testScriptErrorFilterStrategy_data(); + void testScriptErrorFilterStrategy(); + void testNativeAppErrorFilterStrategy_data(); + void testNativeAppErrorFilterStrategy(); void testStaticAnalysisFilterStrategy_data(); void testStaticAnalysisFilterStrategy(); - void benchMarkCompilerFilterAction(); - void testExtractionOfLineAndCulmn_data(); - void testExtractionOfLineAndCulmn(); + void testExtractionOfLineAndColumn_data(); + void testExtractionOfLineAndColumn(); + void benchMarkCompilerFilterAction(); }; } #endif // KDEVPLATFORM_FILTERINGSTRATEGYTEST_H diff --git a/plugins/appwizard/kdevappwizard.desktop.cmake b/plugins/appwizard/kdevappwizard.desktop.cmake index a55edb7b6a..2b084658dd 100644 --- a/plugins/appwizard/kdevappwizard.desktop.cmake +++ b/plugins/appwizard/kdevappwizard.desktop.cmake @@ -1,122 +1,122 @@ [Desktop Entry] Type=Service Icon=project-development-new-template Exec=blubb Comment=Application Wizard Comment[bg]=Съветник на приложение Comment[bs]=Čarobnjak aplikacija Comment[ca]=Assistent d'aplicació Comment[ca@valencia]=Assistent d'aplicació Comment[cs]=Průvodce aplikací Comment[da]=Applikationsguide Comment[de]=Anwendungsassistent Comment[el]=Οδηγός εφαρμογής Comment[en_GB]=Application Wizard Comment[es]=Asistente de aplicaciones Comment[et]=Rakenduse nõustaja Comment[fi]=Opastettu sovelluksen luonti Comment[fr]=Assistant d'applications Comment[ga]=Treoraí Feidhmchláir Comment[gl]=Asistente para programas Comment[hu]=Alkalmazás varázsló Comment[it]=Procedura guidata applicazione Comment[ja]=アプリケーションウィザード Comment[kk]=Қолданба шебері Comment[nb]=Programveiviser Comment[nds]=Programm-Hölper Comment[nl]=Programma-assistent -Comment[pl]=Asystent programu +Comment[pl]=Pomocnik programu Comment[pt]=Assistente de Aplicações Comment[pt_BR]=Assistente de aplicativo Comment[ru]=Мастер создания приложений Comment[sk]=Sprievodca aplikáciou Comment[sl]=Čarovnik za program Comment[sv]=Programguide Comment[tr]=Uygulama Sihirbazı Comment[ug]=ئەمىلى پروگرامما يېتەكچىسى Comment[uk]=Майстер створення програм Comment[x-test]=xxApplication Wizardxx Comment[zh_CN]=应用程序向导 Comment[zh_TW]=應用程式精靈 Name=New Project Wizard Name[bg]=Съветник за нов проект Name[bs]=Čarobnjak novog projekta Name[ca]=Assistent de projecte nou Name[ca@valencia]=Assistent de projecte nou Name[cs]=Průvodce novým projektem Name[da]=Guide til nyt projekt Name[de]=Projekt-Assistent Name[el]=Οδηγός ρύθμισης νέου έργου Name[en_GB]=New Project Wizard Name[es]=Asistente de nuevo proyecto Name[et]=Uue projekti nõustaja Name[fi]=Uuden projektin opastettu luonti Name[fr]=Assistant de création de nouveau projet Name[gl]=Asistente para proxectos novos Name[hu]=Új projekt varázsló Name[it]=Procedura guidata nuovo progetto Name[ja]=新規プロジェクト作成ウィザード Name[kk]=Жаңа жоба шебері Name[nb]=Veiviser for nytt prosjekt Name[nds]=Projekt-Hölper Name[nl]=Nieuwe projectenassistent -Name[pl]=Asystent nowego projektu +Name[pl]=Pomocnik nowego projektu Name[pt]=Assistente de Novos Projectos Name[pt_BR]=Assistente de novo projeto Name[ru]=Мастер создания проекта Name[sk]=Sprievodca novým projektom Name[sl]=Čarovnik za nov projekt Name[sv]=Ny projektguide Name[tr]=Yeni Proje Sihirbazı Name[ug]=يېڭى قۇرۇلۇش يېتەكچىسى Name[uk]=Майстер створення проекту Name[x-test]=xxNew Project Wizardxx Name[zh_CN]=新工程向导 Name[zh_TW]=新增專案精靈 GenericName=Application Wizard GenericName[bg]=Съветник на приложение GenericName[bs]=Čarobnjak aplikacija GenericName[ca]=Assistent d'aplicació GenericName[ca@valencia]=Assistent d'aplicació GenericName[cs]=Průvodce aplikací GenericName[da]=Applikationsguide GenericName[de]=Anwendungsassistent GenericName[el]=Οδηγός εφαρμογής GenericName[en_GB]=Application Wizard GenericName[es]=Asistente de aplicaciones GenericName[et]=Rakenduse nõustaja GenericName[fi]=Opastettu sovelluksen luonti GenericName[fr]=Assistant d'applications GenericName[ga]=Treoraí Feidhmchláir GenericName[gl]=Asistente para programas GenericName[hu]=Alkalmazás varázsló GenericName[it]=Procedura guidata applicazione GenericName[ja]=アプリケーションウィザード GenericName[kk]=Қолданба шебері GenericName[nb]=Programveiviser GenericName[nds]=Programm-Hölper GenericName[nl]=Programma-assistent -GenericName[pl]=Asystent programu +GenericName[pl]=Pomocnik programu GenericName[pt]=Assistente de Aplicações GenericName[pt_BR]=Assistente de aplicativo GenericName[ru]=Мастер создания приложений GenericName[sk]=Sprievodca aplikáciou GenericName[sl]=Čarovnik za program GenericName[sv]=Programguide GenericName[tr]=Uygulama Sihirbazı GenericName[ug]=ئەمىلى پروگرامما يېتەكچىسى GenericName[uk]=Майстер створення програм GenericName[x-test]=xxApplication Wizardxx GenericName[zh_CN]=应用程序向导 GenericName[zh_TW]=應用程式精靈 ServiceTypes=KDevelop/Plugin X-KDE-Library=kdevappwizard X-KDE-PluginInfo-Name=kdevappwizard X-KDE-PluginInfo-Author=Alexander Dymo X-KDE-PluginInfo-Version=0.1 X-KDE-PluginInfo-License=GPL X-KDE-PluginInfo-Category=Core X-KDevelop-Interfaces=org.kdevelop.ITemplateProvider X-KDevelop-Category=Global X-KDevelop-Version=@KDEV_PLUGIN_VERSION@ X-KDevelop-Mode=GUI diff --git a/plugins/codeutils/tests/data/testgenerationtest/templates/test_qtestlib/test_qtestlib.desktop b/plugins/codeutils/tests/data/testgenerationtest/templates/test_qtestlib/test_qtestlib.desktop index d5ad5c9a41..b340649ba2 100644 --- a/plugins/codeutils/tests/data/testgenerationtest/templates/test_qtestlib/test_qtestlib.desktop +++ b/plugins/codeutils/tests/data/testgenerationtest/templates/test_qtestlib/test_qtestlib.desktop @@ -1,132 +1,132 @@ [General] Name=Testing C++ Template Name[bs]=Testiranje C++ Temp predloška Name[ca]=Plantilla C++ de prova Name[ca@valencia]=Plantilla C++ de prova Name[da]=Tester C++-skabelon Name[de]=Testvorlage für C++ Name[el]=Δοκιμή C++ Template Name[es]=Prueba de plantilla C++ Name[et]=C++ malli testimine Name[fi]=Testi-C++-malli Name[fr]=Modèle C++ pour tester Name[gl]=Modelo de C++ de probas Name[hu]=C++ sablon tesztelése Name[it]=Modello di test C++ Name[kk]=Сынақ C++ үлгісі Name[nb]=Mal for C++-testing Name[nl]=C++-sjabloon testen -Name[pl]=Szablon testowy C++ +Name[pl]=Próbny szablon C++ Name[pt]=Modelo de Testes em C++ Name[pt_BR]=Modelo de testes em C++ Name[ru]=Тестовый шаблон C++ Name[sk]=Testovanie C++ šablóny Name[sl]=Preizkusna predloga C++ Name[sv]=C++ mall för test Name[tr]=C++ şablonu testi Name[uk]=Тестовий шаблон C++ Name[x-test]=xxTesting C++ Templatexx Name[zh_CN]=C++ 测试模板 Name[zh_TW]=測試 C++ 樣本 Comment=A unit test using the QTest library without a graphical user interface Comment[bs]=Test jedinice koristeći QTest biblioteku bez grafičkog korisničkog interfejsa Comment[ca]=Una prova unitària utilitzant la biblioteca QTest sense interfície gràfica d'usuari Comment[ca@valencia]=Una prova unitària utilitzant la biblioteca QTest sense interfície gràfica d'usuari Comment[da]=En unittest som bruger biblioteket QTest uden en grafisk brugerflade Comment[de]=Ein Unit-Test, der die QTest-Bibliothek ohne grafische Oberfläche verwendet Comment[el]=Ένα unit test με χρήση βιβλιοθήκης QTest χωρίς γραφικό περιβάλλον χρήστη Comment[es]=Una prueba unitaria que usa la biblioteca QTest sin una interfaz gráfica de usuario Comment[et]=Ühiktest QTesti teegi abil ilma graafilise kasutajaliideseta Comment[fi]=Yksikkötesti, joka käyttää QTest-kirjastoa ilman graafista käyttöliittymää. Comment[fr]=Un test unitaire utilisant la bibliothèque QTest sans interface graphique utilisateur Comment[gl]=Unha proba unitaria que emprega a biblioteca QTest sen interface gráfica de usuario Comment[hu]=Egy egységteszt a QTest könyvtár használatával grafikus felhasználói felület nélkül Comment[it]=Un test d'unità che usa la libreria QTest senza un'interfaccia grafica utente Comment[kk]=Графикалық интерфейссіз QTest жиын файлын қолданатын сынақ модулі Comment[nb]=En enhetstest som bruker QTest-biblioteket uten en grafisk brukerflate Comment[nl]=Een test van een eenheid met gebruik van de QTest-bibliotheek zonder een grafisch gebruikersinterface Comment[pl]=Jednostkowy test wykorzystujący bibliotekę QTest bez graficznego interfejsu użytkownika Comment[pt]=Um teste unitário, com a biblioteca QTest, sem uma interface gráfica do utilizador Comment[pt_BR]=Teste unitário que usa a biblioteca QTest sem uma interface gráfica do usuário Comment[ru]=Модульный тест на базе библиотеки QTest без графического интерфейса Comment[sk]=Unit test pomocou knižnice QTest bez grafického rozhrania Comment[sl]=Preizkus enot z uporabo knjižnice QTest brez grafičnega vmesnika Comment[sv]=En enhetstest som använder QTest-biblioteket utan grafiskt gränssnitt Comment[tr]=Grafiksel bir kullanıcı arayüzü olmadan QTest kütüphanesi testi için kullanılan bir birim Comment[uk]=Перевірка модулів за допомогою бібліотеки QTest без графічного інтерфейсу користувача Comment[x-test]=xxA unit test using the QTest library without a graphical user interfacexx Comment[zh_CN]=一个使用 QTest 库且不含图形界面的单元测试 Comment[zh_TW]=單元測試,用 QTest 函式庫,但不含使用者介面 Category=Test/Test/C++ Files=Header,Implementation [Header] Name=Header Name[bs]=Zaglavlje Name[ca]=Capçalera Name[ca@valencia]=Capçalera Name[da]=Header Name[de]=Header Name[el]=Header Name[es]=Cabecera Name[et]=Päis Name[fi]=Otsikkotiedosto Name[fr]=En-tête Name[gl]=Cabeceira Name[hu]=Fejléc Name[it]=Intestazione Name[kk]=Айдар Name[mr]=हेडर Name[nb]=Hode Name[nl]=Kop Name[pl]=Nagłówek Name[pt]=Inclusão Name[pt_BR]=Cabeçalho Name[ru]=Заголовок Name[sk]=Hlavička Name[sl]=Glava Name[sv]=Deklaration Name[tr]=Başlık Name[ug]=بەت قېشى Name[uk]=Заголовок Name[x-test]=xxHeaderxx Name[zh_CN]=头文件 Name[zh_TW]=標頭 File=class.h OutputFile={{ name }}.h [Implementation] Name=Implementation Name[bs]=Implementacija Name[ca]=Implementació Name[ca@valencia]=Implementació Name[cs]=Implementace Name[da]=Implementering Name[de]=Implementation Name[el]=Υλοποίηση Name[es]=Implementación Name[et]=Teostus Name[fi]=Toteutus Name[fr]=Implémentation Name[gl]=Implementación Name[hu]=Megvalósítás Name[it]=Implementazione Name[kk]=Іске асыруы Name[nb]=Implementering Name[nl]=Implementatie Name[pl]=Implementacja Name[pt]=Implementação Name[pt_BR]=Implementação Name[ru]=Реализация Name[sk]=Implementácia Name[sl]=Izvedba Name[sv]=Implementering Name[tr]=Gerçekleme Name[ug]=ئەمەلگە ئاشۇرۇش Name[uk]=Реалізація Name[x-test]=xxImplementationxx Name[zh_CN]=实现 Name[zh_TW]=實作 File=class.cpp OutputFile={{ name }}.cpp diff --git a/plugins/codeutils/tests/data/testgenerationtest/templates/test_yaml2/test_yaml2.desktop b/plugins/codeutils/tests/data/testgenerationtest/templates/test_yaml2/test_yaml2.desktop index b2f9e8d165..4934ed7203 100644 --- a/plugins/codeutils/tests/data/testgenerationtest/templates/test_yaml2/test_yaml2.desktop +++ b/plugins/codeutils/tests/data/testgenerationtest/templates/test_yaml2/test_yaml2.desktop @@ -1,97 +1,97 @@ [General] Name=Testing YAML Template Name[bs]=Testiranje YAML predloška Name[ca]=Plantilla YAML de prova Name[ca@valencia]=Plantilla YAML de prova Name[da]=Tester YAML-skabelon Name[de]=Testvorlage für YAML Name[el]=Δοκιμή προτύπου YAML Name[es]=Prueba de plantilla YAML Name[et]=YAML malli testimine Name[fi]=Testi-YAML-malli Name[fr]=Test du modèle YAML Name[gl]=Modelo de YAML de probas Name[hu]=YAML sablon tesztelése Name[it]=Modello di test YAML Name[kk]=YAML үлгісін сынау Name[nb]=Mal for YAML-testing Name[nl]=YAML-sjabloon testen -Name[pl]=Szablon testowy YAML +Name[pl]=Próbny szablon YAML Name[pt]=Modelo de Testes em YAML Name[pt_BR]=Testando o modelo em YAML Name[ru]=Тестовый шаблон YAML Name[sk]=Testovanie YAML šablóny Name[sl]=Preizkusna predloga YAML Name[sv]=YAML-mall för test Name[tr]=YAML Şablonu Testi Name[uk]=Тестовий шаблон YAML Name[x-test]=xxTesting YAML Templatexx Name[zh_CN]=YAML 测试模板 Name[zh_TW]=測試 YAML 樣本 Comment=A test description in YAML Comment[bs]=Opis testa u YAML Comment[ca]=Una descripció de prova en YAML Comment[ca@valencia]=Una descripció de prova en YAML Comment[da]=En testbeskrivelse i YAML Comment[de]=Eine Test-Beschreibung in YAML Comment[el]=Μια περιγραφή δοκιμής σε YAML Comment[es]=Una descripción de prueba en YAML Comment[et]=Testi kirjeldus YAML süntaksiga Comment[fi]=Testin kuvaus YAML:llä Comment[fr]=Une description de test en YAML Comment[gl]=Unha descrición de texto en YAML Comment[hu]=Egy teszt leírás YAML-ben Comment[it]=Una descrizione di un test in YAML Comment[kk]=Сынақ YAML сипаттамасы Comment[nb]=En testbeskrivelse i YAML Comment[nl]=Een beschrijving van een in test in YAML Comment[pl]=Opis testu w YAML Comment[pt]=Uma descrição de testes em YAML Comment[pt_BR]=Descrição de testes em YAML Comment[ru]=Описание теста в YAML Comment[sk]=Popis testu v YAML Comment[sl]=Opis testa v YAML Comment[sv]=En testbeskrivning i YAML Comment[tr]=YAML dilinde bir test tanımlaması Comment[uk]=Опис перевірки у форматі YAML Comment[x-test]=xxA test description in YAMLxx Comment[zh_CN]=一个 YAML 写的测试描述 Comment[zh_TW]=用 YAML 的測試描述 Category=Test/Test/YAML Files=Implementation [Implementation] Name=Implementation Name[bs]=Implementacija Name[ca]=Implementació Name[ca@valencia]=Implementació Name[cs]=Implementace Name[da]=Implementering Name[de]=Implementation Name[el]=Υλοποίηση Name[es]=Implementación Name[et]=Teostus Name[fi]=Toteutus Name[fr]=Implémentation Name[gl]=Implementación Name[hu]=Megvalósítás Name[it]=Implementazione Name[kk]=Іске асыруы Name[nb]=Implementering Name[nl]=Implementatie Name[pl]=Implementacja Name[pt]=Implementação Name[pt_BR]=Implementação Name[ru]=Реализация Name[sk]=Implementácia Name[sl]=Izvedba Name[sv]=Implementering Name[tr]=Gerçekleme Name[ug]=ئەمەلگە ئاشۇرۇش Name[uk]=Реалізація Name[x-test]=xxImplementationxx Name[zh_CN]=实现 Name[zh_TW]=實作 File=class.yaml OutputFile={{ name }}.yaml diff --git a/plugins/contextbrowser/browsemanager.cpp b/plugins/contextbrowser/browsemanager.cpp index 5b620f794a..937e5b8c6b 100644 --- a/plugins/contextbrowser/browsemanager.cpp +++ b/plugins/contextbrowser/browsemanager.cpp @@ -1,333 +1,345 @@ /* * This file is part of KDevelop * * Copyright 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 "browsemanager.h" #include #include #include #include #include #include #include #include "contextbrowserview.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "contextbrowser.h" using namespace KDevelop; using namespace KTextEditor; EditorViewWatcher::EditorViewWatcher(QObject* parent) : QObject(parent) { connect(ICore::self()->documentController(), SIGNAL(textDocumentCreated(KDevelop::IDocument*)), this, SLOT(documentCreated(KDevelop::IDocument*))); foreach(KDevelop::IDocument* document, ICore::self()->documentController()->openDocuments()) documentCreated(document); } void EditorViewWatcher::documentCreated( KDevelop::IDocument* document ) { KTextEditor::Document* textDocument = document->textDocument(); if(textDocument) { connect(textDocument, SIGNAL(viewCreated(KTextEditor::Document*,KTextEditor::View*)), this, SLOT(viewCreated(KTextEditor::Document*,KTextEditor::View*))); foreach(KTextEditor::View* view, textDocument->views()) { Q_ASSERT(view->parentWidget()); addViewInternal(view); } } } void EditorViewWatcher::addViewInternal(KTextEditor::View* view) { m_views << view; viewAdded(view); connect(view, SIGNAL(destroyed(QObject*)), this, SLOT(viewDestroyed(QObject*))); } void EditorViewWatcher::viewAdded(KTextEditor::View*) { } void EditorViewWatcher::viewDestroyed(QObject* view) { m_views.removeAll(static_cast(view)); } void EditorViewWatcher::viewCreated(KTextEditor::Document* /*doc*/, KTextEditor::View* view) { Q_ASSERT(view->parentWidget()); addViewInternal(view); } QList EditorViewWatcher::allViews() { return m_views; } void BrowseManager::eventuallyStartDelayedBrowsing() { if(m_browsingByKey && m_browingStartedInView) emit startDelayedBrowsing(m_browingStartedInView); } BrowseManager::BrowseManager(ContextBrowserPlugin* controller) : QObject(controller), m_plugin(controller), m_browsing(false), m_browsingByKey(0), m_watcher(this) { m_delayedBrowsingTimer = new QTimer(this); m_delayedBrowsingTimer->setSingleShot(true); connect(m_delayedBrowsingTimer, SIGNAL(timeout()), this, SLOT(eventuallyStartDelayedBrowsing())); foreach(KTextEditor::View* view, m_watcher.allViews()) viewAdded(view); } KTextEditor::View* viewFromWidget(QWidget* widget) { if(!widget) return 0; KTextEditor::View* view = qobject_cast(widget); if(view) return view; else return viewFromWidget(widget->parentWidget()); } bool BrowseManager::eventFilter(QObject * watched, QEvent * event) { QWidget* widget = qobject_cast(watched); Q_ASSERT(widget); KTextEditor::View* view = viewFromWidget(widget); if(!view) return false; QKeyEvent* keyEvent = dynamic_cast(event); const int browseKey = Qt::Key_Control; const int magicModifier = Qt::Key_Alt; //Eventually start key-browsing if(keyEvent && (keyEvent->key() == browseKey || keyEvent->key() == magicModifier) && !m_browsingByKey && keyEvent->type() == QEvent::KeyPress) { m_browsingByKey = keyEvent->key(); if(keyEvent->key() == magicModifier) { if(dynamic_cast(view) && dynamic_cast(view)->isCompletionActive()) { //Do nothing, completion is active. }else{ m_delayedBrowsingTimer->start(300); m_browingStartedInView = view; } if(magicModifier == Qt::Key_Alt) { //ugly hack: //If the magic modifier is ALT, we have to prevent it from being taken by the menu-bar to switch focus to it. //This behavior depends on the style, but if the menu-bar receives any key-press in between, it doesn't do it. //So we send a meaningless key-press here: The shift-key. QEvent* pressEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_Shift, Qt::AltModifier); QEvent* releaseEvent = new QKeyEvent(QEvent::KeyRelease, Qt::Key_Shift, Qt::AltModifier); QApplication::postEvent(masterWidget(widget), pressEvent); QApplication::postEvent(masterWidget(widget), releaseEvent); } } if(!m_browsing) m_plugin->setAllowBrowsing(true); } QFocusEvent* focusEvent = dynamic_cast(event); //Eventually stop key-browsing if((keyEvent && m_browsingByKey && keyEvent->key() == m_browsingByKey && keyEvent->type() == QEvent::KeyRelease) || (focusEvent && focusEvent->lostFocus())) { if(!m_browsing) m_plugin->setAllowBrowsing(false); m_browsingByKey = 0; emit stopDelayedBrowsing(); } - + + QMouseEvent* mouseEvent = dynamic_cast(event); + + if(mouseEvent) { + if (mouseEvent->type() == QEvent::MouseButtonPress && mouseEvent->button() == Qt::XButton1) { + m_plugin->historyPrevious(); + return true; + } + if (mouseEvent->type() == QEvent::MouseButtonPress && mouseEvent->button() == Qt::XButton2) { + m_plugin->historyNext(); + return true; + } + } + if(!m_browsing && !m_browsingByKey) { resetChangedCursor(); return false; } - - QMouseEvent* mouseEvent = dynamic_cast(event); + if(mouseEvent) { KTextEditor::View* iface = dynamic_cast(view); if(!iface) { kDebug() << "Update kdelibs for the browsing-mode to work"; return false; } QPoint coordinatesInView = widget->mapTo(view, mouseEvent->pos()); KTextEditor::Cursor textCursor = iface->coordinatesToCursor(coordinatesInView); if(textCursor.isValid()) { ///@todo find out why this is needed, fix the code in kate if(textCursor.column() > 0) textCursor.setColumn(textCursor.column()-1); KUrl viewUrl = view->document()->url(); QList languages = ICore::self()->languageController()->languagesForUrl(viewUrl); QPair jumpTo; //Step 1: Look for a special language object(Macro, included header, etc.) foreach( ILanguage* language, languages) { jumpTo = language->languageSupport()->specialLanguageObjectJumpCursor(viewUrl, SimpleCursor(textCursor)); if(jumpTo.first.isValid() && jumpTo.second.isValid()) break; //Found a special object to jump to } //Step 2: Look for a declaration/use if(!jumpTo.first.isValid() || !jumpTo.second.isValid()) { Declaration* foundDeclaration = 0; KDevelop::DUChainReadLocker lock( DUChain::lock() ); foundDeclaration = DUChainUtils::declarationForDefinition( DUChainUtils::itemUnderCursor(view->document()->url(), SimpleCursor(textCursor)) ); if(foundDeclaration && foundDeclaration->url().toUrl().equals(view->document()->url()) && foundDeclaration->range().contains( foundDeclaration->transformToLocalRevision(SimpleCursor(textCursor)))) { ///A declaration was clicked directly. Jumping to it is useless, so jump to the definition or something useful bool foundBetter = false; Declaration* definition = FunctionDefinition::definition(foundDeclaration); if(definition) { foundDeclaration = definition; foundBetter = true; } ForwardDeclaration* forward = dynamic_cast(foundDeclaration); if(forward) { TopDUContext* standardContext = DUChainUtils::standardContextForUrl(view->document()->url()); if(standardContext) { Declaration* resolved = forward->resolve(standardContext); if(resolved) { foundDeclaration = resolved; //This probably won't work foundBetter = true; } } } //This will do a search without visibility-restriction, and that search will prefer non forward declarations if(!foundBetter) { Declaration* betterDecl = foundDeclaration->id().getDeclaration(0); if(betterDecl) { foundDeclaration = betterDecl; foundBetter = true; } } } if( foundDeclaration ) { jumpTo.first = foundDeclaration->url().toUrl(); jumpTo.second = foundDeclaration->rangeInCurrentRevision().start; } } if(jumpTo.first.isValid() && jumpTo.second.isValid()) { if(mouseEvent->button() == Qt::LeftButton) { if(mouseEvent->type() == QEvent::MouseButtonPress) { m_buttonPressPosition = textCursor; // view->setCursorPosition(textCursor); // return false; }else if(mouseEvent->type() == QEvent::MouseButtonRelease && textCursor == m_buttonPressPosition) { ICore::self()->documentController()->openDocument(jumpTo.first, jumpTo.second.textCursor()); // event->accept(); // return true; } }else if(mouseEvent->type() == QEvent::MouseMove) { //Make the cursor a "hand" setHandCursor(widget); return false; } } } resetChangedCursor(); } return false; } void BrowseManager::resetChangedCursor() { QMap, QCursor> cursors = m_oldCursors; m_oldCursors.clear(); for(QMap, QCursor>::iterator it = cursors.begin(); it != cursors.end(); ++it) if(it.key()) it.key()->setCursor(QCursor(Qt::IBeamCursor)); } void BrowseManager::setHandCursor(QWidget* widget) { if(m_oldCursors.contains(widget)) return; //Nothing to do m_oldCursors[widget] = widget->cursor(); widget->setCursor(QCursor(Qt::PointingHandCursor)); } void BrowseManager::applyEventFilter(QWidget* object, bool install) { if(install) object->installEventFilter(this); else object->removeEventFilter(this); foreach(QObject* child, object->children()) if(qobject_cast(child)) applyEventFilter(qobject_cast(child), install); } void BrowseManager::viewAdded(KTextEditor::View* view) { applyEventFilter(view, true); //We need to listen for cursorPositionChanged, to clear the shift-detector. The problem: Kate listens for the arrow-keys using shortcuts, //so those keys are not passed to the event-filter connect(view, SIGNAL(navigateLeft()), m_plugin, SLOT(navigateLeft())); connect(view, SIGNAL(navigateRight()), m_plugin, SLOT(navigateRight())); connect(view, SIGNAL(navigateUp()), m_plugin, SLOT(navigateUp())); connect(view, SIGNAL(navigateDown()), m_plugin, SLOT(navigateDown())); connect(view, SIGNAL(navigateAccept()), m_plugin, SLOT(navigateAccept())); connect(view, SIGNAL(navigateBack()), m_plugin, SLOT(navigateBack())); } void BrowseManager::Watcher::viewAdded(KTextEditor::View* view) { m_manager->viewAdded(view); } void BrowseManager::setBrowsing(bool enabled) { if(m_browsingByKey) return; if(enabled == m_browsing) return; m_browsing = enabled; //This collects all the views if(enabled) { kDebug() << "Enabled browsing-mode"; }else{ kDebug() << "Disabled browsing-mode"; resetChangedCursor(); } } BrowseManager::Watcher::Watcher(BrowseManager* manager) : EditorViewWatcher(manager), m_manager(manager) { foreach(KTextEditor::View* view, allViews()) m_manager->applyEventFilter(view, true); } diff --git a/plugins/contextbrowser/contextbrowser.h b/plugins/contextbrowser/contextbrowser.h index 6c135f7920..29e699d502 100644 --- a/plugins/contextbrowser/contextbrowser.h +++ b/plugins/contextbrowser/contextbrowser.h @@ -1,272 +1,273 @@ /* * 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. */ #ifndef KDEVPLATFORM_PLUGIN_CONTEXTBROWSERPLUGIN_H #define KDEVPLATFORM_PLUGIN_CONTEXTBROWSERPLUGIN_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class QHBoxLayout; class QMenu; class QToolButton; namespace Sublime { class MainWindow; } namespace KDevelop { class IDocument; class ILanguage; class ParseJob; class DUContext; class TopDUContext; class DUChainBase; class AbstractNavigationWidget; } namespace KTextEditor { class Document; class View; } class ContextBrowserViewFactory; class ContextBrowserView; class BrowseManager; QWidget* masterWidget(QWidget* w); struct ViewHighlights { ViewHighlights() : keep(false) { } // Whether the same highlighting should be kept highlighted (usually during typing) bool keep; // The declaration that is highlighted for this view KDevelop::IndexedDeclaration declaration; // Highlighted ranges. Those may also be contained by different views. QList highlights; }; class ContextBrowserPlugin : public KDevelop::IPlugin, public KDevelop::IContextBrowser { Q_OBJECT Q_INTERFACES( KDevelop::IContextBrowser ) public: ContextBrowserPlugin(QObject *parent, const QVariantList & = QVariantList() ); virtual ~ContextBrowserPlugin(); virtual void unload(); void registerToolView(ContextBrowserView* view); void unRegisterToolView(ContextBrowserView* view); virtual KDevelop::ContextMenuExtension contextMenuExtension(KDevelop::Context*); virtual KXMLGUIClient* createGUIForMainWindow( Sublime::MainWindow* window ); ///duchain must be locked ///@param force When this is true, the history-entry is added, no matter whether the context is "interesting" or not void updateHistory(KDevelop::DUContext* context, const KDevelop::SimpleCursor& cursorPosition, bool force = false); void updateDeclarationListBox(KDevelop::DUContext* context); void setAllowBrowsing(bool allow); virtual void showUses(const KDevelop::DeclarationPointer& declaration); public Q_SLOTS: void showUsesDelayed(const KDevelop::DeclarationPointer& declaration); void previousContextShortcut(); void nextContextShortcut(); void startDelayedBrowsing(KTextEditor::View* view); void stopDelayedBrowsing(); void previousUseShortcut(); void nextUseShortcut(); void declarationSelectedInUI(const KDevelop::DeclarationPointer& decl); void parseJobFinished(KDevelop::ParseJob* job); void textDocumentCreated( KDevelop::IDocument* document ); void documentActivated( KDevelop::IDocument* ); void viewDestroyed( QObject* obj ); void cursorPositionChanged( KTextEditor::View* view, const KTextEditor::Cursor& newPosition ); void viewCreated( KTextEditor::Document* , KTextEditor::View* ); void updateViews(); void hideToolTip(); void findUses(); void textInserted(KTextEditor::Document*, KTextEditor::Range); void selectionChanged(KTextEditor::View*); + void historyNext(); + void historyPrevious(); + private slots: // history browsing void documentJumpPerformed( KDevelop::IDocument* newDocument, const KTextEditor::Cursor& newCursor, KDevelop::IDocument* previousDocument, const KTextEditor::Cursor& previousCursor); - void historyNext(); - void historyPrevious(); void nextMenuAboutToShow(); void previousMenuAboutToShow(); void actionTriggered(); void navigateLeft(); void navigateRight(); void navigateUp(); void navigateDown(); void navigateAccept(); void navigateBack(); private: QWidget* toolbarWidgetForMainWindow(Sublime::MainWindow* window); virtual void createActionsForMainWindow(Sublime::MainWindow* window, QString& xmlFile, KActionCollection& actions); void switchUse(bool forward); void clearMouseHover(); void addHighlight( KTextEditor::View* view, KDevelop::Declaration* decl ); /** helper for updateBrowserView(). * Tries to find a 'specialLanguageObject' (eg macro) in @p view under cursor @c. * If found returns true and sets @p pickedLanguage to the language this object belongs to */ KDevelop::Declaration* findDeclaration(KTextEditor::View* view, const KDevelop::SimpleCursor&, bool mouseHighlight); void updateForView(KTextEditor::View* view); // history browsing bool isPreviousEntry(KDevelop::DUContext*, const KDevelop::SimpleCursor& cursor) const; QString actionTextFor(int historyIndex) const; void updateButtonState(); void openDocument(int historyIndex); void fillHistoryPopup(QMenu* menu, const QList& historyIndices); enum NavigationActionType { Accept, Back, Down, Up, Left, Right }; void doNavigate(NavigationActionType action); private: // Returns the currently active and visible context browser view that belongs // to the same context (mainwindow and area) as the given widget ContextBrowserView* browserViewForWidget(QWidget* widget); void showToolTip(KTextEditor::View* view, KTextEditor::Cursor position); QTimer* m_updateTimer; //Contains the range, the old attribute, and the attribute it was replaced with QSet m_updateViews; QMap m_highlightedRanges; //Holds a list of all active context browser tool views QList m_views; //Used to override the next declaration that will be highlighted KDevelop::IndexedDeclaration m_useDeclaration; KDevelop::IndexedDeclaration m_lastHighlightedDeclaration; KUrl m_mouseHoverDocument; KDevelop::SimpleCursor m_mouseHoverCursor; ContextBrowserViewFactory* m_viewFactory; QPointer m_currentToolTip; QPointer m_currentNavigationWidget; KDevelop::IndexedDeclaration m_currentToolTipDeclaration; QAction* m_findUses; QPointer m_lastInsertionDocument; KTextEditor::Cursor m_lastInsertionPos; // outline toolbar QPointer m_outlineLine; QPointer m_toolbarWidgetLayout; QPointer m_toolbarWidget; // history browsing struct HistoryEntry { //Duchain must be locked HistoryEntry(KDevelop::IndexedDUContext ctx = KDevelop::IndexedDUContext(), const KDevelop::SimpleCursor& cursorPosition = KDevelop::SimpleCursor()); HistoryEntry(KDevelop::DocumentCursor pos); //Duchain must be locked void setCursorPosition(const KDevelop::SimpleCursor& cursorPosition); //Duchain does not need to be locked KDevelop::DocumentCursor computePosition() const; KDevelop::IndexedDUContext context; KDevelop::DocumentCursor absoluteCursorPosition; KDevelop::SimpleCursor relativeCursorPosition; //Cursor position relative to the start line of the context QString alternativeString; }; QVector m_history; QPointer m_previousButton; QPointer m_nextButton; QPointer m_previousMenu, m_nextMenu; QPointer m_browseButton; QList m_listDeclarations; KDevelop::IndexedString m_listUrl; BrowseManager* m_browseManager; //Used to not record jumps triggered by the context-browser as history entries QPointer m_focusBackWidget; int m_nextHistoryIndex; friend class ContextBrowserHintProvider; }; class ContextBrowserHintProvider : public KTextEditor::TextHintProvider { public: explicit ContextBrowserHintProvider(ContextBrowserPlugin* plugin); virtual QString textHint(KTextEditor::View* view, const KTextEditor::Cursor& position) Q_DECL_OVERRIDE; private: ContextBrowserPlugin* m_plugin; }; #endif // KDEVPLATFORM_PLUGIN_CONTEXTBROWSERPLUGIN_H // kate: space-indent on; indent-width 2; tab-width 4; replace-tabs on; auto-insert-doxygen on diff --git a/plugins/dashboard/dashboardpluginloader.cpp b/plugins/dashboard/dashboardpluginloader.cpp index af6618c0af..617cb0ac03 100644 --- a/plugins/dashboard/dashboardpluginloader.cpp +++ b/plugins/dashboard/dashboardpluginloader.cpp @@ -1,56 +1,55 @@ /* * This file is part of KDevelop * Copyright 2010 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 "dashboardpluginloader.h" #include #include -#include #include #include #include "dashboarddataengine.h" using namespace KDevelop; using namespace Plasma; DashboardPluginLoader* DashboardPluginLoader::s_loader = new DashboardPluginLoader; DashboardPluginLoader::DashboardPluginLoader() { PluginLoader::setPluginLoader(this); } Plasma::DataEngine* DashboardPluginLoader::internalLoadDataEngine(const QString& name) { if (name == "org.kdevelop.projects") { return engine().data(); } return 0; } QWeakPointer DashboardPluginLoader::engine() { if(!m_engine) m_engine = new DashboardDataEngine; return m_engine; } DashboardPluginLoader* DashboardPluginLoader::self() { return s_loader; } diff --git a/plugins/execute/nativeappjob.cpp b/plugins/execute/nativeappjob.cpp index f85fce0597..c7c425ee2d 100644 --- a/plugins/execute/nativeappjob.cpp +++ b/plugins/execute/nativeappjob.cpp @@ -1,207 +1,213 @@ /* 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 #include #include #include #include "iexecuteplugin.h" #include +using namespace KDevelop; + NativeAppJob::NativeAppJob(QObject* parent, KDevelop::ILaunchConfiguration* cfg) : KDevelop::OutputJob( parent ), proc(0) { kDebug() << "creating native app job"; setCapabilities(Killable); IExecutePlugin* iface = KDevelop::ICore::self()->pluginController()->pluginForExtension("org.kdevelop.IExecutePlugin", "kdevexecute")->extension(); Q_ASSERT(iface); KDevelop::EnvironmentGroupList l(KSharedConfig::openConfig()); QString envgrp = iface->environmentGroup(cfg); QString err; KUrl executable = iface->executable( cfg, err ); if( !err.isEmpty() ) { setError( -1 ); setErrorText( err ); return; } if( envgrp.isEmpty() ) { kWarning() << "Launch Configuration:" << cfg->name() << i18n("No environment group specified, looks like a broken " "configuration, please check run configuration '%1'. " "Using default environment group.", cfg->name() ); envgrp = l.defaultGroup(); } QStringList arguments = iface->arguments( cfg, err ); if( !err.isEmpty() ) { setError( -2 ); setErrorText( err ); } if( error() != 0 ) { kWarning() << "Launch Configuration:" << cfg->name() << "oops, problem" << errorText(); return; } proc = new KProcess( this ); lineMaker = new KDevelop::ProcessLineMaker( proc, this ); setStandardToolView(KDevelop::IOutputView::RunView); setBehaviours(KDevelop::IOutputView::AllowUserClose | KDevelop::IOutputView::AutoScroll); - setModel( new KDevelop::OutputModel ); - + OutputModel* m = new OutputModel; + m->setFilteringStrategy(OutputModel::NativeAppErrorFilter); + setModel(m); + setDelegate( new KDevelop::OutputDelegate ); + connect( lineMaker, SIGNAL(receivedStdoutLines(QStringList)), model(), SLOT(appendLines(QStringList)) ); connect( proc, SIGNAL(error(QProcess::ProcessError)), SLOT(processError(QProcess::ProcessError)) ); connect( proc, SIGNAL(finished(int,QProcess::ExitStatus)), SLOT(processFinished(int,QProcess::ExitStatus)) ); // Now setup the process parameters proc->setEnvironment( l.createEnvironment( envgrp, proc->systemEnvironment()) ); KUrl wc = iface->workingDirectory( cfg ); if( !wc.isValid() || wc.isEmpty() ) { wc = KUrl( QFileInfo( executable.toLocalFile() ).absolutePath() ); } proc->setWorkingDirectory( wc.toLocalFile() ); proc->setProperty( "executable", executable ); kDebug() << "setting app:" << executable << arguments; proc->setOutputChannelMode(KProcess::MergedChannels); if (iface->useTerminal(cfg)) { QStringList args = KShell::splitArgs(iface->terminal(cfg)); for (QStringList::iterator it = args.begin(); it != args.end(); ++it) { if (*it == "%exe") { *it = KShell::quoteArg(executable.toLocalFile()); } else if (*it == "%workdir") { *it = KShell::quoteArg(wc.toLocalFile()); } } args.append( arguments ); proc->setProgram( args ); } else { proc->setProgram( executable.toLocalFile(), arguments ); } setObjectName(cfg->name()); } void NativeAppJob::start() { kDebug() << "launching?" << proc; if( proc ) { startOutput(); appendLine( i18n("Starting: %1", proc->program().join(" ") ) ); proc->start(); } else { kWarning() << "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 NativeAppJob::doKill() { if( proc ) { proc->kill(); appendLine( i18n( "*** Killed Application ***" ) ); } return true; } void NativeAppJob::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); } kDebug() << "Process done"; emitResult(); } void NativeAppJob::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 ***", proc->program().join(" ") ); appendLine( errmsg ); setErrorText( errmsg ); emitResult(); } kDebug() << "Process error"; } void NativeAppJob::appendLine(const QString& l) { if (KDevelop::OutputModel* m = model()) { m->appendLine(l); } } KDevelop::OutputModel* NativeAppJob::model() { return dynamic_cast( OutputJob::model() ); } #include "nativeappjob.moc" diff --git a/plugins/executescript/scriptappconfig.cpp b/plugins/executescript/scriptappconfig.cpp index bc8f510cc8..433ef15413 100644 --- a/plugins/executescript/scriptappconfig.cpp +++ b/plugins/executescript/scriptappconfig.cpp @@ -1,254 +1,255 @@ /* 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 "scriptappconfig.h" #include #include #include #include #include #include #include #include #include "scriptappjob.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "executescriptplugin.h" #include #include #include static const QString interpreterForUrl(const KUrl& url) { auto mimetype = KMimeType::findByUrl(url); static QHash knownMimetypes; if ( knownMimetypes.isEmpty() ) { knownMimetypes["text/x-python"] = "python"; - knownMimetypes["application/x-php"] = "php -e"; + knownMimetypes["application/x-php"] = "php"; knownMimetypes["application/x-ruby"] = "ruby"; knownMimetypes["application/x-shellscript"] = "bash"; knownMimetypes["application/x-perl"] = "perl -e"; } const QString& interp = knownMimetypes.value(mimetype->name()); return interp; } QIcon ScriptAppConfigPage::icon() const { return QIcon::fromTheme("system-run"); } void ScriptAppConfigPage::loadFromConfiguration(const KConfigGroup& cfg, KDevelop::IProject* project ) { bool b = blockSignals( true ); if( project ) { executablePath->setStartDir( project->folder() ); } auto doc = KDevelop::ICore::self()->documentController()->activeDocument(); interpreter->lineEdit()->setText( cfg.readEntry( ExecuteScriptPlugin::interpreterEntry, doc ? interpreterForUrl(doc->url()) : "" ) ); executablePath->setUrl( cfg.readEntry( ExecuteScriptPlugin::executableEntry, "" ) ); remoteHostCheckbox->setChecked( cfg.readEntry( ExecuteScriptPlugin::executeOnRemoteHostEntry, false ) ); remoteHost->setText( cfg.readEntry( ExecuteScriptPlugin::remoteHostEntry, "" ) ); bool runCurrent = cfg.readEntry( ExecuteScriptPlugin::runCurrentFileEntry, true ); if ( runCurrent ) { runCurrentFile->setChecked( true ); } else { runFixedFile->setChecked( true ); } arguments->setText( cfg.readEntry( ExecuteScriptPlugin::argumentsEntry, "" ) ); workingDirectory->setUrl( cfg.readEntry( ExecuteScriptPlugin::workingDirEntry, QUrl() ) ); environment->setCurrentProfile( cfg.readEntry( ExecuteScriptPlugin::environmentGroupEntry, QString() ) ); outputFilteringMode->setCurrentIndex( cfg.readEntry( ExecuteScriptPlugin::outputFilteringEntry, 2u )); //runInTerminal->setChecked( cfg.readEntry( ExecuteScriptPlugin::useTerminalEntry, false ) ); blockSignals( b ); } ScriptAppConfigPage::ScriptAppConfigPage( QWidget* parent ) : LaunchConfigurationPage( parent ) { setupUi(this); interpreter->lineEdit()->setPlaceholderText(i18n("Type or select an interpreter")); //Set workingdirectory widget to ask for directories rather than files workingDirectory->setMode(KFile::Directory | KFile::ExistingOnly | KFile::LocalOnly); //connect signals to changed signal connect( interpreter->lineEdit(), SIGNAL(textEdited(QString)), SIGNAL(changed()) ); connect( executablePath->lineEdit(), SIGNAL(textEdited(QString)), SIGNAL(changed()) ); connect( executablePath, SIGNAL(urlSelected(KUrl)), SIGNAL(changed()) ); connect( arguments, SIGNAL(textEdited(QString)), SIGNAL(changed()) ); connect( workingDirectory, SIGNAL(urlSelected(KUrl)), SIGNAL(changed()) ); connect( workingDirectory->lineEdit(), SIGNAL(textEdited(QString)), SIGNAL(changed()) ); connect( environment, SIGNAL(currentProfileChanged(QString)), SIGNAL(changed()) ); //connect( runInTerminal, SIGNAL(toggled(bool)), SIGNAL(changed()) ); } void ScriptAppConfigPage::saveToConfiguration( KConfigGroup cfg, KDevelop::IProject* project ) const { Q_UNUSED( project ); cfg.writeEntry( ExecuteScriptPlugin::interpreterEntry, interpreter->lineEdit()->text() ); cfg.writeEntry( ExecuteScriptPlugin::executableEntry, executablePath->url() ); cfg.writeEntry( ExecuteScriptPlugin::executeOnRemoteHostEntry, remoteHostCheckbox->isChecked() ); cfg.writeEntry( ExecuteScriptPlugin::remoteHostEntry, remoteHost->text() ); cfg.writeEntry( ExecuteScriptPlugin::runCurrentFileEntry, runCurrentFile->isChecked() ); cfg.writeEntry( ExecuteScriptPlugin::argumentsEntry, arguments->text() ); cfg.writeEntry( ExecuteScriptPlugin::workingDirEntry, workingDirectory->url() ); cfg.writeEntry( ExecuteScriptPlugin::environmentGroupEntry, environment->currentProfile() ); cfg.writeEntry( ExecuteScriptPlugin::outputFilteringEntry, outputFilteringMode->currentIndex() ); //cfg.writeEntry( ExecuteScriptPlugin::useTerminalEntry, runInTerminal->isChecked() ); } QString ScriptAppConfigPage::title() const { return i18n("Configure Script Application"); } QList< KDevelop::LaunchConfigurationPageFactory* > ScriptAppLauncher::configPages() const { return QList(); } QString ScriptAppLauncher::description() const { return i18n("Executes Script Applications"); } QString ScriptAppLauncher::id() { return "scriptAppLauncher"; } QString ScriptAppLauncher::name() const { return i18n("Script Application"); } ScriptAppLauncher::ScriptAppLauncher(ExecuteScriptPlugin* plugin) : m_plugin( plugin ) { } KJob* ScriptAppLauncher::start(const QString& launchMode, KDevelop::ILaunchConfiguration* cfg) { Q_ASSERT(cfg); if( !cfg ) { return 0; } if( launchMode == "execute" ) { return new ScriptAppJob( m_plugin, cfg); } kWarning() << "Unknown launch mode " << launchMode << "for config:" << cfg->name(); return 0; } QStringList ScriptAppLauncher::supportedModes() const { return QStringList() << "execute"; } KDevelop::LaunchConfigurationPage* ScriptAppPageFactory::createWidget(QWidget* parent) { return new ScriptAppConfigPage( parent ); } ScriptAppPageFactory::ScriptAppPageFactory() { } ScriptAppConfigType::ScriptAppConfigType() { factoryList.append( new ScriptAppPageFactory() ); } ScriptAppConfigType::~ScriptAppConfigType() { qDeleteAll(factoryList); factoryList.clear(); } QString ScriptAppConfigType::name() const { return i18n("Script Application"); } QList ScriptAppConfigType::configPages() const { return factoryList; } QString ScriptAppConfigType::id() const { return ExecuteScriptPlugin::_scriptAppConfigTypeId; } QIcon ScriptAppConfigType::icon() const { return QIcon::fromTheme("preferences-plugin-script"); } bool ScriptAppConfigType::canLaunch(const KUrl& file) const { return ! interpreterForUrl(file).isEmpty(); } bool ScriptAppConfigType::canLaunch(KDevelop::ProjectBaseItem* item) const { return ! interpreterForUrl(item->path().toUrl()).isEmpty(); } void ScriptAppConfigType::configureLaunchFromItem(KConfigGroup config, KDevelop::ProjectBaseItem* item) const { config.writeEntry(ExecuteScriptPlugin::executableEntry, QUrl(item->path().toUrl())); config.writeEntry(ExecuteScriptPlugin::interpreterEntry, interpreterForUrl(item->path().toUrl())); config.writeEntry(ExecuteScriptPlugin::outputFilteringEntry, 2u); config.writeEntry(ExecuteScriptPlugin::runCurrentFileEntry, false); config.sync(); } void ScriptAppConfigType::configureLaunchFromCmdLineArguments(KConfigGroup cfg, const QStringList &args) const { QStringList a(args); cfg.writeEntry( ExecuteScriptPlugin::interpreterEntry, a.takeFirst() ); cfg.writeEntry( ExecuteScriptPlugin::executableEntry, a.takeFirst() ); cfg.writeEntry( ExecuteScriptPlugin::argumentsEntry, KShell::joinArgs(a) ); + cfg.writeEntry( ExecuteScriptPlugin::runCurrentFileEntry, false ); cfg.sync(); } #include "scriptappconfig.moc" diff --git a/plugins/externalscript/kdevexternalscript.desktop.cmake b/plugins/externalscript/kdevexternalscript.desktop.cmake index 33ff944467..f5c4cba656 100644 --- a/plugins/externalscript/kdevexternalscript.desktop.cmake +++ b/plugins/externalscript/kdevexternalscript.desktop.cmake @@ -1,113 +1,113 @@ [Desktop Entry] Type=Service Icon=system-run Exec= Comment=Run external scripts or applications to manipulate the editor contents or do other arbitrary actions. -Comment[bs]=Pokreni spoljne skripte ili aplikacija za manipulaciju sadržaja uređivačak ili druge proizvoljne radnje. +Comment[bs]=Pokreni spoljne skripte ili aplikacija za manipulaciju sadržaja uređivača ili druge proizvoljne radnje. Comment[ca]=Executa scripts externs o aplicacions per a manipular el contingut de l'editor o altres accions arbitràries. Comment[ca@valencia]=Executa scripts externs o aplicacions per a manipular el contingut de l'editor o altres accions arbitràries. Comment[da]=Kør eksterne scripts eller programmer til manipulering af editor-indholdet eller foretag andre handlinger. Comment[de]=Führen Sie externe Skripte oder Programme zum Verändern des Editorinhalts oder für beliebige andere Aktionen aus. Comment[el]=Εκτέλεση εξωτερικών σεναρίων ή εφαρμογών για χειρισμό του περιεχομένου ""του κειμενογράφου ή για οποιεσδήποτε άλλες ενέργειες. Comment[en_GB]=Run external scripts or applications to manipulate the editor contents or do other arbitrary actions. Comment[es]=Ejecutar aplicaciones o scripts externos para manipular el contenido del editor o realizar otras acciones. Comment[et]=Välised skriptid või rakendused, mis võimaldavad muuta redaktori sisu või ette võtta mingeid muid toiminguid. Comment[fi]=Suorittaa ulkoisia skriptejä tai sovelluksia editorisisällön manipuloimiseksi tai muiden satunnaisten tehtävien tekemiseksi. Comment[fr]=Lancer des scripts ou des applications externes pour manipuler le contenu de l'éditeur ou effectuer d'autres actions arbitraires. Comment[gl]=Executa scripts externos ou programas para manipular os contidos do editor ou levar a cabo outras accións. Comment[it]=Eseguire script o applicazioni esterne per manipolare il contenuto dell'editor o per fare altre azioni. Comment[kk]=Редактор мазмұнын өңдеу не басқа да істерге арналған сыртқы скрипт не бағдарламаны орындау. Comment[nb]=Kjør eksterne skripter eller programmer for å behandle redigeringsinnholdet eller utføre andre vilkårlige handlinger. Comment[nds]=Utföhren vun extern Skripten oder Programmen för't Ännern vun den Editor-Inholt oder anner Akschonen. Comment[nl]=Externe scripts of programma's uitvoeren om de inhoud van de bewerker te manipuleren of andere acties uit te voeren. Comment[pl]=Uruchamiaj zewnętrzne skrypty lub programy, aby manipulować zawartością edytora lub innymi dowolnymi działaniami. Comment[pt]=Executa programas ou aplicações externa para manipular o conteúdo do editor ou efectua outras acções arbitrárias. Comment[pt_BR]=Execute scripts externos ou aplicativos para manipular os conteúdos do editor ou para fazer outras ações ordinárias. Comment[ru]=Запускает внешние сценарии или приложения, взаимодействующие с редактором кода и выполняющие другие действия. Comment[sk]=Spustí externé skripty alebo aplikácie na manipuláciu s obsahom editora alebo robí iné ľubovoľné akcie. Comment[sl]=Zaganjajte zunanje skripte ali programe, ki upravljajo z vsebino urejevalnika ali pa opravljajo druga poljubna dejanja. Comment[sv]=Kör externa skript eller program för att behandla editorns innehåll eller utför andra godtyckliga åtgärder. Comment[tr]=Düzenleyici içeriğini değiştirmek ve diğer istenilen eylemler için dış betikleri veya uygulamaları çalıştır. Comment[uk]=Запускає зовнішні скрипти або програми для обробки текстових даних редактора або виконання інших потрібних дій. Comment[x-test]=xxRun external scripts or applications to manipulate the editor contents or do other arbitrary actions.xx Comment[zh_CN]=运行外部脚本或应用程序来处理编辑器内容或者执行其它任意动作。 Comment[zh_TW]=執行外部文稿或應用程式來運用編輯器內的內容,或是做各種動作。 Name=External Scripts Name[bg]=Външни скриптове Name[bs]=Spoljnje skripte Name[ca]=Scripts externs Name[ca@valencia]=Scripts externs Name[da]=Eksterne scripts Name[de]=Externe Skripte Name[el]=Εξωτερικά σενάρια Name[en_GB]=External Scripts Name[es]=Scripts externos Name[et]=Välised skriptid Name[fi]=Ulkoiset skriptit Name[fr]=Scripts externes Name[gl]=Scripts externos Name[hu]=Külső parancsfájlok Name[it]=Script esterni Name[ja]=外部スクリプト Name[kk]=Сыртқы скрипттер Name[nb]=Eksterne skripter Name[nds]=Extern Skripten Name[nl]=Externe scripts Name[pl]=Zewnętrzne skrypty Name[pt]=Programas Externos Name[pt_BR]=Scripts externos Name[ru]=Внешние сценарии Name[sk]=Externé skripty Name[sl]=Zunanji skripti Name[sv]=Externa skript Name[tr]=Dış Betikler Name[ug]=سىرتقى قوليازما پروگرامما Name[uk]=Зовнішні скрипти Name[x-test]=xxExternal Scriptsxx Name[zh_CN]=外部脚本 Name[zh_TW]=外部文稿 GenericName=External Scripts GenericName[bg]=Външни скриптове GenericName[bs]=Spoljnje skripte GenericName[ca]=Scripts externs GenericName[ca@valencia]=Scripts externs GenericName[da]=Eksterne scripts GenericName[de]=Externe Skripte GenericName[el]=Εξωτερικά σενάρια GenericName[en_GB]=External Scripts GenericName[es]=Scripts externos GenericName[et]=Välised skriptid GenericName[fi]=Ulkoiset skriptit GenericName[fr]=Scripts externes GenericName[gl]=Scripts externos GenericName[hu]=Külső parancsfájlok GenericName[it]=Script esterni GenericName[ja]=外部スクリプト GenericName[kk]=Сыртқы скрипттер GenericName[nb]=Eksterne skripter GenericName[nds]=Extern Skripten GenericName[nl]=Externe scripts GenericName[pl]=Zewnętrzne skrypty GenericName[pt]=Programas Externos GenericName[pt_BR]=Scripts externos GenericName[ru]=Внешние сценарии GenericName[sk]=Externé skripty GenericName[sl]=Zunanji skripti GenericName[sv]=Externa skript GenericName[tr]=Dış Betikler GenericName[ug]=سىرتقى قوليازما پروگرامما GenericName[uk]=Зовнішні скрипти GenericName[x-test]=xxExternal Scriptsxx GenericName[zh_CN]=外部脚本 GenericName[zh_TW]=外部文稿 ServiceTypes=KDevelop/Plugin X-KDE-Library=kdevexternalscript X-KDevelop-Version=@KDEV_PLUGIN_VERSION@ X-KDevelop-Category=Global X-KDE-PluginInfo-Name=kdevexternalscript X-KDE-PluginInfo-Author=Milian Wolff X-KDE-PluginInfo-License=GPL X-KDE-PluginInfo-Category=Utilities X-KDevelop-Interfaces=org.kdevelop.IPlugin X-KDevelop-Mode=GUI X-KDevelop-IRequired=org.kdevelop.IOutputView diff --git a/plugins/filetemplates/templateclassassistant.cpp b/plugins/filetemplates/templateclassassistant.cpp index 29ab7318cb..0119a44eb8 100644 --- a/plugins/filetemplates/templateclassassistant.cpp +++ b/plugins/filetemplates/templateclassassistant.cpp @@ -1,582 +1,579 @@ /* 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 "templateclassassistant.h" #include "templateselectionpage.h" #include "templateoptionspage.h" #include "classmemberspage.h" #include "classidentifierpage.h" #include "overridespage.h" #include "licensepage.h" #include "outputpage.h" #include "testcasespage.h" #include "defaultcreateclasshelper.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define REMOVE_PAGE(name) \ if (d->name##Page) \ { \ removePage(d->name##Page); \ d->name##Page = 0; \ d->name##PageWidget = 0; \ } #define ZERO_PAGE(name) \ d->name##Page = 0; \ d->name##PageWidget = 0; using namespace KDevelop; class KDevelop::TemplateClassAssistantPrivate { public: TemplateClassAssistantPrivate(const KUrl& baseUrl); ~TemplateClassAssistantPrivate(); void addFilesToTarget (const QHash& fileUrls); KPageWidgetItem* templateSelectionPage; KPageWidgetItem* classIdentifierPage; KPageWidgetItem* overridesPage; KPageWidgetItem* membersPage; KPageWidgetItem* testCasesPage; KPageWidgetItem* licensePage; KPageWidgetItem* templateOptionsPage; KPageWidgetItem* outputPage; KPageWidgetItem* dummyPage; TemplateSelectionPage* templateSelectionPageWidget; ClassIdentifierPage* classIdentifierPageWidget; OverridesPage* overridesPageWidget; ClassMembersPage* membersPageWidget; TestCasesPage* testCasesPageWidget; LicensePage* licensePageWidget; TemplateOptionsPage* templateOptionsPageWidget; OutputPage* outputPageWidget; KUrl baseUrl; SourceFileTemplate fileTemplate; ICreateClassHelper* helper; TemplateClassGenerator* generator; TemplateRenderer* renderer; QString type; QVariantHash templateOptions; }; TemplateClassAssistantPrivate::TemplateClassAssistantPrivate(const KUrl& baseUrl) : baseUrl(baseUrl) , helper(0) , generator(0) , renderer(0) { } TemplateClassAssistantPrivate::~TemplateClassAssistantPrivate() { delete helper; if (generator) { delete generator; } else { // if we got a generator, it should keep ownership of the renderer // otherwise, we created a templaterenderer on our own delete renderer; } } void TemplateClassAssistantPrivate::addFilesToTarget (const QHash< QString, KUrl >& fileUrls) { // Add the generated files to a target, if one is found KUrl url = baseUrl; if (!url.isValid()) { // This was probably not launched from the project manager view // Still, we try to find the common URL where the generated files are located if (!fileUrls.isEmpty()) { url = fileUrls.constBegin().value().upUrl(); } } kDebug() << "Searching for targets with URL" << url.prettyUrl(); IProject* project = ICore::self()->projectController()->findProjectForUrl(url); if (!project || !project->buildSystemManager()) { kDebug() << "No suitable project found"; return; } QList items = project->itemsForUrl(url); if (items.isEmpty()) { kDebug() << "No suitable project items found"; return; } QList targets; ProjectTargetItem* target = 0; foreach (ProjectBaseItem* item, items) { if (ProjectTargetItem* target = item->target()) { targets << target; } } if (targets.isEmpty()) { // If no target was explicitly found yet, try all the targets in the current folder foreach (ProjectBaseItem* item, items) { targets << item->targetList(); } } if (targets.isEmpty()) { // If still no targets, we traverse the tree up to the first directory with targets ProjectBaseItem* item = items.first()->parent(); while (targets.isEmpty() && item) { targets = item->targetList(); item = item->parent(); } } if (targets.size() == 1) { kDebug() << "Only one candidate target," << targets.first()->text() << ", using it"; target = targets.first(); } else if (targets.size() > 1) { // More than one candidate target, show the chooser dialog QPointer d = new KDialog; QWidget* w = new QWidget(d); w->setLayout(new QVBoxLayout); w->layout()->addWidget(new QLabel(i18n("Choose one target to add the file or cancel if you do not want to do so."))); QListWidget* targetsWidget = new QListWidget(w); targetsWidget->setSelectionMode(QAbstractItemView::SingleSelection); foreach(ProjectTargetItem* target, targets) { targetsWidget->addItem(target->text()); } w->layout()->addWidget(targetsWidget); targetsWidget->setCurrentRow(0); d->setButtons( KDialog::Ok | KDialog::Cancel); d->enableButtonOk(true); d->setMainWidget(w); if(d->exec() == QDialog::Accepted) { if (!targetsWidget->selectedItems().isEmpty()) { target = targets[targetsWidget->currentRow()]; } else { kDebug() << "Did not select anything, not adding to a target"; return; } } else { kDebug() << "Canceled select target dialog, not adding to a target"; return; } } else { // No target, not doing anything kDebug() << "No possible targets for URL" << url; return; } Q_ASSERT(target); QList fileItems; foreach (const KUrl& fileUrl, fileUrls) { foreach (ProjectBaseItem* item, project->itemsForUrl(fileUrl.upUrl())) { if (ProjectFolderItem* folder = item->folder()) { ///FIXME: use Path instead of KUrl in the template class assistant if (ProjectFileItem* file = project->projectFileManager()->addFile(Path(fileUrl), folder)) { fileItems << file; break; } } } } if (!fileItems.isEmpty()) { project->buildSystemManager()->addFilesToTarget(fileItems, target); } } TemplateClassAssistant::TemplateClassAssistant(QWidget* parent, const KUrl& baseUrl) : KAssistantDialog(parent) , d(new TemplateClassAssistantPrivate(baseUrl)) { ZERO_PAGE(templateSelection) ZERO_PAGE(templateOptions) ZERO_PAGE(members) ZERO_PAGE(classIdentifier) ZERO_PAGE(overrides) ZERO_PAGE(license) ZERO_PAGE(output) ZERO_PAGE(testCases) setup(); } TemplateClassAssistant::~TemplateClassAssistant() { delete d; } void TemplateClassAssistant::setup() { if (d->baseUrl.isValid()) { setWindowTitle(i18n("Create Files from Template in %1", d->baseUrl.prettyUrl())); } else { setWindowTitle(i18n("Create Files from Template")); } d->templateSelectionPageWidget = new TemplateSelectionPage(this); connect(this, SIGNAL(accepted()), d->templateSelectionPageWidget, SLOT(saveConfig())); d->templateSelectionPage = addPage(d->templateSelectionPageWidget, i18n("Language and Template")); d->templateSelectionPage->setIcon(QIcon::fromTheme("project-development-new-template")); d->dummyPage = addPage(new QWidget(this), QLatin1String("Dummy Page")); // showButton(KDialog::Help, false); } void TemplateClassAssistant::templateChosen(const QString& templateDescription) { d->fileTemplate.setTemplateDescription(templateDescription); d->type = d->fileTemplate.type(); d->generator = 0; if (!d->fileTemplate.isValid()) { return; } kDebug() << "Selected template" << templateDescription << "of type" << d->type; removePage(d->dummyPage); if (d->baseUrl.isValid()) { setWindowTitle(i18n("Create Files from Template %1 in %2", d->fileTemplate.name(), d->baseUrl.prettyUrl())); } else { setWindowTitle(i18n("Create Files from Template %1", d->fileTemplate.name())); } if (d->type == "Class") { d->classIdentifierPageWidget = new ClassIdentifierPage(this); d->classIdentifierPage = addPage(d->classIdentifierPageWidget, i18n("Class Basics")); d->classIdentifierPage->setIcon(QIcon::fromTheme("classnew")); connect(d->classIdentifierPageWidget, SIGNAL(isValid(bool)), SLOT(setCurrentPageValid(bool))); setValid(d->classIdentifierPage, false); d->overridesPageWidget = new OverridesPage(this); d->overridesPage = addPage(d->overridesPageWidget, i18n("Override Methods")); d->overridesPage->setIcon(QIcon::fromTheme("code-class")); setValid(d->overridesPage, true); d->membersPageWidget = new ClassMembersPage(this); d->membersPage = addPage(d->membersPageWidget, i18n("Class Members")); d->membersPage->setIcon(QIcon::fromTheme("field")); setValid(d->membersPage, true); d->helper = 0; QString languageName = d->fileTemplate.languageName(); ILanguage* language = ICore::self()->languageController()->language(languageName); if (language && language->languageSupport()) { d->helper = language->languageSupport()->createClassHelper(); } if (!d->helper) { kDebug() << "No class creation helper for language" << languageName; d->helper = new DefaultCreateClassHelper; } d->generator = d->helper->createGenerator(d->baseUrl); Q_ASSERT(d->generator); d->generator->setTemplateDescription(d->fileTemplate); d->renderer = d->generator->renderer(); } else { if (d->type == "Test") { d->testCasesPageWidget = new TestCasesPage(this); d->testCasesPage = addPage(d->testCasesPageWidget, i18n("Test Cases")); connect(d->testCasesPageWidget, SIGNAL(isValid(bool)), SLOT(setCurrentPageValid(bool))); setValid(d->testCasesPage, false); } d->renderer = new TemplateRenderer; d->renderer->setEmptyLinesPolicy(TemplateRenderer::TrimEmptyLines); } d->licensePageWidget = new LicensePage(this); d->licensePage = addPage(d->licensePageWidget, i18n("License")); d->licensePage->setIcon(QIcon::fromTheme("text-x-copying")); setValid(d->licensePage, true); d->outputPageWidget = new OutputPage(this); d->outputPageWidget->prepareForm(d->fileTemplate); d->outputPage = addPage(d->outputPageWidget, i18n("Output")); d->outputPage->setIcon(QIcon::fromTheme("document-save")); connect(d->outputPageWidget, SIGNAL(isValid(bool)), SLOT(setCurrentPageValid(bool))); setValid(d->outputPage, false); if (d->fileTemplate.hasCustomOptions()) { kDebug() << "Class generator has custom options"; d->templateOptionsPageWidget = new TemplateOptionsPage(this); d->templateOptionsPage = insertPage(d->outputPage, d->templateOptionsPageWidget, i18n("Template Options")); } setCurrentPage(d->templateSelectionPage); } void TemplateClassAssistant::next() { kDebug() << currentPage()->name() << currentPage()->header(); if (currentPage() == d->templateSelectionPage) { // We have chosen the template // Depending on the template's language, we can now create a helper QString description = d->templateSelectionPageWidget->selectedTemplate(); templateChosen(description); if (!d->fileTemplate.isValid()) { return; } } else if (currentPage() == d->classIdentifierPage) { d->generator->setIdentifier(d->classIdentifierPageWidget->identifier()); - foreach (const QString& base, d->classIdentifierPageWidget->inheritanceList()) - { - d->generator->addBaseClass(base); - } + d->generator->setBaseClasses(d->classIdentifierPageWidget->inheritanceList()); } else if (currentPage() == d->overridesPage) { ClassDescription desc = d->generator->description(); desc.methods.clear(); foreach (const DeclarationPointer& declaration, d->overridesPageWidget->selectedOverrides()) { desc.methods << FunctionDescription(declaration); } d->generator->setDescription(desc); } else if (currentPage() == d->membersPage) { ClassDescription desc = d->generator->description(); desc.members = d->membersPageWidget->members(); d->generator->setDescription(desc); } else if (currentPage() == d->licensePage) { if (d->generator) { d->generator->setLicense(d->licensePageWidget->license()); } else { d->renderer->addVariable("license", d->licensePageWidget->license()); } } else if (d->templateOptionsPage && (currentPage() == d->templateOptionsPage)) { if (d->generator) { d->generator->addVariables(d->templateOptionsPageWidget->templateOptions()); } else { d->renderer->addVariables(d->templateOptionsPageWidget->templateOptions()); } } else if (currentPage() == d->testCasesPage) { d->renderer->addVariable("name", d->testCasesPageWidget->name()); d->renderer->addVariable("testCases", d->testCasesPageWidget->testCases()); } KAssistantDialog::next(); if (currentPage() == d->classIdentifierPage) { d->classIdentifierPageWidget->setInheritanceList(d->fileTemplate.defaultBaseClasses()); } else if (currentPage() == d->membersPage) { d->membersPageWidget->setMembers(d->generator->description().members); } else if (currentPage() == d->overridesPage) { d->overridesPageWidget->clear(); d->overridesPageWidget->addCustomDeclarations(i18n("Default"), d->helper->defaultMethods(d->generator->name())); d->overridesPageWidget->addBaseClasses(d->generator->directBaseClasses(), d->generator->allBaseClasses()); } else if (d->templateOptionsPage && (currentPage() == d->templateOptionsPage)) { d->templateOptionsPageWidget->load(d->fileTemplate, d->renderer); } else if (currentPage() == d->outputPage) { d->outputPageWidget->loadFileTemplate(d->fileTemplate, d->baseUrl, d->renderer); } } void TemplateClassAssistant::back() { KAssistantDialog::back(); if (currentPage() == d->templateSelectionPage) { REMOVE_PAGE(classIdentifier) REMOVE_PAGE(overrides) REMOVE_PAGE(members) REMOVE_PAGE(testCases) REMOVE_PAGE(output) REMOVE_PAGE(templateOptions) REMOVE_PAGE(license) delete d->helper; d->helper = 0; if (d->generator) { delete d->generator; } else { delete d->renderer; } d->generator = 0; d->renderer = 0; if (d->baseUrl.isValid()) { setWindowTitle(i18n("Create Files from Template in %1", d->baseUrl.prettyUrl())); } else { setWindowTitle(i18n("Create Files from Template")); } d->dummyPage = addPage(new QWidget(this), QLatin1String("Dummy Page")); } } void TemplateClassAssistant::accept() { // next() is not called for the last page (when the user clicks Finish), so we have to set output locations here QHash fileUrls = d->outputPageWidget->fileUrls(); QHash filePositions = d->outputPageWidget->filePositions(); DocumentChangeSet changes; if (d->generator) { QHash::const_iterator it = fileUrls.constBegin(); for (; it != fileUrls.constEnd(); ++it) { d->generator->setFileUrl(it.key(), it.value()); d->generator->setFilePosition(it.key(), filePositions.value(it.key())); } d->generator->addVariables(d->templateOptions); changes = d->generator->generate(); } else { changes = d->renderer->renderFileTemplate(d->fileTemplate, d->baseUrl, fileUrls); } d->addFilesToTarget(fileUrls); changes.applyAllChanges(); // Open the generated files in the editor foreach (const KUrl& url, fileUrls) { ICore::self()->documentController()->openDocument(url); } KAssistantDialog::accept(); } void TemplateClassAssistant::setCurrentPageValid(bool valid) { setValid(currentPage(), valid); } KUrl TemplateClassAssistant::baseUrl() const { return d->baseUrl; } diff --git a/plugins/git/stashmanagerdialog.cpp b/plugins/git/stashmanagerdialog.cpp index fa4bd4c9c6..542e609dc5 100644 --- a/plugins/git/stashmanagerdialog.cpp +++ b/plugins/git/stashmanagerdialog.cpp @@ -1,148 +1,148 @@ /* * This file is part of KDevelop * Copyright 2010 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 "stashmanagerdialog.h" #include "ui_stashmanagerdialog.h" #include "gitplugin.h" #include "stashpatchsource.h" #include #include #include #include #include #include #include #include StashManagerDialog::StashManagerDialog(const QDir& stashed, GitPlugin* plugin, QWidget* parent) : KDialog(parent), m_plugin(plugin), m_dir(stashed) { setWindowTitle(KDialog::makeStandardCaption(i18n("Stash Manager"))); setButtons(KDialog::Close); QWidget* w = new QWidget(this); m_ui = new Ui::StashManager; m_ui->setupUi(w); StashModel* m = new StashModel(stashed, plugin, this); m_ui->stashView->setModel(m); connect(m_ui->show, SIGNAL(clicked(bool)), SLOT(showStash())); connect(m_ui->apply, SIGNAL(clicked(bool)), SLOT(applyClicked())); connect(m_ui->branch, SIGNAL(clicked(bool)), SLOT(branchClicked())); connect(m_ui->pop, SIGNAL(clicked(bool)), SLOT(popClicked())); connect(m_ui->drop, SIGNAL(clicked(bool)), SLOT(dropClicked())); connect(m, SIGNAL(rowsInserted(QModelIndex,int,int)), SLOT(stashesFound())); setMainWidget(w); w->setEnabled(false); //we won't enable it until we have the model with data and selection } StashManagerDialog::~StashManagerDialog() { delete m_ui; } void StashManagerDialog::stashesFound() { QModelIndex firstIdx=m_ui->stashView->model()->index(0, 0); m_ui->stashView->setCurrentIndex(firstIdx); mainWidget()->setEnabled(true); } QString StashManagerDialog::selection() const { QModelIndex idx = m_ui->stashView->currentIndex(); Q_ASSERT(idx.isValid()); return idx.data().toString(); } void StashManagerDialog::runStash(const QStringList& arguments) { KDevelop::VcsJob* job = m_plugin->gitStash(m_dir, arguments, KDevelop::OutputJob::Verbose); connect(job, SIGNAL(result(KJob*)), SLOT(accept())); mainWidget()->setEnabled(false); KDevelop::ICore::self()->runController()->registerJob(job); } void StashManagerDialog::showStash() { KDevelop::IPatchReview * review = KDevelop::ICore::self()->pluginController()->extensionForPlugin(); KDevelop::IPatchSource::Ptr stashPatch(new StashPatchSource(selection(), m_plugin, m_dir)); review->startReview(stashPatch); accept(); } void StashManagerDialog::applyClicked() { runStash(QStringList("apply") << selection()); } void StashManagerDialog::popClicked() { runStash(QStringList("pop") << selection()); } void StashManagerDialog::dropClicked() { QString sel = selection(); int ret = KMessageBox::questionYesNo(this, i18n("Are you sure you want to drop the stash '%1'?", sel)); if(ret == KMessageBox::Yes) runStash(QStringList("drop") << sel); } void StashManagerDialog::branchClicked() { - QString branchName = QInputDialog::getText(this, i18n("KDevelop - Git Stash"), i18n("Select a name for the new branch")); + QString branchName = QInputDialog::getText(this, i18n("KDevelop - Git Stash"), i18n("Select a name for the new branch:")); if(!branchName.isEmpty()) runStash(QStringList("branch") << branchName << selection()); } //////////////////StashModel StashModel::StashModel(const QDir& dir, GitPlugin* git, QObject* parent) : QStandardItemModel(parent) { KDevelop::VcsJob* job=git->gitStash(dir, QStringList("list"), KDevelop::OutputJob::Silent); connect(job, SIGNAL(finished(KJob*)), SLOT(stashListReady(KJob*))); KDevelop::ICore::self()->runController()->registerJob(job); } void StashModel::stashListReady(KJob* _job) { KDevelop::DVcsJob* job = qobject_cast(_job); QList< QByteArray > output = job->rawOutput().split('\n'); foreach(const QByteArray& line, output) { QList< QByteArray > fields = line.split(':'); QList elements; foreach(const QByteArray& field, fields) elements += new QStandardItem(QString(field.trimmed())); appendRow(elements); } } diff --git a/plugins/grepview/grepdialog.cpp b/plugins/grepview/grepdialog.cpp index 54829061a3..a534556dc8 100644 --- a/plugins/grepview/grepdialog.cpp +++ b/plugins/grepview/grepdialog.cpp @@ -1,503 +1,504 @@ /*************************************************************************** * Copyright 1999-2001 Bernd Gehrmann and the KDevelop Team * * bernd@kdevelop.org * * Copyright 2007 Dukju Ahn * * 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 "grepdialog.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 "grepviewplugin.h" #include "grepjob.h" #include "grepoutputview.h" #include "grepfindthread.h" #include "greputil.h" using namespace KDevelop; namespace { QString allOpenFilesString = i18n("All Open Files"); QString allOpenProjectsString = i18n("All Open Projects"); const QStringList template_desc = QStringList() << "verbatim" << "word" << "assignment" << "->MEMBER(" << "class::MEMBER(" << "OBJECT->member("; const QStringList template_str = QStringList() << "%s" << "\\b%s\\b" << "\\b%s\\b\\s*=[^=]" << "\\->\\s*\\b%s\\b\\s*\\(" << "([a-z0-9_$]+)\\s*::\\s*\\b%s\\b\\s*\\(" << "\\b%s\\b\\s*\\->\\s*([a-z0-9_$]+)\\s*\\("; const QStringList repl_template = QStringList() << "%s" << "%s" << "%s = " << "->%s(" << "\\1::%s(" << "%s->\\1("; const QStringList filepatterns = QStringList() << "*.h,*.hxx,*.hpp,*.hh,*.h++,*.H,*.tlh,*.cpp,*.cc,*.C,*.c++,*.cxx,*.ocl,*.inl,*.idl,*.c,*.m,*.mm,*.M,*.y,*.ypp,*.yxx,*.y++,*.l" << "*.cpp,*.cc,*.C,*.c++,*.cxx,*.ocl,*.inl,*.c,*.m,*.mm,*.M" << "*.h,*.hxx,*.hpp,*.hh,*.h++,*.H,*.tlh,*.idl" << "*.adb" << "*.cs" << "*.f" << "*.html,*.htm" << "*.hs" << "*.java" << "*.js" << "*.php,*.php3,*.php4" << "*.pl" << "*.pp,*.pas" << "*.py" << "*.js,*.css,*.yml,*.rb,*.rhtml,*.html.erb,*.rjs,*.js.rjs,*.rxml,*.xml.builder" << "CMakeLists.txt,*.cmake" << "*"; const QStringList excludepatterns = QStringList() << "/CVS/,/SCCS/,/.svn/,/_darcs/,/build/,/.git/" << ""; ///Separator used to separate search paths. const QString pathsSeparator(";"); ///Max number of items in paths combo box. const int pathsMaxCount = 25; } const KDialog::ButtonCode GrepDialog::SearchButton = KDialog::User1; GrepDialog::GrepDialog( GrepViewPlugin * plugin, QWidget *parent ) : KDialog(parent), Ui::GrepWidget(), m_plugin( plugin ) { setAttribute(Qt::WA_DeleteOnClose); setButtons( SearchButton | KDialog::Cancel ); setButtonText( SearchButton, i18n("Search...") ); setButtonIcon( SearchButton, QIcon::fromTheme("edit-find") ); setCaption( i18n("Find/Replace in Files") ); setDefaultButton( SearchButton ); setupUi(mainWidget()); KConfigGroup cg = ICore::self()->activeSession()->config()->group( "GrepDialog" ); patternCombo->addItems( cg.readEntry("LastSearchItems", QStringList()) ); patternCombo->setInsertPolicy(QComboBox::InsertAtTop); templateTypeCombo->addItems(template_desc); templateTypeCombo->setCurrentIndex( cg.readEntry("LastUsedTemplateIndex", 0) ); templateEdit->addItems( cg.readEntry("LastUsedTemplateString", template_str) ); templateEdit->setEditable(true); templateEdit->setCompletionMode(KCompletion::CompletionPopup); KCompletion* comp = templateEdit->completionObject(); connect(templateEdit, SIGNAL(returnPressed(QString)), comp, SLOT(addItem(QString))); for(int i=0; icount(); i++) comp->addItem(templateEdit->itemText(i)); replacementTemplateEdit->addItems( cg.readEntry("LastUsedReplacementTemplateString", repl_template) ); replacementTemplateEdit->setEditable(true); replacementTemplateEdit->setCompletionMode(KCompletion::CompletionPopup); comp = replacementTemplateEdit->completionObject(); connect(replacementTemplateEdit, SIGNAL(returnPressed(QString)), comp, SLOT(addItem(QString))); for(int i=0; icount(); i++) comp->addItem(replacementTemplateEdit->itemText(i)); regexCheck->setChecked(cg.readEntry("regexp", false )); caseSensitiveCheck->setChecked(cg.readEntry("case_sens", true)); searchPaths->setCompletionObject(new KUrlCompletion()); searchPaths->setAutoDeleteCompletionObject(true); QList projects = m_plugin->core()->projectController()->projects(); searchPaths->addItems(cg.readEntry("SearchPaths", QStringList(!projects.isEmpty() ? allOpenProjectsString : QDir::homePath() ) )); searchPaths->setInsertPolicy(QComboBox::InsertAtTop); syncButton->setIcon(QIcon::fromTheme("dirsync")); syncButton->setMenu(createSyncButtonMenu()); depthSpin->setValue(cg.readEntry("depth", -1)); limitToProjectCheck->setChecked(cg.readEntry("search_project_files", true)); filesCombo->addItems(cg.readEntry("file_patterns", filepatterns)); excludeCombo->addItems(cg.readEntry("exclude_patterns", excludepatterns) ); connect(this, SIGNAL(buttonClicked(KDialog::ButtonCode)), this, SLOT(performAction(KDialog::ButtonCode))); connect(templateTypeCombo, SIGNAL(activated(int)), this, SLOT(templateTypeComboActivated(int))); connect(patternCombo, SIGNAL(editTextChanged(QString)), this, SLOT(patternComboEditTextChanged(QString))); patternComboEditTextChanged( patternCombo->currentText() ); patternCombo->setFocus(); connect(searchPaths, SIGNAL(activated(QString)), this, SLOT(setSearchLocations(QString))); directorySelector->setIcon(QIcon::fromTheme("document-open")); connect(directorySelector, SIGNAL(clicked(bool)), this, SLOT(selectDirectoryDialog()) ); + directoryChanged(directorySelector->text()); } void GrepDialog::selectDirectoryDialog() { QString dirName = KFileDialog::getExistingDirectory(searchPaths->lineEdit()->text(), this, tr("Select directory to search in")); if (!dirName.isEmpty()) { setSearchLocations(dirName); } } void GrepDialog::addUrlToMenu(QMenu* menu, const KUrl& url) { QAction* action = menu->addAction(m_plugin->core()->projectController()->prettyFileName(url, KDevelop::IProjectController::FormatPlain)); action->setData(QVariant(url.pathOrUrl())); connect(action, SIGNAL(triggered(bool)), SLOT(synchronizeDirActionTriggered(bool))); } void GrepDialog::addStringToMenu(QMenu* menu, QString string) { QAction* action = menu->addAction(string); action->setData(QVariant(string)); connect(action, SIGNAL(triggered(bool)), SLOT(synchronizeDirActionTriggered(bool))); } void GrepDialog::synchronizeDirActionTriggered(bool) { QAction* action = qobject_cast(sender()); Q_ASSERT(action); setSearchLocations(action->data().value()); } QMenu* GrepDialog::createSyncButtonMenu() { QMenu* ret = new QMenu; QSet hadUrls; IDocument *doc = m_plugin->core()->documentController()->activeDocument(); if ( doc ) { KUrl url = doc->url(); url.cd(".."); while(m_plugin->core()->projectController()->findProjectForUrl(url)) { url.adjustPath(KUrl::RemoveTrailingSlash); if(hadUrls.contains(url)) break; hadUrls.insert(url); addUrlToMenu(ret, url); if(!url.cd("..")) break; } // if the current file's parent directory is not in the project, add it url = doc->url().upUrl(); url.adjustPath(KUrl::RemoveTrailingSlash); if(!hadUrls.contains(url)) { hadUrls.insert(url); addUrlToMenu(ret, url); } } foreach(IProject* project, m_plugin->core()->projectController()->projects()) { KUrl url = project->folder(); url.adjustPath(KUrl::RemoveTrailingSlash); if(hadUrls.contains(url)) continue; addUrlToMenu(ret, url); } addStringToMenu(ret, allOpenFilesString); addStringToMenu(ret, allOpenProjectsString); return ret; } void GrepDialog::directoryChanged(const QString& dir) { KUrl currentUrl = dir; if( !currentUrl.isValid() ) { setEnableProjectBox(false); return; } bool projectAvailable = true; foreach(KUrl url, getDirectoryChoice()) { IProject *proj = ICore::self()->projectController()->findProjectForUrl( currentUrl ); if( !proj || !proj->folder().isLocalFile() ) projectAvailable = false; } setEnableProjectBox(projectAvailable); } GrepDialog::~GrepDialog() { KConfigGroup cg = ICore::self()->activeSession()->config()->group( "GrepDialog" ); // memorize the last patterns and paths cg.writeEntry("LastSearchItems", qCombo2StringList(patternCombo)); cg.writeEntry("regexp", regexCheck->isChecked()); cg.writeEntry("depth", depthSpin->value()); cg.writeEntry("search_project_files", limitToProjectCheck->isChecked()); cg.writeEntry("case_sens", caseSensitiveCheck->isChecked()); cg.writeEntry("exclude_patterns", qCombo2StringList(excludeCombo)); cg.writeEntry("file_patterns", qCombo2StringList(filesCombo)); cg.writeEntry("LastUsedTemplateIndex", templateTypeCombo->currentIndex()); cg.writeEntry("LastUsedTemplateString", qCombo2StringList(templateEdit)); cg.writeEntry("LastUsedReplacementTemplateString", qCombo2StringList(replacementTemplateEdit)); cg.writeEntry("SearchPaths", qCombo2StringList(searchPaths)); cg.sync(); } void GrepDialog::templateTypeComboActivated(int index) { templateEdit->setCurrentItem( template_str[index], true ); replacementTemplateEdit->setCurrentItem( repl_template[index], true ); } void GrepDialog::setEnableProjectBox(bool enable) { limitToProjectCheck->setEnabled(enable); limitToProjectLabel->setEnabled(enable); } void GrepDialog::setPattern(const QString &pattern) { patternCombo->setEditText(pattern); } void GrepDialog::setSearchLocations(const QString &dir) { if(!dir.isEmpty()) { if(QDir::isAbsolutePath(dir)) { static_cast(searchPaths->completionObject())->setDir( dir ); } if (searchPaths->contains(dir)) { searchPaths->removeItem(searchPaths->findText(dir)); } searchPaths->insertItem(0, dir); searchPaths->setCurrentItem(dir); if (searchPaths->count() > pathsMaxCount) { searchPaths->removeItem(searchPaths->count() - 1); } } directoryChanged(dir); } QString GrepDialog::patternString() const { return patternCombo->currentText(); } QString GrepDialog::templateString() const { return templateEdit->currentText().isEmpty() ? "%s" : templateEdit->currentText(); } QString GrepDialog::replacementTemplateString() const { return replacementTemplateEdit->currentText(); } QString GrepDialog::filesString() const { return filesCombo->currentText(); } QString GrepDialog::excludeString() const { return excludeCombo->currentText(); } bool GrepDialog::useProjectFilesFlag() const { if (!limitToProjectCheck->isEnabled()) return false; return limitToProjectCheck->isChecked(); } bool GrepDialog::regexpFlag() const { return regexCheck->isChecked(); } int GrepDialog::depthValue() const { return depthSpin->value(); } bool GrepDialog::caseSensitiveFlag() const { return caseSensitiveCheck->isChecked(); } void GrepDialog::patternComboEditTextChanged( const QString& text) { enableButton( SearchButton, !text.isEmpty() ); } QList< KUrl > GrepDialog::getDirectoryChoice() const { QList< KUrl > ret; QString text = searchPaths->currentText(); if(text == allOpenFilesString) { foreach(IDocument* doc, ICore::self()->documentController()->openDocuments()) ret << doc->url(); }else if(text == allOpenProjectsString) { foreach(IProject* project, ICore::self()->projectController()->projects()) ret << project->folder(); }else{ QStringList semicolonSeparatedFileList = text.split(pathsSeparator); if(!semicolonSeparatedFileList.isEmpty() && QFileInfo(semicolonSeparatedFileList[0]).exists()) { // We use QFileInfo to make sure this is really a semicolon-separated file list, not a file containing // a semicolon in the name. foreach(QString file, semicolonSeparatedFileList) ret << KUrl::fromPath(file); }else{ ret << KUrl(searchPaths->currentText()); } } return ret; } bool GrepDialog::isPartOfChoice(KUrl url) const { foreach(KUrl choice, getDirectoryChoice()) if(choice.isParentOf(url) || choice.equals(url)) return true; return false; } void GrepDialog::start() { performAction(SearchButton); } void GrepDialog::performAction(KDialog::ButtonCode button) { // a click on cancel trigger this signal too if( button != SearchButton ) return; // search for unsaved documents QList unsavedFiles; QStringList include = GrepFindFilesThread::parseInclude(filesString()); QStringList exclude = GrepFindFilesThread::parseExclude(excludeString()); foreach(IDocument* doc, ICore::self()->documentController()->openDocuments()) { KUrl docUrl = doc->url(); if(doc->state() != IDocument::Clean && isPartOfChoice(docUrl) && QDir::match(include, docUrl.fileName()) && !QDir::match(exclude, docUrl.toLocalFile())) { unsavedFiles << doc; } } if(!ICore::self()->documentController()->saveSomeDocuments(unsavedFiles)) { close(); return; } QList choice = getDirectoryChoice(); GrepJob* job = m_plugin->newGrepJob(); QString descriptionOrUrl(searchPaths->currentText()); QString description = descriptionOrUrl; // Shorten the description if(descriptionOrUrl != allOpenFilesString && descriptionOrUrl != allOpenProjectsString && choice.size() > 1) description = i18np("%2, and %1 more item", "%2, and %1 more items", choice.size() - 1, choice[0].pathOrUrl()); GrepOutputView *toolView = (GrepOutputView*)ICore::self()->uiController()-> findToolView(i18n("Find/Replace in Files"), m_plugin->toolViewFactory(), IUiController::CreateAndRaise); GrepOutputModel* outputModel = toolView->renewModel(patternString(), description); connect(job, SIGNAL(showErrorMessage(QString,int)), toolView, SLOT(showErrorMessage(QString))); //the GrepOutputModel gets the 'showMessage' signal to store it and forward //it to toolView connect(job, SIGNAL(showMessage(KDevelop::IStatus*,QString,int)), outputModel, SLOT(showMessageSlot(KDevelop::IStatus*,QString))); connect(outputModel, SIGNAL(showMessage(KDevelop::IStatus*,QString)), toolView, SLOT(showMessage(KDevelop::IStatus*,QString))); connect(toolView, SIGNAL(outputViewIsClosed()), job, SLOT(kill())); job->setOutputModel(outputModel); job->setPatternString(patternString()); job->setReplacementTemplateString(replacementTemplateString()); job->setTemplateString(templateString()); job->setFilesString(filesString()); job->setExcludeString(excludeString()); job->setDirectoryChoice(choice); job->setProjectFilesFlag( useProjectFilesFlag() ); job->setRegexpFlag( regexpFlag() ); job->setDepth( depthValue() ); job->setCaseSensitive( caseSensitiveFlag() ); ICore::self()->runController()->registerJob(job); m_plugin->rememberSearchDirectory(descriptionOrUrl); close(); } #include "grepdialog.moc" diff --git a/plugins/grepview/grepoutputdelegate.cpp b/plugins/grepview/grepoutputdelegate.cpp index a7ee32b7e2..3bfb01ff27 100644 --- a/plugins/grepview/grepoutputdelegate.cpp +++ b/plugins/grepview/grepoutputdelegate.cpp @@ -1,179 +1,183 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright (C) 2007 Andreas Pakulat * * Copyright 2010 Julien Desgats * * * * 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 "grepoutputdelegate.h" #include "grepoutputmodel.h" #include #include #include #include #include #include #include #include #include #include GrepOutputDelegate* GrepOutputDelegate::m_self = 0; GrepOutputDelegate* GrepOutputDelegate::self() { Q_ASSERT(m_self); return m_self; } GrepOutputDelegate::GrepOutputDelegate( QObject* parent ) : QStyledItemDelegate(parent) { Q_ASSERT(!m_self); m_self = this; } GrepOutputDelegate::~GrepOutputDelegate() { m_self = 0; } QColor GrepOutputDelegate::blendColor(QColor color1, QColor color2, double blend) const { return QColor(color1.red() * blend + color2.red() * (1-blend), color1.green() * blend + color2.green() * (1-blend), color1.blue() * blend + color2.blue() * (1-blend)); } void GrepOutputDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const { // there is no function in QString to left-trim. A call to remove this this regexp does the job static const QRegExp leftspaces("^\\s*", Qt::CaseSensitive, QRegExp::RegExp); // rich text component const GrepOutputModel *model = dynamic_cast(index.model()); const GrepOutputItem *item = dynamic_cast(model->itemFromIndex(index)); QStyleOptionViewItemV4 options = option; initStyleOption(&options, index); // building item representation QTextDocument doc; QTextCursor cur(&doc); QPalette::ColorGroup cg = options.state & QStyle::State_Enabled ? QPalette::Normal : QPalette::Disabled; QPalette::ColorRole cr = options.state & QStyle::State_Selected ? QPalette::HighlightedText : QPalette::Text; QTextCharFormat fmt = cur.charFormat(); fmt.setFont(options.font); if(item && item->isText()) { // Use custom manual highlighting const KDevelop::SimpleRange rng = item->change()->m_range; // the line number appears grayed fmt.setForeground(options.palette.brush(QPalette::Disabled, cr)); cur.insertText(i18n("Line %1: ",item->lineNumber()), fmt); // switch to normal color fmt.setForeground(options.palette.brush(cg, cr)); cur.insertText(item->text().left(rng.start.column).remove(leftspaces), fmt); fmt.setFontWeight(QFont::Bold); // Blend the highlighted background color // For some reason, it is extremely slow to use alpha-blending directly here QColor bgHighlight = blendColor(option.palette.brush(QPalette::Highlight).color(), option.palette.brush(QPalette::Base).color(), 0.3); fmt.setBackground(bgHighlight); cur.insertText(item->text().mid(rng.start.column, rng.end.column - rng.start.column), fmt); fmt.clearBackground(); fmt.setFontWeight(QFont::Normal); cur.insertText(item->text().right(item->text().length() - rng.end.column), fmt); }else{ QString text; if(item) text = item->text(); else text = index.data().toString(); // Simply insert the text as html. We use this for the titles. doc.setHtml(text); } painter->save(); options.text = QString(); // text will be drawn separately options.widget->style()->drawControl(QStyle::CE_ItemViewItem, &options, painter, options.widget); // set correct draw area QRect clip = options.widget->style()->subElementRect(QStyle::SE_ItemViewItemText, &options); QFontMetrics metrics(options.font); painter->translate(clip.topLeft() - QPoint(0, metrics.descent())); // We disable the clipping for now, as it leads to strange clipping errors // clip.setTopLeft(QPoint(0,0)); // painter->setClipRect(clip); QAbstractTextDocumentLayout::PaintContext ctx; // ctx.clip = clip; painter->setBackground(Qt::transparent); doc.documentLayout()->draw(painter, ctx); painter->restore(); } QSize GrepOutputDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const { const GrepOutputModel *model = dynamic_cast(index.model()); const GrepOutputItem *item = dynamic_cast(model->itemFromIndex(index)); QSize ret = QStyledItemDelegate::sizeHint(option, index); //take account of additional width required for highlighting (bold text) //and line numbers. These are not included in the default Qt size calculation. if(item && item->isText()) { QFont font = option.font; + QFontMetrics metrics(font); font.setBold(true); QFontMetrics bMetrics(font); - //TODO: calculate width with more accuracy: here the whole text is considerated as bold - int width = bMetrics.width(item->text()) + - option.fontMetrics.width(i18n("Line %1: ",item->lineNumber())) + - std::max(option.decorationSize.width(), 0); + const KDevelop::SimpleRange rng = item->change()->m_range; + + int width = metrics.width(item->text().left(rng.start.column)) + + metrics.width(item->text().right(item->text().length() - rng.end.column)) + + bMetrics.width(item->text().mid(rng.start.column, rng.end.column - rng.start.column)) + + option.fontMetrics.width(i18n("Line %1: ",item->lineNumber())) + + std::max(option.decorationSize.width(), 0); ret.setWidth(width); }else{ // This is only used for titles, so not very performance critical QString text; if(item) text = item->text(); else text = index.data().toString(); QTextDocument doc; doc.setDocumentMargin(0); doc.setHtml(text); QSize newSize = doc.size().toSize(); if(newSize.height() > ret.height()) ret.setHeight(newSize.height()); } ret.setHeight(ret.height()+2); // We slightly increase the vertical size, else the view looks too crowded return ret; } diff --git a/plugins/grepview/grepoutputview.cpp b/plugins/grepview/grepoutputview.cpp index c26f8a77ee..9b3522f8bc 100644 --- a/plugins/grepview/grepoutputview.cpp +++ b/plugins/grepview/grepoutputview.cpp @@ -1,379 +1,389 @@ /************************************************************************** * Copyright 2010 Silvère Lestang * * 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 "grepoutputview.h" #include "grepoutputmodel.h" #include "grepoutputdelegate.h" #include "ui_grepoutputview.h" #include "grepviewplugin.h" #include "grepdialog.h" #include "greputil.h" #include "grepjob.h" #include #include #include -#include +#include #include #include #include #include #include #include #include using namespace KDevelop; GrepOutputViewFactory::GrepOutputViewFactory(GrepViewPlugin* plugin) : m_plugin(plugin) {} QWidget* GrepOutputViewFactory::create(QWidget* parent) { return new GrepOutputView(parent, m_plugin); } Qt::DockWidgetArea GrepOutputViewFactory::defaultPosition() { return Qt::BottomDockWidgetArea; } QString GrepOutputViewFactory::id() const { return "org.kdevelop.GrepOutputView"; } const int GrepOutputView::HISTORY_SIZE = 5; GrepOutputView::GrepOutputView(QWidget* parent, GrepViewPlugin* plugin) : QWidget(parent) , m_next(0) , m_prev(0) , m_collapseAll(0) , m_expandAll(0) , m_clearSearchHistory(0) , m_statusLabel(0) , m_plugin(plugin) { Ui::GrepOutputView::setupUi(this); setWindowTitle(i18nc("@title:window", "Find/Replace Output View")); setWindowIcon(QIcon::fromTheme("edit-find")); m_prev = new QAction(QIcon::fromTheme("go-previous"), i18n("&Previous Item"), this); m_prev->setEnabled(false); m_next = new QAction(QIcon::fromTheme("go-next"), i18n("&Next Item"), this); m_next->setEnabled(false); m_collapseAll = new QAction(QIcon::fromTheme("arrow-left-double"), i18n("C&ollapse All"), this); // TODO change icon m_collapseAll->setEnabled(false); m_expandAll = new QAction(QIcon::fromTheme("arrow-right-double"), i18n("&Expand All"), this); // TODO change icon m_expandAll->setEnabled(false); QAction *separator = new QAction(this); separator->setSeparator(true); QAction *newSearchAction = new QAction(QIcon::fromTheme("edit-find"), i18n("New &Search"), this); m_clearSearchHistory = new QAction(QIcon::fromTheme("edit-clear-list"), i18n("Clear Search History"), this); addAction(m_prev); addAction(m_next); addAction(m_collapseAll); addAction(m_expandAll); addAction(separator); addAction(newSearchAction); addAction(m_clearSearchHistory); separator = new QAction(this); separator->setSeparator(true); addAction(separator); QWidgetAction *statusWidget = new QWidgetAction(this); m_statusLabel = new QLabel(this); statusWidget->setDefaultWidget(m_statusLabel); addAction(statusWidget); modelSelector->setEditable(false); modelSelector->setContextMenuPolicy(Qt::CustomContextMenu); connect(modelSelector, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(modelSelectorContextMenu(QPoint))); connect(modelSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(changeModel(int))); resultsTreeView->setItemDelegate(GrepOutputDelegate::self()); resultsTreeView->setHeaderHidden(true); resultsTreeView->setUniformRowHeights(false); + resultsTreeView->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); connect(m_prev, SIGNAL(triggered(bool)), this, SLOT(selectPreviousItem())); connect(m_next, SIGNAL(triggered(bool)), this, SLOT(selectNextItem())); connect(m_collapseAll, SIGNAL(triggered(bool)), this, SLOT(collapseAllItems())); connect(m_expandAll, SIGNAL(triggered(bool)), this, SLOT(expandAllItems())); connect(applyButton, SIGNAL(clicked()), this, SLOT(onApply())); connect(m_clearSearchHistory, SIGNAL(triggered(bool)), this, SLOT(clearSearchHistory())); connect(resultsTreeView, SIGNAL(collapsed(QModelIndex)), this, SLOT(updateScrollArea(QModelIndex))); connect(resultsTreeView, SIGNAL(expanded(QModelIndex)), this, SLOT(updateScrollArea(QModelIndex))); IPlugin *outputView = ICore::self()->pluginController()->pluginForExtension("org.kdevelop.IOutputView"); connect(outputView, SIGNAL(selectPrevItem()), this, SLOT(selectPreviousItem())); connect(outputView, SIGNAL(selectNextItem()), this, SLOT(selectNextItem())); KConfigGroup cg = ICore::self()->activeSession()->config()->group( "GrepDialog" ); replacementCombo->addItems( cg.readEntry("LastReplacementItems", QStringList()) ); replacementCombo->setInsertPolicy(QComboBox::InsertAtTop); applyButton->setIcon(QIcon::fromTheme("dialog-ok-apply")); connect(replacementCombo, SIGNAL(editTextChanged(QString)), SLOT(replacementTextChanged(QString))); connect(newSearchAction, SIGNAL(triggered(bool)), this, SLOT(showDialog())); updateCheckable(); } void GrepOutputView::replacementTextChanged(QString) { updateCheckable(); if (model()) { // see https://bugs.kde.org/show_bug.cgi?id=274902 - renewModel can trigger a call here without an active model updateApplyState(model()->index(0, 0), model()->index(0, 0)); } } GrepOutputView::~GrepOutputView() { KConfigGroup cg = ICore::self()->activeSession()->config()->group( "GrepDialog" ); cg.writeEntry("LastReplacementItems", qCombo2StringList(replacementCombo, true)); emit outputViewIsClosed(); } GrepOutputModel* GrepOutputView::renewModel(QString name, QString descriptionOrUrl) { // Crear oldest model while(modelSelector->count() > GrepOutputView::HISTORY_SIZE) { QVariant var = modelSelector->itemData(GrepOutputView::HISTORY_SIZE - 1); qvariant_cast(var)->deleteLater(); modelSelector->removeItem(GrepOutputView::HISTORY_SIZE - 1); } replacementCombo->clearEditText(); GrepOutputModel* newModel = new GrepOutputModel(resultsTreeView); applyButton->setEnabled(false); // text may be already present newModel->setReplacement(replacementCombo->currentText()); connect(newModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(rowsRemoved())); connect(resultsTreeView, SIGNAL(activated(QModelIndex)), newModel, SLOT(activate(QModelIndex))); connect(replacementCombo, SIGNAL(editTextChanged(QString)), newModel, SLOT(setReplacement(QString))); connect(newModel, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(expandElements(QModelIndex))); connect(newModel, SIGNAL(showErrorMessage(QString,int)), this, SLOT(showErrorMessage(QString))); QString prettyUrl = descriptionOrUrl; if(descriptionOrUrl.startsWith('/')) prettyUrl = ICore::self()->projectController()->prettyFileName(descriptionOrUrl, KDevelop::IProjectController::FormatPlain); // appends new model to history QString displayName = i18n("Search \"%1\" in %2 (at time %3)", name, prettyUrl, QTime::currentTime().toString("hh:mm")); modelSelector->insertItem(0, displayName, qVariantFromValue(newModel)); modelSelector->setCurrentIndex(0);//setCurrentItem(displayName); updateCheckable(); return newModel; } GrepOutputModel* GrepOutputView::model() { return static_cast(resultsTreeView->model()); } void GrepOutputView::changeModel(int index) { - disconnect(model(), SIGNAL(showMessage(KDevelop::IStatus*,QString)), - this, SLOT(showMessage(KDevelop::IStatus*,QString))); - disconnect(model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), - this, SLOT(updateApplyState(QModelIndex,QModelIndex))); + if (model()) { + disconnect(model(), SIGNAL(showMessage(KDevelop::IStatus*,QString)), + this, SLOT(showMessage(KDevelop::IStatus*,QString))); + disconnect(model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), + this, SLOT(updateApplyState(QModelIndex,QModelIndex))); + } replacementCombo->clearEditText(); //after deleting the whole search history, index is -1 if(index >= 0) { QVariant var = modelSelector->itemData(index); GrepOutputModel *resultModel = static_cast(qvariant_cast(var)); resultsTreeView->setModel(resultModel); connect(model(), SIGNAL(showMessage(KDevelop::IStatus*,QString)), this, SLOT(showMessage(KDevelop::IStatus*,QString))); connect(model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(updateApplyState(QModelIndex,QModelIndex))); model()->showMessageEmit(); applyButton->setEnabled(model()->hasResults() && model()->getRootItem() && model()->getRootItem()->checkState() != Qt::Unchecked && !replacementCombo->currentText().isEmpty()); if(model()->hasResults()) expandElements(QModelIndex()); } updateCheckable(); updateApplyState(model()->index(0, 0), model()->index(0, 0)); } -void GrepOutputView::setMessage(const QString& msg) +void GrepOutputView::setMessage(const QString& msg, MessageType type) { + if (type == Error) { + QPalette palette = m_statusLabel->palette(); + KColorScheme::adjustForeground(palette, KColorScheme::NegativeText, QPalette::WindowText); + m_statusLabel->setPalette(palette); + } else { + m_statusLabel->setPalette(QPalette()); + } m_statusLabel->setText(msg); } void GrepOutputView::showErrorMessage( const QString& errorMessage ) { - setStyleSheet("QLabel { color : red; }"); - setMessage(errorMessage); + setMessage(errorMessage, Error); } void GrepOutputView::showMessage( KDevelop::IStatus* , const QString& message ) { - setStyleSheet(""); - setMessage(message); + setMessage(message, Information); } void GrepOutputView::onApply() { if(model()) { Q_ASSERT(model()->rowCount()); // ask a confirmation before an empty string replacement if(replacementCombo->currentText().length() == 0 && KMessageBox::questionYesNo(this, i18n("Do you want to replace with an empty string?"), i18n("Start replacement")) == KMessageBox::No) { return; } setEnabled(false); model()->doReplacements(); setEnabled(true); } } void GrepOutputView::showDialog() { m_plugin->showDialog(true); } void GrepOutputView::expandElements(const QModelIndex&) { m_prev->setEnabled(true); m_next->setEnabled(true); m_collapseAll->setEnabled(true); m_expandAll->setEnabled(true); resultsTreeView->expandAll(); + for (int col = 0; col < model()->columnCount(); ++col) + resultsTreeView->resizeColumnToContents(col); } void GrepOutputView::selectPreviousItem() { if (!model()) { return; } QModelIndex prev_idx = model()->previousItemIndex(resultsTreeView->currentIndex()); if (prev_idx.isValid()) { resultsTreeView->setCurrentIndex(prev_idx); model()->activate(prev_idx); } } void GrepOutputView::selectNextItem() { if (!model()) { return; } QModelIndex next_idx = model()->nextItemIndex(resultsTreeView->currentIndex()); if (next_idx.isValid()) { resultsTreeView->setCurrentIndex(next_idx); model()->activate(next_idx); } } void GrepOutputView::collapseAllItems() { // Collapse everything resultsTreeView->collapseAll(); // Now reopen the first children, which correspond to the files. resultsTreeView->expand(resultsTreeView->model()->index(0, 0)); } void GrepOutputView::expandAllItems() { resultsTreeView->expandAll(); } void GrepOutputView::rowsRemoved() { m_prev->setEnabled(model()->rowCount()); m_next->setEnabled(model()->rowCount()); } void GrepOutputView::updateApplyState(const QModelIndex& topLeft, const QModelIndex& bottomRight) { Q_UNUSED(bottomRight); if (!model() || !model()->hasResults()) { applyButton->setEnabled(false); return; } // we only care about the root item if(!topLeft.parent().isValid()) { applyButton->setEnabled(topLeft.data(Qt::CheckStateRole) != Qt::Unchecked && model()->itemsCheckable()); } } void GrepOutputView::updateCheckable() { if(model()) model()->makeItemsCheckable(!replacementCombo->currentText().isEmpty() || model()->itemsCheckable()); } void GrepOutputView::clearSearchHistory() { GrepJob *runningJob = m_plugin->grepJob(); if(runningJob) { runningJob->kill(); } while(modelSelector->count() > 0) { QVariant var = modelSelector->itemData(0); qvariant_cast(var)->deleteLater(); modelSelector->removeItem(0); } applyButton->setEnabled(false); m_statusLabel->setText(QString()); } void GrepOutputView::modelSelectorContextMenu(const QPoint& pos) { QPoint globalPos = modelSelector->mapToGlobal(pos); QMenu myMenu; myMenu.addAction(m_clearSearchHistory); myMenu.exec(globalPos); } void GrepOutputView::updateScrollArea(const QModelIndex& index) { resultsTreeView->resizeColumnToContents( index.column() ); } diff --git a/plugins/grepview/grepoutputview.h b/plugins/grepview/grepoutputview.h index c17a82eb12..881dbc0a94 100644 --- a/plugins/grepview/grepoutputview.h +++ b/plugins/grepview/grepoutputview.h @@ -1,94 +1,99 @@ /************************************************************************** * Copyright 2010 Silvère Lestang * * 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. * * * ***************************************************************************/ #ifndef KDEVPLATFORM_PLUGIN_GREPOUTPUTVIEW_H #define KDEVPLATFORM_PLUGIN_GREPOUTPUTVIEW_H #include #include "ui_grepoutputview.h" namespace KDevelop { class IStatus; } class QModelIndex; class GrepViewPlugin; class GrepOutputModel; class GrepOutputDelegate; class GrepOutputViewFactory: public KDevelop::IToolViewFactory { public: GrepOutputViewFactory(GrepViewPlugin* plugin); virtual QWidget* create(QWidget* parent = 0); virtual Qt::DockWidgetArea defaultPosition(); virtual QString id() const; private: GrepViewPlugin* m_plugin; }; class GrepOutputView : public QWidget, Ui::GrepOutputView { Q_OBJECT public: + enum MessageType { + Information, + Error + }; + GrepOutputView(QWidget* parent, GrepViewPlugin* plugin); ~GrepOutputView(); GrepOutputModel* model(); /** * This causes the creation of a new model, the old one is kept in model history. * Oldest models are deleted if needed. * @return pointer to the new model */ GrepOutputModel* renewModel(QString name, QString descriptionOrUrl); - void setMessage(const QString& msg); + void setMessage(const QString& msg, MessageType type = Information); public Q_SLOTS: void showErrorMessage( const QString& errorMessage ); void showMessage( KDevelop::IStatus*, const QString& message ); void updateApplyState(const QModelIndex &topLeft, const QModelIndex &bottomRight); void changeModel(int index); void replacementTextChanged(QString); Q_SIGNALS: void outputViewIsClosed(); private: static const int HISTORY_SIZE; QAction* m_next; QAction* m_prev; QAction* m_collapseAll; QAction* m_expandAll; QAction* m_clearSearchHistory; QLabel* m_statusLabel; GrepViewPlugin *m_plugin; private slots: void selectPreviousItem(); void selectNextItem(); void collapseAllItems(); void expandAllItems(); void onApply(); void showDialog(); void expandElements( const QModelIndex & parent ); void rowsRemoved(); void clearSearchHistory(); void modelSelectorContextMenu(const QPoint& pos); void updateScrollArea( const QModelIndex &index ); void updateCheckable(); }; #endif // KDEVPLATFORM_PLUGIN_GREPOUTPUTVIEW_H diff --git a/plugins/openwith/openwithplugin.cpp b/plugins/openwith/openwithplugin.cpp index 822d34689b..6f55279231 100644 --- a/plugins/openwith/openwithplugin.cpp +++ b/plugins/openwith/openwithplugin.cpp @@ -1,247 +1,263 @@ /* * This file is part of KDevelop * Copyright 2009 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 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 "openwithplugin.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 using namespace KDevelop; K_PLUGIN_FACTORY(KDevOpenWithFactory, registerPlugin(); ) // K_EXPORT_PLUGIN(KDevOpenWithFactory(KAboutData("kdevopenwith","kdevopenwith", ki18n("Open With"), "0.1", ki18n("This plugin allows to open files with associated external applications."), KAboutData::License_GPL))) OpenWithPlugin::OpenWithPlugin ( QObject* parent, const QVariantList& ) : IPlugin ( "kdevopenwith", parent ), m_actionMap( 0 ) { KDEV_USE_EXTENSION_INTERFACE( IOpenWith ) } OpenWithPlugin::~OpenWithPlugin() { } KDevelop::ContextMenuExtension OpenWithPlugin::contextMenuExtension( KDevelop::Context* context ) { // do not recurse if (context->hasType(KDevelop::Context::OpenWithContext)) { return ContextMenuExtension(); } m_urls.clear(); m_actionMap.reset(); m_services.clear(); FileContext* filectx = dynamic_cast( context ); ProjectItemContext* projctx = dynamic_cast( context ); if ( filectx && filectx->urls().count() > 0 ) { m_urls = filectx->urls(); } else if ( projctx && projctx->items().count() > 0 ) { + // For now, let's handle *either* files only *or* directories only + const int wantedType = projctx->items().first()->type(); foreach( ProjectBaseItem* item, projctx->items() ) { - if( item->file() ) { + if (wantedType == ProjectBaseItem::File && item->file()) { m_urls << item->file()->path().toUrl(); + } else if ((wantedType == ProjectBaseItem::Folder || wantedType == ProjectBaseItem::BuildFolder) && item->folder()) { + m_urls << item->folder()->path().toUrl(); } } } if (m_urls.isEmpty()) { return KDevelop::ContextMenuExtension(); } m_actionMap.reset(new QSignalMapper( this )); connect( m_actionMap.data(), SIGNAL(mapped(QString)), SLOT(open(QString)) ); // Ok, lets fetch the mimetype for the !!first!! url and the relevant services // TODO: Think about possible alternatives to using the mimetype of the first url. KMimeType::Ptr mimetype = KMimeType::findByUrl( m_urls.first() ); - if (mimetype->is("inode/directory")) { - return KDevelop::ContextMenuExtension(); - } - m_mimeType = mimetype->name(); QList partActions = actionsForServiceType("KParts/ReadOnlyPart"); QList appActions = actionsForServiceType("Application"); OpenWithContext subContext(m_urls, mimetype); QList extensions = ICore::self()->pluginController()->queryPluginsForContextMenuExtensions( &subContext ); foreach( const ContextMenuExtension& ext, extensions ) { appActions += ext.actions(ContextMenuExtension::OpenExternalGroup); partActions += ext.actions(ContextMenuExtension::OpenEmbeddedGroup); } // Now setup a menu with actions for each part and app KMenu* menu = new KMenu( i18n("Open With" ) ); menu->setIcon( QIcon::fromTheme( "document-open" ) ); - menu->addTitle(i18n("Embedded Editors")); - menu->addActions( partActions ); - menu->addTitle(i18n("External Applications")); - menu->addActions( appActions ); + if (!partActions.isEmpty()) { + menu->addTitle(i18n("Embedded Editors")); + menu->addActions( partActions ); + } + if (!appActions.isEmpty()) { + menu->addTitle(i18n("External Applications")); + menu->addActions( appActions ); + } QAction* openAction = new QAction( i18n( "Open" ), this ); openAction->setIcon( QIcon::fromTheme( "document-open" ) ); connect( openAction, SIGNAL(triggered()), SLOT(openDefault()) ); KDevelop::ContextMenuExtension ext; ext.addAction( KDevelop::ContextMenuExtension::FileGroup, openAction ); - ext.addAction( KDevelop::ContextMenuExtension::FileGroup, menu->menuAction() ); + if (!menu->isEmpty()) { + ext.addAction(KDevelop::ContextMenuExtension::FileGroup, menu->menuAction()); + } else { + delete menu; + } return ext; } bool sortActions(QAction* left, QAction* right) { return left->text() < right->text(); } bool isTextEditor(const KService::Ptr& service) { return service->serviceTypes().contains( "KTextEditor/Document" ); } QString defaultForMimeType(const QString& mimeType) { KConfigGroup config = KSharedConfig::openConfig()->group("Open With Defaults"); if (config.hasKey(mimeType)) { QString storageId = config.readEntry(mimeType, QString()); if (!storageId.isEmpty() && KService::serviceByStorageId(storageId)) { return storageId; } } return QString(); } QList OpenWithPlugin::actionsForServiceType( const QString& serviceType ) { KService::List list = KMimeTypeTrader::self()->query( m_mimeType, serviceType ); KService::Ptr pref = KMimeTypeTrader::self()->preferredService( m_mimeType, serviceType ); m_services += list; QList actions; QAction* standardAction = 0; const QString defaultId = defaultForMimeType(m_mimeType); foreach( KService::Ptr svc, list ) { QAction* act = new QAction( isTextEditor(svc) ? i18n("Default Editor") : svc->name(), this ); act->setIcon( QIcon::fromTheme( svc->icon() ) ); if (svc->storageId() == defaultId || (defaultId.isEmpty() && isTextEditor(svc))) { QFont font = act->font(); font.setBold(true); act->setFont(font); } connect(act, SIGNAL(triggered()), m_actionMap.data(), SLOT(map())); m_actionMap->setMapping( act, svc->storageId() ); actions << act; if ( isTextEditor(svc) ) { standardAction = act; } else if ( svc->storageId() == pref->storageId() ) { standardAction = act; } } qSort(actions.begin(), actions.end(), sortActions); if (standardAction) { actions.removeOne(standardAction); actions.prepend(standardAction); } return actions; } void OpenWithPlugin::openDefault() { + // check preferred handler const QString defaultId = defaultForMimeType(m_mimeType); if (!defaultId.isEmpty()) { open(defaultId); return; } - foreach( const KUrl& u, m_urls ) { - ICore::self()->documentController()->openDocument( u ); + + // default handlers + if (m_mimeType == "inode/directory") { + KService::Ptr service = KMimeTypeTrader::self()->preferredService(m_mimeType); + KRun::run(*service, m_urls, ICore::self()->uiController()->activeMainWindow()); + } else { + foreach( const KUrl& u, m_urls ) { + ICore::self()->documentController()->openDocument( u ); + } } } void OpenWithPlugin::open( const QString& storageid ) { KService::Ptr svc = KService::serviceByStorageId( storageid ); if( svc->isApplication() ) { KRun::run( *svc, m_urls, ICore::self()->uiController()->activeMainWindow() ); } else { QString prefName = svc->desktopEntryName(); if ( isTextEditor(svc) ) { // If the user chose a KTE part, lets make sure we're creating a TextDocument instead of // a PartDocument by passing no preferredpart to the documentcontroller // TODO: Solve this rather inside DocumentController prefName = ""; } foreach( const KUrl& u, m_urls ) { ICore::self()->documentController()->openDocument( u, prefName ); } } KConfigGroup config = KSharedConfig::openConfig()->group("Open With Defaults"); if (storageid != config.readEntry(m_mimeType, QString())) { int setDefault = KMessageBox::questionYesNo( qApp->activeWindow(), i18nc("%1: mime type name, %2: app/part name", "Do you want to open all '%1' files by default with %2?", m_mimeType, svc->name() ), i18n("Set as default?"), KStandardGuiItem::yes(), KStandardGuiItem::no(), QString("OpenWith-%1").arg(m_mimeType) ); if (setDefault == KMessageBox::Yes) { config.writeEntry(m_mimeType, storageid); } } } void OpenWithPlugin::openFilesInternal( const KUrl::List& files ) { if (files.isEmpty()) { return; } m_urls = files; m_mimeType = KMimeType::findByUrl( m_urls.first() )->name(); openDefault(); } #include "openwithplugin.moc" diff --git a/plugins/problemreporter/problemmodel.cpp b/plugins/problemreporter/problemmodel.cpp index 066342120e..4f01558d89 100644 --- a/plugins/problemreporter/problemmodel.cpp +++ b/plugins/problemreporter/problemmodel.cpp @@ -1,348 +1,348 @@ /* * KDevelop Problem Reporter * * 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 "problemmodel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "problemreporterplugin.h" #include "watcheddocumentset.h" using namespace KDevelop; ProblemModel::ProblemModel(ProblemReporterPlugin * parent) : QAbstractItemModel(parent), m_plugin(parent), m_lock(QReadWriteLock::Recursive), m_showImports(false), m_severity(ProblemData::Hint), m_documentSet(0) { m_minTimer = new QTimer(this); m_minTimer->setInterval(MinTimeout); m_minTimer->setSingleShot(true); connect(m_minTimer, SIGNAL(timeout()), SLOT(timerExpired())); m_maxTimer = new QTimer(this); m_maxTimer->setInterval(MaxTimeout); m_maxTimer->setSingleShot(true); connect(m_maxTimer, SIGNAL(timeout()), SLOT(timerExpired())); setScope(CurrentDocument); connect(ICore::self()->documentController(), SIGNAL(documentActivated(KDevelop::IDocument*)), SLOT(setCurrentDocument(KDevelop::IDocument*))); // CompletionSettings include a list of todo markers we care for, so need to update connect(ICore::self()->languageController()->completionSettings(), SIGNAL(settingsChanged(ICompletionSettings*)), SLOT(forceFullUpdate())); if (ICore::self()->documentController()->activeDocument()) { setCurrentDocument(ICore::self()->documentController()->activeDocument()); } } const int ProblemModel::MinTimeout = 1000; const int ProblemModel::MaxTimeout = 5000; ProblemModel::~ ProblemModel() { m_problems.clear(); } int ProblemModel::rowCount(const QModelIndex & parent) const { if (!parent.isValid()) return m_problems.count(); return 0; } QString getDisplayUrl(const QString &url, const KUrl &base) { KUrl location(url); QString displayedUrl; if ( location.protocol() == base.protocol() && location.user() == base.user() && location.host() == base.host() ) { bool isParent; displayedUrl = KUrl::relativePath(base.path(), location.path(), &isParent ); if ( !isParent ) { displayedUrl = location.pathOrUrl(); } } else { displayedUrl = location.pathOrUrl(); } return displayedUrl; } QVariant ProblemModel::data(const QModelIndex & index, int role) const { if (!index.isValid()) return QVariant(); DUChainReadLocker lock; ProblemPointer p = problemForIndex(index); KUrl baseDirectory = m_currentDocument.upUrl(); switch (role) { case Qt::DisplayRole: switch (index.column()) { case Source: return p->sourceString(); case Error: return p->description(); case File: return getDisplayUrl(p->finalLocation().document.str(), baseDirectory); case Line: if (p->finalLocation().isValid()) return QString::number(p->finalLocation().start.line + 1); break; case Column: if (p->finalLocation().isValid()) return QString::number(p->finalLocation().start.column + 1); break; } break; case Qt::ToolTipRole: return p->explanation(); default: break; } return QVariant(); } -QModelIndex ProblemModel::parent(const QModelIndex & index) const +QModelIndex ProblemModel::parent(const QModelIndex& /*index*/) const { return QModelIndex(); } QModelIndex ProblemModel::index(int row, int column, const QModelIndex & parent) const { DUChainReadLocker lock(DUChain::lock()); if (row < 0 || column < 0 || column >= LastColumn) return QModelIndex(); if (parent.isValid()) { return QModelIndex(); } if (row < m_problems.count()) return createIndex(row, column); return QModelIndex(); } int ProblemModel::columnCount(const QModelIndex & parent) const { Q_UNUSED(parent) return LastColumn; } KDevelop::ProblemPointer ProblemModel::problemForIndex(const QModelIndex & index) const { return m_problems.at(index.row()); } ProblemReporterPlugin* ProblemModel::plugin() { return m_plugin; } QVariant ProblemModel::headerData(int section, Qt::Orientation orientation, int role) const { Q_UNUSED(orientation); if (role != Qt::DisplayRole) return QVariant(); switch (section) { case Source: return i18nc("@title:column source of problem", "Source"); case Error: return i18nc("@title:column problem description", "Problem"); case File: return i18nc("@title:column file where problem was found", "File"); case Line: return i18nc("@title:column line number with problem", "Line"); case Column: return i18nc("@title:column column number with problem", "Column"); } return QVariant(); } void ProblemModel::problemsUpdated(const KDevelop::IndexedString& url) { QReadLocker locker(&m_lock); if (m_documentSet->get().contains(url)) { // m_minTimer will expire in MinTimeout unless some other parsing job finishes in this period. m_minTimer->start(); // m_maxTimer will expire unconditionally in MaxTimeout if (!m_maxTimer->isActive()) { m_maxTimer->start(); } } } void ProblemModel::timerExpired() { m_minTimer->stop(); m_maxTimer->stop(); rebuildProblemList(); } QList ProblemModel::getProblems(const IndexedString& url, bool showImports) { QList result; QSet visitedContexts; DUChainReadLocker lock; getProblemsInternal(DUChain::self()->chainForDocument(url), showImports, visitedContexts, result); return result; } QList< ProblemPointer > ProblemModel::getProblems(QSet< IndexedString > urls, bool showImports) { QList result; QSet visitedContexts; DUChainReadLocker lock; foreach(const IndexedString& url, urls) { getProblemsInternal(DUChain::self()->chainForDocument(url), showImports, visitedContexts, result); } return result; } void ProblemModel::getProblemsInternal(TopDUContext* context, bool showImports, QSet& visitedContexts, QList& result) { if (!context || visitedContexts.contains(context)) { return; } foreach(ProblemPointer p, context->problems()) { if (p && p->severity() <= m_severity) { result.append(p); } } visitedContexts.insert(context); if (showImports) { bool isProxy = context->parsingEnvironmentFile() && context->parsingEnvironmentFile()->isProxyContext(); foreach(const DUContext::Import &ctx, context->importedParentContexts()) { if(!ctx.indexedContext().indexedTopContext().isLoaded()) continue; TopDUContext* topCtx = dynamic_cast(ctx.context(0)); if(topCtx) { //If we are starting at a proxy-context, only recurse into other proxy-contexts, //because those contain the problems. if(!isProxy || (topCtx->parsingEnvironmentFile() && topCtx->parsingEnvironmentFile()->isProxyContext())) getProblemsInternal(topCtx, showImports, visitedContexts, result); } } } } void ProblemModel::rebuildProblemList() { // No locking here, because it may be called from an already locked context m_problems = getProblems(m_documentSet->get(), m_showImports); reset(); } void ProblemModel::setCurrentDocument(KDevelop::IDocument* document) { QWriteLocker locker(&m_lock); m_currentDocument = document->url(); m_documentSet->setCurrentDocument(IndexedString(m_currentDocument)); reset(); } void ProblemModel::setShowImports(bool showImports) { if (m_showImports != showImports) { QWriteLocker locker(&m_lock); m_showImports = showImports; rebuildProblemList(); } } void ProblemModel::setScope(int scope) { Scope cast_scope = static_cast(scope); QWriteLocker locker(&m_lock); if (!m_documentSet || m_documentSet->getScope() != cast_scope) { if (m_documentSet) { delete m_documentSet; } switch (cast_scope) { case CurrentDocument: m_documentSet = new CurrentDocumentSet(IndexedString(m_currentDocument), this); break; case OpenDocuments: m_documentSet = new OpenDocumentSet(this); break; case CurrentProject: m_documentSet = new CurrentProjectSet(IndexedString(m_currentDocument), this); break; case AllProjects: m_documentSet = new AllProjectSet(this); break; } connect(m_documentSet, SIGNAL(changed()), this, SLOT(documentSetChanged())); rebuildProblemList(); } } void ProblemModel::setSeverity(int severity) { ProblemData::Severity cast_severity = static_cast(severity); if (m_severity != cast_severity) { QWriteLocker locker(&m_lock); m_severity = cast_severity; rebuildProblemList(); } } void ProblemModel::documentSetChanged() { rebuildProblemList(); } void ProblemModel::forceFullUpdate() { m_lock.lockForRead(); QSet documents = m_documentSet->get(); m_lock.unlock(); DUChainReadLocker lock(DUChain::lock()); foreach(const IndexedString& document, documents) { if (document.isEmpty()) continue; TopDUContext::Features updateType = TopDUContext::ForceUpdate; if(documents.size() == 1) updateType = TopDUContext::ForceUpdateRecursive; DUChain::self()->updateContextForUrl(document, (TopDUContext::Features)(updateType | TopDUContext::VisibleDeclarationsAndContexts)); } } diff --git a/plugins/projectfilter/kcm_kdevprojectfilter.desktop b/plugins/projectfilter/kcm_kdevprojectfilter.desktop index 1dae652157..8459b48bbb 100644 --- a/plugins/projectfilter/kcm_kdevprojectfilter.desktop +++ b/plugins/projectfilter/kcm_kdevprojectfilter.desktop @@ -1,60 +1,62 @@ [Desktop Entry] Icon=view-filter Type=Service ServiceTypes=KCModule X-KDE-ModuleType=Library X-KDE-Library=kcm_kdevprojectfilter X-KDE-FactoryName=kcm_kdevprojectfilter X-KDE-ParentApp=kdevplatform X-KDE-ParentComponents=KDevProjectFilter X-KDE-CfgDlgHierarchy=GENERAL Name=Project Filter -Name[bs]=Projekt Filter +Name[bs]=Filter projekta Name[ca]=Filtre de projecte Name[ca@valencia]=Filtre de projecte Name[cs]=Filtr projektů Name[da]=Projektfilter Name[de]=Projektfilter Name[es]=Filtro del proyecto Name[fi]=Projektisuodatin Name[fr]=Filtre de projet Name[gl]=Filtro do proxecto Name[hu]=Projektszűrő Name[it]=Filtro progetto Name[kk]=Жоба сүзгісі Name[nb]=Prosjektfilter Name[nl]=Projectfilter +Name[pl]=Filtr projektu Name[pt]=Filtro de Projectos Name[pt_BR]=Filtro de projetos Name[ru]=Фильтр проекта Name[sk]=Filter projektu Name[sl]=Filter projektov Name[sv]=Projektfilter Name[uk]=Фільтр проекту Name[x-test]=xxProject Filterxx Name[zh_TW]=專案過濾器 Comment=Configure which files and folders inside the project folder should be included or excluded. -Comment[bs]=Konfiguriši koje datoteke i direktoriji unutar projekta trebaju biti uključeni ili isključeni. +Comment[bs]=Konfiguriši koje datoteke i koji direktoriji unutar foldera projekta trebaju biti uključeni ili isključeni. Comment[ca]=Configura quins fitxers i carpetes dins de la carpeta del projecte s'han d'incloure o excloure. Comment[ca@valencia]=Configura quins fitxers i carpetes dins de la carpeta del projecte s'han d'incloure o excloure. Comment[da]=Indstil hvilke filer og mapper i projektmappen der skal medtages eller undtages. Comment[de]=Legt fest, welche Dateien und Ordner innerhalb des Projektordners ein- oder ausgeschlossen werden sollen. Comment[es]=Configura qué archivos y carpetas contenidos en la carpeta del proyecto se deben incluir o excluir. Comment[fi]=Valitse, mitkä projektikansiossa olevat tiedostot ja kansiot pitäisi ottaa mukaan ja mitkä pitäisi jättää pois. Comment[fr]=Configure quels fichiers et dossier au sein du projet doivent être inclus ou exclus. Comment[gl]=Configurar cales ficheiros e cartafoles dentro do cartafol do proxecto deben incluírse ou excluírse. Comment[it]=Configura quali file e cartelle nella cartella del progetto devono essere incluse o escluse. Comment[kk]=Жоба қапшығындағы қай файлдар мен қапшықтарды есепке кіргізу қайсын кіргізбеу керек. Comment[nb]=Bestem hvilke filer og mapper inni prosjektmappa skal tas med eller utelates. Comment[nl]=Stel in welke bestanden en mappen in de projectmap meegenomen of uitgesloten moeten worden. +Comment[pl]=Skonfiguruj jakie pliki i katalogi wewnątrz katalogu projektu mają być uwzględniane albo wykluczane. Comment[pt]=Configura os ficheiros e pastas, dentro da pasta do projecto, que devem ser incluídos ou excluídos. Comment[pt_BR]=Configura os arquivos e pastas, dentro da pasta do projeto, que devem ser incluídos ou excluídos. Comment[ru]=Определяет, какие файлы и папки внутри папки проекта будут включены или исключены из него. Comment[sk]=Nastaviť, ktoré súbory a priečinky v priečinku projektu majú byť zahrnuté alebo vylúčené. Comment[sl]=Nastavi katere datoteke in mape znotraj mape projekta naj bodo vključene ali izključene. Comment[sv]=Anpassa vilka filer och kataloger inne i projektkatalogen som ska inkluderas eller exkluderas. Comment[uk]=За допомогою цього модуля можна визначити, які файли і теки у теці проекту має бути включено або виключено з його складу. Comment[x-test]=xxConfigure which files and folders inside the project folder should be included or excluded.xx Comment[zh_TW]=設定專案資料夾中要包含或排除哪些檔案與資料夾。 diff --git a/plugins/projectfilter/kdevprojectfilter.desktop.cmake b/plugins/projectfilter/kdevprojectfilter.desktop.cmake index 4c33cdbcb1..f5ae474f78 100644 --- a/plugins/projectfilter/kdevprojectfilter.desktop.cmake +++ b/plugins/projectfilter/kdevprojectfilter.desktop.cmake @@ -1,65 +1,67 @@ [Desktop Entry] Type=Service ServiceTypes=KDevelop/Plugin Icon=view-filter X-KDE-Library=kdevprojectfilter X-KDE-PluginInfo-Name=KDevProjectFilter X-KDE-PluginInfo-Author=Milian Wolff X-KDE-PluginInfo-Category=Project Management X-KDevelop-Interfaces=org.kdevelop.IProjectFilter X-KDevelop-Version=@KDEV_PLUGIN_VERSION@ X-KDevelop-Category=Project X-KDevelop-Mode=GUI X-KDevelop-LoadMode=AlwaysOn Name=Project Filter -Name[bs]=Projekt Filter +Name[bs]=Filter projekta Name[ca]=Filtre de projecte Name[ca@valencia]=Filtre de projecte Name[cs]=Filtr projektů Name[da]=Projektfilter Name[de]=Projektfilter Name[es]=Filtro del proyecto Name[fi]=Projektisuodatin Name[fr]=Filtre de projet Name[gl]=Filtro do proxecto Name[hu]=Projektszűrő Name[it]=Filtro progetto Name[kk]=Жоба сүзгісі Name[nb]=Prosjektfilter Name[nl]=Projectfilter +Name[pl]=Filtr projektu Name[pt]=Filtro de Projectos Name[pt_BR]=Filtro de projetos Name[ru]=Фильтр проекта Name[sk]=Filter projektu Name[sl]=Filter projektov Name[sv]=Projektfilter Name[uk]=Фільтр проекту Name[x-test]=xxProject Filterxx Name[zh_TW]=專案過濾器 Comment=Configure which files and folders inside the project folder should be included or excluded. -Comment[bs]=Konfiguriši koje datoteke i direktoriji unutar projekta trebaju biti uključeni ili isključeni. +Comment[bs]=Konfiguriši koje datoteke i koji direktoriji unutar foldera projekta trebaju biti uključeni ili isključeni. Comment[ca]=Configura quins fitxers i carpetes dins de la carpeta del projecte s'han d'incloure o excloure. Comment[ca@valencia]=Configura quins fitxers i carpetes dins de la carpeta del projecte s'han d'incloure o excloure. Comment[da]=Indstil hvilke filer og mapper i projektmappen der skal medtages eller undtages. Comment[de]=Legt fest, welche Dateien und Ordner innerhalb des Projektordners ein- oder ausgeschlossen werden sollen. Comment[es]=Configura qué archivos y carpetas contenidos en la carpeta del proyecto se deben incluir o excluir. Comment[fi]=Valitse, mitkä projektikansiossa olevat tiedostot ja kansiot pitäisi ottaa mukaan ja mitkä pitäisi jättää pois. Comment[fr]=Configure quels fichiers et dossier au sein du projet doivent être inclus ou exclus. Comment[gl]=Configurar cales ficheiros e cartafoles dentro do cartafol do proxecto deben incluírse ou excluírse. Comment[it]=Configura quali file e cartelle nella cartella del progetto devono essere incluse o escluse. Comment[kk]=Жоба қапшығындағы қай файлдар мен қапшықтарды есепке кіргізу қайсын кіргізбеу керек. Comment[nb]=Bestem hvilke filer og mapper inni prosjektmappa skal tas med eller utelates. Comment[nl]=Stel in welke bestanden en mappen in de projectmap meegenomen of uitgesloten moeten worden. +Comment[pl]=Skonfiguruj jakie pliki i katalogi wewnątrz katalogu projektu mają być uwzględniane albo wykluczane. Comment[pt]=Configura os ficheiros e pastas, dentro da pasta do projecto, que devem ser incluídos ou excluídos. Comment[pt_BR]=Configura os arquivos e pastas, dentro da pasta do projeto, que devem ser incluídos ou excluídos. Comment[ru]=Определяет, какие файлы и папки внутри папки проекта будут включены или исключены из него. Comment[sk]=Nastaviť, ktoré súbory a priečinky v priečinku projektu majú byť zahrnuté alebo vylúčené. Comment[sl]=Nastavi katere datoteke in mape znotraj mape projekta naj bodo vključene ali izključene. Comment[sv]=Anpassa vilka filer och kataloger inne i projektkatalogen som ska inkluderas eller exkluderas. Comment[uk]=За допомогою цього модуля можна визначити, які файли і теки у теці проекту має бути включено або виключено з його складу. Comment[x-test]=xxConfigure which files and folders inside the project folder should be included or excluded.xx Comment[zh_TW]=設定專案資料夾中要包含或排除哪些檔案與資料夾。 diff --git a/plugins/projectmanagerview/kdevprojectmanagerview.desktop.cmake b/plugins/projectmanagerview/kdevprojectmanagerview.desktop.cmake index 8f58367517..a89f7d5337 100644 --- a/plugins/projectmanagerview/kdevprojectmanagerview.desktop.cmake +++ b/plugins/projectmanagerview/kdevprojectmanagerview.desktop.cmake @@ -1,114 +1,114 @@ [Desktop Entry] Type=Service Exec=blubb Comment=Lets you manage the project contents. Comment[bs]=Omogućava vam upravljanje sadržajem projekta Comment[ca]=Us permet gestionar els continguts del projecte. Comment[ca@valencia]=Vos permet gestionar els continguts del projecte. Comment[da]=Lader dig håndtere projektets indhold. Comment[de]=Lässt Sie den Inhalt Ihres Projekts verwalten. Comment[el]=Σας επιτρέπει τη διαχείριση του περιεχομένου του έργου Comment[en_GB]=Lets you manage the project contents. Comment[es]=Le permite gestionar el contenido del proyecto. Comment[et]=Projektide sisu haldamine Comment[fi]=Sallii projektien sisällön hallinnan. Comment[fr]=Permet de gérer le contenu des projets. Comment[gl]=Permítelle xestionar os contidos do proxecto. Comment[hu]=Kezelheti a projekttartalmakat. Comment[it]=Consente di gestire il contenuto del progetto. Comment[kk]=Жоба мазмұнын басқаруы. Comment[nb]=Brukes til å styre prosjektinnholdet. Comment[nds]=Dien Projektinholden plegen Comment[nl]=Laat u de projectinhoud beheren. Comment[pl]=Pozwala tobie na zarządzanie zawartością projektu. Comment[pt]=Permite-lhe gerir o conteúdo do projecto. Comment[pt_BR]=Permite-lhe gerenciar o conteúdo do projeto. Comment[ru]=Позволяет управлять содержимым проектов Comment[sk]=Umožní vám spravovať obsah projektu. Comment[sl]=Vam pomaga upravljati z vsebino projekta. Comment[sv]=Låter dig hantera projektets innehåll. Comment[tr]=Proje içindekileri yönetmenizi sağlar. Comment[ug]=قۇرۇلۇش مەزمۇنىنى باشقۇرۇڭ. Comment[uk]=Надає вам змогу керувати вмістом проектів. Comment[x-test]=xxLets you manage the project contents.xx Comment[zh_CN]=让您管理项目内容。 Comment[zh_TW]=讓您管理您的專案內容。 Name=Project Manager View Name[bg]=Преглед на редактора на проекти Name[bs]=Pregled menadžera projekta Name[ca]=Vista del gestor de projectes Name[ca@valencia]=Vista del gestor de projectes Name[da]=Visning af projekthåndtering Name[de]=Ansicht für Projektverwaltung Name[el]=Προβολή διαχειριστή έργου Name[en_GB]=Project Manager View Name[es]=Vista del gestor de proyectos Name[et]=Projektihalduri vaade Name[fi]=Projektihallintanäkymä Name[fr]=Vue du gestionnaire de projets Name[gl]=Vista do xestor de proxectos Name[hu]=Projektkezelő nézet Name[it]=Vista gestore progetto Name[ja]=プロジェクトマネージャのビュー Name[kk]=Жоба менеджер көрінісі Name[nb]=Prosjektbehandlervisning Name[nds]=Projektpleger-Ansicht Name[nl]=Projectbeheerder-overzicht Name[pa]=ਪਰੋਜੈਕਟ ਮੈਨੇਜਰ ਝਲਕ -Name[pl]=Widok menadżera projektu +Name[pl]=Widok zarządcy projektu Name[pt]=Área do Gestor de Projectos Name[pt_BR]=Visualizador do gerenciador de projeto Name[ru]=Панель управления проектами Name[sk]=Pohľad na správcu projektu Name[sl]=Prikaz upravljalnika projektov Name[sv]=Projekthanteringsvy Name[tr]=Proje Yöneticisi Görünümü Name[ug]=قۇرۇلۇش باشقۇرغۇ كۆرۈنۈشى Name[uk]=Перегляд керування проектами Name[x-test]=xxProject Manager Viewxx Name[zh_CN]=工程管理器视图 Name[zh_TW]=專案管理員檢視 GenericName=Project Manager View GenericName[bg]=Преглед на редактора на проекти GenericName[bs]=Pregled menadžera projekta GenericName[ca]=Vista del gestor de projectes GenericName[ca@valencia]=Vista del gestor de projectes GenericName[da]=Visning af projekthåndtering GenericName[de]=Ansicht für Projektverwaltung GenericName[el]=Προβολή διαχειριστή έργου GenericName[en_GB]=Project Manager View GenericName[es]=Vista del gestor de proyectos GenericName[et]=Projektihalduri vaade GenericName[fi]=Projektinhallintanäkymä GenericName[fr]=Vue du gestionnaire de projets GenericName[gl]=Vista do xestor de proxectos GenericName[hu]=Projektkezelő nézet GenericName[it]=Vista gestore progetto GenericName[ja]=プロジェクトマネージャのビュー GenericName[kk]=Жоба менеджер көрінісі GenericName[nb]=Prosjektbehandlervisning GenericName[nds]=Projektpleger-Ansicht GenericName[nl]=Projectbeheerder-overzicht -GenericName[pl]=Widok menadżera projektu +GenericName[pl]=Widok zarządcy projektu GenericName[pt]=Área do Gestor de Projectos GenericName[pt_BR]=Visualizador do gerenciador de projeto GenericName[ru]=Панель управления проектами GenericName[sk]=Pohľad na správcu projektu GenericName[sl]=Prikaz upravljalnika projektov GenericName[sv]=Projekthanteringsvy GenericName[tr]=Proje Yönetimi Görünümü GenericName[ug]=قۇرۇلۇش باشقۇرغۇ كۆرۈنۈشى GenericName[uk]=Перегляд керування проектами GenericName[x-test]=xxProject Manager Viewxx GenericName[zh_CN]=工程管理器视图 GenericName[zh_TW]=專案管理員檢視 Icon=kdevelop ServiceTypes=KDevelop/Plugin X-KDE-Library=kdevprojectmanagerview X-KDE-PluginInfo-Name=KDevProjectManagerView X-KDE-PluginInfo-Author=Roberto Raggi X-KDE-PluginInfo-License=LGPL X-KDE-PluginInfo-Category=Core X-KDevelop-Version=@KDEV_PLUGIN_VERSION@ X-KDevelop-Category=Global X-KDevelop-Mode=GUI diff --git a/plugins/projectmanagerview/projectmanagerview.cpp b/plugins/projectmanagerview/projectmanagerview.cpp index 6ff7af60de..99c940b6c8 100644 --- a/plugins/projectmanagerview/projectmanagerview.cpp +++ b/plugins/projectmanagerview/projectmanagerview.cpp @@ -1,268 +1,268 @@ /* This file is part of KDevelop Copyright 2005 Roberto Raggi Copyright 2007 Andreas Pakulat 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 "projectmanagerview.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../openwith/iopenwith.h" #include #include "tests/modeltest.h" #include "projectmanagerviewplugin.h" #include "vcsoverlayproxymodel.h" #include "ui_projectmanagerview.h" using namespace KDevelop; ProjectManagerViewItemContext::ProjectManagerViewItemContext(const QList< ProjectBaseItem* >& items, ProjectManagerView* view) : ProjectItemContext(items), m_view(view) { } ProjectManagerView *ProjectManagerViewItemContext::view() const { return m_view; } static const char* sessionConfigGroup = "ProjectManagerView"; static const char* splitterStateConfigKey = "splitterState"; static const int projectTreeViewStrechFactor = 75; // % static const int projectBuildSetStrechFactor = 25; // % ProjectManagerView::ProjectManagerView( ProjectManagerViewPlugin* plugin, QWidget *parent ) : QWidget( parent ), m_ui(new Ui::ProjectManagerView), m_plugin(plugin) { m_ui->setupUi( this ); m_ui->projectTreeView->installEventFilter(this); setWindowIcon( QIcon::fromTheme( "project-development" ) ); KConfigGroup pmviewConfig(ICore::self()->activeSession()->config(), sessionConfigGroup); if (pmviewConfig.hasKey(splitterStateConfigKey)) { QByteArray geometry = pmviewConfig.readEntry(splitterStateConfigKey, QByteArray()); m_ui->splitter->restoreState(geometry); } else { m_ui->splitter->setStretchFactor(0, projectTreeViewStrechFactor); m_ui->splitter->setStretchFactor(1, projectBuildSetStrechFactor); } m_syncAction = plugin->actionCollection()->action("locate_document"); Q_ASSERT(m_syncAction); m_syncAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); m_syncAction->setText(i18n("Locate Current Document")); m_syncAction->setToolTip(i18n("Locates the current document in the project tree and selects it.")); m_syncAction->setIcon(QIcon::fromTheme("dirsync")); m_syncAction->setShortcut(Qt::ControlModifier + Qt::Key_Less); connect(m_syncAction, SIGNAL(triggered(bool)), this, SLOT(locateCurrentDocument())); addAction(m_syncAction); updateSyncAction(); addAction(plugin->actionCollection()->action("project_build")); addAction(plugin->actionCollection()->action("project_install")); addAction(plugin->actionCollection()->action("project_clean")); connect(m_ui->projectTreeView, SIGNAL(activate(KDevelop::Path)), this, SLOT(open(KDevelop::Path))); m_ui->buildSetView->setProjectView( this ); m_modelFilter = new ProjectProxyModel( this ); m_modelFilter->setSourceModel(ICore::self()->projectController()->projectModel()); m_overlayProxy = new VcsOverlayProxyModel( this ); m_overlayProxy->setSourceModel(m_modelFilter); m_ui->projectTreeView->setModel( m_overlayProxy ); connect( m_ui->projectTreeView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(selectionChanged()) ); connect( KDevelop::ICore::self()->documentController(), SIGNAL(documentClosed(KDevelop::IDocument*)), SLOT(updateSyncAction())); connect( KDevelop::ICore::self()->documentController(), SIGNAL(documentActivated(KDevelop::IDocument*)), SLOT(updateSyncAction())); connect( qobject_cast(KDevelop::ICore::self()->uiController()->activeMainWindow()), SIGNAL(areaChanged(Sublime::Area*)), SLOT(updateSyncAction())); selectionChanged(); //Update the "sync" button after the initialization has completed, to see whether there already is some open documents QMetaObject::invokeMethod(this, "updateSyncAction", Qt::QueuedConnection); // Need to set this to get horizontal scrollbar. Also needs to be done after // the setModel call m_ui->projectTreeView->header()->setResizeMode( QHeaderView::ResizeToContents ); } bool ProjectManagerView::eventFilter(QObject* obj, QEvent* event) { if (obj == m_ui->projectTreeView) { if (event->type() == QEvent::KeyRelease) { QKeyEvent* keyEvent = static_cast(event); - if (keyEvent->key() == Qt::Key_Delete) { + if (keyEvent->key() == Qt::Key_Delete && keyEvent->modifiers() == Qt::NoModifier) { m_plugin->removeItems(selectedItems()); return true; - } else if (keyEvent->key() == Qt::Key_F2) { + } else if (keyEvent->key() == Qt::Key_F2 && keyEvent->modifiers() == Qt::NoModifier) { m_plugin->renameItems(selectedItems()); return true; } else if (keyEvent->key() == Qt::Key_C && keyEvent->modifiers() == Qt::ControlModifier) { m_plugin->copyFromContextMenu(); return true; } else if (keyEvent->key() == Qt::Key_V && keyEvent->modifiers() == Qt::ControlModifier) { m_plugin->pasteFromContextMenu(); return true; } } } return QObject::eventFilter(obj, event); } void ProjectManagerView::selectionChanged() { m_ui->buildSetView->selectionChanged(); QList selected; foreach( const QModelIndex& idx, m_ui->projectTreeView->selectionModel()->selectedRows() ) { selected << ICore::self()->projectController()->projectModel()->itemFromIndex(indexFromView( idx )); } selected.removeAll(0); KDevelop::ICore::self()->selectionController()->updateSelection( new ProjectManagerViewItemContext( selected, this ) ); } void ProjectManagerView::updateSyncAction() { m_syncAction->setEnabled( KDevelop::ICore::self()->documentController()->activeDocument() ); } ProjectManagerView::~ProjectManagerView() { KConfigGroup pmviewConfig(ICore::self()->activeSession()->config(), sessionConfigGroup); pmviewConfig.writeEntry(splitterStateConfigKey, m_ui->splitter->saveState()); pmviewConfig.sync(); delete m_ui; } QList ProjectManagerView::selectedItems() const { QList items; foreach( const QModelIndex &idx, m_ui->projectTreeView->selectionModel()->selectedIndexes() ) { KDevelop::ProjectBaseItem* item = ICore::self()->projectController()->projectModel()->itemFromIndex(indexFromView(idx)); if( item ) items << item; else kDebug(9511) << "adding an unknown item"; } return items; } void ProjectManagerView::selectItems(const QList< ProjectBaseItem* >& items) { QItemSelection selection; foreach (ProjectBaseItem *item, items) { QModelIndex indx = indexToView(item->index()); selection.append(QItemSelectionRange(indx, indx)); m_ui->projectTreeView->setCurrentIndex(indx); } m_ui->projectTreeView->selectionModel()->select(selection, QItemSelectionModel::ClearAndSelect); } void ProjectManagerView::expandItem(ProjectBaseItem* item) { m_ui->projectTreeView->expand( indexToView(item->index())); } void ProjectManagerView::locateCurrentDocument() { ICore::self()->uiController()->raiseToolView(this); KDevelop::IDocument *doc = ICore::self()->documentController()->activeDocument(); if (!doc) { // in theory we should never get a null pointer as the action is only enabled // when there is an active document. // but: in practice it can happen that you close the last document and press // the shortcut to locate a doc or vice versa... so just do the failsafe thing here... return; } QModelIndex bestMatch; foreach (IProject* proj, ICore::self()->projectController()->projects()) { foreach (KDevelop::ProjectFileItem* item, proj->filesForUrl(doc->url())) { QModelIndex index = indexToView(item->index()); if (index.isValid()) { if (!bestMatch.isValid()) { bestMatch = index; } else if (KDevelop::ProjectBaseItem* parent = item->parent()) { // prefer files in their real folders over the 'copies' in the target folders if (!parent->target()) { bestMatch = index; break; } } } } } if (bestMatch.isValid()) { m_ui->projectTreeView->clearSelection(); m_ui->projectTreeView->setCurrentIndex(bestMatch); m_ui->projectTreeView->expand(bestMatch); m_ui->projectTreeView->scrollTo(bestMatch); } } void ProjectManagerView::open( const Path& path ) { IOpenWith::openFiles(KUrl::List() << path.toUrl()); } QModelIndex ProjectManagerView::indexFromView(const QModelIndex& index) const { return m_modelFilter->mapToSource( m_overlayProxy->mapToSource(index) ); } QModelIndex ProjectManagerView::indexToView(const QModelIndex& index) const { return m_overlayProxy->mapFromSource( m_modelFilter->mapFromSource(index) ); } #include "projectmanagerview.moc" diff --git a/plugins/projectmanagerview/projectmanagerviewplugin.cpp b/plugins/projectmanagerview/projectmanagerviewplugin.cpp index 2d18dcce27..50bac5544f 100644 --- a/plugins/projectmanagerview/projectmanagerviewplugin.cpp +++ b/plugins/projectmanagerview/projectmanagerviewplugin.cpp @@ -1,712 +1,712 @@ /* 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 #include "projectmanagerview.h" using namespace KDevelop; K_PLUGIN_FACTORY(ProjectManagerFactory, registerPlugin(); ) // K_EXPORT_PLUGIN(ProjectManagerFactory(KAboutData("kdevprojectmanagerview","kdevprojectmanagerview", ki18n("Project Management View"), "0.1", ki18n("Toolview to do all the project management stuff"), KAboutData::License_GPL))) class KDevProjectManagerViewFactory: public KDevelop::IToolViewFactory { public: KDevProjectManagerViewFactory( ProjectManagerViewPlugin *plugin ): mplugin( plugin ) {} virtual QWidget* create( QWidget *parent = 0 ) { return new ProjectManagerView( mplugin, parent ); } virtual Qt::DockWidgetArea defaultPosition() { return Qt::LeftDockWidgetArea; } virtual QString id() const { return "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( "kdevprojectmanagerview", parent ), d(new ProjectManagerViewPluginPrivate) { d->m_buildAll = new QAction( i18n("Build all Projects"), this ); d->m_buildAll->setIcon(QIcon::fromTheme("run-build")); connect( d->m_buildAll, SIGNAL(triggered()), this, SLOT(buildAllProjects()) ); actionCollection()->addAction( "project_buildall", d->m_buildAll ); d->m_build = new QAction( i18n("Build Selection"), this ); d->m_build->setIconText( i18n("Build") ); d->m_build->setShortcut( Qt::Key_F8 ); d->m_build->setIcon(QIcon::fromTheme("run-build")); d->m_build->setEnabled( false ); connect( d->m_build, SIGNAL(triggered()), this, SLOT(buildProjectItems()) ); actionCollection()->addAction( "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("run-build-install")); d->m_install->setShortcut( Qt::SHIFT + Qt::Key_F8 ); d->m_install->setEnabled( false ); connect( d->m_install, SIGNAL(triggered()), this, SLOT(installProjectItems()) ); actionCollection()->addAction( "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("run-build-clean")); d->m_clean->setEnabled( false ); connect( d->m_clean, SIGNAL(triggered()), this, SLOT(cleanProjectItems()) ); actionCollection()->addAction( "project_clean", d->m_clean ); d->m_configure = new QAction( i18n("Configure Selection"), this ); d->m_configure->setIconText( i18n("Configure") ); d->m_configure->setIcon(QIcon::fromTheme("run-build-configure")); d->m_configure->setEnabled( false ); connect( d->m_configure, SIGNAL(triggered()), this, SLOT(configureProjectItems()) ); actionCollection()->addAction( "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("run-build-prune")); d->m_prune->setEnabled( false ); connect( d->m_prune, SIGNAL(triggered()), this, SLOT(pruneProjectItems()) ); actionCollection()->addAction( "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( "locate_document" ); setXMLFile( "kdevprojectmanagerview.rc" ); d->factory = new KDevProjectManagerViewFactory( this ); core()->uiController()->addToolView( i18n("Projects"), d->factory ); connect( core()->selectionController(), SIGNAL(selectionChanged(KDevelop::Context*)), SLOT(updateActionState(KDevelop::Context*))); connect( ICore::self()->projectController()->buildSetModel(), SIGNAL(rowsInserted(QModelIndex,int,int)), SLOT(updateFromBuildSetChange())); connect( ICore::self()->projectController()->buildSetModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)), SLOT(updateFromBuildSetChange())); connect( ICore::self()->projectController()->buildSetModel(), SIGNAL(modelReset()), SLOT(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() { kDebug() << "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("document-new")); connect( action, SIGNAL(triggered()), this, SLOT(createFileFromContextMenu()) ); menuExt.addAction( ContextMenuExtension::FileGroup, action ); } if ( needsCreateFolder ) { QAction* action = new QAction( i18n( "Create Folder" ), this ); action->setIcon(QIcon::fromTheme("folder-new")); connect( action, SIGNAL(triggered()), this, SLOT(createFolderFromContextMenu()) ); menuExt.addAction( ContextMenuExtension::FileGroup, action ); } if ( needsBuildItems ) { QAction* action = new QAction( i18nc( "@action", "Build" ), this ); action->setIcon(QIcon::fromTheme("run-build")); connect( action, SIGNAL(triggered()), this, SLOT(buildItemsFromContextMenu()) ); menuExt.addAction( ContextMenuExtension::BuildGroup, action ); action = new QAction( i18nc( "@action", "Install" ), this ); action->setIcon(QIcon::fromTheme("run-install")); connect( action, SIGNAL(triggered()), this, SLOT(installItemsFromContextMenu()) ); menuExt.addAction( ContextMenuExtension::BuildGroup, action ); action = new QAction( i18nc( "@action", "Clean" ), this ); action->setIcon(QIcon::fromTheme("run-clean")); connect( action, SIGNAL(triggered()), this, SLOT(cleanItemsFromContextMenu()) ); menuExt.addAction( ContextMenuExtension::BuildGroup, action ); action = new QAction( i18n( "Add to Build Set" ), this ); action->setIcon(QIcon::fromTheme("list-add")); connect( action, SIGNAL(triggered()), this, SLOT(addItemsFromContextMenuToBuildset()) ); menuExt.addAction( ContextMenuExtension::BuildGroup, action ); } if ( needsCloseProjects ) { QAction* close = new QAction( i18np( "Close Project", "Close Projects", items.count() ), this ); close->setIcon(QIcon::fromTheme("project-development-close")); connect( close, SIGNAL(triggered()), this, SLOT(closeProjects()) ); menuExt.addAction( ContextMenuExtension::ProjectGroup, close ); } if ( needsFolderItems ) { QAction* action = new QAction( i18n( "Reload" ), this ); action->setIcon(QIcon::fromTheme("view-refresh")); connect( action, SIGNAL(triggered()), this, SLOT(reloadFromContextMenu()) ); menuExt.addAction( ContextMenuExtension::FileGroup, action ); } if ( needsRemoveAndRename ) { QAction* remove = new QAction( i18n( "Remove" ), this ); remove->setIcon(QIcon::fromTheme("user-trash")); connect( remove, SIGNAL(triggered()), this, SLOT(removeFromContextMenu()) ); menuExt.addAction( ContextMenuExtension::FileGroup, remove ); QAction* rename = new QAction( i18n( "Rename" ), this ); rename->setIcon(QIcon::fromTheme("edit-rename")); connect( rename, SIGNAL(triggered()), this, SLOT(renameItemFromContextMenu()) ); menuExt.addAction( ContextMenuExtension::FileGroup, rename ); } if ( needsRemoveTargetFiles ) { QAction* remove = new QAction( i18n( "Remove From Target" ), this ); remove->setIcon(QIcon::fromTheme("user-trash")); connect( remove, SIGNAL(triggered()), this, SLOT(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()->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) { kDebug() << "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; qSort(sortedItems.begin(), sortedItems.end(), ProjectBaseItem::pathLessThan); Path lastFolder; QMap< 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 QMap< 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 ); QMap< IBuildSystemManager*, QList > itemsByBuildSystem; foreach(ProjectBaseItem *item, items) itemsByBuildSystem[item->project()->buildSystemManager()].append(item->file()); QMap< 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()), + 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")); + QString name = QInputDialog::getText(window, i18n("Create File in %1", item->path().pathOrUrl()), i18n("File name:")); if(name.isEmpty()) return 0; 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()); KUrl::List urls; foreach (ProjectBaseItem* item, ctx->items()) { if (item->folder() || item->file()) { urls << item->path().toUrl(); } } kDebug() << urls; if (!urls.isEmpty()) { QMimeData *data = new QMimeData; urls.populateMimeData(data); 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().first(); if (!destItem->folder()) return; //do nothing if the target is not a directory const QMimeData* data = qApp->clipboard()->mimeData(); kDebug() << 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/tests/quickopentest.cpp b/plugins/quickopen/tests/quickopentest.cpp index 6459fd5c16..ed8dfbfdbc 100644 --- a/plugins/quickopen/tests/quickopentest.cpp +++ b/plugins/quickopen/tests/quickopentest.cpp @@ -1,303 +1,302 @@ /* * Copyright 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) 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 "quickopentest.h" #include #include #include #include QTEST_MAIN(QuickOpenTest); using namespace KDevelop; QuickOpenTest::QuickOpenTest(QObject* parent) : QuickOpenTestBase(Core::Default, parent) { } void QuickOpenTest::testDuchainFilter() { using ItemList = QList; QFETCH(ItemList, items); QFETCH(QString, filter); QFETCH(ItemList, filtered); auto toStringList = [](const ItemList& items) { QStringList result; for ( const DUChainItem& item: items ) { result << item.m_text; } return result; }; TestFilter filterItems; filterItems.setItems(items); filterItems.setFilter(filter); QCOMPARE(toStringList(filterItems.filteredItems()), toStringList(filtered)); } void QuickOpenTest::testDuchainFilter_data() { using ItemList = QList; QTest::addColumn("items"); QTest::addColumn("filter"); QTest::addColumn("filtered"); auto i = [](const QString& text) { auto item = DUChainItem(); item.m_text = text; return item; }; auto items = ItemList() << i("KTextEditor::Cursor") << i("void KTextEditor::Cursor::explode()") << i("QVector SomeNamespace::SomeClass::func(int)"); QTest::newRow("prefix") << items << "KTE" << ( ItemList() << items.at(0) << items.at(1) ); QTest::newRow("prefix_mismatch") << items << "KTEY" << ( ItemList() ); QTest::newRow("prefix_colon") << items << "KTE:" << ( ItemList() << items.at(0) << items.at(1) ); QTest::newRow("prefix_colon_mismatch") << items << "KTE:Y" << ( ItemList() ); QTest::newRow("prefix_colon_mismatch2") << items << "XKTE:" << ( ItemList() ); QTest::newRow("prefix_two_colon") << items << "KTE::" << ( ItemList() << items.at(0) << items.at(1) ); QTest::newRow("prefix_two_colon_mismatch") << items << "KTE::Y" << ( ItemList() ); QTest::newRow("prefix_two_colon_mismatch2") << items << "XKTE::" << ( ItemList() ); QTest::newRow("suffix") << items << "Curs" << ( ItemList() << items.at(0) << items.at(1) ); QTest::newRow("suffix2") << items << "curs" << ( ItemList() << items.at(0) << items.at(1) ); QTest::newRow("mid") << items << "SomeClass" << ( ItemList() << items.at(2) ); QTest::newRow("mid_abbrev") << items << "SClass" << ( ItemList() << items.at(2) ); } void QuickOpenTest::testAbbreviations() { QFETCH(QStringList, items); QFETCH(QString, filter); QFETCH(QStringList, filtered); PathTestFilter filterItems; filterItems.setItems(items); filterItems.setFilter(filter.split('/', QString::SkipEmptyParts)); QCOMPARE(QStringList(filterItems.filteredItems()), filtered); } void QuickOpenTest::testAbbreviations_data() { QTest::addColumn("items"); QTest::addColumn("filter"); QTest::addColumn("filtered"); const QStringList items = QStringList() << "/foo/bar/caz/a.h" << "/KateThing/CMakeLists.txt" - << "/FooBar/FooBar/Foo.h"; + << "/FooBar/FooBar/Footestfoo.h"; - QTest::newRow("path_segments") << items << "fbc" << ( QStringList() << items.first() ); - QTest::newRow("path_segment_abbrev") << items << "kthing" << ( QStringList() << items.at(1) ); - QTest::newRow("path_segment_abbrev_multi") << items << "kthingcml" << ( QStringList() << items.at(1) ); + QTest::newRow("path_segments") << items << "fbc" << ( QStringList() ); + QTest::newRow("path_segment_abbrev") << items << "cmli" << ( QStringList() << items.at(1) ); QTest::newRow("path_segment_old") << items << "kate/cmake" << ( QStringList() << items.at(1) ); - QTest::newRow("path_segment_multi_mixed") << items << "fbfb/foo.h" << ( QStringList() << items.at(2) ); + QTest::newRow("path_segment_multi_mixed") << items << "ftfoo.h" << ( QStringList() << items.at(2) ); } void QuickOpenTest::testSorting() { QFETCH(QStringList, items); QFETCH(QString, filter); QFETCH(QStringList, filtered); PathTestFilter filterItems; filterItems.setItems(items); filterItems.setFilter(filter.split('/', QString::SkipEmptyParts)); QEXPECT_FAIL("bar7", "empty parts are skipped", Continue); QCOMPARE(QStringList(filterItems.filteredItems()), filtered); } void QuickOpenTest::testSorting_data() { QTest::addColumn("items"); QTest::addColumn("filter"); QTest::addColumn("filtered"); const QStringList items = QStringList() << "/foo/a.h" << "/foo/ab.h" << "/foo/bc.h" << "/bar/a.h"; { QTest::newRow("no-filter") << items << QString() << items; } { const QStringList filtered = QStringList() << "/bar/a.h"; QTest::newRow("bar1") << items << QString("bar") << filtered; QTest::newRow("bar2") << items << QString("/bar") << filtered; QTest::newRow("bar3") << items << QString("/bar/") << filtered; QTest::newRow("bar4") << items << QString("bar/") << filtered; QTest::newRow("bar5") << items << QString("ar/") << filtered; QTest::newRow("bar6") << items << QString("r/") << filtered; QTest::newRow("bar7") << items << QString("b/") << filtered; QTest::newRow("bar8") << items << QString("b/a") << filtered; QTest::newRow("bar9") << items << QString("b/a.h") << filtered; QTest::newRow("bar10") << items << QString("b/a.") << filtered; } { const QStringList filtered = QStringList() << "/foo/a.h" << "/foo/ab.h"; QTest::newRow("foo_a1") << items << QString("foo/a") << filtered; QTest::newRow("foo_a2") << items << QString("/f/a") << filtered; } { // now matches ab.h too because of abbreviation matching, but should be sorted last const QStringList filtered = QStringList() << "/foo/a.h" << "/bar/a.h" << "/foo/ab.h"; QTest::newRow("a_h") << items << QString("a.h") << filtered; } { const QStringList base = QStringList() << "/foo/a_test" << "/foo/test_b_1" << "/foo/test_b"; const QStringList sorted = QStringList() << "/foo/test_b" << "/foo/test_b_1"; QTest::newRow("prefer_exact") << base << QString("test_b") << sorted; } { // from commit: 769491f06a4560a4798592ff060675ffb0d990a6 const QString file = "/myProject/someStrangePath/anItem.cpp"; const QStringList base = QStringList() << "/foo/a" << file; const QStringList filtered = QStringList() << file; QTest::newRow("strange") << base << QString("strange/item") << filtered; } { const QStringList base = QStringList() << "/foo/a_test" << "/foo/test_b_1" << "/foo/test_b" << "/foo/test/a"; const QStringList sorted = QStringList() << "/foo/test_b_1" << "/foo/test_b" << "/foo/a_test" << "/foo/test/a"; QTest::newRow("prefer_start1") << base << QString("test") << sorted; QTest::newRow("prefer_start2") << base << QString("foo/test") << sorted; } { const QStringList base = QStringList() << "/muh/kuh/asdf/foo" << "/muh/kuh/foo/asdf"; const QStringList reverse = QStringList() << "/muh/kuh/foo/asdf" << "/muh/kuh/asdf/foo"; QTest::newRow("prefer_start3") << base << QString("f") << base; QTest::newRow("prefer_start4") << base << QString("/fo") << base; QTest::newRow("prefer_start5") << base << QString("/foo") << base; QTest::newRow("prefer_start6") << base << QString("a") << reverse; QTest::newRow("prefer_start7") << base << QString("/a") << reverse; QTest::newRow("prefer_start8") << base << QString("uh/as") << reverse; QTest::newRow("prefer_start9") << base << QString("asdf") << reverse; } { QTest::newRow("duplicate") << (QStringList() << "/muh/kuh/asdf/foo") << QString("kuh/kuh") << QStringList(); } } void QuickOpenTest::testProjectFileFilter() { KTempDir dir; TestProject* project = new TestProject(Path(dir.name())); ProjectFolderItem* foo = createChild(project->projectItem(), "foo"); createChild(foo, "bar"); createChild(foo, "asdf"); createChild(foo, "space bar"); ProjectFolderItem* asdf = createChild(project->projectItem(), "asdf"); createChild(asdf, "bar"); QTemporaryFile tmpFile; tmpFile.setFileName(dir.name() + "aaaa"); QVERIFY(tmpFile.open()); ProjectFileItem* aaaa = new ProjectFileItem("aaaa", project->projectItem()); QCOMPARE(project->fileSet().size(), 5); ProjectFileDataProvider provider; QCOMPARE(provider.itemCount(), 0u); projectController->addProject(project); const QStringList original = QStringList() << "aaaa" << "asdf/bar" << "foo/asdf" << "foo/bar" << "foo/space bar"; // lazy load QCOMPARE(provider.itemCount(), 0u); provider.reset(); QCOMPARE(items(provider), original); QCOMPARE(provider.itemPath(provider.items().first()), aaaa->path()); QCOMPARE(provider.data(0)->text(), QString("aaaa")); // don't show opened file QVERIFY(core->documentController()->openDocument(KUrl(tmpFile.fileName()))); // lazy load again QCOMPARE(items(provider), original); provider.reset(); QCOMPARE(items(provider), QStringList() << "asdf/bar" << "foo/asdf" << "foo/bar" << "foo/space bar"); // prefer files starting with filter provider.setFilterText("as"); qDebug() << items(provider); QCOMPARE(items(provider), QStringList() << "foo/asdf" << "asdf/bar"); // clear filter provider.reset(); QCOMPARE(items(provider), QStringList() << "asdf/bar" << "foo/asdf" << "foo/bar" << "foo/space bar"); // update on document close, lazy load again core->documentController()->closeAllDocuments(); QCOMPARE(items(provider), QStringList() << "asdf/bar" << "foo/asdf" << "foo/bar" << "foo/space bar"); provider.reset(); QCOMPARE(items(provider), original); ProjectFileItem* blub = createChild(project->projectItem(), "blub"); // lazy load QCOMPARE(provider.itemCount(), 5u); provider.reset(); QCOMPARE(provider.itemCount(), 6u); // ensure we don't add stuff multiple times QMetaObject::invokeMethod(&provider, "fileAddedToSet", Q_ARG(KDevelop::IProject*, project), Q_ARG(KDevelop::IndexedString, blub->indexedPath())); QCOMPARE(provider.itemCount(), 6u); provider.reset(); QCOMPARE(provider.itemCount(), 6u); // lazy load in this implementation delete blub; QCOMPARE(provider.itemCount(), 6u); provider.reset(); QCOMPARE(provider.itemCount(), 5u); QCOMPARE(items(provider), original); // allow filtering by path to project provider.setFilterText(dir.name()); QCOMPARE(items(provider), original); Path buildFolderItem(project->path().parent(), ".build/generated.h"); new ProjectFileItem(project, buildFolderItem, project->projectItem()); // lazy load QCOMPARE(items(provider), original); provider.reset(); QCOMPARE(items(provider), QStringList() << "aaaa" << "asdf/bar" << "foo/asdf" << "foo/bar" << "foo/space bar" << "../.build/generated.h"); projectController->closeProject(project); provider.reset(); QVERIFY(!provider.itemCount()); } #include "quickopentest.moc" diff --git a/plugins/subversion/kdevsvncpp/context.cpp b/plugins/subversion/kdevsvncpp/context.cpp index a90cdd7eb3..703ce81e7d 100644 --- a/plugins/subversion/kdevsvncpp/context.cpp +++ b/plugins/subversion/kdevsvncpp/context.cpp @@ -1,714 +1,722 @@ /* * ==================================================================== * Copyright (c) 2002-2009 The RapidSvn Group. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program (in the file GPL.txt. * If not, see . * * This software consists of voluntary contributions made by many * individuals. For exact contribution history, see the revision * history and logs, available at http://rapidsvn.tigris.org/. * ==================================================================== */ /** * @todo implement 1.3 SVN api: * * SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN * svn_client_add3 * svn_client_copy2 * svn_client_commit3 * svn_client_delete2 * svn_client_move3 * svn_client_mkdir2 * svn_client_import2 * svn_client_info */ // Apache Portable Runtime #include "apr_xlate.h" // Subversion api #include "svn_auth.h" #include "svn_config.h" #include "svn_subst.h" //#include "svn_utf.h" // svncpp #include "kdevsvncpp/apr.hpp" #include "kdevsvncpp/context.hpp" #include "kdevsvncpp/context_listener.hpp" namespace svn { struct Context::Data { public: /** The usage of Apr makes sure Apr is initialized * before any use of apr functions. */ Apr apr; ContextListener * listener; bool logIsSet; int promptCounter; Pool pool; svn_client_ctx_t * ctx; std::string username; std::string password; std::string logMessage; std::string configDir; /** * translate native c-string to utf8 */ static svn_error_t * translateString(const char * str, const char ** newStr, apr_pool_t * /*pool*/) { // due to problems with apr_xlate we dont perform // any conversion at this place. YOU will have to make // sure any strings passed are UTF 8 strings // svn_string_t *string = svn_string_create ("", pool); // // string->data = str; // string->len = strlen (str); // // const char * encoding = APR_LOCALE_CHARSET; // // SVN_ERR (svn_subst_translate_string (&string, string, // encoding, pool)); // // *newStr = string->data; *newStr = str; return SVN_NO_ERROR; } /** * the @a baton is interpreted as Data * * Several checks are performed on the baton: * - baton == 0? * - baton->Data * - listener set? * * @param baton * @param data returned data if everything is OK * @retval SVN_NO_ERROR if everything is fine * @retval SVN_ERR_CANCELLED on invalid values */ static svn_error_t * getData(void * baton, Data ** data) { if (baton == NULL) return svn_error_create(SVN_ERR_CANCELLED, NULL, "invalid baton"); Data * data_ = static_cast (baton); if (data_->listener == 0) return svn_error_create(SVN_ERR_CANCELLED, NULL, "invalid listener"); *data = data_; return SVN_NO_ERROR; } Data(const std::string & configDir_) : listener(0), logIsSet(false), promptCounter(0), configDir(configDir_) { const char * c_configDir = 0; if (configDir.length() > 0) c_configDir = configDir.c_str(); // make sure the configuration directory exists svn_config_ensure(c_configDir, pool); // initialize authentication providers // * simple // * username // * simple prompt // * ssl server trust file // * ssl server trust prompt // * ssl client cert pw file // * ssl client cert pw prompt // * ssl client cert file // =================== // 8 providers apr_array_header_t *providers = apr_array_make(pool, 8, sizeof(svn_auth_provider_object_t *)); svn_auth_provider_object_t *provider; svn_client_get_simple_provider( &provider, pool); *(svn_auth_provider_object_t **)apr_array_push(providers) = provider; svn_client_get_username_provider( &provider, pool); *(svn_auth_provider_object_t **)apr_array_push(providers) = provider; svn_client_get_simple_prompt_provider( &provider, onSimplePrompt, this, 100000000, // not very nice. should be infinite... pool); *(svn_auth_provider_object_t **)apr_array_push(providers) = provider; // add ssl providers // file first then prompt providers svn_client_get_ssl_server_trust_file_provider(&provider, pool); *(svn_auth_provider_object_t **)apr_array_push(providers) = provider; svn_client_get_ssl_client_cert_file_provider(&provider, pool); *(svn_auth_provider_object_t **)apr_array_push(providers) = provider; svn_client_get_ssl_client_cert_pw_file_provider(&provider, pool); *(svn_auth_provider_object_t **)apr_array_push(providers) = provider; svn_client_get_ssl_server_trust_prompt_provider( &provider, onSslServerTrustPrompt, this, pool); *(svn_auth_provider_object_t **)apr_array_push(providers) = provider; // plugged in 3 as the retry limit - what is a good limit? svn_client_get_ssl_client_cert_pw_prompt_provider( &provider, onSslClientCertPwPrompt, this, 3, pool); *(svn_auth_provider_object_t **)apr_array_push(providers) = provider; svn_auth_baton_t *ab; svn_auth_open(&ab, providers, pool); // initialize ctx structure svn_client_create_context(&ctx, pool); // get the config based on the configDir passed in svn_config_get_config(&ctx->config, c_configDir, pool); + // disable external diff and diff3 commands + svn_config_t *config = (svn_config_t *)apr_hash_get( + ctx->config, SVN_CONFIG_CATEGORY_CONFIG, APR_HASH_KEY_STRING); + svn_config_set(config, SVN_CONFIG_SECTION_HELPERS, + SVN_CONFIG_OPTION_DIFF_CMD, NULL); + svn_config_set(config, SVN_CONFIG_SECTION_HELPERS, + SVN_CONFIG_OPTION_DIFF3_CMD, NULL); + // tell the auth functions where the config is svn_auth_set_parameter(ab, SVN_AUTH_PARAM_CONFIG_DIR, c_configDir); ctx->auth_baton = ab; ctx->log_msg_func = onLogMsg; ctx->log_msg_baton = this; ctx->notify_func = onNotify; ctx->notify_baton = this; ctx->cancel_func = onCancel; ctx->cancel_baton = this; #if (SVN_VER_MAJOR >= 1) && (SVN_VER_MINOR >= 2) ctx->notify_func2 = onNotify2; ctx->notify_baton2 = this; #endif } void setAuthCache(bool value) { void *param = 0; if (!value) param = (void *)"1"; svn_auth_set_parameter(ctx->auth_baton, SVN_AUTH_PARAM_NO_AUTH_CACHE, param); } /** @see Context::setLogin */ void setLogin(const char * usr, const char * pwd) { username = usr; password = pwd; svn_auth_baton_t * ab = ctx->auth_baton; svn_auth_set_parameter(ab, SVN_AUTH_PARAM_DEFAULT_USERNAME, username.c_str()); svn_auth_set_parameter(ab, SVN_AUTH_PARAM_DEFAULT_PASSWORD, password.c_str()); } /** @see Context::setLogMessage */ void setLogMessage(const char * msg) { logMessage = msg; logIsSet = true; } /** * this function gets called by the subversion api function * when a log message is needed. This is the case on a commit * for example */ static svn_error_t * onLogMsg(const char **log_msg, const char **tmp_file, apr_array_header_t *, //UNUSED commit_items void *baton, apr_pool_t * pool) { Data * data = NULL; SVN_ERR(getData(baton, &data)); std::string msg; if (data->logIsSet) msg = data->getLogMessage(); else { if (!data->retrieveLogMessage(msg)) return svn_error_create(SVN_ERR_CANCELLED, NULL, ""); } *log_msg = apr_pstrdup(pool, msg.c_str()); *tmp_file = NULL; return SVN_NO_ERROR; } /** * this is the callback function for the subversion * api functions to signal the progress of an action */ static void onNotify(void * baton, const char *path, svn_wc_notify_action_t action, svn_node_kind_t kind, const char *mime_type, svn_wc_notify_state_t content_state, svn_wc_notify_state_t prop_state, svn_revnum_t revision) { if (baton == 0) return; Data * data = static_cast (baton); data->notify(path, action, kind, mime_type, content_state, prop_state, revision); } #if (SVN_VER_MAJOR >= 1) && (SVN_VER_MINOR >= 2) /** * this is the callback function for the subversion 1.2 * api functions to signal the progress of an action * * @todo right now we forward only to @a onNotify, * but maybe we should a notify2 to the listener * @since subversion 1.2 */ static void onNotify2(void*baton,const svn_wc_notify_t *action,apr_pool_t *) { if (!baton) return; // for now forward the call to @a onNotify onNotify(baton, action->path, action->action, action->kind, action->mime_type, action->content_state, action->prop_state, action->revision); } #endif /** * this is the callback function for the subversion * api functions to signal the progress of an action */ static svn_error_t * onCancel(void * baton) { if (baton == 0) return SVN_NO_ERROR; Data * data = static_cast (baton); if (data->cancel()) return svn_error_create(SVN_ERR_CANCELLED, NULL, "cancelled by user"); else return SVN_NO_ERROR; } /** * @see svn_auth_simple_prompt_func_t */ static svn_error_t * onSimplePrompt(svn_auth_cred_simple_t **cred, void *baton, const char *realm, const char *username, svn_boolean_t _may_save, apr_pool_t *pool) { Data * data = NULL; SVN_ERR(getData(baton, &data)); bool may_save = _may_save != 0; if (!data->retrieveLogin(username, realm, may_save)) return svn_error_create(SVN_ERR_CANCELLED, NULL, ""); svn_auth_cred_simple_t* lcred = (svn_auth_cred_simple_t*) apr_palloc(pool, sizeof(svn_auth_cred_simple_t)); /* SVN_ERR (svn_utf_cstring_to_utf8 ( &lcred->password, data->getPassword (), pool)); SVN_ERR (svn_utf_cstring_to_utf8 ( &lcred->username, data->getUsername (), pool)); */ lcred->password = data->getPassword(); lcred->username = data->getUsername(); // tell svn if the credentials need to be saved lcred->may_save = may_save; *cred = lcred; return SVN_NO_ERROR; } /** * @see svn_auth_ssl_server_trust_prompt_func_t */ static svn_error_t * onSslServerTrustPrompt(svn_auth_cred_ssl_server_trust_t **cred, void *baton, const char *realm, apr_uint32_t failures, const svn_auth_ssl_server_cert_info_t *info, svn_boolean_t may_save, apr_pool_t *pool) { Data * data = NULL; SVN_ERR(getData(baton, &data)); ContextListener::SslServerTrustData trustData(failures); if (realm != NULL) trustData.realm = realm; trustData.hostname = info->hostname; trustData.fingerprint = info->fingerprint; trustData.validFrom = info->valid_from; trustData.validUntil = info->valid_until; trustData.issuerDName = info->issuer_dname; trustData.maySave = may_save != 0; apr_uint32_t acceptedFailures; ContextListener::SslServerTrustAnswer answer = data->listener->contextSslServerTrustPrompt( trustData, acceptedFailures); if (answer == ContextListener::DONT_ACCEPT) *cred = NULL; else { svn_auth_cred_ssl_server_trust_t *cred_ = (svn_auth_cred_ssl_server_trust_t*) apr_palloc(pool, sizeof(svn_auth_cred_ssl_server_trust_t)); if (answer == ContextListener::ACCEPT_PERMANENTLY) { cred_->may_save = 1; cred_->accepted_failures = acceptedFailures; } *cred = cred_; } return SVN_NO_ERROR; } /** * @see svn_auth_ssl_client_cert_prompt_func_t */ static svn_error_t * onSslClientCertPrompt(svn_auth_cred_ssl_client_cert_t **cred, void *baton, apr_pool_t *pool) { Data * data = NULL; SVN_ERR(getData(baton, &data)); std::string certFile; if (!data->listener->contextSslClientCertPrompt(certFile)) return svn_error_create(SVN_ERR_CANCELLED, NULL, ""); svn_auth_cred_ssl_client_cert_t *cred_ = (svn_auth_cred_ssl_client_cert_t*) apr_palloc(pool, sizeof(svn_auth_cred_ssl_client_cert_t)); /* SVN_ERR (svn_utf_cstring_to_utf8 ( &cred_->cert_file, certFile.c_str (), pool)); */ cred_->cert_file = certFile.c_str(); *cred = cred_; return SVN_NO_ERROR; } /** * @see svn_auth_ssl_client_cert_pw_prompt_func_t */ static svn_error_t * onSslClientCertPwPrompt( svn_auth_cred_ssl_client_cert_pw_t **cred, void *baton, const char *realm, svn_boolean_t maySave, apr_pool_t *pool) { Data * data = NULL; SVN_ERR(getData(baton, &data)); std::string password; bool may_save = maySave != 0; if (!data->listener->contextSslClientCertPwPrompt(password, realm, may_save)) return svn_error_create(SVN_ERR_CANCELLED, NULL, ""); svn_auth_cred_ssl_client_cert_pw_t *cred_ = (svn_auth_cred_ssl_client_cert_pw_t *) apr_palloc(pool, sizeof(svn_auth_cred_ssl_client_cert_pw_t)); /* SVN_ERR (svn_utf_cstring_to_utf8 ( &cred_->password, password.c_str (), pool)); */ cred_->password = password.c_str(); cred_->may_save = may_save; *cred = cred_; return SVN_NO_ERROR; } const char * getUsername() const { return username.c_str(); } const char * getPassword() const { return password.c_str(); } const char * getLogMessage() const { return logMessage.c_str(); } /** * if the @a listener is set, use it to retrieve the log * message using ContextListener::contextGetLogMessage. * This return values is given back, then. * * if the @a listener is not set the its checked whether * the log message has been set using @a setLogMessage * yet. If not, return false otherwise true * * @param msg log message * @retval false cancel */ bool retrieveLogMessage(std::string & msg) { bool ok; if (listener == 0) return false; ok = listener->contextGetLogMessage(logMessage); if (ok) msg = logMessage; else logIsSet = false; return ok; } /** * if the @a listener is set and no password has been * set yet, use it to retrieve login and password using * ContextListener::contextGetLogin. * * if the @a listener is not set, check if setLogin * has been called yet. * * @return continue? * @retval false cancel */ bool retrieveLogin(const char * username_, const char * realm, bool &may_save) { bool ok; if (listener == 0) return false; if (username_ == NULL) username = ""; else username = username_; ok = listener->contextGetLogin(realm, username, password, may_save); return ok; } /** * if the @a listener is set call the method * @a contextNotify */ void notify(const char *path, svn_wc_notify_action_t action, svn_node_kind_t kind, const char *mime_type, svn_wc_notify_state_t content_state, svn_wc_notify_state_t prop_state, svn_revnum_t revision) { if (listener != 0) { listener->contextNotify(path, action, kind, mime_type, content_state, prop_state, revision); } } /** * if the @a listener is set call the method * @a contextCancel */ bool cancel() { if (listener != 0) { return listener->contextCancel(); } else { // don't cancel if no listener return false; } } }; Context::Context(const std::string &configDir) { m = new Data(configDir); } Context::Context(const Context & src) { m = new Data(src.m->configDir); setLogin(src.getUsername(), src.getPassword()); } Context::~Context() { delete m; } void Context::setAuthCache(bool value) { m->setAuthCache(value); } void Context::setLogin(const char * username, const char * password) { m->setLogin(username, password); } Context::operator svn_client_ctx_t * () { return m->ctx; } svn_client_ctx_t * Context::ctx() { return m->ctx; } void Context::setLogMessage(const char * msg) { m->setLogMessage(msg); } const char * Context::getUsername() const { return m->getUsername(); } const char * Context::getPassword() const { return m->getPassword(); } const char * Context::getLogMessage() const { return m->getLogMessage(); } void Context::setListener(ContextListener * listener) { m->listener = listener; } ContextListener * Context::getListener() const { return m->listener; } void Context::reset() { m->promptCounter = 0; m->logIsSet = false; } } /* ----------------------------------------------------------------- * local variables: * eval: (load-file "../../rapidsvn-dev.el") * end: */ diff --git a/plugins/subversion/svndiffjob.cpp b/plugins/subversion/svndiffjob.cpp index 2735389474..633f7e1f80 100644 --- a/plugins/subversion/svndiffjob.cpp +++ b/plugins/subversion/svndiffjob.cpp @@ -1,475 +1,474 @@ /*************************************************************************** * 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 "svndiffjob.h" #include "svndiffjob_p.h" #include #include #include #include #include #include #include #include #include #include "kdevsvncpp/path.hpp" #include "kdevsvncpp/revision.hpp" #include "icore.h" #include "iruncontroller.h" #include "svnclient.h" #include "svncatjob.h" ///@todo The subversion library returns borked diffs, where the headers are at the end. This function /// takes those headers, and moves them into the correct place to create a valid working diff. /// Find the source of this problem. QString repairDiff(QString diff) { kDebug() << "diff before repair:" << diff; QStringList lines = diff.split('\n'); QMap headers; for(int a = 0; a < lines.size()-1; ++a) { if(lines[a].startsWith("Index: ") && lines[a+1].startsWith("=====")) { QString fileName = lines[a].mid(strlen("Index: ")).trimmed(); headers[fileName] = lines[a]; kDebug() << "found header for" << fileName; lines[a] = QString(); if(lines[a+1].startsWith("======")) { headers[fileName] += '\n' + lines[a+1]; lines[a+1] = QString(); } } } QRegExp spaceRegExp("\\s"); for(int a = 0; a < lines.size()-1; ++a) { if(lines[a].startsWith("--- ")) { QString tail = lines[a].mid(strlen("--- ")); if(tail.indexOf(spaceRegExp) != -1) { QString file = tail.left(tail.indexOf(spaceRegExp)); kDebug() << "checking for" << file; if(headers.contains(file)) { kDebug() << "adding header for" << file << ":" << headers[file]; lines[a] = headers[file] + '\n' + lines[a]; } } } } QString ret = lines.join("\n"); kDebug() << "repaired diff:" << ret; return ret; } //@TODO: Handle raw diffs by using SvnCatJob to fetch both files/revisions SvnInternalDiffJob::SvnInternalDiffJob( SvnJobBase* parent ) : SvnInternalJobBase( parent ), m_recursive( true ), m_ignoreAncestry( false ), m_ignoreContentType( false ), m_noDiffOnDelete( false ) { m_pegRevision.setRevisionValue( KDevelop::VcsRevision::Head, KDevelop::VcsRevision::Special ); } void SvnInternalDiffJob::run() { initBeforeRun(); SvnClient cli(m_ctxt); try { QString diff; if( destination().isValid() ) { QByteArray srcba; if( source().type() == KDevelop::VcsLocation::LocalLocation ) { KUrl url = source().localUrl(); if( url.isLocalFile() ) { srcba = url.toLocalFile( KUrl::RemoveTrailingSlash ).toUtf8(); }else { srcba = url.url( KUrl::RemoveTrailingSlash ).toUtf8(); } }else { srcba = source().repositoryServer().toUtf8(); } QByteArray dstba; if( destination().type() == KDevelop::VcsLocation::LocalLocation ) { KUrl url = destination().localUrl(); if( url.isLocalFile() ) { dstba = url.toLocalFile( KUrl::RemoveTrailingSlash ).toUtf8(); }else { dstba = url.url().toUtf8(); } }else { dstba = destination().repositoryServer().toUtf8(); } svn::Revision srcRev = createSvnCppRevisionFromVcsRevision( srcRevision() ); svn::Revision dstRev = createSvnCppRevisionFromVcsRevision( dstRevision() ); if( srcba.isEmpty() || ( dstba.isEmpty() && srcRev.kind() == svn_opt_revision_unspecified && dstRev.kind() == svn_opt_revision_unspecified ) ) { throw svn::ClientException( "Not enough information for a diff"); } diff = cli.diff( svn::Path( srcba.data() ), srcRev, svn::Path( dstba.data() ), dstRev, recursive(), ignoreAncestry(), noDiffOnDelete(), ignoreContentType() ); }else { QByteArray srcba; if( source().type() == KDevelop::VcsLocation::LocalLocation ) { KUrl url = source().localUrl(); if( url.isLocalFile() ) { srcba = url.toLocalFile( KUrl::RemoveTrailingSlash ).toUtf8(); }else { srcba = url.url().toUtf8(); } }else { srcba = source().repositoryServer().toUtf8(); } svn::Revision pegRev = createSvnCppRevisionFromVcsRevision( pegRevision() ); svn::Revision srcRev = createSvnCppRevisionFromVcsRevision( srcRevision() ); svn::Revision dstRev = createSvnCppRevisionFromVcsRevision( dstRevision() ); if( srcba.isEmpty() || pegRev.kind() == svn_opt_revision_unspecified || dstRev.kind() == svn_opt_revision_unspecified || srcRev.kind() == svn_opt_revision_unspecified) { throw svn::ClientException( "Not enough information for a diff"); } - //@TODO: Make sure there's no diff-cmd set via the users configuration file, can be done only via C api diff = cli.diff( svn::Path( srcba.data() ), pegRev, srcRev, dstRev, recursive(), ignoreAncestry(), noDiffOnDelete(), ignoreContentType() ); } diff = repairDiff(diff); emit gotDiff( diff ); }catch( svn::ClientException ce ) { kDebug(9510) << "Exception while doing a diff: " << m_source.localUrl() << m_source.repositoryServer() << m_srcRevision.prettyValue() << m_destination.localUrl() << m_destination.repositoryServer() << m_dstRevision.prettyValue() << QString::fromUtf8( ce.message() ); setErrorMessage( QString::fromUtf8( ce.message() ) ); m_success = false; } } void SvnInternalDiffJob::setSource( const KDevelop::VcsLocation& src ) { QMutexLocker l( m_mutex ); m_source = src; } void SvnInternalDiffJob::setDestination( const KDevelop::VcsLocation& dst ) { QMutexLocker l( m_mutex ); m_destination = dst; } void SvnInternalDiffJob::setSrcRevision( const KDevelop::VcsRevision& srcRev ) { QMutexLocker l( m_mutex ); m_srcRevision = srcRev; } void SvnInternalDiffJob::setDstRevision( const KDevelop::VcsRevision& dstRev ) { QMutexLocker l( m_mutex ); m_dstRevision = dstRev; } void SvnInternalDiffJob::setPegRevision( const KDevelop::VcsRevision& pegRev ) { QMutexLocker l( m_mutex ); m_pegRevision = pegRev; } void SvnInternalDiffJob::setRecursive( bool recursive ) { QMutexLocker l( m_mutex ); m_recursive = recursive; } void SvnInternalDiffJob::setIgnoreAncestry( bool ignoreAncestry ) { QMutexLocker l( m_mutex ); m_ignoreAncestry = ignoreAncestry; } void SvnInternalDiffJob::setIgnoreContentType( bool ignoreContentType ) { QMutexLocker l( m_mutex ); m_ignoreContentType = ignoreContentType; } void SvnInternalDiffJob::setNoDiffOnDelete( bool noDiffOnDelete ) { QMutexLocker l( m_mutex ); m_noDiffOnDelete = noDiffOnDelete; } bool SvnInternalDiffJob::recursive() const { QMutexLocker l( m_mutex ); return m_recursive; } bool SvnInternalDiffJob::ignoreAncestry() const { QMutexLocker l( m_mutex ); return m_ignoreAncestry; } bool SvnInternalDiffJob::ignoreContentType() const { QMutexLocker l( m_mutex ); return m_ignoreContentType; } bool SvnInternalDiffJob::noDiffOnDelete() const { QMutexLocker l( m_mutex ); return m_noDiffOnDelete; } KDevelop::VcsLocation SvnInternalDiffJob::source() const { QMutexLocker l( m_mutex ); return m_source; } KDevelop::VcsLocation SvnInternalDiffJob::destination() const { QMutexLocker l( m_mutex ); return m_destination; } KDevelop::VcsRevision SvnInternalDiffJob::srcRevision() const { QMutexLocker l( m_mutex ); return m_srcRevision; } KDevelop::VcsRevision SvnInternalDiffJob::dstRevision() const { QMutexLocker l( m_mutex ); return m_dstRevision; } KDevelop::VcsRevision SvnInternalDiffJob::pegRevision() const { QMutexLocker l( m_mutex ); return m_pegRevision; } SvnDiffJob::SvnDiffJob( KDevSvnPlugin* parent ) : SvnJobBase( parent, KDevelop::OutputJob::Silent ) { setType( KDevelop::VcsJob::Add ); m_job = new SvnInternalDiffJob( this ); setObjectName(i18n("Subversion Diff")); } QVariant SvnDiffJob::fetchResults() { return qVariantFromValue( m_diff ); } void SvnDiffJob::start() { disconnect( m_job, SIGNAL(done(ThreadWeaver::Job*)), this, SLOT(internalJobDone(ThreadWeaver::Job*)) ); if( !m_job->source().isValid() || ( !m_job->destination().isValid() && ( m_job->srcRevision().revisionType() == KDevelop::VcsRevision::Invalid || m_job->dstRevision().revisionType() == KDevelop::VcsRevision::Invalid ) ) ) { internalJobFailed( m_job ); setErrorText( i18n( "Not enough information given to execute diff" ) ); }else { connect( m_job, SIGNAL(gotDiff(QString)), this, SLOT(setDiff(QString)), Qt::QueuedConnection ); ThreadWeaver::Weaver::instance()->enqueue( m_job ); } } SvnInternalJobBase* SvnDiffJob::internalJob() const { return m_job; } void SvnDiffJob::setSource( const KDevelop::VcsLocation& source ) { if( status() == KDevelop::VcsJob::JobNotStarted ) m_job->setSource( source ); } void SvnDiffJob::setDestination( const KDevelop::VcsLocation& destination ) { if( status() == KDevelop::VcsJob::JobNotStarted ) m_job->setDestination( destination ); } void SvnDiffJob::setPegRevision( const KDevelop::VcsRevision& pegRevision ) { if( status() == KDevelop::VcsJob::JobNotStarted ) m_job->setPegRevision( pegRevision ); } void SvnDiffJob::setSrcRevision( const KDevelop::VcsRevision& srcRevision ) { if( status() == KDevelop::VcsJob::JobNotStarted ) m_job->setSrcRevision( srcRevision ); } void SvnDiffJob::setDstRevision( const KDevelop::VcsRevision& dstRevision ) { if( status() == KDevelop::VcsJob::JobNotStarted ) m_job->setDstRevision( dstRevision ); } void SvnDiffJob::setRecursive( bool recursive ) { if( status() == KDevelop::VcsJob::JobNotStarted ) m_job->setRecursive( recursive ); } void SvnDiffJob::setIgnoreAncestry( bool ignoreAncestry ) { if( status() == KDevelop::VcsJob::JobNotStarted ) m_job->setIgnoreAncestry( ignoreAncestry ); } void SvnDiffJob::setIgnoreContentType( bool ignoreContentType ) { if( status() == KDevelop::VcsJob::JobNotStarted ) m_job->setIgnoreContentType( ignoreContentType ); } void SvnDiffJob::setNoDiffOnDelete( bool noDiffOnDelete ) { if( status() == KDevelop::VcsJob::JobNotStarted ) m_job->setNoDiffOnDelete( noDiffOnDelete ); } void SvnDiffJob::setDiff( const QString& diff ) { m_diff = KDevelop::VcsDiff(); m_diff.setBaseDiff(KUrl("/")); m_diff.setType( KDevelop::VcsDiff::DiffUnified ); m_diff.setContentType( KDevelop::VcsDiff::Text ); m_diff.setDiff( diff ); QRegExp fileRe("(?:^|\n)Index: ([^\n]+)\n"); QStringList paths; int pos = 0; while( ( pos = fileRe.indexIn( diff, pos ) ) != -1 ) { paths << fileRe.cap(1); pos += fileRe.matchedLength(); } if (paths.isEmpty()) { internalJobDone( m_job ); emit resultsReady( this ); return; } foreach( const QString &s, paths ) { if( !s.isEmpty() ) { SvnCatJob* job = new SvnCatJob( m_part ); KDevelop::VcsLocation l = m_job->source(); if( l.type() == KDevelop::VcsLocation::LocalLocation ) { l.setLocalUrl( KUrl( s ) ); }else { QString repoLocation = KUrl( l.repositoryServer() ).toLocalFile( KUrl::RemoveTrailingSlash ); QFileInfo fi( repoLocation ); if( s == fi.fileName() ) { l.setRepositoryServer( l.repositoryServer() ); }else { l.setRepositoryServer( l.repositoryServer() + '/' + s ); } } job->setSource( l ); job->setPegRevision( m_job->pegRevision() ); job->setSrcRevision( m_job->srcRevision() ); m_catJobMap[job] = l; connect( job, SIGNAL(resultsReady(KDevelop::VcsJob*)), this, SLOT(addLeftText(KDevelop::VcsJob*)) ); connect( job, SIGNAL(result(KJob*)), this, SLOT(removeJob(KJob*)) ); KDevelop::ICore::self()->runController()->registerJob(job); } } } void SvnDiffJob::addLeftText( KDevelop::VcsJob* job ) { if( m_catJobMap.contains( job ) ) { QVariant v = job->fetchResults(); m_diff.addLeftText( m_catJobMap[job], v.toString() ); m_catJobMap.remove(job); // KJobs delete themselves when finished } if( m_catJobMap.isEmpty() ) { internalJobDone( m_job ); emit resultsReady( this ); } } void SvnDiffJob::removeJob( KJob* job ) { if( job->error() != 0 ) { KDevelop::VcsJob* j = dynamic_cast( job ); if( j ) { if( m_catJobMap.contains( j ) ) { m_catJobMap.remove(j); // KJobs delete themselves when finished } } } if( m_catJobMap.isEmpty() ) { internalJobDone( m_job ); emit resultsReady( this ); } } void SvnDiffJob::setDiffType( KDevelop::VcsDiff::Type type ) { m_diffType = type; } diff --git a/shell/assistantpopup.cpp b/shell/assistantpopup.cpp index 696eaeae1f..95bd979942 100644 --- a/shell/assistantpopup.cpp +++ b/shell/assistantpopup.cpp @@ -1,228 +1,248 @@ /* Copyright 2009 David Nolden Copyright 2012 Milian Wolff Copyright 2014 Sven Brauch 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 "assistantpopup.h" #include "sublime/holdupdates.h" #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; AssistantPopup::AssistantPopup(KTextEditor::View* parent, const IAssistant::Ptr& assistant) : m_assistant(assistant) , m_view(parent) + , m_shownAtBottom(false) { #if 0 // TODO KF5: Port me QPalette p = palette(); p.setColor(QPalette::Window, Qt::transparent); setPalette(p); setBackgroundRole(QPalette::Window); setBackgroundBrush(QBrush(QColor(0, 0, 0, 0))); #endif setResizeMode(QQuickView::SizeViewToRootObject); m_config = new AssistantPopupConfig(this); auto view = ICore::self()->documentController()->activeTextDocumentView(); m_config->setColorsFromView(view); updateActions(); rootContext()->setContextProperty("config", QVariant::fromValue(m_config)); setSource(QUrl(KStandardDirs::locate("data", "kdevelop/assistantpopup.qml"))); Q_ASSERT(assistant); if ( ! rootObject() ) { kWarning() << "Failed to load assistant markup! The assistant will not work."; return; } connect(m_view, SIGNAL(verticalScrollPositionChanged(KTextEditor::View*,KTextEditor::Cursor)), - this, SLOT(updatePosition())); + this, SLOT(updatePosition(KTextEditor::View*,KTextEditor::Cursor))); - updatePosition(); - connect(this, SIGNAL(sceneResized(QSize)), - this, SLOT(updatePosition())); + updatePosition(m_view, KTextEditor::Cursor::invalid()); connect(m_view, SIGNAL(destroyed(QObject*)), this, SLOT(deleteLater())); m_view->installEventFilter(this); - m_view->setFocus(); +} + +bool AssistantPopup::viewportEvent(QEvent *event) +{ + // For some reason, QGraphicsView posts a WindowActivate event + // when it is shown, even if disabled through setting the WA_ShowWithoutActivate + // attribute. This causes all focus-driven popups (QuickOpen, tooltips, ...) + // to hide when the assistant opens. Thus, prevent it from processing the Show event here. + if ( event->type() == QEvent::Show ) { + return true; + } + return QGraphicsView::viewportEvent(event); } AssistantPopupConfig::AssistantPopupConfig(QObject *parent): QObject(parent) { } void AssistantPopupConfig::setColorsFromView(QObject *view) { auto iface = dynamic_cast(view); Q_ASSERT(iface); m_foreground = iface->configValue("line-number-color").value(); m_background = iface->configValue("icon-border-color").value(); m_highlight = iface->configValue("folding-marker-color").value(); if ( KColorUtils::luma(m_background) < 0.3 ) { m_foreground = KColorUtils::lighten(m_foreground, 0.7); } const float lumaDiff = KColorUtils::luma(m_highlight) - KColorUtils::luma(m_background); if ( fabs(lumaDiff) < 0.5 ) { m_highlight = QColor::fromHsv(m_highlight.hue(), qMin(255, m_highlight.saturation() + 80), lumaDiff > 0 ? qMin(255, m_highlight.value() + 120) : qMax(80, m_highlight.value() - 40)); } } static QWidget* findByClassname(KTextEditor::View* view, const QString& klass) { auto children = view->findChildren(); for ( auto child: children ) { if ( child->metaObject()->className() == klass ) { return child; } } return static_cast(nullptr); }; QRect AssistantPopup::textWidgetGeometry(KTextEditor::View *view) const { // Subtract the width of the right scrollbar int scrollbarWidth = 0; if ( auto scrollbar = findByClassname(view, "KateScrollBar") ) { scrollbarWidth = scrollbar->width(); } // Subtract the width of the bottom scrollbar int bottomScrollbarWidth = 0; if ( auto bottom = findByClassname(view, "QScrollBar") ) { bottomScrollbarWidth = bottom->height(); } auto geom = view->geometry(); geom.adjust(0, 0, -scrollbarWidth, -bottomScrollbarWidth); return geom; } void AssistantPopup::keyReleaseEvent(QKeyEvent *event) { if ( event->key() == Qt::Key_Alt ) { m_view->setFocus(); emit m_config->shouldShowHighlight(false); } QQuickView::keyReleaseEvent(event); } bool AssistantPopup::eventFilter(QObject* object, QEvent* event) { Q_ASSERT(object == m_view); Q_UNUSED(object); if (event->type() == QEvent::Resize) { - updatePosition(); + updatePosition(m_view, KTextEditor::Cursor::invalid()); } else if (event->type() == QEvent::Hide) { executeHideAction(); } else if (event->type() == QEvent::KeyPress) { // While the Alt key is pressed, give focus to the assistant widget // and notify it about that. auto modifiers = static_cast(event)->modifiers(); if (modifiers == Qt::AltModifier) { #if 0 // TODO KF5 Needed? setFocus(); #endif emit m_config->shouldShowHighlight(true); return true; } if (static_cast(event)->key() == Qt::Key_Escape) { executeHideAction(); } } return false; } -void AssistantPopup::updatePosition() +void AssistantPopup::updatePosition(KTextEditor::View* view, const KTextEditor::Cursor& newPos) { - auto editorGeometry = textWidgetGeometry(m_view); - auto cursor = m_view->cursorToCoordinate(KTextEditor::Cursor(0, 0)); + if ( newPos.isValid() && newPos.line() != 0 && ! m_shownAtBottom ) { + // the position is not going to change; don't waste time + return; + } + auto editorGeometry = textWidgetGeometry(view); + auto cursor = view->cursorToCoordinate(KTextEditor::Cursor(0, 0)); const int margin = 12; #if 0 // TODO KF5: Port Sublime::HoldUpdates hold(ICore::self()->uiController()->activeMainWindow()); + QPoint targetLocation; if ( cursor.y() < 0 ) { // Only when the view is not scrolled to the top, place the widget there; otherwise it easily gets // in the way. - move(parentWidget()->mapFromGlobal(m_view->mapToGlobal(editorGeometry.topRight() - + QPoint(-width() - margin, margin)))); + targetLocation = parentWidget()->mapFromGlobal(view->mapToGlobal(editorGeometry.topRight() + + QPoint(-width() - margin, margin))); + m_shownAtBottom = false; } else { - move(parentWidget()->mapFromGlobal(m_view->mapToGlobal(editorGeometry.bottomRight() - + QPoint(-width() - margin, -margin - height())))); + targetLocation = parentWidget()->mapFromGlobal(view->mapToGlobal(editorGeometry.bottomRight() + + QPoint(-width() - margin, -margin - height()))); + m_shownAtBottom = true; + } + if ( pos() != targetLocation ) { + move(targetLocation); } #endif } IAssistant::Ptr AssistantPopup::assistant() const { return m_assistant; } void AssistantPopup::executeHideAction() { if ( isVisible() ) { m_assistant->doHide(); m_view->setFocus(); } } void AssistantPopup::notifyReopened() { emit m_config->shouldCancelAnimation(); } void AssistantPopup::updateActions() { m_assistantActions = m_assistant->actions(); QList items; foreach(IAssistantAction::Ptr action, m_assistantActions) { items << new AssistantButton(action->toKAction(), action->description(), this); } auto hideAction = new QAction(i18n("Hide"), this); connect(hideAction, SIGNAL(triggered()), this, SLOT(executeHideAction())); items << new AssistantButton(hideAction, hideAction->text(), this); m_config->setModel(items); m_config->setTitle(m_assistant->title()); } #include "assistantpopup.moc" diff --git a/shell/assistantpopup.h b/shell/assistantpopup.h index 90cb1c3ae0..7775a93c20 100644 --- a/shell/assistantpopup.h +++ b/shell/assistantpopup.h @@ -1,129 +1,132 @@ /* Copyright 2009 David Nolden Copyright 2012 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 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_ASSISTANTPOPUP_H #define KDEVPLATFORM_ASSISTANTPOPUP_H #include #include #include #include #include namespace KTextEditor { class View; +class Cursor; } class AssistantButton : public QObject { Q_OBJECT Q_PROPERTY(QString name READ name CONSTANT) public: AssistantButton(QAction* action, const QString& text, QObject* parent) : QObject(parent) , m_name(text) , m_action(action) { } QString name() const { return m_name; } Q_INVOKABLE void trigger() { m_action->trigger(); } private: QString m_name; QAction* m_action; }; class AssistantPopupConfig : public QObject { Q_OBJECT Q_PROPERTY(QColor foreground READ foreground CONSTANT) Q_PROPERTY(QColor background READ background CONSTANT) Q_PROPERTY(QColor highlight READ highlight CONSTANT) Q_PROPERTY(QString title READ title CONSTANT) Q_PROPERTY(QList model READ model CONSTANT) public: explicit AssistantPopupConfig(QObject *parent = 0); QColor foreground() const { return m_foreground; } QColor background() const { return m_background; } QColor highlight() const { return m_highlight; } QString title() const { return m_title; } QList model() const { return m_model; } void setTitle(const QString& text) { m_title = text; } void setModel(const QList& model) { m_model = model; } void setColorsFromView(QObject *view); signals: void shouldShowHighlight(bool show); void shouldCancelAnimation(); private: QColor m_foreground; QColor m_background; QColor m_highlight; QString m_title; QList m_model; friend class AssistantPopup; }; Q_DECLARE_METATYPE(AssistantPopupConfig*) class AssistantPopup : public QQuickView { Q_OBJECT public: typedef KSharedPtr Ptr; /** * @p widget The widget below which the assistant should be shown. * The current main window will be used as parent widget for the popup. * This is to make use of the maximal space available and prevent any lines * in e.g. the editor to be hidden by the popup. */ AssistantPopup(KTextEditor::View* widget, const KDevelop::IAssistant::Ptr& assistant); KDevelop::IAssistant::Ptr assistant() const; + virtual bool viewportEvent(QEvent *event); public slots: void executeHideAction(); void notifyReopened(); private slots: - void updatePosition(); + void updatePosition(KTextEditor::View* view, const KTextEditor::Cursor& newPos); private: virtual bool eventFilter(QObject* object, QEvent* event); virtual void keyReleaseEvent(QKeyEvent* event); /** * @brief Get the geometry of the inner part (with the text) of the KTextEditor::View being used. */ QRect textWidgetGeometry(KTextEditor::View *view) const; void updateActions(); QWidget* widgetForAction(const KDevelop::IAssistantAction::Ptr& action, int& mnemonic); KDevelop::IAssistant::Ptr m_assistant; QList m_assistantActions; KTextEditor::View* m_view; AssistantPopupConfig* m_config; + bool m_shownAtBottom; }; #endif // KDEVPLATFORM_ASSISTANTPOPUP_H diff --git a/shell/plugincontroller.h b/shell/plugincontroller.h index 4e852c93ea..63b9b622a1 100644 --- a/shell/plugincontroller.h +++ b/shell/plugincontroller.h @@ -1,182 +1,180 @@ /* This file is part of the KDE project Copyright 2007 Andreas Pakulat Copyright 2004, 2007 Alexander Dymo Copyright 2006 Matt Rogers 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. */ #ifndef KDEVPLATFORM_PLUGINCONTROLLER_H #define KDEVPLATFORM_PLUGINCONTROLLER_H #include #include -#include #include -#include #include #include "shellexport.h" namespace KDevelop { class Core; class CorePrivate; class IPlugin; class PluginControllerPrivate; /** * The KDevelop plugin controller. * The Plugin controller is responsible for querying, loading and unloading * available plugins. */ class KDEVPLATFORMSHELL_EXPORT PluginController: public IPluginController { Q_OBJECT friend class Core; friend class CorePrivate; public: PluginController(Core *core); virtual ~PluginController(); /** * Get the plugin instance based on the ID. The ID should be whatever is * in X-KDE-PluginInfo-Name */ IPlugin* plugin( const QString& ); /** * Get the plugin info for a loaded plugin */ KPluginInfo pluginInfo( const IPlugin* ) const; /** * Get a list of currently loaded plugins */ QList loadedPlugins() const; /** * Returns a uniquely specified plugin. If it isn't already loaded, it will be. * @param pluginName the name of the plugin, as given in the X-KDE-PluginInfo-Name property * @returns a pointer to the plugin instance or 0 */ IPlugin * loadPlugin( const QString & pluginName ); /** * @brief Unloads the plugin specified by @p plugin * * @param plugin The name of the plugin as specified by the * X-KDE-PluginInfo-Name key of the .desktop file for the plugin */ bool unloadPlugin( const QString & plugin ); enum PluginDeletion { Now, Later }; /** * retrieve all plugin infos */ QList allPluginInfos() const; /** * loads not-yet-loaded plugins and unloads plugins * depending on the configuration in the session\ */ void updateLoadedPlugins(); /** * Queries for the plugin which supports given extension interface. * All already loaded plugins will be queried and the first one to support the extension interface * will be returned. Any plugin can be an extension, only "ServiceTypes=..." entry is * required in .desktop file for that plugin. * @param extension The extension interface * @param pluginname The name of the plugin to load if multiple plugins for the extension exist, corresponds to the X-KDE-PluginInfo-Name * @return A KDevelop extension plugin for given service type or 0 if no plugin supports it */ IPlugin *pluginForExtension(const QString &extension, const QString &pluginname = ""); IPlugin *pluginForExtension(const QString &extension, const QStringList &constraints); QList allPluginsForExtension(const QString &extension, const QStringList &constraints = QStringList()); QStringList allPluginNames(); QList queryPluginsForContextMenuExtensions( KDevelop::Context* context ) const; QStringList projectPlugins(); void loadProjectPlugins(); void unloadProjectPlugins(); void resetToDefaults(); private: /** * Directly unload the given \a plugin, either deleting it now or \a deletion. * * \param plugin plugin to unload * \param deletion if true, delete the plugin later, if false, delete it now. */ bool unloadPlugin(IPlugin* plugin, PluginDeletion deletion); /** * @internal * * The internal method for loading plugins. * Called by @ref loadPlugin directly or through the queue for async plugin * loading. */ IPlugin* loadPluginInternal( const QString &pluginId ); /** * @internal * * Find the KPluginInfo structure by key. Reduces some code duplication. * * Returns a null pointer when no plugin info is found. */ KPluginInfo infoForPluginId( const QString &pluginId ) const; bool checkForDependencies( const KPluginInfo& info, QStringList& missing ) const; bool loadDependencies( const KPluginInfo&, QString& failedPlugin ); void loadOptionalDependencies( const KPluginInfo& info ); void cleanup(); virtual void initialize(); bool isEnabled( const KPluginInfo& info ); private: class PluginControllerPrivate* const d; }; } #endif diff --git a/shell/sessioncontroller.cpp b/shell/sessioncontroller.cpp index acff8b6dbb..7792bddba4 100644 --- a/shell/sessioncontroller.cpp +++ b/shell/sessioncontroller.cpp @@ -1,932 +1,920 @@ /* This file is part of KDevelop Copyright 2008 Andreas Pakulat Copyright 2010 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 "sessioncontroller.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "session.h" #include "core.h" #include "uicontroller.h" #include "sessiondialog.h" #include "shellextension.h" #include "sessionlock.h" #include "sessionchooserdialog.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 const int recoveryStorageInterval = 10; ///@todo Make this configurable namespace KDevelop { namespace { int argc = 0; char** argv = 0; }; void SessionController::setArguments(int _argc, char** _argv) { argc = _argc; argv = _argv; } static QStringList standardArguments() { QStringList ret; for(int a = 0; a < argc; ++a) { QString arg = QString::fromLocal8Bit(argv[a]); kWarning() << "ARG:" << "" + arg + ""; /* if(arg.startsWith("--graphicssystem=") || arg.startsWith("--style=")) { ret << arg; }else */ if(arg.startsWith("-graphicssystem") || arg.startsWith("-style")) { ret << '-' + arg; if(a+1 < argc) ret << QString::fromLocal8Bit(argv[a+1]); } } kWarning() << "ARGUMENTS: " << ret << "from" << argc; return ret; } class SessionControllerPrivate : public QObject { Q_OBJECT public: SessionControllerPrivate( SessionController* s ) : q(s) , activeSession(0) , grp(0) , recoveryDirectoryIsOwn(false) { recoveryTimer.setInterval(recoveryStorageInterval * 1000); connect(&recoveryTimer, SIGNAL(timeout()), SLOT(recoveryStorageTimeout())); // Try the recovery only after the initialization has finished connect(ICore::self(), SIGNAL(initializationCompleted()), SLOT(lateInitialization()), Qt::QueuedConnection); recoveryTimer.setSingleShot(false); recoveryTimer.start(); } ~SessionControllerPrivate() { if (activeSession) { // when session was active, we deleted the folder already // in that case activeSession = 0 clearRecoveryDirectory(); } } Session* findSessionForName( const QString& name ) const { foreach( Session* s, sessionActions.keys() ) { if( s->name() == name ) return s; } return 0; } Session* findSessionForId(QString idString) { QUuid id(idString); foreach( Session* s, sessionActions.keys() ) { if( s->id() == id) return s; } return 0; } void newSession() { qsrand(QDateTime::currentDateTime().toTime_t()); Session* session = new Session( QUuid::createUuid().toString() ); KProcess::startDetached(ShellExtension::getInstance()->binaryPath(), QStringList() << "-s" << session->id().toString() << standardArguments()); delete session; #if 0 //Terminate this instance of kdevelop if the user agrees foreach(Sublime::MainWindow* window, Core::self()->uiController()->controller()->mainWindows()) window->close(); #endif } void configureSessions() { SessionDialog dlg(ICore::self()->uiController()-> activeMainWindow()); dlg.exec(); } void deleteCurrentSession() { int choice = KMessageBox::warningContinueCancel(Core::self()->uiController()->activeMainWindow(), i18n("The current session and all contained settings will be deleted. The projects will stay unaffected. Do you really want to continue?")); if(choice == KMessageBox::Continue) { q->deleteSessionFromDisk(sessionLock); q->emitQuitSession(); } } void renameSession() { KDialog dialog; dialog.setWindowTitle(i18n("Rename Session")); QGroupBox box; QHBoxLayout layout(&box); box.setTitle(i18n("New Session Name")); QLineEdit edit; layout.addWidget(&edit); dialog.setButtons(KDialog::Ok | KDialog::Cancel); edit.setText(q->activeSession()->name()); dialog.setMainWidget(&box); edit.setFocus(); if(dialog.exec() == QDialog::Accepted) { static_cast(q->activeSession())->setName(edit.text()); } } bool loadSessionExternally( Session* s ) { Q_ASSERT( s ); KProcess::startDetached(ShellExtension::getInstance()->binaryPath(), QStringList() << "-s" << s->id().toString() << standardArguments()); return true; } TryLockSessionResult activateSession( Session* s ) { Q_ASSERT( s ); activeSession = s; TryLockSessionResult result = SessionController::tryLockSession( s->id().toString()); if( !result.lock ) { activeSession = 0; return result; } Q_ASSERT(s->id().toString() == result.lock->id()); sessionLock = result.lock; KConfigGroup grp = KSharedConfig::openConfig()->group( SessionController::cfgSessionGroup() ); grp.writeEntry( SessionController::cfgActiveSessionEntry(), s->id().toString() ); grp.sync(); if (Core::self()->setupFlags() & Core::NoUi) return result; QHash::iterator it = sessionActions.find(s); Q_ASSERT( it != sessionActions.end() ); (*it)->setCheckable(true); (*it)->setChecked(true); for(it = sessionActions.begin(); it != sessionActions.end(); ++it) { if(it.key() != s) (*it)->setCheckable(false); } return result; } void loadSessionFromAction( QAction* a ) { foreach( Session* s, sessionActions.keys() ) { if( s->id() == QUuid( a->data().toString() ) && s != activeSession ) { loadSessionExternally( s ); break; } } } void addSession( Session* s ) { if (Core::self()->setupFlags() & Core::NoUi) { sessionActions[s] = 0; return; } QAction* a = new QAction( grp ); a->setText( s->description() ); a->setCheckable( false ); a->setData( s->id().toString() ); sessionActions[s] = a; q->actionCollection()->addAction( "session_"+s->id().toString(), a ); q->unplugActionList( "available_sessions" ); q->plugActionList( "available_sessions", grp->actions() ); connect( s, SIGNAL(sessionUpdated(KDevelop::ISession*)), SLOT(sessionUpdated(KDevelop::ISession*)) ); sessionUpdated( s ); } SessionController* q; QHash sessionActions; ISession* activeSession; QActionGroup* grp; ISessionLock::Ptr sessionLock; // Whether this process owns the recovery directory bool recoveryDirectoryIsOwn; QTimer recoveryTimer; QMap currentRecoveryFiles; static QString sessionBaseDirectory() { return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) +'/'+ KGlobal::mainComponent().componentName() + "/sessions/"; } QString ownSessionDirectory() const { Q_ASSERT(activeSession); return q->sessionDirectory( activeSession->id().toString() ); } void clearRecoveryDirectory() { removeDirectory(ownSessionDirectory() + "/recovery"); } public slots: void documentSavedOrClosed( KDevelop::IDocument* document ) { if(currentRecoveryFiles.contains(document->url())) { kDebug() << "deleting recovery-info for" << document->url(); foreach(const QString& recoveryFileName, currentRecoveryFiles[document->url()]) { bool result = QFile::remove(recoveryFileName); kDebug() << "deleted" << recoveryFileName << result; } currentRecoveryFiles.remove(document->url()); } } private slots: void lateInitialization() { performRecovery(); connect(Core::self()->documentController(), SIGNAL(documentSaved(KDevelop::IDocument*)), SLOT(documentSavedOrClosed(KDevelop::IDocument*))); connect(Core::self()->documentController(), SIGNAL(documentClosed(KDevelop::IDocument*)), SLOT(documentSavedOrClosed(KDevelop::IDocument*))); } void performRecovery() { kDebug() << "Checking recovery"; QDir recoveryDir(ownSessionDirectory() + "/recovery"); if(recoveryDir.exists()) { kDebug() << "Have recovery directory, starting recovery"; QFile dateFile(recoveryDir.path() + "/date"); dateFile.open(QIODevice::ReadOnly); QString date = QString::fromUtf8(dateFile.readAll()); QDir recoverySubDir(recoveryDir.path() + "/current"); if(!recoverySubDir.exists()) recoverySubDir = QDir(recoveryDir.path() + "/backup"); if(recoverySubDir.exists()) { kWarning() << "Starting recovery from " << recoverySubDir.absolutePath(); QStringList urlList; for(uint num = 0; ; ++num) { QFile urlFile(recoverySubDir.path() + QString("/%1_url").arg(num)); if(!urlFile.exists()) break; urlFile.open(QIODevice::ReadOnly); KUrl originalFile(QString::fromUtf8(urlFile.readAll())); urlList << originalFile.pathOrUrl(); } if(!urlList.isEmpty()) { //Either recover, or delete the recovery directory ///TODO: user proper runtime locale for date, it might be different /// from what was used when the recovery file was saved KGuiItem recover = KStandardGuiItem::cont(); recover.setIcon(QIcon::fromTheme("edit-redo")); recover.setText(i18n("Recover")); KGuiItem discard = KStandardGuiItem::discard(); int choice = KMessageBox::warningContinueCancelList(qApp->activeWindow(), i18nc("%1: date of the last snapshot", "The session crashed the last time it was used. " "The following modified files can be recovered from a backup from %1.", date), urlList, i18n("Crash Recovery"), recover, discard ); if(choice == KMessageBox::Continue) { //Recover the files for(uint num = 0; ; ++num) { QFile urlFile(recoverySubDir.path() + QString("/%1_url").arg(num)); if(!urlFile.exists()) break; urlFile.open(QIODevice::ReadOnly); KUrl originalFile(QString::fromUtf8(urlFile.readAll())); QFile f(recoverySubDir.path() + '/' + QString("/%1_text").arg(num)); f.open(QIODevice::ReadOnly); QString text = QString::fromUtf8(f.readAll()); if(text.isEmpty()) { KMessageBox::error(ICore::self()->uiController()->activeMainWindow(), i18n("Could not recover %1, the recovery file is empty", originalFile.pathOrUrl()), i18n("Recovery")); continue; } kDebug() << "opening" << originalFile << "for recovery"; KDevelop::IDocument* doc = ICore::self()->documentController()->openDocument(originalFile); if(!doc || !doc->textDocument()) { kWarning() << "The document " << originalFile.prettyUrl() << " could not be opened as a text-document, creating a new document with the recovered contents"; doc = ICore::self()->documentController()->openDocumentFromText(text); }else{ KTextEditor::RecoveryInterface* recovery = qobject_cast(doc->textDocument()); if(recovery && recovery->isDataRecoveryAvailable()) // Use the recovery from the kate swap-file if possible recovery->recoverData(); else // Use a simple recovery through "replace text" doc->textDocument()->setText(text); } } } } } } recoveryDirectoryIsOwn = true; } void sessionUpdated( KDevelop::ISession* s ) { sessionActions[static_cast( s )]->setText( KStringHandler::rsqueeze(s->description()) ); } void recoveryStorageTimeout() { if(!recoveryDirectoryIsOwn) return; currentRecoveryFiles.clear(); QDir recoveryDir(ownSessionDirectory() + "/recovery"); if(!recoveryDir.exists()) { // Try "taking" the recovery directory QDir sessionDir(ownSessionDirectory()); if(!sessionDir.mkdir("recovery")) return; } if (recoveryDir.exists("backup")) { // Clear the old backup recovery directory, as we will create a new one if (!removeDirectory(recoveryDir.absoluteFilePath("backup"))) { kWarning() << "RECOVERY ERROR: Removing the old recovery backup directory failed in " << recoveryDir; return; } } //Make the current recovery dir the backup dir, so we always have a recovery available //This may fail, because "current" might be nonexistent recoveryDir.rename("current", "backup"); { recoveryDir.mkdir("current_incomplete"); QDir recoveryCurrentDir(recoveryDir.path() + "/current_incomplete"); uint num = 0; foreach(KDevelop::IDocument* document, ICore::self()->documentController()->openDocuments()) { if(document->state() == IDocument::Modified || document->state() == IDocument::DirtyAndModified) { //This document was modified, create a recovery-backup if(document->textDocument()) { //Currently we can only back-up text documents QString text = document->textDocument()->text(); if(!text.isEmpty()) { QString urlFilePath = recoveryCurrentDir.path() + QString("/%1_url").arg(num); QFile urlFile(urlFilePath); urlFile.open(QIODevice::WriteOnly); urlFile.write(document->url().pathOrUrl().toUtf8()); urlFile.close(); QString textFilePath = recoveryCurrentDir.path() + '/' + QString("/%1_text").arg(num); QFile f(textFilePath); f.open(QIODevice::WriteOnly); f.write(text.toUtf8()); f.close(); currentRecoveryFiles[document->url()] = QStringList() << (recoveryDir.path() + "/current" + QString("/%1_url").arg(num)) << (recoveryDir.path() + "/current" + QString("/%1_text").arg(num)); if(urlFile.error() != QFile::NoError || f.error() != QFile::NoError) { kWarning() << "RECOVERY ERROR: Failed to write recovery for" << document->url() << "to" << textFilePath; KMessageBox::error(ICore::self()->uiController()->activeMainWindow(), i18n("Failed to write recovery copies to %1. Please make sure that your home directory is writable and not full. This application requires available space in the home directory to run stable. You may experience application crashes until you free up some space.", recoveryCurrentDir.path()), i18n("Recovery Error")); return; } ++num; } } } } } recoveryDir.rename("current_incomplete", "current"); { //Write down the date of the recovery QFile dateFile(recoveryDir.path() + "/date"); dateFile.open(QIODevice::WriteOnly); dateFile.write(QDateTime::currentDateTime().toString(Qt::DefaultLocaleShortDate).toUtf8()); } } }; SessionController::SessionController( QObject *parent ) : QObject( parent ), d(new SessionControllerPrivate(this)) { setObjectName("SessionController"); setComponentName(QStringLiteral("kdevsession"), QStringLiteral("KDevSession")); setXMLFile("kdevsessionui.rc"); QDBusConnection::sessionBus().registerObject( "/kdevelop/SessionController", this, QDBusConnection::ExportScriptableSlots ); if (Core::self()->setupFlags() & Core::NoUi) return; QAction* action = actionCollection()->addAction( "new_session", this, SLOT(newSession()) ); action->setText( i18nc("@action:inmenu", "Start New Session") ); action->setToolTip( i18nc("@info:tooltip", "Start a new KDevelop instance with an empty session") ); action->setIcon(QIcon::fromTheme("window-new")); action = actionCollection()->addAction( "rename_session", this, SLOT(renameSession()) ); action->setText( i18n("Rename Current Session...") ); action->setIcon(QIcon::fromTheme("edit-rename")); action = actionCollection()->addAction( "delete_session", this, SLOT(deleteCurrentSession()) ); action->setText( i18n("Delete Current Session...") ); action->setIcon(QIcon::fromTheme("edit-delete")); action = actionCollection()->addAction( "quit", this, SIGNAL(quitSession()) ); action->setText( i18n("Quit") ); action->setShortcut(Qt::CTRL | Qt::Key_Q); action->setIcon(QIcon::fromTheme("application-exit")); #if 0 action = actionCollection()->addAction( "configure_sessions", this, SLOT(configureSessions()) ); action->setText( i18n("Configure Sessions...") ); action->setToolTip( i18n("Create/Delete/Activate Sessions") ); action->setWhatsThis( i18n( "Shows a dialog to Create/Delete Sessions and set a new active session." ) ); #endif d->grp = new QActionGroup( this ); connect( d->grp, SIGNAL(triggered(QAction*)), this, SLOT(loadSessionFromAction(QAction*)) ); } SessionController::~SessionController() { delete d; } void SessionController::startNewSession() { d->newSession(); } void SessionController::cleanup() { d->recoveryTimer.stop(); Q_ASSERT(d->activeSession->id().toString() == d->sessionLock->id()); ISession* active = d->activeSession; d->activeSession = 0; if (active->isTemporary()) { deleteSessionFromDisk(d->sessionLock); } d->sessionLock.clear(); qDeleteAll(d->sessionActions); d->sessionActions.clear(); } void SessionController::initialize( const QString& session ) { QDir sessiondir( SessionControllerPrivate::sessionBaseDirectory() ); foreach( const QString& s, sessiondir.entryList( QDir::AllDirs | QDir::NoDotAndDotDot ) ) { QUuid id( s ); if( id.isNull() ) continue; // Only create sessions for directories that represent proper uuid's Session* ses = new Session( id.toString(), this ); //Delete sessions that have no name and are empty if( ses->containedProjects().isEmpty() && ses->name().isEmpty() && (session.isEmpty() || (ses->id().toString() != session && ses->name() != session)) ) { TryLockSessionResult result = tryLockSession(s); if (result.lock) { deleteSessionFromDisk(result.lock); } delete ses; } else { d->addSession( ses ); } } loadDefaultSession( session ); } ISession* SessionController::activeSession() const { return d->activeSession; } ISessionLock::Ptr SessionController::activeSessionLock() const { return d->sessionLock; } void SessionController::loadSession( const QString& nameOrId ) { d->loadSessionExternally( session( nameOrId ) ); } QList SessionController::sessionNames() const { QStringList l; foreach( const Session* s, d->sessionActions.keys() ) { l << s->name(); } return l; } QList< const KDevelop::Session* > SessionController::sessions() const { QList< const KDevelop::Session* > ret; foreach( const Session* s, d->sessionActions.keys() ) { ret << s; } return ret; } Session* SessionController::createSession( const QString& name ) { Session* s; if(name.startsWith('{')) { s = new Session( QUuid(name).toString() ); }else{ qsrand(QDateTime::currentDateTime().toTime_t()); s = new Session( QUuid::createUuid().toString() ); s->setName( name ); } d->addSession( s ); return s; } void SessionController::deleteSession( const ISessionLock::Ptr& lock ) { Session* s = session(lock->id()); QHash::iterator it = d->sessionActions.find(s); Q_ASSERT( it != d->sessionActions.end() ); unplugActionList( "available_sessions" ); actionCollection()->removeAction(*it); if (d->grp) { // happens in unit tests d->grp->removeAction(*it); plugActionList( "available_sessions", d->grp->actions() ); } deleteSessionFromDisk(lock); emit sessionDeleted( s->id().toString() ); d->sessionActions.remove(s); s->deleteLater(); } void SessionController::deleteSessionFromDisk( const ISessionLock::Ptr& lock ) { removeDirectory( sessionDirectory(lock->id()) ); ItemRepositoryRegistry::deleteRepositoryFromDisk( lock ); } void SessionController::loadDefaultSession( const QString& session ) { QString load = session; if (load.isEmpty()) { KConfigGroup grp = KSharedConfig::openConfig()->group( cfgSessionGroup() ); load = grp.readEntry( cfgActiveSessionEntry(), "default" ); } // Iteratively try to load the session, asking user what to do in case of failure // If showForceOpenDialog() returns empty string, stop trying Session* s = 0; do { s = this->session( load ); if( !s ) { s = createSession( load ); } TryLockSessionResult result = d->activateSession( s ); if( result.lock ) { Q_ASSERT(d->activeSession == s); Q_ASSERT(d->sessionLock = result.lock); break; } load = handleLockedSession( s->name(), s->id().toString(), result.runInfo ); } while( !load.isEmpty() ); } Session* SessionController::session( const QString& nameOrId ) const { Session* ret = d->findSessionForName( nameOrId ); if(ret) return ret; return d->findSessionForId( nameOrId ); } QString SessionController::cloneSession( const QString& nameOrid ) { Session* origSession = session( nameOrid ); qsrand(QDateTime::currentDateTime().toTime_t()); QUuid id = QUuid::createUuid(); KIO::NetAccess::dircopy( sessionDirectory( origSession->id().toString() ), sessionDirectory( id.toString() ), Core::self()->uiController()->activeMainWindow() ); Session* newSession = new Session( id.toString() ); newSession->setName( i18n( "Copy of %1", origSession->name() ) ); d->addSession(newSession); return newSession->name(); } void SessionController::plugActions() { unplugActionList( "available_sessions" ); plugActionList( "available_sessions", d->grp->actions() ); } QString SessionController::cfgSessionGroup() { return "Sessions"; } QString SessionController::cfgActiveSessionEntry() { return "Active Session ID"; } QList< SessionInfo > SessionController::availableSessionInfo() { QList< SessionInfo > available; foreach( const QString& sessionId, QDir( SessionControllerPrivate::sessionBaseDirectory() ).entryList( QDir::AllDirs ) ) { if( !QUuid( sessionId ).isNull() ) { available << Session::parse( sessionId ); } } return available; } QString SessionController::sessionDirectory(const QString& sessionId) { return SessionControllerPrivate::sessionBaseDirectory() + sessionId; } TryLockSessionResult SessionController::tryLockSession(const QString& id) { return SessionLock::tryLockSession(id, true); } bool SessionController::isSessionRunning(const QString& id) { return sessionRunInfo(id).isRunning; } SessionRunInfo SessionController::sessionRunInfo(const QString& id) { return SessionLock::tryLockSession(id, false).runInfo; } QString SessionController::showSessionChooserDialog(QString headerText, bool onlyRunning) { // The catalog hasn't been loaded yet KGlobal::locale()->insertCatalog("kdevplatform"); ///FIXME: move this code into sessiondialog.cpp QListView* view = new QListView; KLineEdit* filter = new KLineEdit; filter->setClearButtonShown( true ); filter->setClickMessage(i18n("Search")); QStandardItemModel* model = new QStandardItemModel(view); QSortFilterProxyModel *proxy = new QSortFilterProxyModel(model); proxy->setSourceModel(model); proxy->setFilterKeyColumn( 1 ); proxy->setFilterCaseSensitivity( Qt::CaseInsensitive ); connect(filter, SIGNAL(textChanged(QString)), proxy, SLOT(setFilterFixedString(QString))); SessionChooserDialog dialog(view, proxy, filter); view->setEditTriggers(QAbstractItemView::NoEditTriggers); QVBoxLayout layout(dialog.mainWidget()); if(!headerText.isEmpty()) { QLabel* heading = new QLabel(headerText); QFont font = heading->font(); font.setBold(true); heading->setFont(font); layout.addWidget(heading); } model->setColumnCount(3); model->setHeaderData(0, Qt::Horizontal,i18n("Identity")); model->setHeaderData(1, Qt::Horizontal, i18n("Contents")); model->setHeaderData(2, Qt::Horizontal,i18n("State")); view->setModel(proxy); view->setModelColumn(1); QHBoxLayout* filterLayout = new QHBoxLayout(); filterLayout->addWidget(new QLabel(i18n("Filter:"))); filterLayout->addWidget(filter); layout.addLayout(filterLayout); layout.addWidget(view); filter->setFocus(); int row = 0; - int defaultRow = 0; QString defaultSession = KSharedConfig::openConfig()->group( cfgSessionGroup() ).readEntry( cfgActiveSessionEntry(), "default" ); - foreach(const KDevelop::SessionInfo& si, KDevelop::SessionController::availableSessionInfo()) { if ( si.name.isEmpty() && si.projects.isEmpty() ) { continue; } bool running = KDevelop::SessionController::isSessionRunning(si.uuid.toString()); if(onlyRunning && !running) continue; - if(si.uuid.toString() == defaultSession) - defaultRow = row; - model->setItem(row, 0, new QStandardItem(si.uuid.toString())); model->setItem(row, 1, new QStandardItem(si.description)); model->setItem(row, 2, new QStandardItem); - if(defaultRow == row && running) - ++defaultRow; - ++row; } model->sort(1); - int cnsRow = row; if(!onlyRunning) { model->setItem(row, 0, new QStandardItem); model->setItem(row, 1, new QStandardItem(QIcon::fromTheme("window-new"), i18n("Create New Session"))); } dialog.updateState(); dialog.mainWidget()->layout()->setContentsMargins(0,0,0,0); - view->selectionModel()->setCurrentIndex(proxy->mapFromSource(model->index(defaultRow, 0)), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); + const QModelIndex defaultSessionIndex = model->match(model->index(0, 0), Qt::DisplayRole, defaultSession, 1, Qt::MatchExactly).value(0); + view->selectionModel()->setCurrentIndex(proxy->mapFromSource(defaultSessionIndex), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); view->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); ///@todo We need a way to get a proper size-hint from the view, but unfortunately, that only seems possible after the view was shown. dialog.setInitialSize(QSize(900, 600)); if(dialog.exec() != QDialog::Accepted) { return QString(); } QModelIndex selected = view->selectionModel()->currentIndex(); - if(selected.isValid()) - { - QString ret; - if( selected.row() == cnsRow ) { - qsrand(QDateTime::currentDateTime().toTime_t()); - ret = QUuid::createUuid().toString(); - } else { - selected = selected.sibling(selected.row(), 0); - ret = selected.data().toString(); - } - return ret; - } + if (!selected.isValid()) + return QString(); - return QString(); + const QString selectedSessionId = selected.sibling(selected.row(), 0).data().toString(); + if (selectedSessionId.isEmpty()) { + // "Create New Session" item selected, return a fresh UUID + qsrand(QDateTime::currentDateTime().toTime_t()); + return QUuid::createUuid().toString(); + } + return selectedSessionId; } QString SessionController::handleLockedSession( const QString& sessionName, const QString& sessionId, const SessionRunInfo& runInfo ) { return SessionLock::handleLockedSession(sessionName, sessionId, runInfo); } QString SessionController::sessionDir() { if( !activeSession() ) return QString(); return d->ownSessionDirectory(); } QString SessionController::sessionName() { if(!activeSession()) return QString(); return activeSession()->description(); } } #include "sessioncontroller.moc" #include "moc_sessioncontroller.cpp" diff --git a/shell/sessioncontroller.h b/shell/sessioncontroller.h index b7c5c0b01d..7de7592673 100644 --- a/shell/sessioncontroller.h +++ b/shell/sessioncontroller.h @@ -1,175 +1,178 @@ /* This file is part of KDevelop Copyright 2008 Andreas Pakulat 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. */ #ifndef KDEVPLATFORM_SESSIONCONTROLLER_H #define KDEVPLATFORM_SESSIONCONTROLLER_H #include "shellexport.h" #include "session.h" #include #include #include namespace KDevelop { struct SessionRunInfo { SessionRunInfo() : isRunning(false) , holderPid(-1) {} bool operator==(const SessionRunInfo& o) const { return isRunning == o.isRunning && holderPid == o.holderPid && holderApp == o.holderApp && holderHostname == o.holderHostname; } bool operator!=(const SessionRunInfo& o) const { return !(operator==(o)); } // if this is true, this session is currently running in an external process bool isRunning; // if the session is running, this contains the PID of its process int holderPid; // if the session is running, this contains the name of its process QString holderApp; // if the session is running, this contains the host name where the process runs QString holderHostname; }; struct TryLockSessionResult { TryLockSessionResult(const ISessionLock::Ptr& _lock) : lock(_lock) {} TryLockSessionResult(const SessionRunInfo& _runInfo) : runInfo(_runInfo) {} // if this is non-null then the session was locked ISessionLock::Ptr lock; // otherwise this contains information about who is locking the session SessionRunInfo runInfo; }; class KDEVPLATFORMSHELL_EXPORT SessionController : public QObject, public KXMLGUIClient { Q_OBJECT public: SessionController( QObject *parent = 0 ); virtual ~SessionController(); void initialize( const QString& session ); void cleanup(); /// Returns whether the given session can be locked (i. e., is not locked currently). /// @param doLocking whether to really lock the session or just "dry-run" the locking process static TryLockSessionResult tryLockSession(const QString& id); /** * @return true when the given session is currently running, false otherwise */ static bool isSessionRunning(const QString& id); /** * @return information about whether the session @p id is running */ static SessionRunInfo sessionRunInfo(const QString& id); /// The application should call this on startup to tell the /// session-controller about the received arguments. /// Some of them may need to be passed to newly opened sessions. static void setArguments(int argc, char** argv); ///Finds a session by its name or by its UUID Session* session( const QString& nameOrId ) const; virtual ISession* activeSession() const; ISessionLock::Ptr activeSessionLock() const; QList sessionNames() const; Session* createSession( const QString& name ); QList sessions() const; void loadDefaultSession( const QString& session ); void startNewSession(); void loadSession( const QString& nameOrId ); void deleteSession( const ISessionLock::Ptr& lock ); static void deleteSessionFromDisk( const ISessionLock::Ptr& lock ); QString cloneSession( const QString& nameOrid ); /** * Path to session directory for the session with the given @p sessionId. */ static QString sessionDirectory( const QString& sessionId ); static QString cfgSessionGroup(); static QString cfgActiveSessionEntry(); static QList< SessionInfo > availableSessionInfo(); - /// Shows a dialog where the user can choose the session - /// @param headerText an additional text that will be shown at the top in a label - /// @param onlyRunning whether only currently running sessions should be shown + /** + * Shows a dialog where the user can choose the session + * @param headerText an additional text that will be shown at the top in a label + * @param onlyRunning whether only currently running sessions should be shown + * @return UUID on success, empty string in any other case + */ static QString showSessionChooserDialog(QString headerText = QString(), bool onlyRunning = false); /// Should be called if session to be opened is locked. /// It attempts to bring existing instance's window up via a DBus call; if that succeeds, empty string is returned. /// Otherwise (if the app did not respond) it shows a dialog where the user may choose /// 1) to force-remove the lockfile and continue, /// 2) to select another session via \ref showSessionChooserDialog, /// 3) to quit the current (starting-up) instance. /// @param sessionName session name (for the message) /// @param sessionId current session GUID (to return if user chooses force-removal) /// @param runInfo the run information about the session /// @return new session GUID to try or an empty string if application startup shall be aborted static QString handleLockedSession( const QString& sessionName, const QString& currentSessionId, const SessionRunInfo& runInfo ); void plugActions(); void emitQuitSession() { emit quitSession(); } public Q_SLOTS: // Returns the pretty name of the currently active session (used in the shell integration) virtual Q_SCRIPTABLE QString sessionName(); // Returns the directory associated to the active session (used in the shell integration) virtual Q_SCRIPTABLE QString sessionDir(); Q_SIGNALS: void sessionLoaded( ISession* ); void sessionDeleted( const QString& id); void quitSession(); private: Q_PRIVATE_SLOT( d, void newSession() ) Q_PRIVATE_SLOT( d, void configureSessions() ) Q_PRIVATE_SLOT( d, void deleteCurrentSession() ) Q_PRIVATE_SLOT( d, void renameSession() ) Q_PRIVATE_SLOT( d, void loadSessionFromAction( QAction* ) ) class SessionControllerPrivate* const d; }; } #endif diff --git a/shell/settings/editstyledialog.cpp b/shell/settings/editstyledialog.cpp index 12dac3b586..646658fd16 100644 --- a/shell/settings/editstyledialog.cpp +++ b/shell/settings/editstyledialog.cpp @@ -1,111 +1,112 @@ /* 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 "editstyledialog.h" #include #include #include #include #include #include #include #include #include using KDevelop::ISourceFormatter; using KDevelop::SettingsWidget; using KDevelop::SourceFormatterStyle; EditStyleDialog::EditStyleDialog(ISourceFormatter *formatter, const KMimeType::Ptr &mime, const SourceFormatterStyle &style, QWidget *parent) : KDialog(parent), m_sourceFormatter(formatter), m_mimeType(mime), m_style( style ) { m_content = new QWidget(); m_ui.setupUi(m_content); setMainWidget(m_content); m_settingsWidget = m_sourceFormatter->editStyleWidget(mime); init(); if (m_settingsWidget) m_settingsWidget->load(style); } EditStyleDialog::~EditStyleDialog() { } void EditStyleDialog::init() { // add plugin settings widget if(m_settingsWidget) { QVBoxLayout *layout = new QVBoxLayout(m_ui.settingsWidgetParent); layout->addWidget(m_settingsWidget); m_ui.settingsWidgetParent->setLayout(layout); connect(m_settingsWidget, SIGNAL(previewTextChanged(QString)), this, SLOT(updatePreviewText(QString))); } m_document = KTextEditor::Editor::instance()->createDocument(this); m_document->setReadWrite(false); - QString mode = m_sourceFormatter->highlightModeForMime(m_mimeType); - m_document->setHighlightingMode(mode); + m_document->setHighlightingMode(m_style.modeForMimetype(m_mimeType)); m_view = m_document->createView(m_ui.textEditor); QVBoxLayout *layout2 = new QVBoxLayout(m_ui.textEditor); layout2->addWidget(m_view); m_ui.textEditor->setLayout(layout2); m_view->show(); KTextEditor::ConfigInterface *iface = qobject_cast(m_view); if (iface) { iface->setConfigValue("dynamic-word-wrap", false); iface->setConfigValue("icon-bar", false); } - if (m_sourceFormatter) - updatePreviewText(m_sourceFormatter->previewText(m_mimeType)); + if (m_sourceFormatter) { + QString text = m_sourceFormatter->previewText(&m_style, m_mimeType); + updatePreviewText(text); + } } void EditStyleDialog::updatePreviewText(const QString &text) { m_document->setReadWrite(true); m_style.setContent( content() ); if (m_sourceFormatter) { m_document->setText(m_sourceFormatter->formatSourceWithStyle( m_style, text, KUrl(), m_mimeType )); } else { m_document->setText( i18n( "No Source Formatter available" ) ); } m_view->setCursorPosition( KTextEditor::Cursor( 0, 0 ) ); m_document->setReadWrite(false); } QString EditStyleDialog::content() { if(m_settingsWidget) return m_settingsWidget->save(); return QString(); } #include "editstyledialog.moc" // kate: indent-mode cstyle; space-indent off; tab-width 4; diff --git a/shell/settings/kcm_kdev_bgsettings.desktop b/shell/settings/kcm_kdev_bgsettings.desktop index f38f29704c..b9bf1f8cb4 100644 --- a/shell/settings/kcm_kdev_bgsettings.desktop +++ b/shell/settings/kcm_kdev_bgsettings.desktop @@ -1,82 +1,82 @@ [Desktop Entry] Icon=code-context Type=Service ServiceTypes=KCModule X-KDE-ModuleType=Library X-KDE-Library=kcm_kdev_bgsettings X-KDE-FactoryName=kcm_kdev_bgsettings X-KDE-ParentApp=kdevplatform X-KDE-ParentComponents=kdevplatform X-KDE-CfgDlgHierarchy=GENERAL X-KDE-Weight=4 Name=Background Parser Name[bg]=Фонов анализатор на синтаксиса Name[bs]=Pozadniski parser Name[ca]=Analitzador en segon pla Name[ca@valencia]=Analitzador en segon pla Name[da]=Baggrundsfortolker Name[de]=Hintergrund-Parser Name[el]=Αναλυτής παρασκηνίου Name[en_GB]=Background Parser Name[es]=Analizador en segundo plano Name[et]=Taustaparser Name[fi]=Taustajäsennin Name[fr]=Analyseur syntaxique d'arrière-plan Name[gl]=Analizador sintáctico en segundo plano Name[hu]=Háttérfeldolgozó Name[it]=Analizzatore in background Name[ja]=バックグラウンドパーサ Name[kk]=Қатар істейтін синтаксис талдағышы Name[nb]=Bakgrunnstolker Name[nds]=Achtergrund-Inleser Name[nl]=Achtergrond ontleder -Name[pl]=Parser w tle +Name[pl]=Przetwarzanie w tle Name[pt]=Processamento em Segundo Plano Name[pt_BR]=Analisador em plano de fundo Name[ru]=Фоновый анализ кода Name[sk]=Analyzátor na pozadí Name[sl]=Razčlenjevalnik v ozadju Name[sv]=Bakgrundstolk Name[tr]=Arkaplan Ayrıştırıcı Name[ug]=تەگلىك تەھلىل قىلغۇچ Name[uk]=Інструмент фонової обробки Name[x-test]=xxBackground Parserxx Name[zh_CN]=后台解析 Name[zh_TW]=背景剖析器 Comment=Configure Background Parser Comment[bg]=Настройки на фонов анализатор на синтаксиса Comment[bs]=Konfiguriši kontroler pozadine Comment[ca]=Configura l'analitzador en segon pla Comment[ca@valencia]=Configura l'analitzador en segon pla Comment[da]=Indstil baggrundsfortolker Comment[de]=Hintergrund-Parser einrichten Comment[el]=Διαμόρφωση συντακτικού αναλυτή παρασκηνίου Comment[en_GB]=Configure Background Parser Comment[es]=Configurar el analizador en segundo plano Comment[et]=Taustaparseri seadistamine Comment[fi]=Taustajäsentimen asetukset Comment[fr]=Configurer l'analyseur syntaxique d'arrière-plan Comment[gl]=Configurar o analizador en segundo plano Comment[hu]=Háttérfeldolgozó beállítása Comment[it]=Configura l'analizzatore in background Comment[ja]=バックグラウンドパーサを設定します Comment[kk]=Қатар істейтін талдағышын баптау Comment[nb]=Sett opp bakgrunnstolker Comment[nds]=Achtergrund-Inleser instellen Comment[nl]=Achtergrondontleder instellen -Comment[pl]=Konfiguracja parsera w tle +Comment[pl]=Konfiguracja analizatora składni w tle Comment[pt]=Configurar o Processamento em Segundo Plano Comment[pt_BR]=Configurar o processamento em segundo plano Comment[ru]=Настройка фонового анализа кода Comment[sk]=Nastaviť analyzátor na pozadí Comment[sl]=Nastavite razhroščevalnik v ozadju Comment[sv]=Anpassa bakgrundstolk Comment[tr]=Arkaplan Ayrıştırıcısını Yapılandır Comment[ug]=تەگلىك تەھلىل قىلغۇچنى سەپلەش Comment[uk]=Налаштування інструмента фонової обробки Comment[x-test]=xxConfigure Background Parserxx Comment[zh_CN]=配置后台解析 Comment[zh_TW]=設定背景剖析器 diff --git a/shell/settings/sourceformattersettings.cpp b/shell/settings/sourceformattersettings.cpp index 0101647853..c2c47bdba3 100644 --- a/shell/settings/sourceformattersettings.cpp +++ b/shell/settings/sourceformattersettings.cpp @@ -1,529 +1,515 @@ /* 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 #include #include "editstyledialog.h" #define STYLE_ROLE (Qt::UserRole+1) K_PLUGIN_FACTORY(SourceFormatterSettingsFactory, registerPlugin();) // K_EXPORT_PLUGIN(SourceFormatterSettingsFactory("kcm_kdevsourceformattersettings")) using KDevelop::Core; using KDevelop::ISourceFormatter; using KDevelop::SourceFormatterStyle; using KDevelop::SourceFormatterController; - +using KDevelop::SourceFormatter; const QString SourceFormatterSettings::userStylePrefix( "User" ); -SourceFormatter::~SourceFormatter() -{ - qDeleteAll(styles); -} - LanguageSettings::LanguageSettings() : selectedFormatter(0), selectedStyle(0) { } SourceFormatterSettings::SourceFormatterSettings(QWidget *parent, const QVariantList &args) : KCModule(KAboutData::pluginData("kcm_kdevsourceformattersettings"), parent, args) { setupUi(this); connect( cbLanguages, SIGNAL(currentIndexChanged(int)), SLOT(selectLanguage(int)) ); connect( cbFormatters, SIGNAL(currentIndexChanged(int)), SLOT(selectFormatter(int)) ); connect( chkKateModelines, SIGNAL(toggled(bool)), SLOT(somethingChanged()) ); connect( chkKateOverrideIndentation, SIGNAL(toggled(bool)), SLOT(somethingChanged()) ); connect( styleList, SIGNAL(currentRowChanged(int)), SLOT(selectStyle(int)) ); connect( btnDelStyle, SIGNAL(clicked()), SLOT(deleteStyle()) ); connect( btnNewStyle, SIGNAL(clicked()), SLOT(newStyle()) ); connect( btnEditStyle, SIGNAL(clicked()), SLOT(editStyle()) ); connect( styleList, SIGNAL(itemChanged(QListWidgetItem*)), SLOT(styleNameChanged(QListWidgetItem*)) ); m_document = KTextEditor::Editor::instance()->createDocument(this); m_document->setReadWrite(false); m_view = m_document->createView(textEditor); 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("dynamic-word-wrap", false); iface->setConfigValue("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::load() { SourceFormatterController* fmtctrl = Core::self()->sourceFormatterControllerInternal(); foreach( KDevelop::IPlugin* plugin, KDevelop::ICore::self()->pluginController()->allPluginsForExtension( "org.kdevelop.ISourceFormatter" ) ) { KDevelop::ISourceFormatter* ifmt = plugin->extension(); KPluginInfo info = KDevelop::Core::self()->pluginControllerInternal()->pluginInfo( plugin ); - SourceFormatter* formatter; + KDevelop::SourceFormatter* formatter; FormatterMap::const_iterator iter = formatters.constFind(ifmt->name()); if (iter == formatters.constEnd()) { - formatter = new SourceFormatter(); - formatter->formatter = ifmt; + formatter = fmtctrl->createFormatterForPlugin(ifmt); formatters[ifmt->name()] = formatter; - // Inserted a new formatter. Now fill it with styles - foreach( const KDevelop::SourceFormatterStyle& style, ifmt->predefinedStyles() ) - { - formatter->styles[ style.name() ] = new SourceFormatterStyle(style); - } - KConfigGroup grp = fmtctrl->configuration(); - if( grp.hasGroup( ifmt->name() ) ) - { - KConfigGroup fmtgrp = grp.group( ifmt->name() ); - foreach( const QString& subgroup, fmtgrp.groupList() ) { - SourceFormatterStyle* s = new SourceFormatterStyle( subgroup ); - KConfigGroup stylegrp = fmtgrp.group( subgroup ); - s->setCaption( stylegrp.readEntry( SourceFormatterController::styleCaptionKey, "" ) ); - s->setContent( stylegrp.readEntry( SourceFormatterController::styleContentKey, "" ) ); - formatter->styles[ s->name() ] = s; - } - } } else { formatter = iter.value(); } - foreach( const QString& mime, info.property( SourceFormatterController::supportedMimeTypesKey ).toStringList() ) - { - KMimeType::Ptr mimePtr = KMimeType::mimeType(mime); - if (!mimePtr) { - kWarning() << "plugin" << info.name() << "supports unknown mimetype entry" << mime; - continue; + for( const SourceFormatterStyle* style: formatter->styles ) { + for ( const SourceFormatterStyle::MimeHighlightPair& item: style->mimeTypes() ) { + KMimeType::Ptr mimePtr = KMimeType::mimeType(item.mimeType); + if (!mimePtr) { + kWarning() << "plugin" << info.name() << "supports unknown mimetype entry" << item.mimeType; + continue; + } + QString languageName = item.highlightMode; + LanguageSettings& l = languages[languageName]; + l.mimetypes.append( mimePtr ); + l.formatters.insert( formatter ); } - QString languageName = formatter->formatter->highlightModeForMime(mimePtr); - LanguageSettings& l = languages[languageName]; - l.mimetypes.append( mimePtr ); - l.formatters.insert( formatter ); } } // Sort the languages, preferring firstly active, then loaded languages QList sortedLanguages; foreach( KDevelop::ILanguage* language, KDevelop::ICore::self()->languageController()->activeLanguages() + KDevelop::ICore::self()->languageController()->loadedLanguages() ) - if( languages.contains( language->name() ) && !sortedLanguages.contains(language->name()) ) + { + 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->configuration(); LanguageSettings& l = languages[name]; foreach (const KMimeType::Ptr& mimetype, l.mimetypes) { QStringList formatterAndStyleName = grp.readEntry( mimetype->name(), "" ).split( "||", QString::KeepEmptyParts ); FormatterMap::const_iterator formatterIter = formatters.constFind(formatterAndStyleName.first()); if (formatterIter == formatters.constEnd()) { kDebug() << "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()) { kDebug() << "No style" << formatterAndStyleName.at( 1 ) << "found for formatter" << formatterAndStyleName.first(); selectAvailableStyle(l); } else { l.selectedStyle = styleIter.value(); } } break; } 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->configuration().readEntry( SourceFormatterController::kateModeLineConfigKey, false ) ); chkKateOverrideIndentation->setChecked( fmtctrl->configuration().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::save() { KConfigGroup grp = Core::self()->sourceFormatterControllerInternal()->configuration(); for ( LanguageMap::const_iterator iter = languages.constBegin(); iter != languages.constEnd(); ++iter ) { foreach( const KMimeType::Ptr& mime, iter.value().mimetypes ) { grp.writeEntry( mime->name(), QString("%1||%2").arg(iter.value().selectedFormatter->formatter->name()).arg( iter.value().selectedStyle->name() ) ); } } foreach( SourceFormatter* fmt, formatters ) { KConfigGroup fmtgrp = grp.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( userStylePrefix ) ) { fmtgrp.deleteGroup( subgrp ); } } foreach( const SourceFormatterStyle* style, fmt->styles ) { if( style->name().startsWith( 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() ); } } } grp.writeEntry( SourceFormatterController::kateModeLineConfigKey, chkKateModelines->isChecked() ); grp.writeEntry( SourceFormatterController::kateOverrideIndentationConfigKey, chkKateOverrideIndentation->isChecked() ); grp.sync(); Core::self()->sourceFormatterControllerInternal()->settingsChanged(); } void SourceFormatterSettings::enableStyleButtons() { bool userEntry = styleList->currentItem() && styleList->currentItem()->data( STYLE_ROLE ).toString().startsWith( 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 && 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 ); bool b = cbFormatters->blockSignals( true ); 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())); cbFormatters->blockSignals(b); selectFormatter( cbFormatters->currentIndex() ); emit changed( true ); } 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 = 0; // 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 == 0) { styleList->setCurrentRow(0); } enableStyleButtons(); emit changed( true ); } 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( true ); } 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("\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( true ); } void SourceFormatterSettings::editStyle() { QString language = cbLanguages->currentText(); Q_ASSERT( languages.contains( language ) ); LanguageSettings& l = languages[ language ]; SourceFormatter* fmt = l.selectedFormatter; KMimeType::Ptr mimetype = l.mimetypes.first(); if( fmt->formatter->editStyleWidget( mimetype ) != 0 ) { EditStyleDialog dlg( fmt->formatter, mimetype, *l.selectedStyle, this ); if( dlg.exec() == QDialog::Accepted ) { l.selectedStyle->setContent(dlg.content()); } updatePreview(); emit changed( true ); } } 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( userStylePrefix ) && name.mid( userStylePrefix.length() ).toInt() >= idx ) { idx = name.mid( userStylePrefix.length() ).toInt(); } } // Increase number for next style idx++; SourceFormatterStyle* s = new SourceFormatterStyle( QString( "%1%2" ).arg( userStylePrefix ).arg( idx ) ); if( item ) { SourceFormatterStyle* existstyle = fmt->styles[ item->data( STYLE_ROLE ).toString() ]; s->setCaption( i18n( "New %1", existstyle->caption() ) ); - s->setContent( existstyle->content() ); + 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( true ); } void SourceFormatterSettings::styleNameChanged( QListWidgetItem* item ) { if ( !item->isSelected() ) { return; } LanguageSettings& l = languages[ cbLanguages->currentText() ]; l.selectedStyle->setCaption( item->text() ); emit changed( true ); } 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( 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->description().isEmpty() ) descriptionLabel->hide(); else descriptionLabel->show(); if( style->usePreview() ) { ISourceFormatter* ifmt = fmt->formatter; KMimeType::Ptr mime = l.mimetypes.first(); - m_document->setHighlightingMode( ifmt->highlightModeForMime( mime ) ); + 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("replace-tabs"); iface->setConfigValue("replace-tabs", false); } - m_document->setText( ifmt->formatSourceWithStyle( *style, ifmt->previewText( mime ), KUrl(), mime ) ); + m_document->setText( ifmt->formatSourceWithStyle( *style, ifmt->previewText( style, mime ), KUrl(), mime ) ); if (iface) { iface->setConfigValue("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" unmanagedWidgetChangeState(true); } #include "sourceformattersettings.moc" diff --git a/shell/settings/sourceformattersettings.h b/shell/settings/sourceformattersettings.h index 112d1a175f..a46287579f 100644 --- a/shell/settings/sourceformattersettings.h +++ b/shell/settings/sourceformattersettings.h @@ -1,100 +1,93 @@ /* 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. */ #ifndef KDEVPLATFORM_SOURCEFORMATTERSETTINGS_H #define KDEVPLATFORM_SOURCEFORMATTERSETTINGS_H #include #include #include +#include + #include "ui_sourceformattersettings.h" class QListWidgetItem; namespace KTextEditor { class Document; class View; } namespace KDevelop { class ISourceFormatter; class SourceFormatterStyle; } -struct SourceFormatter -{ - KDevelop::ISourceFormatter* formatter; - // style name -> style. style objects owned by this - typedef QMap StyleMap; - StyleMap styles; - ~SourceFormatter(); -}; - struct LanguageSettings { LanguageSettings(); QList mimetypes; - QSet formatters; + QSet formatters; // weak pointers to selected formatter and style, no ownership - SourceFormatter* selectedFormatter; // Should never be zero + KDevelop::SourceFormatter* selectedFormatter; // Should never be zero KDevelop::SourceFormatterStyle* selectedStyle; // TODO: can this be zero? Assume that not }; /** \short The settings modulefor the Source formatter plugin. * It supports predefined and custom styles. A live preview of the style * is shown on the right side of the page.s */ class SourceFormatterSettings : public KCModule, public Ui::SourceFormatterSettingsUI { Q_OBJECT public: SourceFormatterSettings( QWidget *parent, const QVariantList &args ); virtual ~SourceFormatterSettings(); public slots: virtual void load(); virtual void save(); private slots: void deleteStyle(); void editStyle(); void newStyle(); void selectLanguage( int ); void selectFormatter( int ); void selectStyle( int ); void styleNameChanged( QListWidgetItem* ); void somethingChanged(); private: void updatePreview(); QListWidgetItem* addStyle( const KDevelop::SourceFormatterStyle& s ); static const QString userStylePrefix; void enableStyleButtons(); // Language name -> language settings typedef QMap LanguageMap; LanguageMap languages; // formatter name -> formatter. Formatters owned by this - typedef QMap FormatterMap; + typedef QMap FormatterMap; FormatterMap formatters; KTextEditor::Document* m_document; KTextEditor::View* m_view; }; #endif // KDEVPLATFORM_SOURCEFORMATTERSETTINGS_H diff --git a/shell/sourceformattercontroller.cpp b/shell/sourceformattercontroller.cpp index 9a7e895a6d..fc801b61b9 100644 --- a/shell/sourceformattercontroller.cpp +++ b/shell/sourceformattercontroller.cpp @@ -1,627 +1,658 @@ /* This file is part of KDevelop Copyright 2009 Andreas Pakulat Copyright (C) 2008 Cédric Pasteur 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 "sourceformattercontroller.h" #include #include #include #include #include #include #include #include #include #include #include #include "core.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "plugincontroller.h" #include namespace KDevelop { const QString SourceFormatterController::kateModeLineConfigKey = "ModelinesEnabled"; const QString SourceFormatterController::kateOverrideIndentationConfigKey = "OverrideKateIndentation"; const QString SourceFormatterController::styleCaptionKey = "Caption"; const QString SourceFormatterController::styleContentKey = "Content"; -const QString SourceFormatterController::supportedMimeTypesKey = "X-KDevelop-SupportedMimeTypes"; +const QString SourceFormatterController::styleMimeTypesKey = "MimeTypes"; +const QString SourceFormatterController::styleSampleKey = "StyleSample"; SourceFormatterController::SourceFormatterController(QObject *parent) : ISourceFormatterController(parent), m_enabled(true) { setObjectName("SourceFormatterController"); setComponentName("kdevsourceformatter", "kdevsourceformatter"); setXMLFile("kdevsourceformatter.rc"); if (Core::self()->setupFlags() & Core::NoUi) return; m_formatTextAction = actionCollection()->addAction("edit_reformat_source"); m_formatTextAction->setText(i18n("&Reformat Source")); m_formatTextAction->setToolTip(i18n("Reformat source using AStyle")); m_formatTextAction->setWhatsThis(i18n("Source reformatting functionality using astyle library.")); connect(m_formatTextAction, SIGNAL(triggered()), this, SLOT(beautifySource())); m_formatLine = actionCollection()->addAction("edit_reformat_line"); m_formatLine->setText(i18n("Reformat Line")); m_formatLine->setToolTip(i18n("Reformat current line using AStyle")); m_formatLine->setWhatsThis(i18n("Source reformatting of line under cursor using astyle library.")); connect(m_formatLine, SIGNAL(triggered()), this, SLOT(beautifyLine())); m_formatFilesAction = actionCollection()->addAction("tools_astyle"); m_formatFilesAction->setText(i18n("Format Files")); m_formatFilesAction->setToolTip(i18n("Format file(s) using the current theme")); m_formatFilesAction->setWhatsThis(i18n("Formatting functionality using astyle library.")); connect(m_formatFilesAction, SIGNAL(triggered()), this, SLOT(formatFiles())); m_formatTextAction->setEnabled(false); m_formatFilesAction->setEnabled(true); connect(Core::self()->documentController(), SIGNAL(documentActivated(KDevelop::IDocument*)), this, SLOT(activeDocumentChanged(KDevelop::IDocument*))); // Use a queued connection, because otherwise the view is not yet fully set up connect(Core::self()->documentController(), SIGNAL(documentLoaded(KDevelop::IDocument*)), this, SLOT(documentLoaded(KDevelop::IDocument*)), Qt::QueuedConnection); activeDocumentChanged(Core::self()->documentController()->activeDocument()); } void SourceFormatterController::documentLoaded( IDocument* doc ) { // NOTE: explicitly check this here to prevent crashes on shutdown // when this slot gets called (note: delayed connection) // but the text document was already destroyed // there have been unit tests that failed due to that... if (!doc->textDocument()) { return; } KMimeType::Ptr mime = KMimeType::findByUrl(doc->url()); adaptEditorIndentationMode( doc->textDocument(), formatterForMimeType(mime) ); } void SourceFormatterController::initialize() { } SourceFormatterController::~SourceFormatterController() { } ISourceFormatter* SourceFormatterController::formatterForUrl(const KUrl &url) { KMimeType::Ptr mime = KMimeType::findByUrl(url); return formatterForMimeType(mime); } -KConfigGroup SourceFormatterController::configuration() +KConfigGroup SourceFormatterController::configuration() const { return Core::self()->activeSession()->config()->group( "SourceFormatter" ); } -static ISourceFormatter* findFirstFormatterForMimeType( const KMimeType::Ptr& mime ) +ISourceFormatter* SourceFormatterController::findFirstFormatterForMimeType( const KMimeType::Ptr& mime ) const { static QHash knownFormatters; if (knownFormatters.contains(mime->name())) return knownFormatters[mime->name()]; foreach( IPlugin* p, Core::self()->pluginController()->allPluginsForExtension( "org.kdevelop.ISourceFormatter" ) ) { KPluginInfo info = Core::self()->pluginController()->pluginInfo( p ); - if( info.property( SourceFormatterController::supportedMimeTypesKey ).toStringList().contains( mime->name() ) ) { - ISourceFormatter *formatter = p->extension(); - knownFormatters[mime->name()] = formatter; - return formatter; + ISourceFormatter *iformatter = p->extension(); + QSharedPointer formatter(createFormatterForPlugin(iformatter)); + if( formatter->supportedMimeTypes().contains(mime->name()) ) { + knownFormatters[mime->name()] = iformatter; + return iformatter; } } knownFormatters[mime->name()] = 0; return 0; } +static void populateStyleFromConfigGroup(SourceFormatterStyle* s, const KConfigGroup& stylegrp) +{ + s->setCaption( stylegrp.readEntry( SourceFormatterController::styleCaptionKey, QString() ) ); + s->setContent( stylegrp.readEntry( SourceFormatterController::styleContentKey, QString() ) ); + s->setMimeTypes( stylegrp.readEntry( SourceFormatterController::styleMimeTypesKey, QStringList() ) ); + s->setOverrideSample( stylegrp.readEntry( SourceFormatterController::styleSampleKey, QString() ) ); +} + +SourceFormatter* SourceFormatterController::createFormatterForPlugin(ISourceFormatter *ifmt) const +{ + SourceFormatter* formatter = new SourceFormatter(); + formatter->formatter = ifmt; + + // Inserted a new formatter. Now fill it with styles + foreach( const KDevelop::SourceFormatterStyle& style, ifmt->predefinedStyles() ) { + formatter->styles[ style.name() ] = new SourceFormatterStyle(style); + } + KConfigGroup grp = configuration(); + if( grp.hasGroup( ifmt->name() ) ) { + KConfigGroup fmtgrp = grp.group( ifmt->name() ); + foreach( const QString& subgroup, fmtgrp.groupList() ) { + SourceFormatterStyle* s = new SourceFormatterStyle( subgroup ); + KConfigGroup stylegrp = fmtgrp.group( subgroup ); + populateStyleFromConfigGroup(s, stylegrp); + formatter->styles[ s->name() ] = s; + } + } + return formatter; +} + ISourceFormatter* SourceFormatterController::formatterForMimeType(const KMimeType::Ptr &mime) { if( !m_enabled || !isMimeTypeSupported( mime ) ) { return 0; } QString formatter = configuration().readEntry( mime->name(), "" ); if( formatter.isEmpty() ) { return findFirstFormatterForMimeType( mime ); } QStringList formatterinfo = formatter.split( "||", QString::SkipEmptyParts ); if( formatterinfo.size() != 2 ) { kDebug() << "Broken formatting entry for mime:" << mime << "current value:" << formatter; return 0; } return Core::self()->pluginControllerInternal()->extensionForPlugin( "org.kdevelop.ISourceFormatter", formatterinfo.at(0) ); } bool SourceFormatterController::isMimeTypeSupported(const KMimeType::Ptr &mime) { if( findFirstFormatterForMimeType( mime ) ) { return true; } return false; } QString SourceFormatterController::indentationMode(const KMimeType::Ptr &mime) { if (mime->is("text/x-c++src") || mime->is("text/x-chdr") || mime->is("text/x-c++hdr") || mime->is("text/x-csrc") || mime->is("text/x-java") || mime->is("text/x-csharp")) return "cstyle"; return "none"; } QString SourceFormatterController::addModelineForCurrentLang(QString input, const KUrl& url, const KMimeType::Ptr& mime) { if( !isMimeTypeSupported(mime) ) return input; QRegExp kateModelineWithNewline("\\s*\\n//\\s*kate:(.*)$"); // If there already is a modeline in the document, adapt it while formatting, even // if "add modeline" is disabled. if( !configuration().readEntry( SourceFormatterController::kateModeLineConfigKey, false ) && kateModelineWithNewline.indexIn( input ) == -1 ) return input; ISourceFormatter* fmt = formatterForMimeType( mime ); ISourceFormatter::Indentation indentation = fmt->indentation(url); if( !indentation.isValid() ) return input; QString output; QTextStream os(&output, QIODevice::WriteOnly); QTextStream is(&input, QIODevice::ReadOnly); Q_ASSERT(fmt); QString modeline("// kate: "); QString indentLength = QString::number(indentation.indentWidth); QString tabLength = QString::number(indentation.indentationTabWidth); // add indentation style modeline.append("indent-mode ").append(indentationMode(mime).append("; ")); if(indentation.indentWidth) // We know something about indentation-width modeline.append(QString("indent-width %1; ").arg(indentation.indentWidth)); if(indentation.indentationTabWidth != 0) // We know something about tab-usage { modeline.append(QString("replace-tabs %1; ").arg((indentation.indentationTabWidth == -1) ? "on" : "off")); if(indentation.indentationTabWidth > 0) modeline.append(QString("tab-width %1; ").arg(indentation.indentationTabWidth)); } kDebug() << "created modeline: " << modeline << endl; QRegExp kateModeline("^\\s*//\\s*kate:(.*)$"); bool modelinefound = false; QRegExp knownOptions("\\s*(indent-width|space-indent|tab-width|indent-mode|replace-tabs)"); while (!is.atEnd()) { QString line = is.readLine(); // replace only the options we care about if (kateModeline.indexIn(line) >= 0) { // match kDebug() << "Found a kate modeline: " << line << endl; modelinefound = true; QString options = kateModeline.cap(1); QStringList optionList = options.split(';', QString::SkipEmptyParts); os << modeline; foreach(QString s, optionList) { if (knownOptions.indexIn(s) < 0) { // unknown option, add it if(s.startsWith(' ')) s=s.mid(1); os << s << ";"; kDebug() << "Found unknown option: " << s << endl; } } os << endl; } else os << line << endl; } if (!modelinefound) os << modeline << endl; return output; } void SourceFormatterController::cleanup() { } void SourceFormatterController::activeDocumentChanged(IDocument* doc) { bool enabled = false; if (doc) { KMimeType::Ptr mime = KMimeType::findByUrl(doc->url()); if (isMimeTypeSupported(mime)) enabled = true; } m_formatTextAction->setEnabled(enabled); } void SourceFormatterController::beautifySource() { IDocument* idoc = KDevelop::ICore::self()->documentController()->activeDocument(); KTextEditor::View* view = idoc->activeTextView(); if (!view) return; KTextEditor::Document* doc = view->document(); // load the appropriate formatter KMimeType::Ptr mime = KMimeType::findByUrl(doc->url()); ISourceFormatter *formatter = formatterForMimeType(mime); if( !formatter ) { kDebug() << "no formatter available for" << mime; return; } // Ignore the modeline, as the modeline will be changed anyway adaptEditorIndentationMode( doc, formatter, true ); bool has_selection = view->selection(); if (has_selection) { QString original = view->selectionText(); QString output = formatter->formatSource(view->selectionText(), doc->url(), mime, doc->text(KTextEditor::Range(KTextEditor::Cursor(0,0),view->selectionRange().start())), doc->text(KTextEditor::Range(view->selectionRange().end(), doc->documentRange().end()))); //remove the final newline character, unless it should be there if (!original.endsWith('\n') && output.endsWith('\n')) output.resize(output.length() - 1); //there was a selection, so only change the part of the text related to it // We don't use KTextEditor::Document directly, because CodeRepresentation transparently works // around a possible tab-replacement incompatibility between kate and kdevelop DynamicCodeRepresentation::Ptr code = DynamicCodeRepresentation::Ptr::dynamicCast( KDevelop::createCodeRepresentation( IndexedString( doc->url() ) ) ); Q_ASSERT( code ); code->replace( view->selectionRange(), original, output ); } else { formatDocument(idoc, formatter, mime); } } void SourceFormatterController::beautifyLine() { KDevelop::IDocumentController *docController = KDevelop::ICore::self()->documentController(); KDevelop::IDocument *doc = docController->activeDocument(); if (!doc || !doc->isTextDocument()) return; KTextEditor::Document *tDoc = doc->textDocument(); KTextEditor::View* view = doc->activeTextView(); if (!view) return; // load the appropriate formatter KMimeType::Ptr mime = KMimeType::findByUrl(doc->url()); ISourceFormatter *formatter = formatterForMimeType(mime); if( !formatter ) { kDebug() << "no formatter available for" << mime; return; } const KTextEditor::Cursor cursor = view->cursorPosition(); const QString line = tDoc->line(cursor.line()); const QString prev = tDoc->text(KTextEditor::Range(0, 0, cursor.line(), 0)); const QString post = '\n' + tDoc->text(KTextEditor::Range(KTextEditor::Cursor(cursor.line() + 1, 0), tDoc->documentEnd())); const QString formatted = formatter->formatSource(line, doc->url(), mime, prev, post); // We don't use KTextEditor::Document directly, because CodeRepresentation transparently works // around a possible tab-replacement incompatibility between kate and kdevelop DynamicCodeRepresentation::Ptr code = DynamicCodeRepresentation::Ptr::dynamicCast( KDevelop::createCodeRepresentation( IndexedString( doc->url() ) ) ); Q_ASSERT( code ); code->replace( KTextEditor::Range(cursor.line(), 0, cursor.line(), line.length()), line, formatted ); // advance cursor one line view->setCursorPosition(KTextEditor::Cursor(cursor.line() + 1, 0)); } void SourceFormatterController::formatDocument(KDevelop::IDocument *doc, ISourceFormatter *formatter, const KMimeType::Ptr &mime) { // We don't use KTextEditor::Document directly, because CodeRepresentation transparently works // around a possible tab-replacement incompatibility between kate and kdevelop CodeRepresentation::Ptr code = KDevelop::createCodeRepresentation( IndexedString( doc->url() ) ); KTextEditor::Cursor cursor = doc->cursorPosition(); QString text = formatter->formatSource(code->text(), doc->url(), mime); text = addModelineForCurrentLang(text, doc->url(), mime); code->setText(text); doc->setCursorPosition(cursor); } void SourceFormatterController::settingsChanged() { if( configuration().readEntry( SourceFormatterController::kateOverrideIndentationConfigKey, false ) ) foreach( KDevelop::IDocument* doc, ICore::self()->documentController()->openDocuments() ) adaptEditorIndentationMode( doc->textDocument(), formatterForUrl(doc->url()) ); } /** * Kate commands: * Use spaces for indentation: * "set-replace-tabs 1" * Use tabs for indentation (eventually mixed): * "set-replace-tabs 0" * Indent width: * "set-indent-width X" * Tab width: * "set-tab-width X" * */ void SourceFormatterController::adaptEditorIndentationMode(KTextEditor::Document *doc, ISourceFormatter *formatter, bool ignoreModeline ) { if( !formatter || !configuration().readEntry( SourceFormatterController::kateOverrideIndentationConfigKey, false ) || !doc ) return; kDebug() << "adapting mode for" << doc->url(); QRegExp kateModelineWithNewline("\\s*\\n//\\s*kate:(.*)$"); // modelines should always take precedence if( !ignoreModeline && kateModelineWithNewline.indexIn( doc->text() ) != -1 ) { kDebug() << "ignoring because a kate modeline was found"; return; } ISourceFormatter::Indentation indentation = formatter->indentation(doc->url()); if(indentation.isValid()) { struct CommandCaller { CommandCaller(KTextEditor::Document* _doc) : doc(_doc), ci(qobject_cast(KTextEditor::Editor::instance())) { Q_ASSERT(ci); } void operator()(QString cmd) { KTextEditor::Command* command = ci->queryCommand( cmd ); Q_ASSERT(command); QString msg; kDebug() << "calling" << cmd; foreach(KTextEditor::View* view, doc->views()) if( !command->exec( view, cmd, msg ) ) kWarning() << "setting indentation width failed: " << msg; } KTextEditor::Document* doc; KTextEditor::CommandInterface* ci; } call(doc); if( indentation.indentWidth ) // We know something about indentation-width call( QString("set-indent-width %1").arg(indentation.indentWidth ) ); if( indentation.indentationTabWidth != 0 ) // We know something about tab-usage { call( QString("set-replace-tabs %1").arg( (indentation.indentationTabWidth == -1) ? 1 : 0 ) ); if( indentation.indentationTabWidth > 0 ) call( QString("set-tab-width %1").arg(indentation.indentationTabWidth ) ); } }else{ kDebug() << "found no valid indentation"; } } void SourceFormatterController::formatFiles() { if (m_prjItems.isEmpty()) return; //get a list of all files in this folder recursively QList folders; foreach(KDevelop::ProjectBaseItem *item, m_prjItems) { if (!item) continue; if (item->folder()) folders.append(item->folder()); else if (item->file()) m_urls.append(item->file()->path().toUrl()); else if (item->target()) { foreach(KDevelop::ProjectFileItem *f, item->fileList()) m_urls.append(f->path().toUrl()); } } while (!folders.isEmpty()) { KDevelop::ProjectFolderItem *item = folders.takeFirst(); foreach(KDevelop::ProjectFolderItem *f, item->folderList()) folders.append(f); foreach(KDevelop::ProjectTargetItem *f, item->targetList()) { foreach(KDevelop::ProjectFileItem *child, f->fileList()) m_urls.append(child->path().toUrl()); } foreach(KDevelop::ProjectFileItem *f, item->fileList()) m_urls.append(f->path().toUrl()); } formatFiles(m_urls); } void SourceFormatterController::formatFiles(KUrl::List &list) { //! \todo IStatus for (int fileCount = 0; fileCount < list.size(); fileCount++) { // check mimetype KMimeType::Ptr mime = KMimeType::findByUrl(list[fileCount]); kDebug() << "Checking file " << list[fileCount].pathOrUrl() << " of mime type " << mime->name() << endl; ISourceFormatter *formatter = formatterForMimeType(mime); if (!formatter) // unsupported mime type continue; // if the file is opened in the editor, format the text in the editor without saving it KDevelop::IDocumentController *docController = KDevelop::ICore::self()->documentController(); KDevelop::IDocument *doc = docController->documentForUrl(list[fileCount]); if (doc) { kDebug() << "Processing file " << list[fileCount].pathOrUrl() << "opened in editor" << endl; formatDocument(doc, formatter, mime); continue; } kDebug() << "Processing file " << list[fileCount].pathOrUrl() << endl; QString tmpFile, output; if (KIO::NetAccess::download(list[fileCount], tmpFile, 0)) { QFile file(tmpFile); // read file if (file.open(QFile::ReadOnly)) { QTextStream is(&file); output = formatter->formatSource(is.readAll(), list[fileCount], mime); file.close(); } else KMessageBox::error(0, i18n("Unable to read %1", list[fileCount].prettyUrl())); //write new content if (file.open(QFile::WriteOnly | QIODevice::Truncate)) { QTextStream os(&file); os << addModelineForCurrentLang(output, list[fileCount], mime); file.close(); } else KMessageBox::error(0, i18n("Unable to write to %1", list[fileCount].prettyUrl())); if (!KIO::NetAccess::upload(tmpFile, list[fileCount], 0)) KMessageBox::error(0, KIO::NetAccess::lastErrorString()); KIO::NetAccess::removeTempFile(tmpFile); } else KMessageBox::error(0, KIO::NetAccess::lastErrorString()); } } KDevelop::ContextMenuExtension SourceFormatterController::contextMenuExtension(KDevelop::Context* context) { KDevelop::ContextMenuExtension ext; m_urls.clear(); m_prjItems.clear(); if (context->hasType(KDevelop::Context::EditorContext)) { if(m_formatTextAction->isEnabled()) ext.addAction(KDevelop::ContextMenuExtension::EditGroup, m_formatTextAction); } else if (context->hasType(KDevelop::Context::FileContext)) { KDevelop::FileContext* filectx = dynamic_cast(context); m_urls = filectx->urls(); ext.addAction(KDevelop::ContextMenuExtension::EditGroup, m_formatFilesAction); } else if (context->hasType(KDevelop::Context::CodeContext)) { } else if (context->hasType(KDevelop::Context::ProjectItemContext)) { KDevelop::ProjectItemContext* prjctx = dynamic_cast(context); m_prjItems = prjctx->items(); if ( !m_prjItems.isEmpty() ) { ext.addAction(KDevelop::ContextMenuExtension::ExtensionGroup, m_formatFilesAction); } } return ext; } SourceFormatterStyle SourceFormatterController::styleForMimeType( const KMimeType::Ptr& mime ) { QStringList formatter = configuration().readEntry( mime->name(), "" ).split( "||", QString::SkipEmptyParts ); if( formatter.count() == 2 ) { SourceFormatterStyle s( formatter.at( 1 ) ); KConfigGroup fmtgrp = configuration().group( formatter.at(0) ); if( fmtgrp.hasGroup( formatter.at(1) ) ) { KConfigGroup stylegrp = fmtgrp.group( formatter.at(1) ); - s.setCaption( stylegrp.readEntry( styleCaptionKey, "" ) ); - s.setContent( stylegrp.readEntry( styleContentKey, "" ) ); + populateStyleFromConfigGroup(&s, stylegrp); } return s; } return SourceFormatterStyle(); } void SourceFormatterController::disableSourceFormatting(bool disable) { m_enabled = !disable; } bool SourceFormatterController::sourceFormattingEnabled() { return m_enabled; } /* Code copied from source formatter plugin, unused currently but shouldn't be just thrown away QString SourceFormatterPlugin::replaceSpacesWithTab(const QString &input, ISourceFormatter *formatter) { QString output(input); int wsCount = formatter->indentationLength(); ISourceFormatter::IndentationType type = formatter->indentationType(); if (type == ISourceFormatter::IndentWithTabs) { // tabs and wsCount spaces to be a tab QString replace; for (int i = 0; i < wsCount;i++) replace += ' '; output = output.replace(replace, QChar('\t')); // input = input.remove(' '); } else if (type == ISourceFormatter::IndentWithSpacesAndConvertTabs) { //convert tabs to spaces QString replace; for (int i = 0;i < wsCount;i++) replace += ' '; output = output.replace(QChar('\t'), replace); } return output; } QString SourceFormatterPlugin::addIndentation(QString input, const QString indentWith) { QString output; QTextStream os(&output, QIODevice::WriteOnly); QTextStream is(&input, QIODevice::ReadOnly); while (!is.atEnd()) os << indentWith << is.readLine() << endl; return output; } */ } #include "sourceformattercontroller.moc" // kate: indent-mode cstyle; space-indent off; tab-width 4; diff --git a/shell/sourceformattercontroller.h b/shell/sourceformattercontroller.h index 69db294885..2e52884b58 100644 --- a/shell/sourceformattercontroller.h +++ b/shell/sourceformattercontroller.h @@ -1,128 +1,167 @@ /* This file is part of KDevelop Copyright 2009 Andreas Pakulat Copyright (C) 2008 Cédric Pasteur 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. */ #ifndef KDEVPLATFORM_SOURCEFORMATTERCONTROLLER_H #define KDEVPLATFORM_SOURCEFORMATTERCONTROLLER_H #include #include #include #include +#include + #include #include #include #include #include #include "shellexport.h" namespace KTextEditor { class Document; } class QAction; namespace KDevelop { class Context; class ContextMenuExtension; class ProjectBaseItem; class IDocument; class ISourceFormatter; class IPlugin; +struct SourceFormatter +{ + KDevelop::ISourceFormatter* formatter; + // style name -> style. style objects owned by this + typedef QMap StyleMap; + StyleMap styles; + // Get a list of supported mime types from the style map. + QSet supportedMimeTypes() const + { + QSet supported; + for ( auto style: styles ) { + for ( auto item: style->mimeTypes() ) { + supported.insert(item.mimeType); + } + } + return supported; + } + ~SourceFormatter() + { + qDeleteAll(styles); + }; +}; + /** \short A singleton class managing all source formatter plugins */ class KDEVPLATFORMSHELL_EXPORT SourceFormatterController : public ISourceFormatterController, public KXMLGUIClient { Q_OBJECT public: static const QString kateModeLineConfigKey; static const QString kateOverrideIndentationConfigKey; static const QString styleCaptionKey; static const QString styleContentKey; - static const QString supportedMimeTypesKey; + static const QString styleMimeTypesKey; + static const QString styleSampleKey; SourceFormatterController(QObject *parent = 0); virtual ~SourceFormatterController(); void initialize(); void cleanup(); //----------------- Public API defined in interfaces ------------------- /** \return The formatter corresponding to the language * of the document corresponding to the \arg url. */ ISourceFormatter* formatterForUrl(const KUrl &url); /** Loads and returns a source formatter for this mime type. * The language is then activated and the style is loaded. * The source formatter is then ready to use on a file. */ ISourceFormatter* formatterForMimeType(const KMimeType::Ptr &mime); /** \return Whether this mime type is supported by any plugin. */ bool isMimeTypeSupported(const KMimeType::Ptr &mime); + /** + * @brief Instantiate a Formatter for the given plugin and load its configuration. + * + * @param ifmt The ISourceFormatter interface of the plugin + * @return KDevelop::SourceFormatter* the SourceFormatter instance for the plugin, including config items + */ + SourceFormatter* createFormatterForPlugin(KDevelop::ISourceFormatter* ifmt) const; + + /** + * @brief Find the first formatter which supports a given mime type. + */ + ISourceFormatter* findFirstFormatterForMimeType( const KMimeType::Ptr& mime ) const; + KDevelop::ContextMenuExtension contextMenuExtension(KDevelop::Context* context); virtual KDevelop::SourceFormatterStyle styleForMimeType( const KMimeType::Ptr& mime ); - KConfigGroup configuration(); + KConfigGroup configuration() const; void settingsChanged(); virtual void disableSourceFormatting(bool disable); virtual bool sourceFormattingEnabled(); private Q_SLOTS: void activeDocumentChanged(KDevelop::IDocument *doc); void beautifySource(); void beautifyLine(); void formatFiles(); void documentLoaded( KDevelop::IDocument* ); private: /** \return A modeline string (to add at the end or the beginning of a file) * corresponding to the settings of the active language. */ QString addModelineForCurrentLang(QString input, const KUrl& url, const KMimeType::Ptr&); /** \return The name of kate indentation mode for the mime type. * examples are cstyle, python, etc. */ QString indentationMode(const KMimeType::Ptr &mime); void formatDocument(KDevelop::IDocument *doc, ISourceFormatter *formatter, const KMimeType::Ptr &mime); // Adapts the mode of the editor regarding indentation-style void adaptEditorIndentationMode(KTextEditor::Document* doc, KDevelop::ISourceFormatter* formatter, bool ignoreModeline = false); void formatFiles(KUrl::List &list); // GUI actions QAction* m_formatTextAction; QAction* m_formatFilesAction; QAction* m_formatLine; QList m_prjItems; KUrl::List m_urls; bool m_enabled; }; } #endif // KDEVPLATFORM_SOURCEFORMATTERMANAGER_H // kate: indent-mode cstyle; space-indent off; tab-width 4; diff --git a/tests/autotestshell.cpp b/tests/autotestshell.cpp index d3381133b9..afc27e0a77 100644 --- a/tests/autotestshell.cpp +++ b/tests/autotestshell.cpp @@ -1,30 +1,30 @@ /*************************************************************************** - * Copyright 2013 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. */ #ifndef KDEVPLATFORM_JSONDECLARATIONTESTS_H #define KDEVPLATFORM_JSONDECLARATIONTESTS_H #include "language/duchain/ducontext.h" #include "language/duchain/declaration.h" #include "language/duchain/indexeddeclaration.h" #include "language/duchain/identifier.h" #include "language/duchain/abstractfunctiondeclaration.h" #include "language/duchain/types/typeutils.h" #include "language/duchain/types/identifiedtype.h" #include #include "language/duchain/duchain.h" #include "language/duchain/functiondefinition.h" #include "language/duchain/definitions.h" #include #include "jsontesthelpers.h" /** * JSON Object Specification: * DeclTestObject: Mapping of (string) declaration test names to values * TypeTestObject: Mapping of (string) type test names to values * CtxtTestObject: Mapping of (string) context test names to values * * Quick Reference: * useCount : int * identifier : string * qualifiedIdentifier : string * internalContext : CtxtTestObject * internalFunctionContext : CtxtTestObject * type : TypeTestObject * unaliasedType : TypeTestObject * targetType : TypeTestObject + * returnType : TypeTestObject * identifiedTypeQid : string * isVirtual : bool * isStatic : bool * declaration : DeclTestObject * definition : DeclTestObject * null : bool * defaultParameter : string * toString : string * range : string * kind : string */ namespace KDevelop { template<> QString TestSuite::objectInformation(Declaration *decl) { if (!decl) return "(null declaration)"; return QString("(Declaration on line %1 in %2)") .arg(decl->range().start.line + 1) .arg(decl->topContext()->url().str()); } namespace DeclarationTests { using namespace JsonTestHelpers; ///JSON type: int ///@returns whether the declaration's number of uses matches the given value DeclarationTest(useCount) { int uses = 0; foreach(const QList& useRanges, decl->uses()) { uses += useRanges.size(); } return compareValues(uses, value, "Declaration's use count "); } ///JSON type: string ///@returns whether the declaration's identifier matches the given value DeclarationTest(identifier) { return compareValues(decl->identifier().toString(), value, "Declaration's identifier"); } ///JSON type: string ///@returns whether the declaration's qualified identifier matches the given value DeclarationTest(qualifiedIdentifier) { return compareValues(decl->qualifiedIdentifier().toString(), value, "Declaration's qualified identifier"); } ///JSON type: CtxtTestObject ///@returns whether the tests for the declaration's internal context pass DeclarationTest(internalContext) { return testObject(decl->internalContext(), value, "Declaration's internal context"); } ///JSON type: CtxtTestObject ///@returns whether the tests for the declaration's internal function context pass DeclarationTest(internalFunctionContext) { const QString NO_INTERNAL_CTXT = "%1 has no internal function context."; AbstractFunctionDeclaration *absFuncDecl = dynamic_cast(decl); if (!absFuncDecl || !absFuncDecl->internalFunctionContext()) return NO_INTERNAL_CTXT.arg(decl->qualifiedIdentifier().toString()); return testObject(absFuncDecl->internalFunctionContext(), value, "Declaration's internal function context"); } /*FIXME: The type functions need some renaming and moving around * Some (all?) functions from cpp's TypeUtils should be moved to the kdevplatform type utils * targetType is exactly like realType except it also tosses pointers * shortenTypeForViewing should go to type utils (and it's broken, it places const to the left of all *'s when it should be left or right of the type) * UnaliasedType seems to be unable to understand aliases involving templates, perhaps a cpp version is in order */ ///JSON type: TypeTestObject ///@returns whether the tests for the declaration's type pass DeclarationTest(type) { return testObject(decl->abstractType(), value, "Declaration's type"); } ///JSON type: TypeTestObject ///@returns whether the tests for the declaration's unaliased type pass (TypeUtils::unaliasedType) DeclarationTest(unaliasedType) { return testObject(TypeUtils::unAliasedType(decl->abstractType()), value, "Declaration's unaliased type"); } ///JSON type: TypeTestObject ///@returns whether the tests for the declaration's target type pass (TypeUtils::targetType) DeclarationTest(targetType) { return testObject(TypeUtils::targetType(decl->abstractType(), decl->topContext()), value, "Declaration's target type"); } ///JSON type: TestTypeObject ///@returns the DeclarationTest(returnType) { FunctionType::Ptr functionType = decl->abstractType().cast(); AbstractType::Ptr returnType; if (functionType) { returnType = functionType->returnType(); } return testObject(returnType, value, "Declaration's return type"); } ///JSON type: string ///@returns whether the declaration's type's declaration can be identified and if it's qualified identifier matches the given value DeclarationTest(identifiedTypeQid) { VERIFY_TYPE(QString); const QString UN_ID_ERROR = "Unable to identify declaration of type \"%1\"."; AbstractType::Ptr type = decl->abstractType(); IdentifiedType* idType = dynamic_cast(type.unsafeData()); Declaration* idDecl = idType ? idType->declaration(decl->topContext()) : 0; if (!idDecl) return UN_ID_ERROR.arg(type->toString()); return compareValues(idDecl->qualifiedIdentifier().toString(), value, "Declaration's identified type"); } ///JSON type: bool ///@returns whether the (function) declaration's isVirtual matches the given value DeclarationTest(isVirtual) { const QString NOT_A_FUNCTION = "Non-function declaration cannot be virtual."; AbstractFunctionDeclaration *absFuncDecl = dynamic_cast(decl); if (!absFuncDecl) return NOT_A_FUNCTION; return compareValues(absFuncDecl->isVirtual(), value, "Declaration's isVirtual"); } ///JSON type: bool ///@returns whether the (class-member) declaration's isStatic matches the given value DeclarationTest(isStatic) { const QString NOT_A_MEMBER = "Non-class-member declaration cannot be static."; auto memberDecl = dynamic_cast(decl); if (!memberDecl) return NOT_A_MEMBER; return compareValues(memberDecl->isStatic(), value, "Declaration's isStatic"); } ///JSON type: DeclTestObject ///@returns whether the tests for the function declaration's definition pass DeclarationTest(definition) { KDevVarLengthArray definitions = DUChain::definitions()->definitions(decl->id()); Declaration *declDef = 0; if (definitions.size()) declDef = definitions.at(0).declaration(); return testObject(declDef, value, "Declaration's definition"); } ///JSON type: DeclTestObject ///@returns whether the tests for the function definition's declaration pass DeclarationTest(declaration) { FunctionDefinition *def = dynamic_cast(decl); Declaration *defDecl = nullptr; if (def) defDecl = def->declaration(decl->topContext()); return testObject(defDecl, value, "Definition's declaration"); } ///JSON type: bool ///@returns whether the declaration's nullity matches the given value DeclarationTest(null) { return compareValues(decl == 0, value, "Declaration's nullity"); } ///JSON type: bool ///@returns whether the declaration's default parameter matches the given value DeclarationTest(defaultParameter) { const QString NOT_IN_FUNC_CTXT = "Asked for a default parameter for a declaration outside of a function context."; const QString OWNER_NOT_FUNC = "Function context not owned by function declaration (what on earth did you do?)."; DUContext *context = decl->context(); if (!context || context->type() != DUContext::Function) return NOT_IN_FUNC_CTXT; AbstractFunctionDeclaration *funcDecl = dynamic_cast(context->owner()); if (!funcDecl) return OWNER_NOT_FUNC; int argIndex = context->localDeclarations().indexOf(decl); return compareValues(funcDecl->defaultParameterForArgument(argIndex).str(), value, "Declaration's default parameter"); } ///JSON type: string ///@returns stringified declaration DeclarationTest(toString) { if (!decl) { return "Invalid Declaration"; } return compareValues(decl->toString(), value, "Declaration's toString"); } ///JSON type: string ///@returns stringified declaration DeclarationTest(range) { if (!decl) { return "Invalid Declaration"; } auto range = decl->range(); QString string = QString("[(%1, %2), (%3, %4)]") .arg(range.start.line) .arg(range.start.column) .arg(range.end.line) .arg(range.end.column); return compareValues(string, value, "Declaration's toString"); } ///JSON type: string ///@returns stringified declaration kind DeclarationTest(kind) { if (!decl) { return "Invalid Declaration"; } QString kind; switch (decl->kind()) { case KDevelop::Declaration::Alias: kind = "Alias"; break; case KDevelop::Declaration::Import: kind = "Import"; break; case KDevelop::Declaration::Instance: kind = "Instance"; break; case KDevelop::Declaration::Namespace: kind = "Namespace"; break; case KDevelop::Declaration::NamespaceAlias: kind = "NamespaceAlias"; break; case KDevelop::Declaration::Type: kind = "Type"; break; } return compareValues(kind, value, "Declaration's kind"); } } } #endif //KDEVPLATFORM_JSONDECLARATIONTESTS_H diff --git a/util/autoorientedsplitter.cpp b/util/autoorientedsplitter.cpp index a19f383e1f..85a1c73472 100644 --- a/util/autoorientedsplitter.cpp +++ b/util/autoorientedsplitter.cpp @@ -1,46 +1,46 @@ /* This file is part of KDevelop - Copyright 2014 Kevin Funk + 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 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 "autoorientedsplitter.h" #include using namespace KDevelop; AutoOrientedSplitter::AutoOrientedSplitter(QWidget* parent) : QSplitter(parent) { } AutoOrientedSplitter::AutoOrientedSplitter(Qt::Orientation orientation, QWidget* parent) : QSplitter(orientation, parent) { } void AutoOrientedSplitter::resizeEvent(QResizeEvent* e) { const QSize size = e->size(); const float ratio = (float)size.width() / size.height(); const Qt::Orientation orientation = (ratio < 1.0 ? Qt::Vertical : Qt::Horizontal); setOrientation(orientation); QSplitter::resizeEvent(e); } #include "autoorientedsplitter.moc" diff --git a/util/autoorientedsplitter.h b/util/autoorientedsplitter.h index 797c525460..574c532396 100644 --- a/util/autoorientedsplitter.h +++ b/util/autoorientedsplitter.h @@ -1,52 +1,52 @@ /* This file is part of KDevelop - Copyright 2014 Kevin Funk + 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 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. */ #ifndef KDEVPLATFORM_AUTOORIENTEDSPLITTER_H #define KDEVPLATFORM_AUTOORIENTEDSPLITTER_H #include "utilexport.h" #include namespace KDevelop { /** * Auto-oriented version of QSplitter based on the aspect ratio of the widget size * * In case this widget is resized, we check whether we're currently in * "portrait" (width < height) or "landscape" (width >= height) mode. * Consequently, in "portrait" mode the QSplitter orientation is set to Qt::Vertical * in order to get a vertical layout of the items -- Qt::Horizontal is set for * "landscape" mode */ class KDEVPLATFORMUTIL_EXPORT AutoOrientedSplitter : public QSplitter { Q_OBJECT public: explicit AutoOrientedSplitter(QWidget* parent = 0); explicit AutoOrientedSplitter(Qt::Orientation orientation, QWidget* parent = 0); protected: virtual void resizeEvent(QResizeEvent*); }; } #endif diff --git a/util/placeholderitemproxymodel.cpp b/util/placeholderitemproxymodel.cpp index a84081e3f1..bbf32dc35f 100644 --- a/util/placeholderitemproxymodel.cpp +++ b/util/placeholderitemproxymodel.cpp @@ -1,193 +1,193 @@ /* - * Copyright 2013 Kevin Funk + * 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 using namespace KDevelop; struct PlaceholderItemProxyModel::Private { 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; } 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::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/util/placeholderitemproxymodel.h b/util/placeholderitemproxymodel.h index fbbd93da1a..34d0ff00fc 100644 --- a/util/placeholderitemproxymodel.h +++ b/util/placeholderitemproxymodel.h @@ -1,117 +1,117 @@ /* - * Copyright 2013 Kevin Funk + * 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 . * */ #ifndef KDEVPLATFORM_PLACEHOLDERITEMPROXYMODEL_H #define KDEVPLATFORM_PLACEHOLDERITEMPROXYMODEL_H #include "utilexport.h" #include #include namespace KDevelop { /** * Proxy model adding a placeholder item for new entries * * This is mostly a QIdentityProxyModel, with one additional row added at the end * * Example use: * * @code * PlaceholderItemProxyModel* proxyModel = new PlaceholderItemProxyModel; * proxyModel->setSourceModel(new MyItemModel); * proxyModel->setColumnHint(0, "(Add new entry)"); * connect(proxyModel, SIGNAL(dataInserted(...), SLOT(handleDataInserted(...)); * @endcode * * In this case MyItemModel has exactly two entries, "Item1" and "Item2" * * This will end up in PlaceholderItemProxyModel holding the following indices: * - "Item1" (from source model) * - "Item2" (from source model) * - "(Add new entry)" (from PlaceholderItemProxyModel) * * In case the last entry is edited, and a non-empty value is supplied, * dataInserted() is emitted to notify the user about newly created rows. * The user then has to make sure the signal is handled accordingly and * new items are added to the source model. * * @see dataInserted * * @note WARNING: This implementation is only suitable for flat models * It will fall apart when you use a tree model as source */ class KDEVPLATFORMUTIL_EXPORT PlaceholderItemProxyModel : public QIdentityProxyModel { Q_OBJECT public: explicit PlaceholderItemProxyModel(QObject* parent = 0); virtual ~PlaceholderItemProxyModel(); QVariant columnHint(int column) const; /** * Set the hint value for @p column to @p hint * * This text is going to be displayed in the place holder item row * * Only columns with non-empty hints are clickable and editable and * eventually cause the dataInserted() signal to be triggered */ void setColumnHint(int column, const QVariant& hint); virtual void setSourceModel(QAbstractItemModel* sourceModel); virtual Qt::ItemFlags flags(const QModelIndex& index) const; virtual int rowCount(const QModelIndex& parent = QModelIndex()) const; virtual QVariant data(const QModelIndex& proxyIndex, int role = Qt::DisplayRole) const; virtual bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole); virtual QModelIndex parent(const QModelIndex& child) const; virtual QModelIndex buddy(const QModelIndex& index) const; virtual QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const; virtual QModelIndex mapToSource(const QModelIndex& proxyIndex) const; /** * Implement in subclass. * * @return True in case the input was valid, and the filter should notify * external observers via the dataInserted signal. * * By default, this method returns true only in case @p value is non-empty * * @sa dataInserted() */ virtual bool validateRow(const QModelIndex& index, const QVariant& value) const; Q_SIGNALS: void dataInserted(int column, const QVariant& values); private: struct Private; QScopedPointer const d; }; } #endif // KDEVPLATFORM_PLACEHOLDERITEMPROXYMODEL_H diff --git a/util/richtextpushbutton.cpp b/util/richtextpushbutton.cpp index fd67419f6f..4d3384ddd5 100644 --- a/util/richtextpushbutton.cpp +++ b/util/richtextpushbutton.cpp @@ -1,128 +1,118 @@ /* Copyright 2010 Unknown Author (Qt Centre) Copyright 2010 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 "richtextpushbutton.h" #include #include #include #include #include #include #include using namespace KDevelop; RichTextPushButton::RichTextPushButton(QWidget *parent) : QPushButton(parent) { } void RichTextPushButton::setHtml(const QString &text) { htmlText = text; isRichText = true; QPalette palette; palette.setBrush(QPalette::ButtonText, Qt::transparent); setPalette(palette); } void RichTextPushButton::setText(const QString &text) { isRichText = false; QPushButton::setText(text); } -QString RichTextPushButton::text() const -{ - if (isRichText) { - QTextDocument richText; - richText.setHtml(htmlText); - return richText.toPlainText(); - } else - return QPushButton::text(); -} - QSize RichTextPushButton::sizeHint() const { if(!isRichText) { return QPushButton::sizeHint(); } else{ QTextDocument richTextLabel; richTextLabel.setHtml(htmlText); return richTextLabel.size().toSize(); } } void RichTextPushButton::paintEvent(QPaintEvent *event) { if (isRichText) { QStylePainter p(this); QRect buttonRect = rect(); QPoint point; QTextDocument richTextLabel; richTextLabel.setHtml(htmlText); QPixmap richTextPixmap(richTextLabel.size().width(), richTextLabel.size().height()); richTextPixmap.fill(Qt::transparent); QPainter richTextPainter(&richTextPixmap); richTextLabel.drawContents(&richTextPainter, richTextPixmap.rect()); if (!icon().isNull()) point = QPoint(buttonRect.x() + buttonRect.width() / 2 + iconSize().width() / 2 + 2, buttonRect.y() + buttonRect.height() / 2); else point = QPoint(buttonRect.x() + buttonRect.width() / 2 - 1, buttonRect.y() + buttonRect.height() / 2); buttonRect.translate(point.x() - richTextPixmap.width() / 2, point.y() - richTextPixmap.height() / 2); p.drawControl(QStyle::CE_PushButton, getStyleOption()); p.drawPixmap(buttonRect.left(), buttonRect.top(), richTextPixmap.width(), richTextPixmap.height(),richTextPixmap); } else QPushButton::paintEvent(event); } QStyleOptionButton RichTextPushButton::getStyleOption() const { QStyleOptionButton opt; opt.initFrom(this); opt.features = QStyleOptionButton::None; if (isFlat()) opt.features |= QStyleOptionButton::Flat; if (menu()) opt.features |= QStyleOptionButton::HasMenu; if (autoDefault() || isDefault()) opt.features |= QStyleOptionButton::AutoDefaultButton; if (isDefault()) opt.features |= QStyleOptionButton::DefaultButton; if (isDown() || (menu() && menu()->isVisible())) opt.state |= QStyle::State_Sunken; if (isChecked()) opt.state |= QStyle::State_On; if (!isFlat() && !isDown()) opt.state |= QStyle::State_Raised; if (!isRichText) opt.text = QPushButton::text(); opt.icon = icon(); opt.iconSize = iconSize(); return opt; } diff --git a/vcs/dvcs/dvcsplugin.cpp b/vcs/dvcs/dvcsplugin.cpp index c79e0b8d43..ee1ad6b45a 100644 --- a/vcs/dvcs/dvcsplugin.cpp +++ b/vcs/dvcs/dvcsplugin.cpp @@ -1,160 +1,161 @@ /*************************************************************************** * This file was partly taken from KDevelop's cvs plugin * * Copyright 2007 Robert Gruber * * * * Adapted for DVCS (added templates) * * 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 . * ***************************************************************************/ #ifndef DVCS_PLUGIN_CC #define DVCS_PLUGIN_CC #include "dvcsplugin.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dvcsjob.h" #include "ui/importmetadatawidget.h" #include "ui/branchmanager.h" #include "ui/revhistory/commitlogmodel.h" #include "ui/revhistory/commitView.h" #include namespace KDevelop { struct DistributedVersionControlPluginPrivate { explicit DistributedVersionControlPluginPrivate(DistributedVersionControlPlugin * pThis) : m_common(new VcsPluginHelper(pThis, pThis)) {} ~DistributedVersionControlPluginPrivate() { delete m_common; } VcsPluginHelper* m_common; }; //class DistributedVersionControlPlugin DistributedVersionControlPlugin::DistributedVersionControlPlugin(QObject *parent, const QString& componentName) : IPlugin(componentName, parent) , d(new DistributedVersionControlPluginPrivate(this)) {} DistributedVersionControlPlugin::~DistributedVersionControlPlugin() { //TODO: Find out why this crashes on the svn tests delete d->m_factory; delete d; } // End: KDevelop::IBasicVersionControl // Begin: KDevelop::IDistributedVersionControl // End: KDevelop::IDistributedVersionControl KDevelop::VcsImportMetadataWidget* DistributedVersionControlPlugin::createImportMetadataWidget(QWidget* parent) { return new ImportMetadataWidget(parent); } KDevelop::ContextMenuExtension DistributedVersionControlPlugin::contextMenuExtension(Context* context) { d->m_common->setupFromContext(context); KUrl::List const & ctxUrlList = d->m_common->contextUrlList(); bool isWorkingDirectory = false; foreach(const KUrl &url, ctxUrlList) { if (isValidDirectory(url)) { isWorkingDirectory = true; break; } } if (!isWorkingDirectory) { // Not part of a repository return ContextMenuExtension(); } QMenu * menu = d->m_common->commonActions(); menu->addSeparator(); menu->addAction(i18n("Branches..."), this, SLOT(ctxBranchManager()))->setEnabled(ctxUrlList.count()==1); menu->addAction(i18n("Revision Graph..."), this, SLOT(ctxRevHistory()))->setEnabled(ctxUrlList.count()==1); additionalMenuEntries(menu, ctxUrlList); ContextMenuExtension menuExt; menuExt.addAction(ContextMenuExtension::VcsGroup, menu->menuAction()); return menuExt; } void DistributedVersionControlPlugin::additionalMenuEntries(QMenu* /*menu*/, const KUrl::List& /*urls*/) {} static QString stripPathToDir(const QString &path) { - return QFileInfo(path).absolutePath(); + QFileInfo info = QFileInfo(path); + return info.isDir() ? info.absoluteFilePath() : info.absolutePath(); } void DistributedVersionControlPlugin::ctxBranchManager() { KUrl::List const & ctxUrlList = d->m_common->contextUrlList(); Q_ASSERT(!ctxUrlList.isEmpty()); ICore::self()->documentController()->saveAllDocuments(); BranchManager branchManager(stripPathToDir(ctxUrlList.front().toLocalFile()), this, core()->uiController()->activeMainWindow()); branchManager.exec(); } // This is redundant with the normal VCS "history" action void DistributedVersionControlPlugin::ctxRevHistory() { KUrl::List const & ctxUrlList = d->m_common->contextUrlList(); Q_ASSERT(!ctxUrlList.isEmpty()); KDialog d; CommitLogModel* model = new CommitLogModel(this, ctxUrlList.first().toLocalFile(), &d); CommitView *revTree = new CommitView(&d); revTree->setModel(model); d.setButtons(KDialog::Close); d.setMainWidget(revTree); d.exec(); } } #endif diff --git a/vcs/widgets/vcsdiffpatchsources.cpp b/vcs/widgets/vcsdiffpatchsources.cpp index 764521dcf5..77144d9a2c 100644 --- a/vcs/widgets/vcsdiffpatchsources.cpp +++ b/vcs/widgets/vcsdiffpatchsources.cpp @@ -1,300 +1,300 @@ /* 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 "vcsjob.h" #include "vcsdiff.h" #include #include #include #include #include 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()); m_commitMessageEdit = new KTextEdit; m_commitMessageEdit.data()->setFont( KGlobalSettings::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_oldMessages = new KComboBox(m_commitMessageWidget.data()); m_oldMessages->addItem(i18n("Old Messages")); foreach(QString message, oldMessages()) m_oldMessages->addItem(message, message); m_oldMessages->setMaximumWidth(200); connect(m_oldMessages, SIGNAL(currentIndexChanged(QString)), this, SLOT(oldMessageChanged(QString))); titleLayout->addWidget(m_oldMessages); layout->addLayout(titleLayout); layout->addWidget(m_commitMessageEdit.data()); connect(this, SIGNAL(reviewCancelled(QString)), SLOT(addMessageToHistory(QString))); connect(this, SIGNAL(reviewFinished(QString,QList)), SLOT(addMessageToHistory(QString))); } 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); } } VCSDiffPatchSource::VCSDiffPatchSource(VCSDiffUpdater* updater) : m_updater(updater) { update(); KDevelop::IBasicVersionControl* vcs = m_updater->vcs(); KUrl url = m_updater->url(); QScopedPointer statusJob(vcs->status(url)); QVariant varlist; if( statusJob->exec() && statusJob->status() == VcsJob::JobSucceeded ) { varlist = statusJob->fetchResults(); foreach( const QVariant &var, varlist.toList() ) { VcsStatusInfo info = qVariantValue( var ); m_infos += info; if(info.state()!=VcsStatusInfo::ItemUpToDate) m_selectable[info.url()] = info.state(); } } else kDebug() << "Couldn't get status for urls: " << url; } VCSDiffPatchSource::VCSDiffPatchSource(const KDevelop::VcsDiff& diff) : m_updater(0) { updateFromDiff(diff); } VCSDiffPatchSource::~VCSDiffPatchSource() { QFile::remove(m_file.toLocalFile()); delete m_updater; } KUrl VCSDiffPatchSource::baseDir() const { return m_base; } KUrl VCSDiffPatchSource::file() const { return m_file; } QString VCSDiffPatchSource::name() const { return m_name; } void VCSDiffPatchSource::updateFromDiff(VcsDiff vcsdiff) { if(!m_file.isValid()) { KTemporaryFile temp2; temp2.setSuffix("2.patch"); temp2.setAutoRemove(false); temp2.open(); QTextStream t2(&temp2); t2 << vcsdiff.diff(); kDebug() << "filename:" << temp2.fileName(); m_file = KUrl(temp2.fileName()); temp2.close(); }else{ QFile file(m_file.path()); file.open(QIODevice::WriteOnly); QTextStream t2(&file); t2 << vcsdiff.diff(); } kDebug() << "using file" << m_file << vcsdiff.diff() << "base" << vcsdiff.baseDiff(); m_name = "VCS Diff"; m_base = vcsdiff.baseDiff(); m_base.addPath("/"); 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< KUrl, 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< KUrl > selection) { QString message; if (m_commitMessageEdit) message = m_commitMessageEdit.data()->toPlainText(); kDebug() << "Finishing with selection" << selection; QString files; foreach(const KUrl& 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(0, text, i18n("About to commit to repository"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), "ShouldAskConfirmCommit"); if (res != KMessageBox::Continue) { return false; } emit reviewFinished(message, selection); VcsJob* job=m_vcs->commit(message, selection, KDevelop::IBasicVersionControl::NonRecursive); ICore::self()->runController()->registerJob(job); deleteLater(); return true; } static KDevelop::IPatchSource::Ptr currentShownDiff; bool showVcsDiff(IPatchSource* vcsDiff) { KDevelop::IPatchReview* patchReview = ICore::self()->pluginController()->extensionForPlugin("org.kdevelop.IPatchReview"); //Only give one VCS diff at a time to the patch review plugin delete currentShownDiff; currentShownDiff = vcsDiff; if( patchReview ) { patchReview->startReview(currentShownDiff); return true; } else { kWarning() << "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))); VcsDiff diff; bool correctDiff = diffJob->exec(); if (correctDiff) diff = diffJob->fetchResults().value(); if (!correctDiff) KMessageBox::error(0, i18n("Could not create a patch for the current version.")); return diff; } VCSStandardDiffUpdater::VCSStandardDiffUpdater(IBasicVersionControl* vcs, KUrl url) : m_vcs(vcs), m_url(url) { } VCSStandardDiffUpdater::~VCSStandardDiffUpdater() { } VCSDiffUpdater::~VCSDiffUpdater() { } #include "vcsdiffpatchsources.moc"