diff --git a/debugger/framestack/framestackmodel.cpp b/debugger/framestack/framestackmodel.cpp index 3f0d640a3..340101ef2 100644 --- a/debugger/framestack/framestackmodel.cpp +++ b/debugger/framestack/framestackmodel.cpp @@ -1,411 +1,411 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2009 Niko Sams * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "framestackmodel.h" #include #include #include #include #include #include #include "../../interfaces/icore.h" #include "../../interfaces/idebugcontroller.h" #include "../../interfaces/iprojectcontroller.h" #include "../interfaces/isession.h" #include "util/debug.h" namespace KDevelop { class FrameStackModelPrivate { public: - FrameStackModelPrivate(FrameStackModel* q) : q(q) {} + explicit FrameStackModelPrivate(FrameStackModel* q) : q(q) {} void update(); QModelIndex indexForThreadNumber(int threadNumber); FrameStackModel* q; int m_currentThread = -1; int m_currentFrame = -1; int m_crashedThreadIndex = -1; // used to count how often a user has scrolled down and more frames needed to be fetched; // this way, the number of frames fetched in each chunk can be increased if the user wants // to scroll far int m_subsequentFrameFetchOperations = 0; bool m_updateCurrentFrameOnNextFetch = false; QList m_threads; QHash > m_frames; QHash m_hasMoreFrames; // Caches QHash m_fileExistsCache; }; FrameStackModel::FrameStackModel(IDebugSession *session) : IFrameStackModel(session) , d(new FrameStackModelPrivate(this)) { connect(session, &IDebugSession::stateChanged, this, &FrameStackModel::stateChanged); } FrameStackModel::~FrameStackModel() { } void FrameStackModel::setThreads(const QList &threads) { qCDebug(DEBUGGER) << threads.count(); if (!d->m_threads.isEmpty()) { beginRemoveRows(QModelIndex(), 0, d->m_threads.count()-1); d->m_threads.clear(); endRemoveRows(); } if (!threads.isEmpty()) { beginInsertRows(QModelIndex(), 0, threads.count()-1); d->m_threads = threads; endInsertRows(); } } QModelIndex FrameStackModelPrivate::indexForThreadNumber(int threadNumber) { int i=0; foreach (const auto &t, m_threads) { if (t.nr == threadNumber) { return q->index(i, 0); } i++; } return QModelIndex(); } void FrameStackModel::setFrames(int threadNumber, QList frames) { QModelIndex threadIndex = d->indexForThreadNumber(threadNumber); Q_ASSERT(threadIndex.isValid()); if (!d->m_frames[threadNumber].isEmpty()) { beginRemoveRows(threadIndex, 0, d->m_frames[threadNumber].count()-1); d->m_frames[threadNumber].clear(); endRemoveRows(); } if (!frames.isEmpty()) { beginInsertRows(threadIndex, 0, frames.count()-1); d->m_frames[threadNumber] = frames; endInsertRows(); } if (d->m_currentThread == threadNumber && d->m_updateCurrentFrameOnNextFetch) { d->m_currentFrame = 0; d->m_updateCurrentFrameOnNextFetch = false; } d->m_fileExistsCache.clear(); session()->raiseEvent(IDebugSession::thread_or_frame_changed); // FIXME: Ugly hack. Apparently, when rows are added, the selection // in the view is cleared. Emit this so that some frame is still // selected. emit currentFrameChanged(d->m_currentFrame); } void FrameStackModel::insertFrames(int threadNumber, const QList &frames) { QModelIndex threadIndex = d->indexForThreadNumber(threadNumber); Q_ASSERT(threadIndex.isValid()); beginInsertRows(threadIndex, d->m_frames[threadNumber].count()-1, d->m_frames[threadNumber].count()+frames.count()-1); d->m_frames[threadNumber] << frames; endInsertRows(); } void FrameStackModel::setHasMoreFrames(int threadNumber, bool hasMoreFrames) { d->m_hasMoreFrames[threadNumber] = hasMoreFrames; } FrameStackModel::FrameItem FrameStackModel::frame(const QModelIndex& index) { Q_ASSERT(index.internalId()); Q_ASSERT(static_cast(d->m_threads.count()) >= index.internalId()); const ThreadItem &thread = d->m_threads.at(index.internalId()-1); return d->m_frames[thread.nr].at(index.row()); } QList FrameStackModel::frames(int threadNumber) const { return d->m_frames.value(threadNumber); } QVariant FrameStackModel::data(const QModelIndex& index, int role) const { if (!index.internalId()) { //thread if (d->m_threads.count() <= index.row()) return QVariant(); const ThreadItem &thread = d->m_threads.at(index.row()); if (index.column() == 0) { if (role == Qt::DisplayRole) { if (thread.nr == d->m_crashedThreadIndex) { return i18nc("#thread-id at function-name or address", "#%1 at %2 (crashed)", thread.nr, thread.name); } else { return i18nc("#thread-id at function-name or address", "#%1 at %2", thread.nr, thread.name); } } else if (role == Qt::TextColorRole) { if (thread.nr == d->m_crashedThreadIndex) { KColorScheme scheme(QPalette::Active); return scheme.foreground(KColorScheme::NegativeText).color(); } } } } else { //frame if (static_cast(d->m_threads.count()) < index.internalId()) return QVariant(); const ThreadItem &thread = d->m_threads.at(index.internalId()-1); if (d->m_frames[thread.nr].count() <= index.row()) return QVariant(); const FrameItem &frame = d->m_frames[thread.nr].at(index.row()); if (index.column() == 0) { if (role == Qt::DisplayRole) { return QVariant(QString::number(frame.nr)); } } else if (index.column() == 1) { if (role == Qt::DisplayRole) { return QVariant(frame.name); } } else if (index.column() == 2) { if (role == Qt::DisplayRole) { QString ret = ICore::self()->projectController() ->prettyFileName(frame.file, IProjectController::FormatPlain); if (frame.line != -1) { ret += ':' + QString::number(frame.line + 1); } return ret; } else if (role == Qt::DecorationRole) { QMimeType mime = QMimeDatabase().mimeTypeForUrl(frame.file); return QIcon::fromTheme(mime.iconName()); } else if (role == Qt::TextColorRole) { const auto fileName = frame.file.toLocalFile(); auto cacheIt = d->m_fileExistsCache.find(fileName); if (cacheIt == d->m_fileExistsCache.end()) { cacheIt = d->m_fileExistsCache.insert(fileName, QFileInfo::exists(fileName)); } const bool fileExists = cacheIt.value(); if (!fileExists) { KColorScheme scheme(QPalette::Active); return scheme.foreground(KColorScheme::InactiveText).color(); } } } } return QVariant(); } int FrameStackModel::columnCount(const QModelIndex& parent) const { Q_UNUSED(parent); return 3; } int FrameStackModel::rowCount(const QModelIndex& parent) const { if (!parent.isValid()) { return d->m_threads.count(); } else if (!parent.internalId() && parent.column() == 0) { if (parent.row() < d->m_threads.count()) { return d->m_frames[d->m_threads.at(parent.row()).nr].count(); } } return 0; } QModelIndex FrameStackModel::parent(const QModelIndex& child) const { if (!child.internalId()) { return QModelIndex(); } else { return index(child.internalId()-1, 0); } } QModelIndex FrameStackModel::index(int row, int column, const QModelIndex& parent) const { if (parent.isValid()) { Q_ASSERT(!parent.internalId()); Q_ASSERT(parent.column() == 0); Q_ASSERT(parent.row() < d->m_threads.count()); return createIndex(row, column, parent.row()+1); } else { return createIndex(row, column); } } QVariant FrameStackModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { if (section == 0) { return i18n("Depth"); } else if (section == 1) { return i18n("Function"); } else if (section == 2) { return i18n("Source"); } } return QAbstractItemModel::headerData(section, orientation, role); } void FrameStackModel::setCurrentThread(int threadNumber) { qCDebug(DEBUGGER) << threadNumber; if (d->m_currentThread != threadNumber && threadNumber != -1) { // FIXME: this logic means that if we switch to thread 3 and // then to thread 2 and then to thread 3, we'll request frames // for thread 3 again, even if the program was not run in between // and therefore frames could not have changed. d->m_currentFrame = 0; //set before fetchFrames else --frame argument would be wrong d->m_updateCurrentFrameOnNextFetch = true; fetchFrames(threadNumber, 0, 20); } if (threadNumber != d->m_currentThread) { d->m_currentFrame = 0; d->m_currentThread = threadNumber; emit currentFrameChanged(d->m_currentFrame); } qCDebug(DEBUGGER) << "currentThread: " << d->m_currentThread << "currentFrame: " << d->m_currentFrame; emit currentThreadChanged(threadNumber); session()->raiseEvent(IDebugSession::thread_or_frame_changed); } void FrameStackModel::setCurrentThread(const QModelIndex& index) { Q_ASSERT(index.isValid()); Q_ASSERT(!index.internalId()); Q_ASSERT(index.column() == 0); setCurrentThread(d->m_threads[index.row()].nr); } void FrameStackModel::setCrashedThreadIndex(int index) { d->m_crashedThreadIndex = index; } int FrameStackModel::crashedThreadIndex() const { return d->m_crashedThreadIndex; } int FrameStackModel::currentThread() const { return d->m_currentThread; } QModelIndex FrameStackModel::currentThreadIndex() const { int i = 0; foreach (const ThreadItem &t, d->m_threads) { if (t.nr == currentThread()) { return index(i, 0); } ++i; } return QModelIndex(); } int FrameStackModel::currentFrame() const { return d->m_currentFrame; } QModelIndex FrameStackModel::currentFrameIndex() const { QModelIndex idx = currentThreadIndex(); return idx.child(d->m_currentFrame, 0); } void FrameStackModel::setCurrentFrame(int frame) { qCDebug(DEBUGGER) << frame; if (frame != d->m_currentFrame) { d->m_currentFrame = frame; session()->raiseEvent(IDebugSession::thread_or_frame_changed); emit currentFrameChanged(frame); } } void FrameStackModelPrivate::update() { m_subsequentFrameFetchOperations = 0; q->fetchThreads(); if (m_currentThread != -1) { q->fetchFrames(m_currentThread, 0, 20); } } void FrameStackModel::handleEvent(IDebugSession::event_t event) { switch (event) { case IDebugSession::program_state_changed: d->update(); break; default: break; } } void FrameStackModel::stateChanged(IDebugSession::DebuggerState state) { if (state == IDebugSession::PausedState) { setCurrentFrame(-1); d->m_updateCurrentFrameOnNextFetch = true; } else if (state == IDebugSession::EndedState || state == IDebugSession::NotStartedState) { setThreads(QList()); } } // FIXME: it should be possible to fetch more frames for // an arbitrary thread, without making it current. void FrameStackModel::fetchMoreFrames() { d->m_subsequentFrameFetchOperations += 1; const int fetch = 20 * d->m_subsequentFrameFetchOperations * d->m_subsequentFrameFetchOperations; if (d->m_currentThread != -1 && d->m_hasMoreFrames[d->m_currentThread]) { setHasMoreFrames(d->m_currentThread, false); fetchFrames(d->m_currentThread, d->m_frames[d->m_currentThread].count(), d->m_frames[d->m_currentThread].count()-1+fetch); } } } diff --git a/debugger/interfaces/idebugsession.cpp b/debugger/interfaces/idebugsession.cpp index e7bf10c1d..c8356dbb3 100644 --- a/debugger/interfaces/idebugsession.cpp +++ b/debugger/interfaces/idebugsession.cpp @@ -1,136 +1,136 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2009 Niko Sams * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "idebugsession.h" #include "iframestackmodel.h" #include "ivariablecontroller.h" #include "util/debug.h" #include #include #include namespace KDevelop { class IDebugSessionPrivate { public: - IDebugSessionPrivate(IDebugSession* q) : q(q) {} + explicit IDebugSessionPrivate(IDebugSession* q) : q(q) {} void slotStateChanged(IDebugSession::DebuggerState state); IDebugSession* q; /// Current position in debugged program, gets set when the state changes QUrl m_url; int m_line; QString m_addr; }; IDebugSession::IDebugSession() : d(new IDebugSessionPrivate(this)) { connect(this, &IDebugSession::stateChanged, this, [&](IDebugSession::DebuggerState state) { d->slotStateChanged(state); }); } IDebugSession::~IDebugSession() { } bool IDebugSession::isRunning() const { DebuggerState s = state(); return (s == ActiveState || s == PausedState); } void IDebugSession::raiseEvent(event_t e) { if (IFrameStackModel* model = frameStackModel()) { model->handleEvent(e); } if (IVariableController* variables = variableController()) { variables->handleEvent(e); } // FIXME: consider if we actually need signals emit event(e); } QPair IDebugSession::convertToLocalUrl(const QPair &remoteUrl) const { return remoteUrl; } QPair IDebugSession::convertToRemoteUrl(const QPair& localUrl) const { return localUrl; } void IDebugSession::clearCurrentPosition() { qCDebug(DEBUGGER); d->m_url.clear(); d->m_addr.clear(); d->m_line = -1; emit clearExecutionPoint(); } void IDebugSession::setCurrentPosition(const QUrl& url, int line, const QString& addr) { qCDebug(DEBUGGER) << url << line << addr; if (url.isEmpty() || !QFileInfo::exists(convertToLocalUrl(qMakePair(url,line)).first.path())) { clearCurrentPosition(); d->m_addr = addr; emit showStepInDisassemble(addr); } else { d->m_url = url; d->m_line = line; d->m_addr = addr; emit showStepInSource(url, line, addr); } } QUrl IDebugSession::currentUrl() const { return d->m_url; } int IDebugSession::currentLine() const { return d->m_line; } QString IDebugSession::currentAddr() const { return d->m_addr; } void IDebugSessionPrivate::slotStateChanged(IDebugSession::DebuggerState state) { if (state != IDebugSession::PausedState) { q->clearCurrentPosition(); } } } #include "moc_idebugsession.cpp" diff --git a/debugger/util/treeitem.h b/debugger/util/treeitem.h index 38bfdc4c9..f924649f5 100644 --- a/debugger/util/treeitem.h +++ b/debugger/util/treeitem.h @@ -1,128 +1,128 @@ /* * This file is part of KDevelop * * Copyright 2008 Vladimir Prus * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_TREEITEM_H #define KDEVPLATFORM_TREEITEM_H #include #include #include #include #include #include #include namespace KDevelop { class TreeModel; class KDEVPLATFORMDEBUGGER_EXPORT TreeItem: public QObject { Q_OBJECT public: ~TreeItem() override; // FIXME: should be protected public: // Methods that the derived classes should implement /** Fetches more children, and adds them by calling appendChild. The amount of children to fetch is up to the implementation. After fetching, should call setHasMore. */ virtual void fetchMoreChildren()=0; virtual void setColumn(int index, const QVariant& data) { Q_UNUSED(index); Q_UNUSED(data); } void emitAllChildrenFetched(); protected: // Interface for derived classes /** Creates a tree item with the specified data. FIXME: do we actually have to have the model pointer. */ - TreeItem(TreeModel* model, TreeItem *parent = nullptr); + explicit TreeItem(TreeModel* model, TreeItem *parent = nullptr); /** Set the data to be shown for the item itself. */ void setData(const QVector &data); /** Adds a new child and notifies the interested parties. Clears the "hasMore" flag. */ void appendChild(TreeItem *child, bool initial = false); void insertChild(int position, TreeItem *child, bool initial = false); void removeChild(int index); void removeSelf(); void deleteChildren(); /** Report change in data of this item. */ void reportChange(); void reportChange(int column); /** Clears all children. */ void clear(); /** Sets a flag that tells if we have some more children that are not fetched yet. */ void setHasMore(bool more); void setHasMoreInitial(bool more); TreeModel* model() { return model_; } bool isExpanded() const { return expanded_; } Q_SIGNALS: void expanded(); void collapsed(); void allChildrenFetched(); protected: // Backend implementation of Model/View friend class TreeModel; TreeItem *child(int row); int childCount() const; int columnCount() const; virtual QVariant data(int column, int role) const; int row() const; TreeItem *parent(); bool hasMore() const { return more_; } void setExpanded(bool b); virtual void clicked() {} virtual QVariant icon(int column) const; protected: QVector childItems; QVector itemData; TreeItem *parentItem; TreeModel *model_; bool more_; TreeItem *ellipsis_; bool expanded_; }; } #endif diff --git a/debugger/variable/variablesortmodel.h b/debugger/variable/variablesortmodel.h index a0dd89d54..c44f61bdf 100644 --- a/debugger/variable/variablesortmodel.h +++ b/debugger/variable/variablesortmodel.h @@ -1,48 +1,48 @@ /* * KDevelop Debugger Support * * Copyright 2016 Mikhail Ivchenko * * 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_VARIABLESORTMODEL_H #define KDEVPLATFORM_VARIABLESORTMODEL_H #include #include class QModelIndex; namespace KDevelop { class VariableSortProxyModel : public QSortFilterProxyModel { Q_OBJECT public: - VariableSortProxyModel(QObject *parent = nullptr); + explicit VariableSortProxyModel(QObject *parent = nullptr); protected: bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; private: QCollator m_collator; }; } #endif diff --git a/debugger/variable/variabletooltip.cpp b/debugger/variable/variabletooltip.cpp index 3dcec39ed..e3e48d37b 100644 --- a/debugger/variable/variabletooltip.cpp +++ b/debugger/variable/variabletooltip.cpp @@ -1,221 +1,221 @@ /* * 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 #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 { Q_OBJECT public: - SizeGrip(QWidget* parent) : QWidget(parent) { + explicit SizeGrip(QWidget* parent) : QWidget(parent) { m_parent = parent; } protected: void paintEvent(QPaintEvent *) override { QPainter painter(this); QStyleOptionSizeGrip opt; opt.init(this); opt.corner = Qt::BottomRightCorner; style()->drawControl(QStyle::CE_SizeGrip, &opt, &painter, this); } void mousePressEvent(QMouseEvent* e) override { if (e->button() == Qt::LeftButton) { m_pos = e->globalPos(); m_startSize = m_parent->size(); e->ignore(); } } void mouseReleaseEvent(QMouseEvent*) override { m_pos = QPoint(); } void mouseMoveEvent(QMouseEvent* e) override { 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() ); m_model = new TreeModel(QVector() << i18n("Name") << i18n("Value") << i18n("Type"), this); TooltipRoot* tr = new TooltipRoot(m_model); m_model->setRootItem(tr); m_var = ICore::self()->debugController()->currentSession()-> variableController()->createVariable( m_model, tr, identifier); tr->init(m_var); m_var->attachMaybe(this, "variableCreated"); QVBoxLayout* l = new QVBoxLayout(this); l->setContentsMargins(0, 0, 0, 0); // setup proxy model m_proxy = new QSortFilterProxyModel; m_view = new AsyncTreeView(m_model, m_proxy, this); m_proxy->setSourceModel(m_model); m_view->setModel(m_proxy); m_view->header()->resizeSection(0, 150); m_view->header()->resizeSection(1, 90); m_view->setSelectionBehavior(QAbstractItemView::SelectRows); m_view->setSelectionMode(QAbstractItemView::SingleSelection); m_view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); m_view->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Expanding); l->addWidget(m_view); m_itemHeight = m_view->indexRowSizeHint(m_model->indexForItem(m_var, 0)); connect(m_view->verticalScrollBar(), &QScrollBar::rangeChanged, this, &VariableToolTip::slotRangeChanged); m_selection = m_view->selectionModel(); m_selection->select(m_model->indexForItem(m_var, 0), QItemSelectionModel::Rows | QItemSelectionModel::ClearAndSelect); QHBoxLayout* buttonBox = new QHBoxLayout(); buttonBox->setContentsMargins(11, 0, 11, 6); QPushButton* watchThisButton = new QPushButton(i18n("Watch This")); buttonBox->addWidget(watchThisButton); QPushButton* stopOnChangeButton = new QPushButton(i18n("Stop on Change")); buttonBox->addWidget(stopOnChangeButton); QSignalMapper* mapper = new QSignalMapper(this); connect(watchThisButton, &QPushButton::clicked, mapper, static_cast(&QSignalMapper::map)); mapper->setMapping(watchThisButton, QStringLiteral("add_watch")); connect(stopOnChangeButton, &QPushButton::clicked, mapper, static_cast(&QSignalMapper::map)); mapper->setMapping(stopOnChangeButton, QStringLiteral("add_watchpoint")); connect(mapper, static_cast(&QSignalMapper::mapped), this, &VariableToolTip::slotLinkActivated); QHBoxLayout* inner = new QHBoxLayout(); l->addLayout(inner); inner->setContentsMargins(0, 0, 0, 0); inner->addLayout(buttonBox); 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) { m_view->resizeColumns(); if (hasValue) { ActiveToolTip::showToolTip(this, 0.0); } else { close(); } } void VariableToolTip::slotLinkActivated(const QString& link) { Variable* v = m_var; QItemSelection s = m_selection->selection(); if (!s.empty()) { QModelIndex index = s.front().topLeft(); const auto sourceIndex = m_proxy->mapToSource(index); TreeItem *item = m_model->itemForIndex(sourceIndex); if (item) { Variable* v2 = qobject_cast(item); if (v2) v = v2; } } IDebugSession *session = ICore::self()->debugController()->currentSession(); if (session && session->state() != IDebugSession::NotStartedState && session->state() != IDebugSession::EndedState) { if (link == QLatin1String("add_watch")) { session->variableController()->addWatch(v); } else if (link == QLatin1String("add_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*m_itemHeight < rect.bottom()) resize(width(), height() + max*m_itemHeight); else { // Oh, well, I'm sorry, but here's the scrollbar you was // longing to see m_view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); } } } #include "variabletooltip.moc" diff --git a/interfaces/configpage.cpp b/interfaces/configpage.cpp index 65e0f6c73..898c6b8ee 100644 --- a/interfaces/configpage.cpp +++ b/interfaces/configpage.cpp @@ -1,129 +1,129 @@ /* * This file is part of KDevelop * Copyright 2014 Alex Richardson * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "configpage.h" #include #include namespace KDevelop { struct ConfigPagePrivate { - ConfigPagePrivate(IPlugin* plugin) + explicit ConfigPagePrivate(IPlugin* plugin) : plugin(plugin) {} QScopedPointer configManager; KCoreConfigSkeleton* configSkeleton = nullptr; IPlugin* plugin; }; ConfigPage::ConfigPage(IPlugin* plugin, KCoreConfigSkeleton* config, QWidget* parent) : KTextEditor::ConfigPage(parent) , d(new ConfigPagePrivate(plugin)) { setConfigSkeleton(config); } ConfigPage::~ConfigPage() { } void ConfigPage::apply() { // if d->configManager is null, this method must be overriden Q_ASSERT_X(d->configManager, metaObject()->className(), "Config page does not use a KConfigSkeleton, but doesn't override apply()"); QSignalBlocker blockSigs(this); // we don't want to emit changed() while calling apply() d->configManager->updateSettings(); d->configSkeleton->load(); d->configManager->updateWidgets(); } void ConfigPage::defaults() { // if d->configManager is null, this method must be overriden Q_ASSERT_X(d->configManager, metaObject()->className(), "Config page does not use a KConfigSkeleton, but doesn't override defaults()"); d->configManager->updateWidgetsDefault(); } void ConfigPage::reset() { // if d->configManager is null, this method must be overriden Q_ASSERT_X(d->configManager, metaObject()->className(), "Config page does not use a KConfigSkeleton, but doesn't override reset()"); d->configManager->updateWidgets(); } void ConfigPage::initConfigManager() { if (d->configManager) { d->configManager->addWidget(this); } } KCoreConfigSkeleton* ConfigPage::configSkeleton() const { return d->configSkeleton; } void ConfigPage::setConfigSkeleton(KCoreConfigSkeleton* skel) { if (d->configSkeleton == skel) { return; } d->configSkeleton = skel; if (!skel) { d->configManager.reset(); return; } // create the config dialog manager if it didn't exist or recreate it. // This is needed because the used config skeleton has changed // and no setter for that exists in KConfigDialogManager d->configManager.reset(new KConfigDialogManager(this, d->configSkeleton)); connect(d->configManager.data(), &KConfigDialogManager::widgetModified, this, &ConfigPage::changed); // d->configManager->addWidget(this) must be called from the config dialog, // since the widget tree is probably not complete when calling this function } int ConfigPage::childPages() const { return 0; } ConfigPage* ConfigPage::childPage(int number) { Q_UNUSED(number) return nullptr; } IPlugin* ConfigPage::plugin() const { return d->plugin; } ConfigPage::ConfigPageType ConfigPage::configPageType() const { return DefaultConfigPage; } } // namespace KDevelop diff --git a/interfaces/context.cpp b/interfaces/context.cpp index 4602a69bb..7e80ca391 100644 --- a/interfaces/context.cpp +++ b/interfaces/context.cpp @@ -1,149 +1,149 @@ /* 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 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 "context.h" #include #include #include namespace KDevelop { Context::Context() : d(nullptr) {} Context::~Context() {} bool Context::hasType( int aType ) const { return aType == this->type(); } class FileContextPrivate { public: - FileContextPrivate( const QList &urls ) + explicit FileContextPrivate( const QList &urls ) : m_urls( urls ) {} QList m_urls; }; FileContext::FileContext( const QList &urls ) : Context(), d( new FileContextPrivate( urls ) ) {} FileContext::~FileContext() { delete d; } int FileContext::type() const { return Context::FileContext; } QList FileContext::urls() const { return d->m_urls; } class ProjectItemContextPrivate { public: - ProjectItemContextPrivate( const QList &items ) + explicit ProjectItemContextPrivate( const QList &items ) : m_items( items ) {} QList m_items; }; ProjectItemContext::ProjectItemContext( const QList &items ) : Context(), d( new ProjectItemContextPrivate( items ) ) {} ProjectItemContext::~ProjectItemContext() { delete d; } int ProjectItemContext::type() const { return Context::ProjectItemContext; } QList ProjectItemContext::items() const { return d->m_items; } class OpenWithContextPrivate { public: OpenWithContextPrivate(const QList& urls, const QMimeType& mimeType) : m_urls( urls ) , m_mimeType( mimeType ) {} QList m_urls; QMimeType m_mimeType; }; OpenWithContext::OpenWithContext(const QList& urls, const QMimeType& mimeType) : Context() , d(new OpenWithContextPrivate(urls, mimeType)) { } OpenWithContext::~OpenWithContext() { delete d; } int OpenWithContext::type() const { return Context::OpenWithContext; } QList OpenWithContext::urls() const { return d->m_urls; } QMimeType OpenWithContext::mimeType() const { return d->m_mimeType; } } diff --git a/interfaces/icore.h b/interfaces/icore.h index 027808586..37eac915a 100644 --- a/interfaces/icore.h +++ b/interfaces/icore.h @@ -1,154 +1,154 @@ /* This file is part of KDevelop Copyright 2007 Alexander Dymo Copyright 2007 Kris Wong 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_ICORE_H #define KDEVPLATFORM_ICORE_H #include #include "interfacesexport.h" #include "isessionlock.h" class KAboutData; namespace KParts { class PartManager; } /** * The KDevelop namespace contains all classes provided by the KDevelop * platform libraries. */ namespace KDevelop { class IUiController; class IPluginController; class IProjectController; class ILanguageController; class IDocumentController; class ISessionController; class IRunController; class ISourceFormatterController; class ISession; class ISelectionController; class IDocumentationController; class IDebugController; class IPartController; class IDashboardController; class ITestController; /** * ICore is the container class for all the various objects in use by * KDevelop. If access is needed to a particular controller, then this class * should be used. * * ICore can provide the user with instances of the following things: * - the main window(s) * - the document controller(s) * - the plugin controller * - the project controller * - the language controller * - the KPart manager * * When an object is provided to ICore so it can be used later, ICore * will take ownership of the object and upon application shutdown will take * responsibility for deleting the objects stored by ICore. */ class KDEVPLATFORMINTERFACES_EXPORT ICore: public QObject { Q_OBJECT public: ~ICore() override; /** @return the static ICore instance */ static ICore *self(); /** @return ui controller */ Q_SCRIPTABLE virtual KDevelop::IUiController *uiController() = 0; /** @return plugin controller */ Q_SCRIPTABLE virtual KDevelop::IPluginController *pluginController() = 0; /** @return project controller */ Q_SCRIPTABLE virtual KDevelop::IProjectController *projectController() = 0; /** @return language controller */ Q_SCRIPTABLE virtual KDevelop::ILanguageController *languageController() = 0; /** @return part manager */ Q_SCRIPTABLE virtual KDevelop::IPartController *partController() = 0; /** @return document controller */ Q_SCRIPTABLE virtual KDevelop::IDocumentController *documentController() = 0; /** @return run controller */ Q_SCRIPTABLE virtual KDevelop::IRunController *runController() = 0; /** @return the active session */ Q_SCRIPTABLE virtual KDevelop::ISession *activeSession() = 0; /** @return the session lock for the active session */ Q_SCRIPTABLE virtual KDevelop::ISessionLock::Ptr activeSessionLock() = 0; /** @return the sourceformatter controller */ Q_SCRIPTABLE virtual KDevelop::ISourceFormatterController *sourceFormatterController() = 0; /** @return the selection controller */ Q_SCRIPTABLE virtual KDevelop::ISelectionController* selectionController() = 0; /** @return the documentation controller */ Q_SCRIPTABLE virtual KDevelop::IDocumentationController* documentationController() = 0; /** @return the debug controller */ Q_SCRIPTABLE virtual KDevelop::IDebugController* debugController() = 0; /** @return the test controller */ Q_SCRIPTABLE virtual KDevelop::ITestController* testController() = 0; /** @return the about data of the framework, different from the main about data which is created by the application */ virtual KAboutData aboutData() const = 0; /** @return true if the application is currently being shut down */ virtual bool shuttingDown() const = 0; Q_SIGNALS: /** Emitted when the initialization of the core components has been completed */ void initializationCompleted(); /** * Emitted immediately before tearing down the session and UI. Useful when performing any last minute * preparations such as saving settings. */ void aboutToShutdown(); /** * Emitted when the teardown of the core components has been completed. */ void shutdownCompleted(); protected: - ICore(QObject *parent = nullptr); + explicit ICore(QObject *parent = nullptr); static ICore *m_self; }; } #endif diff --git a/interfaces/idocument.cpp b/interfaces/idocument.cpp index 21ddab628..c79afb0d8 100644 --- a/interfaces/idocument.cpp +++ b/interfaces/idocument.cpp @@ -1,146 +1,146 @@ /*************************************************************************** * 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 "idocument.h" #include "icore.h" #include "idocumentcontroller.h" namespace KDevelop { class IDocumentPrivate { public: - inline IDocumentPrivate(KDevelop::ICore *core) + inline explicit IDocumentPrivate(KDevelop::ICore *core) : m_core(core), scriptWrapper(nullptr) {} KDevelop::ICore* m_core; QObject *scriptWrapper; QString m_prettyName; /* Internal access to the wrapper script object */ static inline QObject *&getWrapper(IDocument *doc) { return doc->d->scriptWrapper; } }; /* This allows the scripting backend to register the scripting wrapper. Not beautiful, but makes sure it doesn't expand to much code. */ QObject *&getWrapper(IDocument *doc) { return IDocumentPrivate::getWrapper(doc); } IDocument::IDocument( KDevelop::ICore* core ) : d(new IDocumentPrivate(core)) { } IDocument::~IDocument() { delete d->scriptWrapper; delete d; } KDevelop::ICore* IDocument::core() { return d->m_core; } void IDocument::notifySaved() { emit core()->documentController()->documentSaved(this); } void IDocument::notifyStateChanged() { emit core()->documentController()->documentStateChanged(this); } void IDocument::notifyActivated() { emit core()->documentController()->documentActivated(this); } void IDocument::notifyContentChanged() { emit core()->documentController()->documentContentChanged(this); } bool IDocument::isTextDocument() const { return false; } void IDocument::notifyTextDocumentCreated() { emit core()->documentController()->textDocumentCreated(this); } KTextEditor::Range IDocument::textSelection() const { return KTextEditor::Range::invalid(); } QString IDocument::textLine() const { return QString(); } QString IDocument::textWord() const { return QString(); } QString IDocument::prettyName() const { return d->m_prettyName; } void IDocument::setPrettyName(QString name) { d->m_prettyName = name; } void IDocument::notifyUrlChanged() { emit core()->documentController()->documentUrlChanged(this); } void IDocument::notifyLoaded() { emit core()->documentController()->documentLoadedPrepare(this); emit core()->documentController()->documentLoaded(this); } KTextEditor::View* IDocument::activeTextView() const { return nullptr; } QString KDevelop::IDocument::text(const KTextEditor::Range& range) const { Q_UNUSED(range); return {}; } } diff --git a/interfaces/idocument.h b/interfaces/idocument.h index 732a7dc22..dcb216c02 100644 --- a/interfaces/idocument.h +++ b/interfaces/idocument.h @@ -1,219 +1,219 @@ /*************************************************************************** * Copyright 2006 Hamish Rodda * * Copyright 2007 Alexander Dymo * * Copyright 2007 Andreas Pakulat * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef KDEVPLATFORM_IDOCUMENT_H #define KDEVPLATFORM_IDOCUMENT_H #include #include "interfacesexport.h" namespace KParts { class Part; class MainWindow; } namespace KTextEditor { class Cursor; class Document; class Range; class View; } namespace Sublime{ class View; } class QMimeType; class QWidget; namespace KDevelop { class ICore; /** * A single document being edited by the IDE. * * The base class for tracking a document. It contains the URL, * the part, and any associated metadata for the document. * * The advantages are: * - an easier key for use in maps and the like * - a value which does not change when the filename changes * - clearer distinction in the code between an open document and a url * (which may or may not be open) */ class KDEVPLATFORMINTERFACES_EXPORT IDocument { public: virtual ~IDocument(); /**Document state.*/ enum DocumentState { Clean, /**< Document is not touched.*/ Modified, /**< Document is modified inside the IDE.*/ Dirty, /**< Document is modified by an external process.*/ DirtyAndModified /**< Document is modified inside the IDE and at the same time by an external process.*/ }; enum DocumentSaveMode { Default = 0x0 /**< standard save mode, gives a warning message if the file was modified outside the editor */, Silent = 0x1 /**< silent save mode, doesn't warn the user if the file was modified outside the editor */, Discard = 0x2 /**< discard mode, don't save any unchanged data */ }; /** * Returns the URL of this document. */ virtual QUrl url() const = 0; /** * Returns the mimetype of the document. */ virtual QMimeType mimeType() const = 0; /** * Returns the part for given @p view if this document is a KPart document or 0 otherwise. */ virtual KParts::Part* partForView(QWidget *view) const = 0; /** * Returns whether this document is a text document. */ virtual bool isTextDocument() const; /** * Set a 'pretty' name for this document. That name will be used when displaying the document in the UI, * instead of the filename and/or path. * @param name The pretty name to use. Give an empty name to reset. * */ virtual void setPrettyName(QString name); /** * returns the pretty name of this document that was set through setPrettyName(...). * If no pretty name was set, an empty string is returned. * */ virtual QString prettyName() const; /** * Returns the text editor, if this is a text document or 0 otherwise. */ virtual KTextEditor::Document* textDocument() const = 0; /** * Saves the document. * @return true if the document was saved, false otherwise */ virtual bool save(DocumentSaveMode mode = Default) = 0; /** * Reloads the document. */ virtual void reload() = 0; /** * Requests that the document be closed. * * \returns whether the document was successfully closed. */ virtual bool close(DocumentSaveMode mode = Default) = 0; /** * Enquires whether this document is currently active in the currently active mainwindow. */ virtual bool isActive() const = 0; /** * Checks the state of this document. * @return The document state. */ virtual DocumentState state() const = 0; /** * Access the current text cursor position, if possible. * * \returns the current text cursor position, or an invalid cursor otherwise. */ virtual KTextEditor::Cursor cursorPosition() const = 0; /** * Set the current text cursor position, if possible. * * \param cursor new cursor position. */ virtual void setCursorPosition(const KTextEditor::Cursor &cursor) = 0; /** * Retrieve the current text selection, if one exists. * * \returns the current text selection */ virtual KTextEditor::Range textSelection() const; /** * Set the current text selection, if possible. * * \param range new cursor position. */ virtual void setTextSelection(const KTextEditor::Range &range) = 0; /** * @returns the text in a given range */ virtual QString text(const KTextEditor::Range &range) const; /** * Retrieve the current text line, if one exists. * * @returns the current text line */ virtual QString textLine() const; /** * Retrieve the current text word, if one exists. * * @returns the current text word */ virtual QString textWord() const; /** * Performs document activation actions if any. * This needs to call notifyActivated() */ virtual void activate(Sublime::View *activeView, KParts::MainWindow *mainWindow) = 0; /** * @returns the active text view in case it's a text document and it has one. */ virtual KTextEditor::View* activeTextView() const; protected: ICore* core(); - IDocument( ICore* ); + explicit IDocument( ICore* ); void notifySaved(); void notifyStateChanged(); void notifyActivated(); void notifyContentChanged(); void notifyTextDocumentCreated(); void notifyUrlChanged(); void notifyLoaded(); private: friend class IDocumentPrivate; class IDocumentPrivate* const d; }; } #endif diff --git a/interfaces/iplugin.cpp b/interfaces/iplugin.cpp index a96dfffd9..9b87920f4 100644 --- a/interfaces/iplugin.cpp +++ b/interfaces/iplugin.cpp @@ -1,208 +1,208 @@ /* This file is part of the KDE project Copyright 2002 Simon Hausmann Copyright 2002 Matthias Hoelzer-Kluepfel Copyright 2002 Harald Fernengel Copyright 2002 Falk Brettschneider Copyright 2003 Julian Rockey Copyright 2003 Roberto Raggi Copyright 2003 Jens Dagerbo Copyright 2003 Mario Scalas Copyright 2003-2004,2007 Alexander Dymo Copyright 2006 Adam Treat 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 "iplugin.h" #include #include #include #include #include "icore.h" #include "iplugincontroller.h" #include "iprojectcontroller.h" #include "iproject.h" #include "contextmenuextension.h" namespace KDevelop { class IPluginPrivate { public: - IPluginPrivate(IPlugin *q) + explicit IPluginPrivate(IPlugin *q) : q(q) {} ~IPluginPrivate() { } void guiClientAdded(KXMLGUIClient *client) { if (client != q) return; q->initializeGuiState(); updateState(); } void updateState() { const int projectCount = ICore::self()->projectController()->projectCount(); IPlugin::ReverseStateChange reverse = IPlugin::StateNoReverse; if (! projectCount) reverse = IPlugin::StateReverse; q->stateChanged(QStringLiteral("has_project"), reverse); } IPlugin *q; ICore *core; QString m_errorDescription; }; IPlugin::IPlugin( const QString &componentName, QObject *parent ) : QObject(parent) , KXMLGUIClient() , d(new IPluginPrivate(this)) { // The following cast is safe, there's no component in KDevPlatform that // creates plugins except the plugincontroller. The controller passes // Core::self() as parent to KServiceTypeTrader::createInstanceFromQuery // so we know the parent is always a Core* pointer. // This is the only way to pass the Core pointer to the plugin during its // creation so plugins have access to ICore during their creation. Q_ASSERT(qobject_cast(parent)); d->core = static_cast(parent); setComponentName(componentName, componentName); auto clientAdded = [=] (KXMLGUIClient* client) { d->guiClientAdded(client); }; foreach (KMainWindow* mw, KMainWindow::memberList()) { KXmlGuiWindow* guiWindow = qobject_cast(mw); if (! guiWindow) continue; connect(guiWindow->guiFactory(), &KXMLGUIFactory::clientAdded, this, clientAdded); } auto updateState = [=] { d->updateState(); }; connect(ICore::self()->projectController(), &IProjectController::projectOpened, this, updateState); connect(ICore::self()->projectController(), &IProjectController::projectClosed, this, updateState); } IPlugin::~IPlugin() { delete d; } void IPlugin::unload() { } ICore *IPlugin::core() const { return d->core; } } KDevelop::ContextMenuExtension KDevelop::IPlugin::contextMenuExtension( KDevelop::Context* ) { return KDevelop::ContextMenuExtension(); } void KDevelop::IPlugin::initializeGuiState() { } class CustomXmlGUIClient : public KXMLGUIClient { public: - CustomXmlGUIClient(const QString& componentName) + explicit CustomXmlGUIClient(const QString& componentName) { // TODO KF5: Get rid off this setComponentName(componentName, componentName); } using KXMLGUIClient::setXMLFile; }; KXMLGUIClient* KDevelop::IPlugin::createGUIForMainWindow(Sublime::MainWindow* window) { QScopedPointer ret(new CustomXmlGUIClient(componentName())); QString file; createActionsForMainWindow(window, file, *ret->actionCollection()); if (ret->actionCollection()->isEmpty()) { return nullptr; } Q_ASSERT(!file.isEmpty()); //A file must have been set ret->setXMLFile(file); return ret.take(); } void KDevelop::IPlugin::createActionsForMainWindow( Sublime::MainWindow* /*window*/, QString& /*xmlFile*/, KActionCollection& /*actions*/ ) { } bool KDevelop::IPlugin::hasError() const { return !d->m_errorDescription.isEmpty(); } void KDevelop::IPlugin::setErrorDescription(const QString& description) { d->m_errorDescription = description; } QString KDevelop::IPlugin::errorDescription() const { return d->m_errorDescription; } int KDevelop::IPlugin::configPages() const { return 0; } KDevelop::ConfigPage* KDevelop::IPlugin::configPage (int, QWidget*) { return nullptr; } int KDevelop::IPlugin::perProjectConfigPages() const { return 0; } KDevelop::ConfigPage* KDevelop::IPlugin::perProjectConfigPage(int, const ProjectConfigOptions&, QWidget*) { return nullptr; } #include "moc_iplugin.cpp" diff --git a/language/assistant/renameassistant.cpp b/language/assistant/renameassistant.cpp index ed5d2799b..75f228fe6 100644 --- a/language/assistant/renameassistant.cpp +++ b/language/assistant/renameassistant.cpp @@ -1,238 +1,238 @@ /* Copyright 2010 Olivier de Gaalon Copyright 2014 Kevin Funk This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "renameassistant.h" #include "renameaction.h" #include "renamefileaction.h" #include "util/debug.h" #include "../codegen/basicrefactoring.h" #include "../codegen/documentchangeset.h" #include "../duchain/duchain.h" #include "../duchain/duchainlock.h" #include "../duchain/duchainutils.h" #include "../duchain/declaration.h" #include "../duchain/functiondefinition.h" #include "../duchain/classfunctiondeclaration.h" #include #include #include #include #include using namespace KDevelop; namespace { bool rangesConnect(const KTextEditor::Range& firstRange, const KTextEditor::Range& secondRange) { return !firstRange.intersect(secondRange + KTextEditor::Range(0, -1, 0, +1)).isEmpty(); } Declaration* getDeclarationForChangedRange(KTextEditor::Document* doc, const KTextEditor::Range& changed) { const KTextEditor::Cursor cursor(changed.start()); Declaration* declaration = DUChainUtils::itemUnderCursor(doc->url(), cursor).declaration; //If it's null we could be appending, but there's a case where appending gives a wrong decl //and not a null declaration ... "type var(init)", so check for that too if (!declaration || !rangesConnect(declaration->rangeInCurrentRevision(), changed)) { declaration = DUChainUtils::itemUnderCursor(doc->url(), KTextEditor::Cursor(cursor.line(), cursor.column()-1)).declaration; } //In this case, we may either not have a decl at the cursor, or we got a decl, but are editing its use. //In either of those cases, give up and return 0 if (!declaration || !rangesConnect(declaration->rangeInCurrentRevision(), changed)) { return nullptr; } return declaration; } } struct RenameAssistant::Private { - Private(RenameAssistant* qq) + explicit Private(RenameAssistant* qq) : q(qq) , m_isUseful(false) , m_renameFile(false) { } void reset() { q->doHide(); q->clearActions(); m_oldDeclarationName = Identifier(); m_newDeclarationRange.reset(); m_oldDeclarationUses.clear(); m_isUseful = false; m_renameFile = false; } RenameAssistant* q; KDevelop::Identifier m_oldDeclarationName; QString m_newDeclarationName; KDevelop::PersistentMovingRange::Ptr m_newDeclarationRange; QVector m_oldDeclarationUses; bool m_isUseful; bool m_renameFile; KTextEditor::Cursor m_lastChangedLocation; QPointer m_lastChangedDocument = nullptr; }; RenameAssistant::RenameAssistant(ILanguageSupport* supportedLanguage) : StaticAssistant(supportedLanguage) , d(new Private(this)) { } RenameAssistant::~RenameAssistant() { } QString RenameAssistant::title() const { return tr("Rename"); } bool RenameAssistant::isUseful() const { return d->m_isUseful; } void RenameAssistant::textChanged(KTextEditor::Document* doc, const KTextEditor::Range& invocationRange, const QString& removedText) { clearActions(); d->m_lastChangedLocation = invocationRange.end(); d->m_lastChangedDocument = doc; if (!supportedLanguage()->refactoring()) { qCWarning(LANGUAGE) << "Refactoring not supported. Aborting."; return; } if (!doc) return; //If the inserted text isn't valid for a variable name, consider the editing ended QRegExp validDeclName("^[0-9a-zA-Z_]*$"); if (removedText.isEmpty() && !validDeclName.exactMatch(doc->text(invocationRange))) { d->reset(); return; } const QUrl url = doc->url(); const IndexedString indexedUrl(url); DUChainReadLocker lock; //If we've stopped editing m_newDeclarationRange or switched the view, // reset and see if there's another declaration being edited if (!d->m_newDeclarationRange.data() || !rangesConnect(d->m_newDeclarationRange->range(), invocationRange) || d->m_newDeclarationRange->document() != indexedUrl) { d->reset(); Declaration* declAtCursor = getDeclarationForChangedRange(doc, invocationRange); if (!declAtCursor) { // not editing a declaration return; } if (supportedLanguage()->refactoring()->shouldRenameUses(declAtCursor)) { QMap< IndexedString, QList > declUses = declAtCursor->uses(); if (declUses.isEmpty()) { // new declaration has no uses return; } for(QMap< IndexedString, QList< RangeInRevision > >::const_iterator it = declUses.constBegin(); it != declUses.constEnd(); ++it) { foreach(const RangeInRevision range, it.value()) { KTextEditor::Range currentRange = declAtCursor->transformFromLocalRevision(range); if(currentRange.isEmpty() || doc->text(currentRange) != declAtCursor->identifier().identifier().str()) { return; // One of the uses is invalid. Maybe the replacement has already been performed. } } } d->m_oldDeclarationUses = RevisionedFileRanges::convert(declUses); } else if (supportedLanguage()->refactoring()->shouldRenameFile(declAtCursor)) { d->m_renameFile = true; } else { // not a valid declaration return; } d->m_oldDeclarationName = declAtCursor->identifier(); KTextEditor::Range newRange = declAtCursor->rangeInCurrentRevision(); if (removedText.isEmpty() && newRange.intersect(invocationRange).isEmpty()) { newRange = newRange.encompass(invocationRange); //if text was added to the ends, encompass it } d->m_newDeclarationRange = new PersistentMovingRange(newRange, indexedUrl, true); } //Unfortunately this happens when you make a selection including one end of the decl's range and replace it if (removedText.isEmpty() && d->m_newDeclarationRange->range().intersect(invocationRange).isEmpty()) { d->m_newDeclarationRange = new PersistentMovingRange( d->m_newDeclarationRange->range().encompass(invocationRange), indexedUrl, true); } d->m_newDeclarationName = doc->text(d->m_newDeclarationRange->range()); if (d->m_newDeclarationName == d->m_oldDeclarationName.toString()) { d->reset(); return; } if (d->m_renameFile && supportedLanguage()->refactoring()->newFileName(url, d->m_newDeclarationName) == url.fileName()) { // no change, don't do anything return; } d->m_isUseful = true; IAssistantAction::Ptr action; if (d->m_renameFile) { action = new RenameFileAction(supportedLanguage()->refactoring(), url, d->m_newDeclarationName); } else { action = new RenameAction(d->m_oldDeclarationName, d->m_newDeclarationName, d->m_oldDeclarationUses); } connect(action.data(), &IAssistantAction::executed, this, [&] { d->reset(); }); addAction(action); emit actionsChanged(); } KTextEditor::Range KDevelop::RenameAssistant::displayRange() const { if ( !d->m_lastChangedDocument ) { return {}; } auto range = d->m_lastChangedDocument->wordRangeAt(d->m_lastChangedLocation); qDebug() << "range:" << range; return range; } #include "moc_renameassistant.cpp" diff --git a/language/assistant/staticassistantsmanager.cpp b/language/assistant/staticassistantsmanager.cpp index e995d152b..608c5a401 100644 --- a/language/assistant/staticassistantsmanager.cpp +++ b/language/assistant/staticassistantsmanager.cpp @@ -1,190 +1,190 @@ /* Copyright 2009 David Nolden Copyright 2014 Kevin Funk This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "staticassistantsmanager.h" #include #include "util/debug.h" #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; using namespace KTextEditor; struct StaticAssistantsManager::Private { - Private(StaticAssistantsManager* qq) + explicit Private(StaticAssistantsManager* qq) : q(qq) { } void updateReady(const IndexedString& document, const KDevelop::ReferencedTopDUContext& topContext); void documentLoaded(KDevelop::IDocument*); void textInserted(KTextEditor::Document* document, const Cursor& cursor, const QString& text); void textRemoved(KTextEditor::Document* document, const Range& cursor, const QString& removedText); StaticAssistantsManager* q; QVector m_registeredAssistants; }; StaticAssistantsManager::StaticAssistantsManager(QObject* parent) : QObject(parent) , d(new Private(this)) { connect(KDevelop::ICore::self()->documentController(), &IDocumentController::documentLoaded, this, [&] (IDocument* document) { d->documentLoaded(document); }); foreach (IDocument* document, ICore::self()->documentController()->openDocuments()) { d->documentLoaded(document); } connect(DUChain::self(), &DUChain::updateReady, this, &StaticAssistantsManager::notifyAssistants); } StaticAssistantsManager::~StaticAssistantsManager() { } void StaticAssistantsManager::registerAssistant(const StaticAssistant::Ptr assistant) { if (d->m_registeredAssistants.contains(assistant)) return; d->m_registeredAssistants << assistant; } void StaticAssistantsManager::unregisterAssistant(const StaticAssistant::Ptr assistant) { d->m_registeredAssistants.removeOne(assistant); } QVector StaticAssistantsManager::registeredAssistants() const { return d->m_registeredAssistants; } void StaticAssistantsManager::Private::documentLoaded(IDocument* document) { if (document->textDocument()) { auto doc = document->textDocument(); connect(doc, &KTextEditor::Document::textInserted, q, [&] (KTextEditor::Document* doc, const Cursor& cursor, const QString& text) { textInserted(doc, cursor, text); }); connect(doc, &KTextEditor::Document::textRemoved, q, [&] (KTextEditor::Document* doc, const Range& range, const QString& removedText) { textRemoved(doc, range, removedText); }); } } void StaticAssistantsManager::Private::textInserted(Document* doc, const Cursor& cursor, const QString& text) { auto changed = false; Q_FOREACH ( auto assistant, m_registeredAssistants ) { auto range = Range(cursor, cursor+Cursor(0, text.size())); auto wasUseful = assistant->isUseful(); assistant->textChanged(doc, range, {}); if ( wasUseful != assistant->isUseful() ) { changed = true; } } if ( changed ) { Q_EMIT q->problemsChanged(IndexedString(doc->url())); } } void StaticAssistantsManager::Private::textRemoved(Document* doc, const Range& range, const QString& removedText) { auto changed = false; Q_FOREACH ( auto assistant, m_registeredAssistants ) { auto wasUseful = assistant->isUseful(); assistant->textChanged(doc, range, removedText); if ( wasUseful != assistant->isUseful() ) { changed = true; } } if ( changed ) { Q_EMIT q->problemsChanged(IndexedString(doc->url())); } } void StaticAssistantsManager::notifyAssistants(const IndexedString& url, const KDevelop::ReferencedTopDUContext& context) { Q_FOREACH ( auto assistant, d->m_registeredAssistants ) { assistant->updateReady(url, context); } } QVector KDevelop::StaticAssistantsManager::problemsForContext(const KDevelop::ReferencedTopDUContext& top) { View* view = ICore::self()->documentController()->activeTextDocumentView(); if (!view || !top || IndexedString(view->document()->url()) != top->url()) { return {}; } auto doc = top->url(); auto language = ICore::self()->languageController()->languagesForUrl(doc.toUrl()).value(0); if (!language) { return {}; } auto ret = QVector(); qCDebug(LANGUAGE) << "Trying to find assistants for language" << language->name(); foreach (const auto& assistant, d->m_registeredAssistants) { if (assistant->supportedLanguage() != language) continue; if (assistant->isUseful()) { qDebug() << "assistant is now useful:" << assistant.data(); auto p = new KDevelop::StaticAssistantProblem(); auto range = assistant->displayRange(); qDebug() << "range:" << range; p->setFinalLocation(DocumentRange(doc, range)); p->setSource(KDevelop::IProblem::SemanticAnalysis); p->setSeverity(KDevelop::IProblem::Warning); p->setDescription(assistant->title()); p->setSolutionAssistant(IAssistant::Ptr(assistant.data())); ret.append(KDevelop::Problem::Ptr(p)); } } return ret; } #include "moc_staticassistantsmanager.cpp" diff --git a/language/codecompletion/codecompletionitem.h b/language/codecompletion/codecompletionitem.h index cacd3a88d..666190916 100644 --- a/language/codecompletion/codecompletionitem.h +++ b/language/codecompletion/codecompletionitem.h @@ -1,152 +1,152 @@ /* * KDevelop Generic Code Completion Support * * Copyright 2007-2008 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_KDEV_CODECOMPLETIONITEM_H #define KDEVPLATFORM_KDEV_CODECOMPLETIONITEM_H #include #include "../duchain/duchainpointer.h" #include "codecompletioncontext.h" namespace KTextEditor { class CodeCompletionModel; class Range; class Cursor; } class QModelIndex; namespace KDevelop { class CodeCompletionModel; struct CompletionTreeNode; class CompletionTreeItem; class IndexedType; class KDEVPLATFORMLANGUAGE_EXPORT CompletionTreeElement : public QSharedData { public: CompletionTreeElement(); virtual ~CompletionTreeElement(); CompletionTreeElement* parent() const; ///Reparenting is not supported. This is only allowed if parent() is still zero. void setParent(CompletionTreeElement*); int rowInParent() const; int columnInParent() const; ///Each element is either a node, or an item. CompletionTreeNode* asNode(); CompletionTreeItem* asItem(); template T* asItem() { return dynamic_cast(this); } template const T* asItem() const { return dynamic_cast(this); } const CompletionTreeNode* asNode() const; const CompletionTreeItem* asItem() const; private: CompletionTreeElement* m_parent; int m_rowInParent; }; struct KDEVPLATFORMLANGUAGE_EXPORT CompletionTreeNode : public CompletionTreeElement { CompletionTreeNode(); ~CompletionTreeNode() override; KTextEditor::CodeCompletionModel::ExtraItemDataRoles role; QVariant roleValue; ///Will append the child, and initialize it correctly to create a working tree-structure void appendChild(QExplicitlySharedDataPointer); void appendChildren(QList >); void appendChildren(QList >); ///@warning Do not manipulate this directly, that's bad for consistency. Use appendChild instead. QList > children; }; class KDEVPLATFORMLANGUAGE_EXPORT CompletionTreeItem : public CompletionTreeElement { public: ///Execute the completion item. The default implementation does nothing. virtual void execute(KTextEditor::View* view, const KTextEditor::Range& word); ///Should return normal completion data, @see KTextEditor::CodeCompletionModel ///The default implementation returns "unimplemented", so re-implement it! ///The duchain is not locked when this is called ///Navigation-widgets should be registered to the model, then it will care about the interaction. virtual QVariant data(const QModelIndex& index, int role, const CodeCompletionModel* model) const; ///Should return the inheritance-depth. The completion-items don't need to return it through the data() function. virtual int inheritanceDepth() const; ///Should return the argument-hint depth. The completion-items don't need to return it through the data() function. virtual int argumentHintDepth() const; ///The default-implementation calls DUChainUtils::completionProperties virtual KTextEditor::CodeCompletionModel::CompletionProperties completionProperties() const; ///If this item represents a Declaration, this should return the declaration. ///The default-implementation returns zero. virtual DeclarationPointer declaration() const; ///Should return the types should be used for matching items against this one when it's an argument hint. ///The matching against all types should be done, and the best one will be used as final match result. virtual QList typeForArgumentMatching() const; ///Should return whether this completion-items data changes with input done by the user during code-completion. ///Returning true is very expensive. virtual bool dataChangedWithInput() const; }; ///A custom-group node, that can be used as-is. Just create it, and call appendChild to add group items. ///The items in the group will be shown in the completion-list with a group-header that contains the given name struct KDEVPLATFORMLANGUAGE_EXPORT CompletionCustomGroupNode : public CompletionTreeNode { ///@param inheritanceDepth @ref KTextEditor::CodeCompletionModel::GroupRole - CompletionCustomGroupNode(QString groupName, int inheritanceDepth = 700); + explicit CompletionCustomGroupNode(QString groupName, int inheritanceDepth = 700); int inheritanceDepth; }; typedef QExplicitlySharedDataPointer CompletionTreeItemPointer; typedef QExplicitlySharedDataPointer CompletionTreeElementPointer; } Q_DECLARE_METATYPE(KDevelop::CompletionTreeElementPointer); #endif diff --git a/language/codecompletion/codecompletionmodel.cpp b/language/codecompletion/codecompletionmodel.cpp index c2e54c092..63bbe8a95 100644 --- a/language/codecompletion/codecompletionmodel.cpp +++ b/language/codecompletion/codecompletionmodel.cpp @@ -1,454 +1,454 @@ /* * KDevelop Generic Code Completion Support * * Copyright 2006-2008 Hamish Rodda * Copyright 2007-2008 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "codecompletionmodel.h" #include #include #include #include #include #include #include #include #include #include "../duchain/declaration.h" #include "../duchain/classfunctiondeclaration.h" #include "../duchain/ducontext.h" #include "../duchain/duchain.h" #include "../duchain/namespacealiasdeclaration.h" #include "../duchain/parsingenvironment.h" #include "../duchain/duchainlock.h" #include "../duchain/duchainbase.h" #include "../duchain/topducontext.h" #include "../duchain/duchainutils.h" #include "../interfaces/quickopendataprovider.h" #include "../interfaces/icore.h" #include "../interfaces/ilanguagecontroller.h" #include "../interfaces/icompletionsettings.h" #include "util/debug.h" #include "codecompletionworker.h" #include "codecompletioncontext.h" #include #if KTEXTEDITOR_VERSION < QT_VERSION_CHECK(5, 10, 0) Q_DECLARE_METATYPE(KTextEditor::Cursor) #endif using namespace KTextEditor; //Multi-threaded completion creates some multi-threading related crashes, and sometimes shows the completions in the wrong position if the cursor was moved // #define SINGLE_THREADED_COMPLETION namespace KDevelop { class CompletionWorkerThread : public QThread { Q_OBJECT public: - CompletionWorkerThread(CodeCompletionModel* model) + explicit CompletionWorkerThread(CodeCompletionModel* model) : QThread(model), m_model(model), m_worker(m_model->createCompletionWorker()) { Q_ASSERT(m_worker->parent() == nullptr); // Must be null, else we cannot change the thread affinity! m_worker->moveToThread(this); Q_ASSERT(m_worker->thread() == this); } ~CompletionWorkerThread() override { delete m_worker; } void run () override { //We connect directly, so we can do the pre-grouping within the background thread connect(m_worker, &CodeCompletionWorker::foundDeclarationsReal, m_model, &CodeCompletionModel::foundDeclarations, Qt::QueuedConnection); connect(m_model, &CodeCompletionModel::completionsNeeded, m_worker, static_cast,const Cursor&,View*)>(&CodeCompletionWorker::computeCompletions), Qt::QueuedConnection); connect(m_model, &CodeCompletionModel::doSpecialProcessingInBackground, m_worker, &CodeCompletionWorker::doSpecialProcessing); exec(); } CodeCompletionModel* m_model; CodeCompletionWorker* m_worker; }; bool CodeCompletionModel::forceWaitForModel() { return m_forceWaitForModel; } void CodeCompletionModel::setForceWaitForModel(bool wait) { m_forceWaitForModel = wait; } CodeCompletionModel::CodeCompletionModel( QObject * parent ) : KTextEditor::CodeCompletionModel(parent) , m_forceWaitForModel(false) , m_fullCompletion(true) , m_mutex(new QMutex) , m_thread(nullptr) { qRegisterMetaType(); } void CodeCompletionModel::initialize() { if(!m_thread) { m_thread = new CompletionWorkerThread(this); #ifdef SINGLE_THREADED_COMPLETION m_thread->m_worker = createCompletionWorker(); #endif m_thread->start(); } } CodeCompletionModel::~CodeCompletionModel() { if(m_thread->m_worker) m_thread->m_worker->abortCurrentCompletion(); m_thread->quit(); m_thread->wait(); delete m_thread; delete m_mutex; } void CodeCompletionModel::addNavigationWidget(const CompletionTreeElement* element, QWidget* widget) const { Q_ASSERT(dynamic_cast(widget)); m_navigationWidgets[element] = widget; } bool CodeCompletionModel::fullCompletion() const { return m_fullCompletion; } KDevelop::CodeCompletionWorker* CodeCompletionModel::worker() const { return m_thread->m_worker; } void CodeCompletionModel::clear() { beginResetModel(); m_completionItems.clear(); m_navigationWidgets.clear(); m_completionContext.reset(); endResetModel(); } void CodeCompletionModel::completionInvokedInternal(KTextEditor::View* view, const KTextEditor::Range& range, InvocationType invocationType, const QUrl& url) { Q_ASSERT(m_thread == worker()->thread()); Q_UNUSED(invocationType) DUChainReadLocker lock(DUChain::lock(), 400); if( !lock.locked() ) { qCDebug(LANGUAGE) << "could not lock du-chain in time"; return; } TopDUContext* top = DUChainUtils::standardContextForUrl( url ); if(!top) { return; } setCurrentTopContext(TopDUContextPointer(top)); RangeInRevision rangeInRevision = top->transformToLocalRevision(KTextEditor::Range(range)); if (top) { qCDebug(LANGUAGE) << "completion invoked for context" << (DUContext*)top; if( top->parsingEnvironmentFile() && top->parsingEnvironmentFile()->modificationRevision() != ModificationRevision::revisionForFile(IndexedString(url.toString())) ) { qCDebug(LANGUAGE) << "Found context is not current. Its revision is " /*<< top->parsingEnvironmentFile()->modificationRevision() << " while the document-revision is " << ModificationRevision::revisionForFile(IndexedString(url.toString()))*/; } DUContextPointer thisContext; { qCDebug(LANGUAGE) << "apply specialization:" << range.start(); thisContext = SpecializationStore::self().applySpecialization(top->findContextAt(rangeInRevision.start), top); if ( thisContext ) { qCDebug(LANGUAGE) << "after specialization:" << thisContext->localScopeIdentifier().toString() << thisContext->rangeInCurrentRevision(); } if(!thisContext) thisContext = top; qCDebug(LANGUAGE) << "context is set to" << thisContext.data(); if( !thisContext ) { qCDebug(LANGUAGE) << "================== NO CONTEXT FOUND ======================="; beginResetModel(); m_completionItems.clear(); m_navigationWidgets.clear(); endResetModel(); return; } } lock.unlock(); if(m_forceWaitForModel) emit waitForReset(); emit completionsNeeded(thisContext, range.start(), view); } else { qCDebug(LANGUAGE) << "Completion invoked for unknown context. Document:" << url << ", Known documents:" << DUChain::self()->documents(); } } void CodeCompletionModel::completionInvoked(KTextEditor::View* view, const KTextEditor::Range& range, InvocationType invocationType) { //If this triggers, initialize() has not been called after creation. Q_ASSERT(m_thread); KDevelop::ICompletionSettings::CompletionLevel level = KDevelop::ICore::self()->languageController()->completionSettings()->completionLevel(); if(level == KDevelop::ICompletionSettings::AlwaysFull || (invocationType != AutomaticInvocation && level == KDevelop::ICompletionSettings::MinimalWhenAutomatic)) m_fullCompletion = true; else m_fullCompletion = false; //Only use grouping in full completion mode setHasGroups(m_fullCompletion); Q_UNUSED(invocationType) if (!worker()) { qCWarning(LANGUAGE) << "Completion invoked on a completion model which has no code completion worker assigned!"; } beginResetModel(); m_navigationWidgets.clear(); m_completionItems.clear(); endResetModel(); worker()->abortCurrentCompletion(); worker()->setFullCompletion(m_fullCompletion); QUrl url = view->document()->url(); completionInvokedInternal(view, range, invocationType, url); } void CodeCompletionModel::foundDeclarations(const QList>& items, const QExplicitlySharedDataPointer& completionContext) { m_completionContext = completionContext; if(m_completionItems.isEmpty() && items.isEmpty()) { if(m_forceWaitForModel) { // TODO KF5: Check if this actually works beginResetModel(); endResetModel(); //If we need to reset the model, reset it } return; //We don't need to reset, which is bad for target model } beginResetModel(); m_completionItems = items; endResetModel(); if(m_completionContext) { qCDebug(LANGUAGE) << "got completion-context with " << m_completionContext->ungroupedElements().size() << "ungrouped elements"; } } KTextEditor::CodeCompletionModelControllerInterface::MatchReaction CodeCompletionModel::matchingItem(const QModelIndex& /*matched*/) { return None; } void CodeCompletionModel::setCompletionContext(QExplicitlySharedDataPointer completionContext) { QMutexLocker lock(m_mutex); m_completionContext = completionContext; if(m_completionContext) { qCDebug(LANGUAGE) << "got completion-context with " << m_completionContext->ungroupedElements().size() << "ungrouped elements"; } } QExplicitlySharedDataPointer CodeCompletionModel::completionContext() const { QMutexLocker lock(m_mutex); return m_completionContext; } void CodeCompletionModel::executeCompletionItem(View* view, const KTextEditor::Range& word, const QModelIndex& index) const { //We must not lock the duchain at this place, because the items might rely on that CompletionTreeElement* element = static_cast(index.internalPointer()); if( !element || !element->asItem() ) return; element->asItem()->execute(view, word); } QExplicitlySharedDataPointer< KDevelop::CompletionTreeElement > CodeCompletionModel::itemForIndex(QModelIndex index) const { CompletionTreeElement* element = static_cast(index.internalPointer()); return QExplicitlySharedDataPointer< KDevelop::CompletionTreeElement >(element); } QVariant CodeCompletionModel::data(const QModelIndex& index, int role) const { if ( role == Qt::TextAlignmentRole && index.column() == 0 ) { return Qt::AlignRight; } auto element = static_cast(index.internalPointer()); if( !element ) return QVariant(); if( role == CodeCompletionModel::GroupRole ) { if( element->asNode() ) { return QVariant(element->asNode()->role); }else { qCDebug(LANGUAGE) << "Requested group-role from leaf tree element"; return QVariant(); } }else{ if( element->asNode() ) { if( role == CodeCompletionModel::InheritanceDepth ) { auto customGroupNode = dynamic_cast(element); if(customGroupNode) return QVariant(customGroupNode->inheritanceDepth); } if( role == element->asNode()->role ) { return element->asNode()->roleValue; } else { return QVariant(); } } } if(!element->asItem()) { qCWarning(LANGUAGE) << "Error in completion model"; return QVariant(); } //Navigation widget interaction is done here, the other stuff is done within the tree-elements switch (role) { case CodeCompletionModel::InheritanceDepth: return element->asItem()->inheritanceDepth(); case CodeCompletionModel::ArgumentHintDepth: return element->asItem()->argumentHintDepth(); case CodeCompletionModel::ItemSelected: { DeclarationPointer decl = element->asItem()->declaration(); if(decl) { DUChain::self()->emitDeclarationSelected(decl); } break; } } //In minimal completion mode, hide all columns except the "name" one if(!m_fullCompletion && role == Qt::DisplayRole && index.column() != Name && (element->asItem()->argumentHintDepth() == 0 || index.column() == Prefix)) return QVariant(); //In reduced completion mode, don't show information text with the selected items if(role == ItemSelected && (!m_fullCompletion || !ICore::self()->languageController()->completionSettings()->showMultiLineSelectionInformation())) return QVariant(); return element->asItem()->data(index, role, this); } KDevelop::TopDUContextPointer CodeCompletionModel::currentTopContext() const { return m_currentTopContext; } void CodeCompletionModel::setCurrentTopContext(KDevelop::TopDUContextPointer topContext) { m_currentTopContext = topContext; } QModelIndex CodeCompletionModel::index(int row, int column, const QModelIndex& parent) const { if( parent.isValid() ) { CompletionTreeElement* element = static_cast(parent.internalPointer()); CompletionTreeNode* node = element->asNode(); if( !node ) { qCDebug(LANGUAGE) << "Requested sub-index of leaf node"; return QModelIndex(); } if (row < 0 || row >= node->children.count() || column < 0 || column >= ColumnCount) return QModelIndex(); return createIndex(row, column, node->children[row].data()); } else { if (row < 0 || row >= m_completionItems.count() || column < 0 || column >= ColumnCount) return QModelIndex(); return createIndex(row, column, const_cast(m_completionItems[row].data())); } } QModelIndex CodeCompletionModel::parent ( const QModelIndex & index ) const { if(rowCount() == 0) return QModelIndex(); if( index.isValid() ) { CompletionTreeElement* element = static_cast(index.internalPointer()); if( element->parent() ) return createIndex( element->rowInParent(), element->columnInParent(), element->parent() ); } return QModelIndex(); } int CodeCompletionModel::rowCount ( const QModelIndex & parent ) const { if( parent.isValid() ) { CompletionTreeElement* element = static_cast(parent.internalPointer()); CompletionTreeNode* node = element->asNode(); if( !node ) return 0; return node->children.count(); }else{ return m_completionItems.count(); } } QString CodeCompletionModel::filterString(KTextEditor::View* view, const KTextEditor::Range& range, const KTextEditor::Cursor& position) { m_filterString = KTextEditor::CodeCompletionModelControllerInterface::filterString(view, range, position); return m_filterString; } } #include "moc_codecompletionmodel.cpp" #include "codecompletionmodel.moc" diff --git a/language/codegen/applychangeswidget.cpp b/language/codegen/applychangeswidget.cpp index 904c851f1..ad3d8b3d3 100644 --- a/language/codegen/applychangeswidget.cpp +++ b/language/codegen/applychangeswidget.cpp @@ -1,214 +1,214 @@ /* Copyright 2008 Aleix Pol * Copyright 2009 Ramón Zarazúa * * 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 "applychangeswidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "coderepresentation.h" #include #include #include #if KTEXTEDITOR_VERSION < QT_VERSION_CHECK(5, 10, 0) Q_DECLARE_METATYPE(KTextEditor::Range) #endif namespace KDevelop { class ApplyChangesWidgetPrivate { public: - ApplyChangesWidgetPrivate(ApplyChangesWidget * p) + explicit ApplyChangesWidgetPrivate(ApplyChangesWidget * p) : parent(p), m_index(0) {} ~ApplyChangesWidgetPrivate() { qDeleteAll(m_temps); } void createEditPart(const KDevelop::IndexedString& url); ApplyChangesWidget * const parent; int m_index; QList m_editParts; QList m_temps; QList m_files; QTabWidget * m_documentTabs; QLabel* m_info; }; ApplyChangesWidget::ApplyChangesWidget(QWidget* parent) : QDialog(parent), d(new ApplyChangesWidgetPrivate(this)) { setSizeGripEnabled(true); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel); auto mainLayout = new QVBoxLayout(this); auto okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); connect(buttonBox, &QDialogButtonBox::accepted, this, &ApplyChangesWidget::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &ApplyChangesWidget::reject); QWidget* w=new QWidget(this); d->m_info=new QLabel(w); d->m_documentTabs = new QTabWidget(w); connect(d->m_documentTabs, &QTabWidget::currentChanged, this, &ApplyChangesWidget::indexChanged); QVBoxLayout* l = new QVBoxLayout(w); l->addWidget(d->m_info); l->addWidget(d->m_documentTabs); mainLayout->addWidget(w); mainLayout->addWidget(buttonBox); resize(QSize(800, 400)); } ApplyChangesWidget::~ApplyChangesWidget() { delete d; } bool ApplyChangesWidget::hasDocuments() const { return d->m_editParts.size() > 0; } KTextEditor::Document* ApplyChangesWidget::document() const { return qobject_cast(d->m_editParts[d->m_index]); } void ApplyChangesWidget::setInformation(const QString & info) { d->m_info->setText(info); } void ApplyChangesWidget::addDocuments(const IndexedString & original) { int idx=d->m_files.indexOf(original); if(idx<0) { QWidget * w = new QWidget; d->m_documentTabs->addTab(w, original.str()); d->m_documentTabs->setCurrentWidget(w); d->m_files.insert(d->m_index, original); d->createEditPart(original); } else { d->m_index=idx; } } bool ApplyChangesWidget::applyAllChanges() { /// @todo implement safeguard in case a file saving fails bool ret = true; for(int i = 0; i < d->m_files.size(); ++i ) if(d->m_editParts[i]->saveAs(d->m_files[i].toUrl())) { IDocument* doc = ICore::self()->documentController()->documentForUrl(d->m_files[i].toUrl()); if(doc && doc->state()==IDocument::Dirty) doc->reload(); } else ret = false; return ret; } } namespace KDevelop { void ApplyChangesWidgetPrivate::createEditPart(const IndexedString & file) { QWidget * widget = m_documentTabs->currentWidget(); Q_ASSERT(widget); QVBoxLayout *m=new QVBoxLayout(widget); QSplitter *v=new QSplitter(widget); m->addWidget(v); QUrl url = file.toUrl(); QMimeType mimetype = QMimeDatabase().mimeTypeForUrl(url); KParts::ReadWritePart* part=KMimeTypeTrader::self()->createPartInstanceFromQuery(mimetype.name(), widget, widget); KTextEditor::Document* document=qobject_cast(part); Q_ASSERT(document); Q_ASSERT(document->action("file_save")); document->action("file_save")->setEnabled(false); m_editParts.insert(m_index, part); //Open the best code representation, even if it is artificial CodeRepresentation::Ptr repr = createCodeRepresentation(file); if(!repr->fileExists()) { const QString templateName = QDir::tempPath() + QLatin1Char('/') + url.fileName().split('.').last(); QTemporaryFile * temp(new QTemporaryFile(templateName)); temp->open(); temp->write(repr->text().toUtf8()); temp->close(); url = QUrl::fromLocalFile(temp->fileName()); m_temps << temp; } m_editParts[m_index]->openUrl(url); v->addWidget(m_editParts[m_index]->widget()); v->setSizes(QList() << 400 << 100); } void ApplyChangesWidget::indexChanged(int newIndex) { Q_ASSERT(newIndex != -1); d->m_index = newIndex; } void ApplyChangesWidget::updateDiffView(int index) { int prevIndex = d->m_index; d->m_index = index == -1 ? d->m_index : index; d->m_index = prevIndex; } } diff --git a/language/codegen/basicrefactoring.cpp b/language/codegen/basicrefactoring.cpp index c971ee412..0b17a7c7b 100644 --- a/language/codegen/basicrefactoring.cpp +++ b/language/codegen/basicrefactoring.cpp @@ -1,361 +1,363 @@ /* This file is part of KDevelop * * Copyright 2014 Miquel Sabaté * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // Qt #include // KDE / KDevelop #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "progressdialogs/refactoringdialog.h" #include "util/debug.h" #include "ui_basicrefactoring.h" namespace { QPair splitFileAtExtension(const QString& fileName) { int idx = fileName.indexOf('.'); if (idx == -1) { return qMakePair(fileName, QString()); } return qMakePair(fileName.left(idx), fileName.mid(idx)); } } using namespace KDevelop; //BEGIN: BasicRefactoringCollector BasicRefactoringCollector::BasicRefactoringCollector(const IndexedDeclaration &decl) : UsesWidgetCollector(decl) { setCollectConstructors(true); setCollectDefinitions(true); setCollectOverloads(true); } QVector BasicRefactoringCollector::allUsingContexts() const { return m_allUsingContexts; } void BasicRefactoringCollector::processUses(KDevelop::ReferencedTopDUContext topContext) { m_allUsingContexts << IndexedTopDUContext(topContext.data()); UsesWidgetCollector::processUses(topContext); } //END: BasicRefactoringCollector //BEGIN: BasicRefactoring BasicRefactoring::BasicRefactoring(QObject *parent) : QObject(parent) { /* There's nothing to do here. */ } void BasicRefactoring::fillContextMenu(ContextMenuExtension &extension, Context *context) { DeclarationContext *declContext = dynamic_cast(context); if (!declContext) return; DUChainReadLocker lock; Declaration *declaration = declContext->declaration().data(); if (declaration && acceptForContextMenu(declaration)) { QFileInfo finfo(declaration->topContext()->url().str()); if (finfo.isWritable()) { QAction *action = new QAction(i18n("Rename \"%1\"...", declaration->qualifiedIdentifier().toString()), nullptr); action->setData(QVariant::fromValue(IndexedDeclaration(declaration))); action->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename"))); connect(action, &QAction::triggered, this, &BasicRefactoring::executeRenameAction); extension.addAction(ContextMenuExtension::RefactorGroup, action); } } } bool BasicRefactoring::shouldRenameUses(KDevelop::Declaration* declaration) const { // Now we know we're editing a declaration, but some declarations we don't offer a rename for // basically that's any declaration that wouldn't be fully renamed just by renaming its uses(). if (declaration->internalContext() || declaration->isForwardDeclaration()) { //make an exception for non-class functions if (!declaration->isFunctionDeclaration() || dynamic_cast(declaration)) return false; } return true; } QString BasicRefactoring::newFileName(const QUrl& current, const QString& newName) { QPair nameExtensionPair = splitFileAtExtension(current.fileName()); // if current file is lowercased, keep that if (nameExtensionPair.first == nameExtensionPair.first.toLower()) { return newName.toLower() + nameExtensionPair.second; } else { return newName + nameExtensionPair.second; } } DocumentChangeSet::ChangeResult BasicRefactoring::addRenameFileChanges(const QUrl& current, const QString& newName, DocumentChangeSet* changes) { return changes->addDocumentRenameChange( IndexedString(current), IndexedString(newFileName(current, newName))); } bool BasicRefactoring::shouldRenameFile(Declaration* declaration) { // only try to rename files when we renamed a class/struct if (!dynamic_cast(declaration)) { return false; } const QUrl currUrl = declaration->topContext()->url().toUrl(); const QString fileName = currUrl.fileName(); const QPair nameExtensionPair = splitFileAtExtension(fileName); // check whether we renamed something that is called like the document it lives in return nameExtensionPair.first.compare(declaration->identifier().toString(), Qt::CaseInsensitive) == 0; } DocumentChangeSet::ChangeResult BasicRefactoring::applyChanges(const QString &oldName, const QString &newName, DocumentChangeSet &changes, DUContext *context, int usedDeclarationIndex) { if (usedDeclarationIndex == std::numeric_limits::max()) - return DocumentChangeSet::ChangeResult(true); + return DocumentChangeSet::ChangeResult::successfulResult(); for (int a = 0; a < context->usesCount(); ++a) { const Use &use(context->uses()[a]); if (use.m_declarationIndex != usedDeclarationIndex) continue; if (use.m_range.isEmpty()) { qCDebug(LANGUAGE) << "found empty use"; continue; } DocumentChangeSet::ChangeResult result = changes.addChange(DocumentChange(context->url(), context->transformFromLocalRevision(use.m_range), oldName, newName)); if (!result) return result; } foreach (DUContext *child, context->childContexts()) { DocumentChangeSet::ChangeResult result = applyChanges(oldName, newName, changes, child, usedDeclarationIndex); if (!result) return result; } - return DocumentChangeSet::ChangeResult(true); + + return DocumentChangeSet::ChangeResult::successfulResult(); } DocumentChangeSet::ChangeResult BasicRefactoring::applyChangesToDeclarations(const QString &oldName, const QString &newName, DocumentChangeSet &changes, const QList &declarations) { foreach (const IndexedDeclaration decl, declarations) { Declaration *declaration = decl.data(); if (!declaration) continue; if (declaration->range().isEmpty()) qCDebug(LANGUAGE) << "found empty declaration"; TopDUContext *top = declaration->topContext(); DocumentChangeSet::ChangeResult result = changes.addChange(DocumentChange(top->url(), declaration->rangeInCurrentRevision(), oldName, newName)); if (!result) return result; } - return DocumentChangeSet::ChangeResult(true); + + return DocumentChangeSet::ChangeResult::successfulResult(); } KDevelop::IndexedDeclaration BasicRefactoring::declarationUnderCursor(bool allowUse) { KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView(); if (!view) return KDevelop::IndexedDeclaration(); KTextEditor::Document* doc = view->document(); DUChainReadLocker lock; if (allowUse) return DUChainUtils::itemUnderCursor(doc->url(), KTextEditor::Cursor(view->cursorPosition())).declaration; else return DUChainUtils::declarationInLine(KTextEditor::Cursor(view->cursorPosition()), DUChainUtils::standardContextForUrl(doc->url())); } void BasicRefactoring::startInteractiveRename(const KDevelop::IndexedDeclaration &decl) { DUChainReadLocker lock(DUChain::lock()); Declaration *declaration = decl.data(); if (!declaration) { KMessageBox::error(ICore::self()->uiController()->activeMainWindow(), i18n("No declaration under cursor")); return; } QFileInfo info(declaration->topContext()->url().str()); if (!info.isWritable()) { KMessageBox::error(ICore::self()->uiController()->activeMainWindow(), i18n("Declaration is located in non-writeable file %1.", declaration->topContext()->url().str())); return; } QString originalName = declaration->identifier().identifier().str(); lock.unlock(); NameAndCollector nc = newNameForDeclaration(DeclarationPointer(declaration)); if (nc.newName == originalName || nc.newName.isEmpty()) return; renameCollectedDeclarations(nc.collector.data(), nc.newName, originalName); } bool BasicRefactoring::acceptForContextMenu(const Declaration *decl) { // Default implementation. Some language plugins might override it to // handle some cases. Q_UNUSED(decl); return true; } void BasicRefactoring::executeRenameAction() { QAction *action = qobject_cast(sender()); if (action) { IndexedDeclaration decl = action->data().value(); if(!decl.isValid()) decl = declarationUnderCursor(); if(!decl.isValid()) return; startInteractiveRename(decl); } } BasicRefactoring::NameAndCollector BasicRefactoring::newNameForDeclaration(const KDevelop::DeclarationPointer& declaration) { DUChainReadLocker lock; if (!declaration) { return {}; } QSharedPointer collector(new BasicRefactoringCollector(declaration.data())); Ui::RenameDialog renameDialog; QDialog dialog; renameDialog.setupUi(&dialog); UsesWidget uses(declaration.data(), collector); //So the context-links work QWidget *navigationWidget = declaration->context()->createNavigationWidget(declaration.data()); AbstractNavigationWidget* abstractNavigationWidget = dynamic_cast(navigationWidget); if (abstractNavigationWidget) connect(&uses, &UsesWidget::navigateDeclaration, abstractNavigationWidget, &AbstractNavigationWidget::navigateDeclaration); QString declarationName = declaration->toString(); dialog.setWindowTitle(i18nc("Renaming some declaration", "Rename \"%1\"", declarationName)); renameDialog.edit->setText(declaration->identifier().identifier().str()); renameDialog.edit->selectAll(); renameDialog.tabWidget->addTab(&uses, i18n("Uses")); if (navigationWidget) renameDialog.tabWidget->addTab(navigationWidget, i18n("Declaration Info")); lock.unlock(); if (dialog.exec() != QDialog::Accepted) return {}; 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; DUChainReadLocker lock; foreach (const KDevelop::IndexedTopDUContext collected, collector->allUsingContexts()) { QSet hadIndices; foreach (const IndexedDeclaration decl, collector->declarations()) { uint usedDeclarationIndex = collected.data()->indexForUsedDeclaration(decl.data(), false); if (hadIndices.contains(usedDeclarationIndex)) continue; hadIndices.insert(usedDeclarationIndex); DocumentChangeSet::ChangeResult result = applyChanges(originalName, replacementName, changes, collected.data(), usedDeclarationIndex); if (!result) { KMessageBox::error(nullptr, i18n("Applying changes failed: %1", result.m_failureReason)); return {}; } } } DocumentChangeSet::ChangeResult result = applyChangesToDeclarations(originalName, replacementName, changes, collector->declarations()); if (!result) { KMessageBox::error(nullptr, i18n("Applying changes failed: %1", result.m_failureReason)); return {}; } ///We have to ignore failed changes for now, since uses of a constructor or of operator() may be created on "(" parens changes.setReplacementPolicy(DocumentChangeSet::IgnoreFailedChange); if (!apply) { return changes; } result = changes.applyAllChanges(); if (!result) { KMessageBox::error(nullptr, i18n("Applying changes failed: %1", result.m_failureReason)); } return {}; } //END: BasicRefactoring diff --git a/language/codegen/codedescription.h b/language/codegen/codedescription.h index 183a0a780..4ef1f56a8 100644 --- a/language/codegen/codedescription.h +++ b/language/codegen/codedescription.h @@ -1,272 +1,272 @@ /* 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_CODEDESCRIPTION_H #define KDEVPLATFORM_CODEDESCRIPTION_H #include #include #include #include #include /** * NOTE: changes in this file will quite probably also require changes * in codedescriptionmetatype.h! */ namespace KDevelop { /** * @brief Represents a variable * * A variable has two main properties: its type and name. **/ struct KDEVPLATFORMLANGUAGE_EXPORT VariableDescription { /** * Creates a variable with no type and no name * **/ VariableDescription(); /** * Creates a variable with type @p type and name @p name * * @param type the type of this variable * @param name the name of this variable **/ VariableDescription(const QString& type, const QString& name); /** * Creates a variable and determines it type and name from the @p declaration * **/ - VariableDescription(const DeclarationPointer& declaration); + explicit VariableDescription(const DeclarationPointer& declaration); /** * The name of this variable **/ QString name; /** * The type of this variable. * * In weakly typed languages, this field can be empty. **/ QString type; /** * Access specifier, only relevant for class members. * * Not all languages use these, so it can be left empty. **/ QString access; /** * The default value of this variable. */ QString value; }; /** * List of variable descriptions **/ typedef QVector VariableDescriptionList; /** * @brief Represents a function * * A function has a name and any number of arguments and return values **/ struct KDEVPLATFORMLANGUAGE_EXPORT FunctionDescription { /** * Creates a function with no name and no arguments * **/ FunctionDescription(); /** * Creates a function with name @p and specified @p arguments and @p returnArguments * * @param name the name of the new function * @param arguments a list of variables that are passed to this function as arguments * @param returnArguments a list of variables that this function returns **/ FunctionDescription(const QString& name, const VariableDescriptionList& arguments, const VariableDescriptionList& returnArguments); /** * Creates a function and determines its properties from the @p declaration * * @param declaration a function declaration **/ - FunctionDescription(const DeclarationPointer& declaration); + explicit FunctionDescription(const DeclarationPointer& declaration); /** * Convenience method, returns the type of the first variable in returnArguments * or an empty string if this function has no return arguments */ QString returnType() const; /** * The name of this function **/ QString name; /** * This function's input arguments **/ QVector arguments; /** * This function's return values **/ QVector returnArguments; /** * Access specifier, only relevant for class members. * * Not all languages use these, so it can be left empty. **/ QString access; /** * Specifies whether this function is a class constructor **/ bool isConstructor : 1; /** * Specifies whether this function is a class destructor */ bool isDestructor : 1; /** * Specifies whether this function is virtual and can be overridden by subclasses **/ bool isVirtual : 1; /** * Specifies whether this function is static and can be called without a class instance **/ bool isStatic : 1; /** * Specifies whether this function is a slot **/ bool isSlot : 1; /** * Specifies whether this function is a signal **/ bool isSignal : 1; /** * Specifies whether this function is constant **/ bool isConst : 1; }; /** * List of function descriptions **/ typedef QVector FunctionDescriptionList; /** * Description of an inheritance relation. **/ struct KDEVPLATFORMLANGUAGE_EXPORT InheritanceDescription { /** * @brief The mode of this inheritance. * * For C++ classes, mode string are the same as access specifiers (public, protected, private). * In other languages, the mode is used to differentiate between extends/implements * or other possible inheritance types. * * Some languages do not recognise distinct inheritance modes at all. **/ QString inheritanceMode; /** * The name of the base class **/ QString baseType; }; /** * List of inheritance descriptions **/ typedef QVector InheritanceDescriptionList; /** * @brief Represents a class * * A class descriptions stores its name, its member variables and functions, as well as its superclasses and inheritance types. **/ struct KDEVPLATFORMLANGUAGE_EXPORT ClassDescription { /** * Creates an empty class * **/ ClassDescription(); /** * Creates an empty class named @p name * * @param name the name of the new class **/ - ClassDescription(const QString& name); + explicit ClassDescription(const QString& name); /** * The name of this class **/ QString name; /** * List of base classes (classes from which this one inherits) as well as inheritance types **/ InheritanceDescriptionList baseClasses; /** * List of all member variables in this class **/ VariableDescriptionList members; /** * List of all member functions (methods) in this class **/ FunctionDescriptionList methods; }; namespace CodeDescription { template QVariant toVariantList(const QVector& list) { QVariantList ret; foreach (const T& t, list) { ret << QVariant::fromValue(t); } return QVariant::fromValue(ret); } } } Q_DECLARE_TYPEINFO(KDevelop::VariableDescription, Q_MOVABLE_TYPE); Q_DECLARE_TYPEINFO(KDevelop::FunctionDescription, Q_MOVABLE_TYPE); Q_DECLARE_TYPEINFO(KDevelop::InheritanceDescription, Q_MOVABLE_TYPE); Q_DECLARE_TYPEINFO(KDevelop::ClassDescription, Q_MOVABLE_TYPE); Q_DECLARE_METATYPE(KDevelop::VariableDescription) Q_DECLARE_METATYPE(KDevelop::VariableDescriptionList) Q_DECLARE_METATYPE(KDevelop::FunctionDescription) Q_DECLARE_METATYPE(KDevelop::FunctionDescriptionList) Q_DECLARE_METATYPE(KDevelop::InheritanceDescription) Q_DECLARE_METATYPE(KDevelop::InheritanceDescriptionList) Q_DECLARE_METATYPE(KDevelop::ClassDescription) #endif // KDEVPLATFORM_CODEDESCRIPTION_H diff --git a/language/codegen/coderepresentation.cpp b/language/codegen/coderepresentation.cpp index fe4b25d13..80b0635e1 100644 --- a/language/codegen/coderepresentation.cpp +++ b/language/codegen/coderepresentation.cpp @@ -1,377 +1,377 @@ /* 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 "coderepresentation.h" #include #include #include #include #include #include #include #include namespace KDevelop { static bool onDiskChangesForbidden = false; QString CodeRepresentation::rangeText(const KTextEditor::Range& range) const { Q_ASSERT(range.end().line() < lines()); //Easier for single line ranges which should happen most of the time if(range.onSingleLine()) return QString( line( range.start().line() ).mid( range.start().column(), range.columnWidth() ) ); //Add up al the requested lines QString rangedText = line(range.start().line()).mid(range.start().column()); for(int i = range.start().line() + 1; i <= range.end().line(); ++i) rangedText += '\n' + ((i == range.end().line()) ? line(i).left(range.end().column()) : line(i)); return rangedText; } static void grepLine(const QString& identifier, const QString& lineText, int lineNumber, QVector& ret, bool surroundedByBoundary) { if (identifier.isEmpty()) return; int pos = 0; while(true) { pos = lineText.indexOf(identifier, pos); if(pos == -1) break; int start = pos; pos += identifier.length(); int end = pos; if(!surroundedByBoundary || ( (end == lineText.length() || !lineText[end].isLetterOrNumber() || lineText[end] != '_') && (start-1 < 0 || !lineText[start-1].isLetterOrNumber() || lineText[start-1] != '_')) ) { ret << KTextEditor::Range(lineNumber, start, lineNumber, end); } } } class EditorCodeRepresentation : public DynamicCodeRepresentation { public: - EditorCodeRepresentation(KTextEditor::Document* document) : m_document(document) { + explicit EditorCodeRepresentation(KTextEditor::Document* document) : m_document(document) { m_url = IndexedString(m_document->url()); } QVector< KTextEditor::Range > grep ( const QString& identifier, bool surroundedByBoundary ) const override { QVector< KTextEditor::Range > ret; if (identifier.isEmpty()) return ret; for(int line = 0; line < m_document->lines(); ++line) grepLine(identifier, m_document->line(line), line, ret, surroundedByBoundary); return ret; } KDevEditingTransaction::Ptr makeEditTransaction() override { return KDevEditingTransaction::Ptr(new KDevEditingTransaction(m_document)); } QString line(int line) const override { if(line < 0 || line >= m_document->lines()) return QString(); return m_document->line(line); } int lines() const override { return m_document->lines(); } QString text() const override { return m_document->text(); } bool setText(const QString& text) override { bool ret; { KDevEditingTransaction t(m_document); ret = m_document->setText(text); } ModificationRevision::clearModificationCache(m_url); return ret; } bool fileExists() override{ return QFile(m_document->url().path()).exists(); } bool replace(const KTextEditor::Range& range, const QString& oldText, const QString& newText, bool ignoreOldText) override { QString old = m_document->text(range); if(oldText != old && !ignoreOldText) { return false; } bool ret; { KDevEditingTransaction t(m_document); ret = m_document->replaceText(range, newText); } ModificationRevision::clearModificationCache(m_url); return ret; } QString rangeText(const KTextEditor::Range& range) const override { return m_document->text(range); } private: KTextEditor::Document* m_document; IndexedString m_url; }; class FileCodeRepresentation : public CodeRepresentation { public: - FileCodeRepresentation(const IndexedString& document) : m_document(document) { + explicit FileCodeRepresentation(const IndexedString& document) : m_document(document) { QString localFile(document.toUrl().toLocalFile()); QFile file( localFile ); if ( file.open(QIODevice::ReadOnly) ) { data = QString::fromLocal8Bit(file.readAll()); lineData = data.split('\n'); } m_exists = file.exists(); } QString line(int line) const override { if(line < 0 || line >= lineData.size()) return QString(); return lineData.at(line); } QVector< KTextEditor::Range > grep ( const QString& identifier, bool surroundedByBoundary ) const override { QVector< KTextEditor::Range > ret; if (identifier.isEmpty()) return ret; for(int line = 0; line < lineData.count(); ++line) grepLine(identifier, lineData.at(line), line, ret, surroundedByBoundary); return ret; } int lines() const override { return lineData.count(); } QString text() const override { return data; } bool setText(const QString& text) override { Q_ASSERT(!onDiskChangesForbidden); QString localFile(m_document.toUrl().toLocalFile()); QFile file( localFile ); if ( file.open(QIODevice::WriteOnly) ) { QByteArray data = text.toLocal8Bit(); if(file.write(data) == data.size()) { ModificationRevision::clearModificationCache(m_document); return true; } } return false; } bool fileExists() override{ return m_exists; } private: //We use QByteArray, because the column-numbers are measured in utf-8 IndexedString m_document; bool m_exists; QStringList lineData; QString data; }; class ArtificialStringData : public QSharedData { public: - ArtificialStringData(const QString& data) { + explicit ArtificialStringData(const QString& data) { setData(data); } void setData(const QString& data) { m_data = data; m_lineData = m_data.split('\n'); } QString data() const { return m_data; } const QStringList& lines() const { return m_lineData; } private: QString m_data; QStringList m_lineData; }; class StringCodeRepresentation : public CodeRepresentation { public: - StringCodeRepresentation(QExplicitlySharedDataPointer _data) : data(_data) { + explicit StringCodeRepresentation(QExplicitlySharedDataPointer _data) : data(_data) { Q_ASSERT(data); } QString line(int line) const override { if(line < 0 || line >= data->lines().size()) return QString(); return data->lines().at(line); } int lines() const override { return data->lines().count(); } QString text() const override { return data->data(); } bool setText(const QString& text) override { data->setData(text); return true; } bool fileExists() override{ return false; } QVector< KTextEditor::Range > grep ( const QString& identifier, bool surroundedByBoundary ) const override { QVector< KTextEditor::Range > ret; if (identifier.isEmpty()) return ret; for(int line = 0; line < data->lines().count(); ++line) grepLine(identifier, data->lines().at(line), line, ret, surroundedByBoundary); return ret; } private: QExplicitlySharedDataPointer data; }; static QHash > artificialStrings; //Return the representation for the given URL if it exists, or an empty pointer otherwise static QExplicitlySharedDataPointer representationForPath(const IndexedString& path) { if(artificialStrings.contains(path)) return artificialStrings[path]; else { IndexedString constructedPath(CodeRepresentation::artificialPath(path.str())); if(artificialStrings.contains(constructedPath)) return artificialStrings[constructedPath]; else return QExplicitlySharedDataPointer(); } } bool artificialCodeRepresentationExists(const IndexedString& path) { return representationForPath(path); } CodeRepresentation::Ptr createCodeRepresentation(const IndexedString& path) { if(artificialCodeRepresentationExists(path)) return CodeRepresentation::Ptr(new StringCodeRepresentation(representationForPath(path))); IDocument* document = ICore::self()->documentController()->documentForUrl(path.toUrl()); if(document && document->textDocument()) return CodeRepresentation::Ptr(new EditorCodeRepresentation(document->textDocument())); else return CodeRepresentation::Ptr(new FileCodeRepresentation(path)); } void CodeRepresentation::setDiskChangesForbidden(bool changesForbidden) { onDiskChangesForbidden = changesForbidden; } QString CodeRepresentation::artificialPath(const QString& name) { QUrl url = QUrl::fromLocalFile(name); return QLatin1String("/kdev-artificial/") + url.adjusted(QUrl::NormalizePathSegments).path(); } InsertArtificialCodeRepresentation::InsertArtificialCodeRepresentation(const IndexedString& file, const QString& text) : m_file(file) { // make it simpler to use this by converting relative strings into artificial paths if(QUrl(m_file.str()).isRelative()) { m_file = IndexedString(CodeRepresentation::artificialPath(file.str())); int idx = 0; while(artificialStrings.contains(m_file)) { ++idx; m_file = IndexedString(CodeRepresentation::artificialPath(QStringLiteral("%1_%2").arg(idx).arg(file.str()))); } } Q_ASSERT(!artificialStrings.contains(m_file)); artificialStrings.insert(m_file, QExplicitlySharedDataPointer(new ArtificialStringData(text))); } IndexedString InsertArtificialCodeRepresentation::file() { return m_file; } InsertArtificialCodeRepresentation::~InsertArtificialCodeRepresentation() { Q_ASSERT(artificialStrings.contains(m_file)); artificialStrings.remove(m_file); } void InsertArtificialCodeRepresentation::setText(const QString& text) { Q_ASSERT(artificialStrings.contains(m_file)); artificialStrings[m_file]->setData(text); } QString InsertArtificialCodeRepresentation::text() { Q_ASSERT(artificialStrings.contains(m_file)); return artificialStrings[m_file]->data(); } } // kate: indent-width 4; diff --git a/language/codegen/coderepresentation.h b/language/codegen/coderepresentation.h index 287f5aa10..df09571a6 100644 --- a/language/codegen/coderepresentation.h +++ b/language/codegen/coderepresentation.h @@ -1,186 +1,186 @@ /* 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_CODEREPRESENTATION_H #define KDEVPLATFORM_CODEREPRESENTATION_H #include #include #include #include #include class QString; namespace KTextEditor { class Range; } namespace KDevelop { struct KDevEditingTransaction; class IndexedString; //NOTE: this is ugly, but otherwise kate might remove tabs again :-/ // see also: https://bugs.kde.org/show_bug.cgi?id=291074 struct EditorDisableReplaceTabs { - EditorDisableReplaceTabs(KTextEditor::Document* document) : m_iface(qobject_cast(document)), m_count(0) { + explicit EditorDisableReplaceTabs(KTextEditor::Document* document) : m_iface(qobject_cast(document)), m_count(0) { ++m_count; if( m_count > 1 ) return; if ( m_iface ) { m_oldReplaceTabs = m_iface->configValue(configKey()); m_iface->setConfigValue(configKey(), false); } } ~EditorDisableReplaceTabs() { --m_count; if( m_count > 0 ) return; Q_ASSERT( m_count == 0 ); if (m_iface) m_iface->setConfigValue(configKey(), m_oldReplaceTabs); } inline QString configKey() const { return QStringLiteral("replace-tabs"); } KTextEditor::ConfigInterface* m_iface; int m_count; QVariant m_oldReplaceTabs; }; struct KDevEditingTransaction { - KDevEditingTransaction(KTextEditor::Document* document) + explicit KDevEditingTransaction(KTextEditor::Document* document) : edit(document) , disableReplaceTabs(document) {} // NOTE: It's important to close the transaction first and only then destroy the EditorDisableReplaceTabs. Otherwise we hit asserts in KTextEditor. KTextEditor::Document::EditingTransaction edit; EditorDisableReplaceTabs disableReplaceTabs; typedef std::unique_ptr Ptr; }; /** * Allows getting code-lines conveniently, either through an open editor, or from a disk-loaded file. */ class KDEVPLATFORMLANGUAGE_EXPORT CodeRepresentation : public QSharedData { public: virtual ~CodeRepresentation() { } virtual QString line(int line) const = 0; virtual int lines() const = 0; virtual QString text() const = 0; virtual QString rangeText(const KTextEditor::Range& range) const; /** * Search for the given identifier in the document, and returns all ranges * where it was found. * @param identifier The identifier to search for * @param surroundedByBoundary Whether only matches that are surrounded by typical word-boundaries * should be acceded. Everything except letters, numbers, and the _ character * counts as word boundary. * */ virtual QVector grep(const QString& identifier, bool surroundedByBoundary = true) const = 0; /** * Overwrites the text in the file with the new given one * * @return true on success */ virtual bool setText(const QString&) = 0; /** @return true if this representation represents an actual file on disk */ virtual bool fileExists() = 0; /** * Can be used for example from tests to disallow on-disk changes. When such a change is done, an assertion is triggered. * You should enable this within tests, unless you really want to work on the disk. */ static void setDiskChangesForbidden(bool changesForbidden); /** * Returns the specified name as a url for aritificial source code * suitable for code being inserted into the parser */ static QString artificialPath(const QString & name); typedef QExplicitlySharedDataPointer Ptr; }; class KDEVPLATFORMLANGUAGE_EXPORT DynamicCodeRepresentation : public CodeRepresentation { public: /** Used to group edit-history together. Call this optionally before a bunch * of replace() calls, to group them together. */ virtual KDevEditingTransaction::Ptr makeEditTransaction() = 0; virtual bool replace(const KTextEditor::Range& range, const QString& oldText, const QString& newText, bool ignoreOldText = false) = 0; typedef QExplicitlySharedDataPointer Ptr; }; /** * Creates a code-representation for the given url, that allows conveniently accessing its data. Returns zero on failure. */ KDEVPLATFORMLANGUAGE_EXPORT CodeRepresentation::Ptr createCodeRepresentation(const IndexedString& url); /** * @return true if an artificial code representation already exists for the specified URL */ KDEVPLATFORMLANGUAGE_EXPORT bool artificialCodeRepresentationExists(const IndexedString& url); /** * Allows inserting artificial source-code into the code-representation and parsing framework. * The source-code logically represents a file. * * The artificial code is automatically removed when the object is destroyed. */ class KDEVPLATFORMLANGUAGE_EXPORT InsertArtificialCodeRepresentation : public QSharedData { public: /** * Inserts an artifial source-code representation with filename @p file and the contents @p text * If @p file is not an absolute path or url, it will be made absolute using the CodeRepresentation::artifialUrl() * function, while ensuring that the name is unique. */ InsertArtificialCodeRepresentation(const IndexedString& file, const QString& text); ~InsertArtificialCodeRepresentation(); void setText(const QString& text); QString text(); /** * Returns the file-name for this code-representation. */ IndexedString file(); private: InsertArtificialCodeRepresentation(const InsertArtificialCodeRepresentation&); InsertArtificialCodeRepresentation& operator=(const InsertArtificialCodeRepresentation&); IndexedString m_file; }; typedef QExplicitlySharedDataPointer InsertArtificialCodeRepresentationPointer; } #endif diff --git a/language/codegen/documentchangeset.cpp b/language/codegen/documentchangeset.cpp index 1358c978d..af05f2e72 100644 --- a/language/codegen/documentchangeset.cpp +++ b/language/codegen/documentchangeset.cpp @@ -1,593 +1,593 @@ /* Copyright 2008 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "documentchangeset.h" #include "coderepresentation.h" #include "util/debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace KDevelop { typedef QList ChangesList; typedef QHash ChangesHash; struct DocumentChangeSetPrivate { DocumentChangeSet::ReplacementPolicy replacePolicy; DocumentChangeSet::FormatPolicy formatPolicy; DocumentChangeSet::DUChainUpdateHandling updatePolicy; DocumentChangeSet::ActivationPolicy activationPolicy; ChangesHash changes; QHash documentsRename; DocumentChangeSet::ChangeResult addChange(const DocumentChangePointer& change); DocumentChangeSet::ChangeResult replaceOldText(CodeRepresentation* repr, const QString& newText, const ChangesList& sortedChangesList); DocumentChangeSet::ChangeResult generateNewText(const IndexedString& file, ChangesList& sortedChanges, const CodeRepresentation* repr, QString& output); DocumentChangeSet::ChangeResult removeDuplicates(const IndexedString& file, ChangesList& filteredChanges); void formatChanges(); void updateFiles(); }; // Simple helpers to clear up code clutter namespace { inline bool changeIsValid(const DocumentChange& change, const QStringList& textLines) { return change.m_range.start() <= change.m_range.end() && change.m_range.end().line() < textLines.size() && change.m_range.start().line() >= 0 && change.m_range.start().column() >= 0 && change.m_range.start().column() <= textLines[change.m_range.start().line()].length() && change.m_range.end().column() >= 0 && change.m_range.end().column() <= textLines[change.m_range.end().line()].length(); } inline bool duplicateChanges(const DocumentChangePointer& previous, const DocumentChangePointer& current) { // Given the option of considering a duplicate two changes in the same range // but with different old texts to be ignored return previous->m_range == current->m_range && previous->m_newText == current->m_newText && (previous->m_oldText == current->m_oldText || (previous->m_ignoreOldText && current->m_ignoreOldText)); } inline QString rangeText(const KTextEditor::Range& range, const QStringList& textLines) { QStringList ret; ret.reserve(range.end().line() - range.start().line() + 1); for(int line = range.start().line(); line <= range.end().line(); ++line) { const QString lineText = textLines.at(line); int startColumn = 0; int endColumn = lineText.length(); if (line == range.start().line()) { startColumn = range.start().column(); } if (line == range.end().line()) { endColumn = range.end().column(); } ret << lineText.mid(startColumn, endColumn - startColumn); } return ret.join(QStringLiteral("\n")); } // need to have it as otherwise the arguments can exceed the maximum of 10 static QString printRange(const KTextEditor::Range& r) { return i18nc("text range line:column->line:column", "%1:%2->%3:%4", r.start().line(), r.start().column(), r.end().line(), r.end().column()); } } DocumentChangeSet::DocumentChangeSet() : d(new DocumentChangeSetPrivate) { d->replacePolicy = StopOnFailedChange; d->formatPolicy = AutoFormatChanges; d->updatePolicy = SimpleUpdate; d->activationPolicy = DoNotActivate; } DocumentChangeSet::DocumentChangeSet(const DocumentChangeSet& rhs) : d(new DocumentChangeSetPrivate(*rhs.d)) { } DocumentChangeSet& DocumentChangeSet::operator=(const DocumentChangeSet& rhs) { *d = *rhs.d; return *this; } DocumentChangeSet::~DocumentChangeSet() { delete d; } DocumentChangeSet::ChangeResult DocumentChangeSet::addChange(const DocumentChange& change) { return d->addChange(DocumentChangePointer(new DocumentChange(change))); } DocumentChangeSet::ChangeResult DocumentChangeSet::addChange(const DocumentChangePointer& change) { return d->addChange(change); } DocumentChangeSet::ChangeResult DocumentChangeSet::addDocumentRenameChange(const IndexedString& oldFile, const IndexedString& newname) { d->documentsRename.insert(oldFile, newname); - return true; + return DocumentChangeSet::ChangeResult::successfulResult(); } DocumentChangeSet::ChangeResult DocumentChangeSetPrivate::addChange(const DocumentChangePointer& change) { changes[change->m_document].append(change); - return true; + return DocumentChangeSet::ChangeResult::successfulResult(); } void DocumentChangeSet::setReplacementPolicy(DocumentChangeSet::ReplacementPolicy policy) { d->replacePolicy = policy; } void DocumentChangeSet::setFormatPolicy(DocumentChangeSet::FormatPolicy policy) { d->formatPolicy = policy; } void DocumentChangeSet::setUpdateHandling(DocumentChangeSet::DUChainUpdateHandling policy) { d->updatePolicy = policy; } void DocumentChangeSet::setActivationPolicy(DocumentChangeSet::ActivationPolicy policy) { d->activationPolicy = policy; } DocumentChangeSet::ChangeResult DocumentChangeSet::applyAllChanges() { QUrl oldActiveDoc; if (IDocument* activeDoc = ICore::self()->documentController()->activeDocument()) { oldActiveDoc = activeDoc->url(); } QList allFiles; foreach (IndexedString file, d->documentsRename.keys().toSet() + d->changes.keys().toSet()) { allFiles << file.toUrl(); } if (!KDevelop::ensureWritable(allFiles)) { return ChangeResult(QStringLiteral("some affected files are not writable")); } // rename files QHash::const_iterator it = d->documentsRename.constBegin(); for(; it != d->documentsRename.constEnd(); ++it) { QUrl url = it.key().toUrl(); IProject* p = ICore::self()->projectController()->findProjectForUrl(url); if(p) { QList files = p->filesForPath(it.key()); if(!files.isEmpty()) { ProjectBaseItem::RenameStatus renamed = files.first()->rename(it.value().str()); if(renamed == ProjectBaseItem::RenameOk) { const QUrl newUrl = Path(Path(url).parent(), it.value().str()).toUrl(); if (url == oldActiveDoc) { oldActiveDoc = newUrl; } IndexedString idxNewDoc(newUrl); // ensure changes operate on new file name ChangesHash::iterator iter = d->changes.find(it.key()); if (iter != d->changes.end()) { // copy changes ChangesList value = iter.value(); // remove old entry d->changes.erase(iter); // adapt to new url ChangesList::iterator itChange = value.begin(); ChangesList::iterator itEnd = value.end(); for(; itChange != itEnd; ++itChange) { (*itChange)->m_document = idxNewDoc; } d->changes[idxNewDoc] = value; } } else { ///FIXME: share code with project manager for the error code string representation return ChangeResult(i18n("Could not rename '%1' to '%2'", url.toDisplayString(QUrl::PreferLocalFile), it.value().str())); } } else { //TODO: do it outside the project management? qCWarning(LANGUAGE) << "tried to rename file not tracked by project - not implemented"; } } else { qCWarning(LANGUAGE) << "tried to rename a file outside of a project - not implemented"; } } QMap codeRepresentations; QMap newTexts; ChangesHash filteredSortedChanges; - ChangeResult result(true); + ChangeResult result = ChangeResult::successfulResult(); QList files(d->changes.keys()); foreach(const IndexedString &file, files) { CodeRepresentation::Ptr repr = createCodeRepresentation(file); if(!repr) { return ChangeResult(QStringLiteral("Could not create a Representation for %1").arg(file.str())); } codeRepresentations[file] = repr; QList& sortedChangesList(filteredSortedChanges[file]); { result = d->removeDuplicates(file, sortedChangesList); if(!result) return result; } { result = d->generateNewText(file, sortedChangesList, repr.data(), newTexts[file]); if(!result) return result; } } QMap oldTexts; //Apply the changes to the files foreach(const IndexedString &file, files) { oldTexts[file] = codeRepresentations[file]->text(); result = d->replaceOldText(codeRepresentations[file].data(), newTexts[file], filteredSortedChanges[file]); if(!result && d->replacePolicy == StopOnFailedChange) { //Revert all files foreach(const IndexedString &revertFile, oldTexts.keys()) { codeRepresentations[revertFile]->setText(oldTexts[revertFile]); } return result; } } d->updateFiles(); if(d->activationPolicy == Activate) { foreach(const IndexedString& file, files) { ICore::self()->documentController()->openDocument(file.toUrl()); } } // ensure the old document is still activated if (oldActiveDoc.isValid()) { ICore::self()->documentController()->openDocument(oldActiveDoc); } return result; } DocumentChangeSet::ChangeResult DocumentChangeSetPrivate::replaceOldText(CodeRepresentation* repr, const QString& newText, const ChangesList& sortedChangesList) { DynamicCodeRepresentation* dynamic = dynamic_cast(repr); if(dynamic) { auto transaction = dynamic->makeEditTransaction(); //Replay the changes one by one for(int pos = sortedChangesList.size()-1; pos >= 0; --pos) { const DocumentChange& change(*sortedChangesList[pos]); if(!dynamic->replace(change.m_range, change.m_oldText, change.m_newText, change.m_ignoreOldText)) { QString warningString = i18nc("Inconsistent change in between , found (encountered ) -> ", "Inconsistent change in %1 between %2, found %3 (encountered \"%4\") -> \"%5\"" , change.m_document.str() , printRange(change.m_range) , change.m_oldText , dynamic->rangeText(change.m_range) , change.m_newText); if(replacePolicy == DocumentChangeSet::WarnOnFailedChange) { qCWarning(LANGUAGE) << warningString; } else if(replacePolicy == DocumentChangeSet::StopOnFailedChange) { return DocumentChangeSet::ChangeResult(warningString); } //If set to ignore failed changes just continue with the others } } - return true; + return DocumentChangeSet::ChangeResult::successfulResult(); } //For files on disk if (!repr->setText(newText)) { QString warningString = i18n("Could not replace text in the document: %1", sortedChangesList.begin()->data()->m_document.str()); if(replacePolicy == DocumentChangeSet::WarnOnFailedChange) { qCWarning(LANGUAGE) << warningString; } return DocumentChangeSet::ChangeResult(warningString); } - return true; + return DocumentChangeSet::ChangeResult::successfulResult(); } DocumentChangeSet::ChangeResult DocumentChangeSetPrivate::generateNewText(const IndexedString & file, ChangesList& sortedChanges, const CodeRepresentation * repr, QString & output) { ISourceFormatter* formatter = nullptr; if(ICore::self()) { formatter = ICore::self()->sourceFormatterController()->formatterForUrl(file.toUrl()); } //Create the actual new modified file QStringList textLines = repr->text().split('\n'); QUrl url = file.toUrl(); QMimeType mime = QMimeDatabase().mimeTypeForUrl(url); QVector removedLines; for(int pos = sortedChanges.size()-1; pos >= 0; --pos) { DocumentChange& change(*sortedChanges[pos]); QString encountered; if(changeIsValid(change, textLines) && //We demand this, although it should be fixed ((encountered = rangeText(change.m_range, textLines)) == change.m_oldText || change.m_ignoreOldText)) { ///Problem: This does not work if the other changes significantly alter the context @todo Use the changed context QString leftContext = QStringList(textLines.mid(0, change.m_range.start().line()+1)).join(QStringLiteral("\n")); leftContext.chop(textLines[change.m_range.start().line()].length() - change.m_range.start().column()); QString rightContext = QStringList(textLines.mid(change.m_range.end().line())).join(QStringLiteral("\n")).mid(change.m_range.end().column()); if(formatter && (formatPolicy == DocumentChangeSet::AutoFormatChanges || formatPolicy == DocumentChangeSet::AutoFormatChangesKeepIndentation)) { QString oldNewText = change.m_newText; change.m_newText = formatter->formatSource(change.m_newText, url, mime, leftContext, rightContext); if(formatPolicy == DocumentChangeSet::AutoFormatChangesKeepIndentation) { // Reproduce the previous indentation QStringList oldLines = oldNewText.split('\n'); QStringList newLines = change.m_newText.split('\n'); if(oldLines.size() == newLines.size()) { for(int line = 0; line < newLines.size(); ++line) { // Keep the previous indentation QString oldIndentation; for (int a = 0; a < oldLines[line].size(); ++a) { if (oldLines[line][a].isSpace()) { oldIndentation.append(oldLines[line][a]); } else { break; } } int newIndentationLength = 0; for(int a = 0; a < newLines[line].size(); ++a) { if(newLines[line][a].isSpace()) { newIndentationLength = a; } else { break; } } newLines[line].replace(0, newIndentationLength, oldIndentation); } change.m_newText = newLines.join(QStringLiteral("\n")); } else { qCDebug(LANGUAGE) << "Cannot keep the indentation because the line count has changed" << oldNewText; } } } QString& line = textLines[change.m_range.start().line()]; if (change.m_range.start().line() == change.m_range.end().line()) { // simply replace existing line content line.replace(change.m_range.start().column(), change.m_range.end().column()-change.m_range.start().column(), change.m_newText); } else { // replace first line contents line.replace(change.m_range.start().column(), line.length() - change.m_range.start().column(), change.m_newText); // null other lines and remember for deletion for(int i = change.m_range.start().line() + 1; i <= change.m_range.end().line(); ++i) { textLines[i].clear(); removedLines << i; } } }else{ QString warningString = i18nc("Inconsistent change in at " " = (encountered ) -> ", "Inconsistent change in %1 at %2" " = \"%3\"(encountered \"%4\") -> \"%5\"" , file.str() , printRange(change.m_range) , change.m_oldText , encountered , change.m_newText); if(replacePolicy == DocumentChangeSet::IgnoreFailedChange) { //Just don't do the replacement } else if(replacePolicy == DocumentChangeSet::WarnOnFailedChange) { qCWarning(LANGUAGE) << warningString; } else { return DocumentChangeSet::ChangeResult(warningString, sortedChanges[pos]); } } } if (!removedLines.isEmpty()) { int offset = 0; std::sort(removedLines.begin(), removedLines.end()); foreach(int l, removedLines) { textLines.removeAt(l - offset); ++offset; } } output = textLines.join(QStringLiteral("\n")); - return true; + return DocumentChangeSet::ChangeResult::successfulResult(); } //Removes all duplicate changes for a single file, and then returns (via filteredChanges) the filtered duplicates DocumentChangeSet::ChangeResult DocumentChangeSetPrivate::removeDuplicates(const IndexedString& file, ChangesList& filteredChanges) { typedef QMultiMap ChangesMap; ChangesMap sortedChanges; foreach(const DocumentChangePointer &change, changes[file]) { sortedChanges.insert(change->m_range.end(), change); } //Remove duplicates ChangesMap::iterator previous = sortedChanges.begin(); for(ChangesMap::iterator it = ++sortedChanges.begin(); it != sortedChanges.end(); ) { if(( *previous ) && ( *previous )->m_range.end() > (*it)->m_range.start()) { //intersection if(duplicateChanges(( *previous ), *it)) { //duplicate, remove one it = sortedChanges.erase(it); continue; } //When two changes contain each other, and the container change is set to ignore old text, then it should be safe to //just ignore the contained change, and apply the bigger change else if((*it)->m_range.contains(( *previous )->m_range) && (*it)->m_ignoreOldText ) { qCDebug(LANGUAGE) << "Removing change: " << ( *previous )->m_oldText << "->" << ( *previous )->m_newText << ", because it is contained by change: " << (*it)->m_oldText << "->" << (*it)->m_newText; sortedChanges.erase(previous); } //This case is for when both have the same end, either of them could be the containing range else if((*previous)->m_range.contains((*it)->m_range) && (*previous)->m_ignoreOldText ) { qCDebug(LANGUAGE) << "Removing change: " << (*it)->m_oldText << "->" << (*it)->m_newText << ", because it is contained by change: " << ( *previous )->m_oldText << "->" << ( *previous )->m_newText; it = sortedChanges.erase(it); continue; } else { return DocumentChangeSet::ChangeResult( i18nc("Inconsistent change-request at :" "intersecting changes: " " -> @ & " " -> @", "Inconsistent change-request at %1; " "intersecting changes: " "\"%2\"->\"%3\"@%4 & \"%5\"->\"%6\"@%7 " , file.str() , ( *previous )->m_oldText , ( *previous )->m_newText , printRange(( *previous )->m_range) , (*it)->m_oldText , (*it)->m_newText , printRange((*it)->m_range))); } } previous = it; ++it; } filteredChanges = sortedChanges.values(); - return true; + return DocumentChangeSet::ChangeResult::successfulResult(); } void DocumentChangeSetPrivate::updateFiles() { ModificationRevisionSet::clearCache(); foreach(const IndexedString& file, changes.keys()) { ModificationRevision::clearModificationCache(file); } if(updatePolicy != DocumentChangeSet::NoUpdate && ICore::self()) { // The active document should be updated first, so that the user sees the results instantly if(IDocument* activeDoc = ICore::self()->documentController()->activeDocument()) { ICore::self()->languageController()->backgroundParser()->addDocument(IndexedString(activeDoc->url())); } // If there are currently open documents that now need an update, update them too foreach(const IndexedString& doc, ICore::self()->languageController()->backgroundParser()->managedDocuments()) { DUChainReadLocker lock(DUChain::lock()); TopDUContext* top = DUChainUtils::standardContextForUrl(doc.toUrl(), true); if((top && top->parsingEnvironmentFile() && top->parsingEnvironmentFile()->needsUpdate()) || !top) { lock.unlock(); ICore::self()->languageController()->backgroundParser()->addDocument(doc); } } // Eventually update _all_ affected files foreach(const IndexedString &file, changes.keys()) { if(!file.toUrl().isValid()) { qCWarning(LANGUAGE) << "Trying to apply changes to an invalid document"; continue; } ICore::self()->languageController()->backgroundParser()->addDocument(file); } } } } diff --git a/language/codegen/documentchangeset.h b/language/codegen/documentchangeset.h index 6267c8bcc..d73ed26e2 100644 --- a/language/codegen/documentchangeset.h +++ b/language/codegen/documentchangeset.h @@ -1,140 +1,147 @@ /* 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_DOCUMENTCHANGESET_H #define KDEVPLATFORM_DOCUMENTCHANGESET_H #include #include #include #include "coderepresentation.h" namespace KDevelop { struct DocumentChangeSetPrivate; class KDEVPLATFORMLANGUAGE_EXPORT DocumentChange : public QSharedData { public: DocumentChange(const IndexedString& document, const KTextEditor::Range& range, const QString& oldText, const QString& newText) : m_document(document), m_range(range), m_oldText(oldText), m_newText(newText), m_ignoreOldText(false) { //Clean the URL, so we don't get the same file be stored as a different one QUrl url = m_document.toUrl(); m_document = IndexedString(url.adjusted(QUrl::NormalizePathSegments)); } IndexedString m_document; KTextEditor::Range m_range; QString m_oldText; QString m_newText; bool m_ignoreOldText; //Set this to disable the verification of m_oldText. This can be used to overwrite arbitrary text, but is dangerous! }; typedef QExplicitlySharedDataPointer DocumentChangePointer; /** * Object representing an arbitrary set of changes to an arbitrary set of files that can be applied atomically. */ class KDEVPLATFORMLANGUAGE_EXPORT DocumentChangeSet { public: DocumentChangeSet(); ~DocumentChangeSet(); DocumentChangeSet(const DocumentChangeSet& rhs); DocumentChangeSet& operator=(const DocumentChangeSet& rhs); //Returns true on success - struct ChangeResult + class ChangeResult { - ChangeResult(bool success) - : m_success(success) - { } - ChangeResult(const QString& failureReason, const DocumentChangePointer& reasonChange = DocumentChangePointer()) - : m_failureReason(failureReason) - , m_reasonChange(reasonChange) - , m_success(false) - { } + public: + explicit ChangeResult(const QString& failureReason, const DocumentChangePointer& reasonChange = DocumentChangePointer()) + : ChangeResult(failureReason, reasonChange, false) {} + + static ChangeResult successfulResult() + { + return ChangeResult({}, {}, true); + } operator bool() const { return m_success; } /// Reason why the change failed QString m_failureReason; /// Specific change that caused the problem (might be 0) DocumentChangePointer m_reasonChange; bool m_success; + + private: + explicit ChangeResult(const QString& failureReason, const DocumentChangePointer& reasonChange, bool success) + : m_failureReason(failureReason) + , m_reasonChange(reasonChange) + , m_success(success) + {} }; /// Add an individual local change to this change-set. ChangeResult addChange(const DocumentChange& change); ChangeResult addChange(const DocumentChangePointer& change); ///given a file @old, rename it to the @newname ChangeResult addDocumentRenameChange(const IndexedString& oldFile, const IndexedString& newname); enum ReplacementPolicy { IgnoreFailedChange,///If this is given, all changes that could not be applied are simply ignored WarnOnFailedChange,///If this is given to applyAllChanges, a warning is given when a change could not be applied, ///but following changes are applied, and success is returned. StopOnFailedChange ///If this is given to applyAllChanges, then all replacements are reverted and an error returned on problems (default) }; ///@param policy What should be done when a change could not be applied? void setReplacementPolicy(ReplacementPolicy policy); enum FormatPolicy { NoAutoFormat, ///If this option is given, no automatic formatting is applied AutoFormatChanges, ///If this option is given, all changes are automatically reformatted using the formatter plugin for the mime type (default) AutoFormatChangesKeepIndentation ///Same as AutoFormatChanges, except that the indentation of inserted lines is kept equal }; ///@param policy How the changed text should be formatted. The default is AutoFormatChanges. void setFormatPolicy(FormatPolicy policy); enum DUChainUpdateHandling { NoUpdate, ///No updates will be scheduled SimpleUpdate ///The changed documents will be added to the background parser, plus all documents that are currently open and recursively import those documents (default) //FullUpdate ///All documents in all open projects that recursively import any of the changed documents will be updated }; ///@param policy Whether a duchain update should be triggered for all affected documents void setUpdateHandling(DUChainUpdateHandling policy); enum ActivationPolicy { Activate, ///The affected files will be activated DoNotActivate ///The affected files will not be activated (default) }; ///@param policy Whether the affected documents should be activated when the change is applied void setActivationPolicy(ActivationPolicy policy); /// Apply all the changes registered in this changeset to the actual files ChangeResult applyAllChanges(); private: DocumentChangeSetPrivate * d; }; } #endif diff --git a/language/codegen/templatesmodel.cpp b/language/codegen/templatesmodel.cpp index 2e6d7463e..6c2a60bba 100644 --- a/language/codegen/templatesmodel.cpp +++ b/language/codegen/templatesmodel.cpp @@ -1,431 +1,431 @@ /* This file is part of KDevelop Copyright 2007 Alexander Dymo Copyright 2012 Miha Čančula This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "templatesmodel.h" #include "util/debug.h" #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; class KDevelop::TemplatesModelPrivate { public: - TemplatesModelPrivate(const QString& typePrefix); + explicit TemplatesModelPrivate(const QString& typePrefix); QString typePrefix; QStringList searchPaths; QMap templateItems; /** * Extracts description files from all available template archives and saves them to a location * determined by descriptionResourceSuffix(). **/ void extractTemplateDescriptions(); /** * Creates a model item for the template @p name in category @p category * * @param name the name of the new template * @param category the category of the new template * @param parent the parent item * @return the created item **/ QStandardItem *createItem(const QString& name, const QString& category, QStandardItem* parent); enum ResourceType { Description, Template, Preview }; QString resourceFilter(ResourceType type, const QString &suffix = QString()) { QString filter = typePrefix; switch(type) { case Description: filter += QLatin1String("template_descriptions/"); break; case Template: filter += QLatin1String("templates/"); break; case Preview: filter += QLatin1String("template_previews/"); break; } return filter + suffix; } }; TemplatesModelPrivate::TemplatesModelPrivate(const QString& _typePrefix) : typePrefix(_typePrefix) { if (!typePrefix.endsWith('/')) { typePrefix.append('/'); } } TemplatesModel::TemplatesModel(const QString& typePrefix, QObject* parent) : QStandardItemModel(parent) , d(new TemplatesModelPrivate(typePrefix)) { const QStringList dataPaths = {QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)}; foreach(const QString& dataPath, dataPaths) { addDataPath(dataPath); } } TemplatesModel::~TemplatesModel() { delete d; } void TemplatesModel::refresh() { clear(); d->templateItems.clear(); d->templateItems[QString()] = invisibleRootItem(); d->extractTemplateDescriptions(); QStringList templateArchives; foreach(const QString& archivePath, d->searchPaths) { const QStringList files = QDir(archivePath).entryList(QDir::Files); foreach(const QString& file, files) { templateArchives.append(archivePath + file); } } QStringList templateDescriptions; const QStringList templatePaths = {QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) +'/'+ d->resourceFilter(TemplatesModelPrivate::Description)}; foreach(const QString& templateDescription, templatePaths) { const QStringList files = QDir(templateDescription).entryList(QDir::Files); foreach(const QString& file, files) { templateDescriptions.append(templateDescription + file); } } foreach (const QString &templateDescription, templateDescriptions) { QFileInfo fi(templateDescription); bool archiveFound = false; foreach( const QString& templateArchive, templateArchives ) { if( QFileInfo(templateArchive).baseName() == fi.baseName() ) { archiveFound = true; KConfig templateConfig(templateDescription); KConfigGroup general(&templateConfig, "General"); QString name = general.readEntry("Name"); QString category = general.readEntry("Category"); QString comment = general.readEntry("Comment"); QStandardItem *templateItem = d->createItem(name, category, invisibleRootItem()); templateItem->setData(templateDescription, DescriptionFileRole); templateItem->setData(templateArchive, ArchiveFileRole); templateItem->setData(comment, CommentRole); if (general.hasKey("Icon")) { QString icon = QStandardPaths::locate(QStandardPaths::GenericDataLocation, d->resourceFilter(TemplatesModelPrivate::Preview, general.readEntry("Icon"))); if (QFile::exists(icon)) { templateItem->setData(icon, IconNameRole); } } } } if (!archiveFound) { // Template file doesn't exist anymore, so remove the description // saves us the extra lookups for templateExists on the next run QFile(templateDescription).remove(); } } } QStandardItem *TemplatesModelPrivate::createItem(const QString& name, const QString& category, QStandardItem* parent) { QStringList path = category.split('/'); QStringList currentPath; foreach (const QString& entry, path) { currentPath << entry; if (!templateItems.contains(currentPath.join(QLatin1Char('/')))) { QStandardItem *item = new QStandardItem(entry); item->setEditable(false); parent->appendRow(item); templateItems[currentPath.join(QLatin1Char('/'))] = item; parent = item; } else { parent = templateItems[currentPath.join(QLatin1Char('/'))]; } } QStandardItem *templateItem = new QStandardItem(name); templateItem->setEditable(false); parent->appendRow(templateItem); return templateItem; } void TemplatesModelPrivate::extractTemplateDescriptions() { QStringList templateArchives; searchPaths << QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, resourceFilter(Template), QStandardPaths::LocateDirectory); searchPaths.removeDuplicates(); foreach(const QString &archivePath, searchPaths) { const QStringList files = QDir(archivePath).entryList(QDir::Files); foreach(const QString& file, files) { if(file.endsWith(QLatin1String(".zip")) || file.endsWith(QLatin1String(".tar.bz2"))) { QString archfile = archivePath + file; templateArchives.append(archfile); } } } QString localDescriptionsDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) +'/'+ resourceFilter(Description); QDir dir(localDescriptionsDir); if(!dir.exists()) dir.mkpath(QStringLiteral(".")); foreach (const QString &archName, templateArchives) { qCDebug(LANGUAGE) << "processing template" << archName; QScopedPointer templateArchive; if (QFileInfo(archName).completeSuffix() == QLatin1String("zip")) { templateArchive.reset(new KZip(archName)); } else { templateArchive.reset(new KTar(archName)); } if (templateArchive->open(QIODevice::ReadOnly)) { /* * This class looks for template description files in the following order * * - "basename.kdevtemplate" * - "*.kdevtemplate" * - "basename.desktop" * - "*.desktop" * * This is done because application templates can contain .desktop files used by the application * so the kdevtemplate suffix must have priority. */ QFileInfo templateInfo(archName); const KArchiveEntry *templateEntry = templateArchive->directory()->entry(templateInfo.baseName() + ".kdevtemplate"); if (!templateEntry || !templateEntry->isFile()) { /* * First, if the .kdevtemplate file is not found by name, * we check all the files in the archive for any .kdevtemplate file * * This is needed because kde-files.org renames downloaded files */ foreach (const QString& entryName, templateArchive->directory()->entries()) { if (entryName.endsWith(QLatin1String(".kdevtemplate"))) { templateEntry = templateArchive->directory()->entry(entryName); break; } } } if (!templateEntry || !templateEntry->isFile()) { templateEntry = templateArchive->directory()->entry(templateInfo.baseName() + ".desktop"); } if (!templateEntry || !templateEntry->isFile()) { foreach (const QString& entryName, templateArchive->directory()->entries()) { if (entryName.endsWith(QLatin1String(".desktop"))) { templateEntry = templateArchive->directory()->entry(entryName); break; } } } if (!templateEntry || !templateEntry->isFile()) { qCDebug(LANGUAGE) << "template" << archName << "does not contain .kdevtemplate or .desktop file"; continue; } const KArchiveFile *templateFile = static_cast(templateEntry); qCDebug(LANGUAGE) << "copy template description to" << localDescriptionsDir; templateFile->copyTo(localDescriptionsDir); /* * Rename the extracted description * so that its basename matches the basename of the template archive */ QFileInfo descriptionInfo(localDescriptionsDir + templateEntry->name()); QString destinationName = localDescriptionsDir + templateInfo.baseName() + '.' + descriptionInfo.suffix(); QFile::rename(descriptionInfo.absoluteFilePath(), destinationName); KConfig config(destinationName); KConfigGroup group(&config, "General"); if (group.hasKey("Icon")) { const KArchiveEntry* iconEntry = templateArchive->directory()->entry(group.readEntry("Icon")); if (iconEntry && iconEntry->isFile()) { const KArchiveFile* iconFile = static_cast(iconEntry); const QString saveDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) +'/'+ resourceFilter(Preview); iconFile->copyTo(saveDir); QFileInfo iconInfo(saveDir + templateEntry->name()); QFile::rename(iconInfo.absoluteFilePath(), saveDir + templateInfo.baseName() + '.' + iconInfo.suffix()); } } } else { qCWarning(LANGUAGE) << "could not open template" << archName; } } } QModelIndexList TemplatesModel::templateIndexes(const QString& fileName) const { QFileInfo info(fileName); QString description = QStandardPaths::locate(QStandardPaths::GenericDataLocation, d->resourceFilter(TemplatesModelPrivate::Description, info.baseName() + ".kdevtemplate")); if (description.isEmpty()) { description = QStandardPaths::locate(QStandardPaths::GenericDataLocation, d->resourceFilter(TemplatesModelPrivate::Description, info.baseName() + ".desktop")); } QModelIndexList indexes; if (!description.isEmpty()) { KConfig templateConfig(description); KConfigGroup general(&templateConfig, "General"); QStringList categories = general.readEntry("Category").split('/'); QStringList levels; foreach (const QString& category, categories) { levels << category; indexes << d->templateItems[levels.join(QString('/'))]->index(); } if (!indexes.isEmpty()) { QString name = general.readEntry("Name"); QStandardItem* categoryItem = d->templateItems[levels.join(QString('/'))]; for (int i = 0; i < categoryItem->rowCount(); ++i) { QStandardItem* templateItem = categoryItem->child(i); if (templateItem->text() == name) { indexes << templateItem->index(); break; } } } } return indexes; } QString TemplatesModel::typePrefix() const { return d->typePrefix; } void TemplatesModel::addDataPath(const QString& path) { QString realpath = path + d->resourceFilter(TemplatesModelPrivate::Template); d->searchPaths.append(realpath); } QString TemplatesModel::loadTemplateFile(const QString& fileName) { QString saveLocation = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) +'/'+ d->resourceFilter(TemplatesModelPrivate::Template); QDir dir(saveLocation); if(!dir.exists()) dir.mkpath(QStringLiteral(".")); QFileInfo info(fileName); QString destination = saveLocation + info.baseName(); QMimeType mimeType = QMimeDatabase().mimeTypeForFile(fileName); qCDebug(LANGUAGE) << "Loaded file" << fileName << "with type" << mimeType.name(); if (mimeType.name() == QLatin1String("application/x-desktop")) { qCDebug(LANGUAGE) << "Loaded desktop file" << info.absoluteFilePath() << ", compressing"; #ifdef Q_WS_WIN destination += ".zip"; KZip archive(destination); #else destination += QLatin1String(".tar.bz2"); KTar archive(destination, QStringLiteral("application/x-bzip")); #endif //Q_WS_WIN archive.open(QIODevice::WriteOnly); QDir dir(info.absoluteDir()); QDir::Filters filter = QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot; foreach (const QFileInfo& entry, dir.entryInfoList(filter)) { if (entry.isFile()) { archive.addLocalFile(entry.absoluteFilePath(), entry.fileName()); } else if (entry.isDir()) { archive.addLocalDirectory(entry.absoluteFilePath(), entry.fileName()); } } archive.close(); } else { qCDebug(LANGUAGE) << "Copying" << fileName << "to" << saveLocation; QFile::copy(fileName, saveLocation + info.fileName()); } refresh(); return destination; } diff --git a/language/duchain/abstractfunctiondeclaration.h b/language/duchain/abstractfunctiondeclaration.h index 45ed284e6..3f66aa85f 100644 --- a/language/duchain/abstractfunctiondeclaration.h +++ b/language/duchain/abstractfunctiondeclaration.h @@ -1,127 +1,127 @@ /* This file is part of KDevelop Copyright 2007 Hamish Rodda Copyright 2007-2008 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_ABSTRACTFUNCTIONDECLARATION_H #define KDEVPLATFORM_ABSTRACTFUNCTIONDECLARATION_H #include #include #include "indexedducontext.h" namespace KDevelop { class DUContext; class IndexedString; class AbstractFunctionDeclarationData { public: AbstractFunctionDeclarationData() : m_isVirtual(false), m_isInline(false), m_isExplicit(false) { } IndexedDUContext m_functionContext; bool m_isVirtual: 1; ///@todo move into ClassFunctionDeclaration(Only valid for class-functions) bool m_isInline: 1; bool m_isExplicit: 1; ///@todo move into ClassFunctionDeclaration(Only valid for class-functions) }; /** * Provides an interface to declarations which represent functions in a definition-use chain. * Don't inherit from this directly, use MergeAbstractFunctionDeclaration instead. */ class KDEVPLATFORMLANGUAGE_EXPORT AbstractFunctionDeclaration { public: virtual ~AbstractFunctionDeclaration(); enum FunctionSpecifier { VirtualSpecifier = 0x1 /**< indicates a virtual function */, InlineSpecifier = 0x2 /**< indicates a inline function */, ExplicitSpecifier = 0x4 /**< indicates a explicit function */ }; Q_DECLARE_FLAGS(FunctionSpecifiers, FunctionSpecifier) void setFunctionSpecifiers(FunctionSpecifiers specifiers); bool isInline() const; void setInline(bool isInline); ///Only used for class-member function declarations(see ClassFunctionDeclaration) bool isVirtual() const; void setVirtual(bool isVirtual); ///Only used for class-member function declarations(see ClassFunctionDeclaration) bool isExplicit() const; void setExplicit(bool isExplicit); ///Return the DUContext::Function type ducontext (the function parameter context) of this function ///Same as internalContext if the function has no definition DUContext* internalFunctionContext() const; void setInternalFunctionContext(DUContext *context); /** * Returns the default-parameters that are set. The last default-parameter matches the last * argument of the function, but the returned vector will only contain default-values for those * arguments that have one, for performance-reasons. * * So the vector may be empty or smaller than the count of function-arguments. * */ virtual const IndexedString* defaultParameters() const = 0; virtual unsigned int defaultParametersSize() const = 0; virtual void addDefaultParameter(const IndexedString& str) = 0; virtual void clearDefaultParameters() = 0; ///Returns the default parameter assigned to the given argument number. ///This is a convenience-function. IndexedString defaultParameterForArgument(int index) const; private: //Must be implemented by sub-classes to provide a pointer to the data virtual const AbstractFunctionDeclarationData* data() const = 0; virtual AbstractFunctionDeclarationData* dynamicData() = 0; }; ///Use this to merge AbstractFunctionDeclaration into the class hierarchy. Base must be the base-class ///in the hierarchy, and Data must be the Data class of the following Declaration, and must be based on AbstractFunctionDeclarationData ///and BaseData. template class MergeAbstractFunctionDeclaration : public Base, public AbstractFunctionDeclaration { public: template - MergeAbstractFunctionDeclaration(BaseData& data) : Base(data) { + explicit MergeAbstractFunctionDeclaration(BaseData& data) : Base(data) { } template MergeAbstractFunctionDeclaration(BaseData& data, const Arg2& arg2) : Base(data, arg2) { } template MergeAbstractFunctionDeclaration(BaseData& data, const Arg2& arg2, const Arg3& arg3) : Base(data, arg2, arg3) { } private: virtual const AbstractFunctionDeclarationData* data() const { return static_cast(Base::d_func()); } virtual AbstractFunctionDeclarationData* dynamicData() { return static_cast<_Data*>(Base::d_func_dynamic()); } }; } Q_DECLARE_OPERATORS_FOR_FLAGS(KDevelop::AbstractFunctionDeclaration::FunctionSpecifiers) #endif // KDEVPLATFORM_ABSTRACTFUNCTIONDECLARATION_H diff --git a/language/duchain/appendedlist.h b/language/duchain/appendedlist.h index 8ec996c9c..60f8aec5b 100644 --- a/language/duchain/appendedlist.h +++ b/language/duchain/appendedlist.h @@ -1,395 +1,395 @@ /* 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_APPENDEDLIST_H #define KDEVPLATFORM_APPENDEDLIST_H #include #include #include #include #include #include #include namespace KDevelop { class AbstractItemRepository; /** * This file contains macros and classes that can be used to conveniently implement classes that store the data of an arbitrary count * of additional lists within the same memory block directly behind the class data, in a way that one the whole data can be stored by one copy-operation * to another place, like needed in ItemRepository. These macros simplify having two versions of a class: One that has its lists attached in memory, * and one version that has them contained as a directly accessible KDevVarLengthArray. Both versions have their lists accessible through access-functions, * have a completeSize() function that computes the size of the one-block version, and a copyListsFrom(..) function which can copy the lists from one * version to the other. * * @warning Always follow these rules: * \li You must call initializeAppendedLists(bool) on construction, also in any copy-constructor, but before calling copyFrom(..). * \li The parameter to that function should be whether the lists in the items should be dynamic, and thus most times "true". * \li You must call freeAppendedLists() on destruction, our you will be leaking memory(only when dynamic) * * For each embedded list, you must use macros to define a global hash that will be used to allocate the temporary lists. * For example in @c identifier.cpp we have: * * @code * DEFINE_LIST_MEMBER_HASH(IdentifierPrivate, templateIdentifiers, uint); * @endcode * * In general, see @c identifier.cpp for an example on how to use these macros. * * @todo Document this a bit more * */ enum { DynamicAppendedListMask = 1 << 31 }; enum { DynamicAppendedListRevertMask = ~DynamicAppendedListMask }; /** * Manages a repository of items for temporary usage. The items will be allocated with an index on alloc(), * and freed on free(index). When freed, the same index will be re-used for a later allocation, thus no real allocations * will be happening in most cases. * The returned indices will always be ored with DynamicAppendedListMask. * */ template class TemporaryDataManager { public: - TemporaryDataManager(const QByteArray& id = {}) + explicit TemporaryDataManager(const QByteArray& id = {}) : m_id(id) { int first = alloc(); //Allocate the zero item, just to reserve that index Q_ASSERT(first == (int)DynamicAppendedListMask); Q_UNUSED(first); } ~TemporaryDataManager() { free(DynamicAppendedListMask); //Free the zero index, so we don't get wrong warnings int cnt = usedItemCount(); if(cnt) //Don't use qDebug, because that may not work during destruction std::cout << m_id.constData() << " There were items left on destruction: " << usedItemCount() << "\n"; for (int a = 0; a < m_items.size(); ++a) delete m_items.at(a); } inline T& getItem(int index) { //For performance reasons this function does not lock the mutex, it's called too often and must be //extremely fast. There is special measures in alloc() to make this safe. Q_ASSERT(index & DynamicAppendedListMask); return *m_items.at(index & KDevelop::DynamicAppendedListRevertMask); } ///Allocates an item index, which from now on you can get using getItem, until you call free(..) on the index. ///The returned item is not initialized and may contain random older content, so you should clear it after getting it for the first time int alloc() { if(threadSafe) m_mutex.lock(); int ret; if(!m_freeIndicesWithData.isEmpty()) { ret = m_freeIndicesWithData.pop(); }else if(!m_freeIndices.isEmpty()) { ret = m_freeIndices.pop(); Q_ASSERT(!m_items.at(ret)); m_items[ret] = new T; }else{ if(m_items.size() >= m_items.capacity()) { //We need to re-allocate const int newItemsSize = m_items.capacity() + 20 + (m_items.capacity()/3); const QVector oldItems = m_items; // backup m_items.reserve(newItemsSize); // detach, grow container //The only function that does not lock the mutex is getItem(..), because that function must be very efficient. //Since it's only a few instructions from the moment m_items is read to the moment it's used, //deleting the old data after a few seconds should be safe. m_deleteLater.append(qMakePair(time(nullptr), oldItems)); //We do this in this place so it isn't called too often. The result is that we will always have some additional data around. //However the index itself should anyway not consume too much data. if(!m_deleteLater.isEmpty()) { while(!m_deleteLater.isEmpty()) { //We delete after 5 seconds if(time(nullptr) - m_deleteLater.first().first > 5) { m_deleteLater.removeFirst(); }else{ break; } } } } ret = m_items.size(); m_items.append(new T); Q_ASSERT(m_items.size() <= m_items.capacity()); } if(threadSafe) m_mutex.unlock(); Q_ASSERT(!(ret & DynamicAppendedListMask)); return ret | DynamicAppendedListMask; } void free(int index) { Q_ASSERT(index & DynamicAppendedListMask); index &= KDevelop::DynamicAppendedListRevertMask; if(threadSafe) m_mutex.lock(); freeItem(m_items.at(index)); m_freeIndicesWithData.push(index); //Hold the amount of free indices with data between 100 and 200 if(m_freeIndicesWithData.size() > 200) { for(int a = 0; a < 100; ++a) { int deleteIndexData = m_freeIndicesWithData.pop(); delete m_items.at(deleteIndexData); m_items[deleteIndexData] = nullptr; m_freeIndices.push(deleteIndexData); } } if(threadSafe) m_mutex.unlock(); } int usedItemCount() const { int ret = 0; for(int a = 0; a < m_items.size(); ++a) if(m_items.at(a)) ++ret; return ret - m_freeIndicesWithData.size(); } private: //To save some memory, clear the lists void freeItem(T* item) { item->clear(); ///@todo make this a template specialization that only does this for containers } QVector m_items; /// note: non-shared, ref count of 1 when accessed with non-const methods => no detach Stack m_freeIndicesWithData; Stack m_freeIndices; QMutex m_mutex; QByteArray m_id; QList > > m_deleteLater; }; ///Foreach macro that takes a container and a function-name, and will iterate through the vector returned by that function, using the length returned by the function-name with "Size" appended. //This might be a little slow #define FOREACH_FUNCTION(item, container) \ for(uint a__ = 0, mustDo__ = 1, containerSize = container ## Size(); a__ < containerSize; ++a__) \ if((mustDo__ == 0 || mustDo__ == 1) && (mustDo__ = 2)) \ for(item(container()[a__]); mustDo__; mustDo__ = 0) #define DEFINE_LIST_MEMBER_HASH(container, member, type) \ typedef KDevelop::TemporaryDataManager > temporaryHash ## container ## member ## Type; \ Q_GLOBAL_STATIC_WITH_ARGS(temporaryHash ## container ## member ## Type, temporaryHash ## container ## member ## Static, ( #container "::" #member )) \ temporaryHash ## container ## member ## Type& temporaryHash ## container ## member() { \ return *temporaryHash ## container ## member ## Static; \ } #define DECLARE_LIST_MEMBER_HASH(container, member, type) \ KDevelop::TemporaryDataManager >& temporaryHash ## container ## member(); ///This implements the interfaces so this container can be used as a predecessor for classes with appended lists. ///You should do this within the abstract base class that opens a tree of classes that can have appended lists, ///so each class that uses them, can also give its predecessor to START_APPENDE_LISTS, to increase flexibility. ///This creates a boolean entry that is initialized when initializeAppendedLists is called. ///You can call appendedListsDynamic() to find out whether the item is marked as dynamic. ///When this item is used, the same rules have to be followed as for a class with appended lists: You have to call ///initializeAppendedLists(...) and freeAppendedLists(..) ///Also, when you use this, you have to implement a uint classSize() function, that returns the size of the class including derived classes, ///but not including the dynamic data. Optionally you can implement a static bool appendedListDynamicDefault() function, that returns the default-value for the "dynamic" parameter. ///to initializeAppendedLists. #define APPENDED_LISTS_STUB(container) \ bool m_dynamic : 1; \ unsigned int offsetBehindLastList() const { return 0; } \ uint dynamicSize() const { return classSize(); } \ template bool listsEqual(const T& /*rhs*/) const { return true; } \ template void copyAllFrom(const T& /*rhs*/) const { } \ void initializeAppendedLists(bool dynamic = appendedListDynamicDefault()) { m_dynamic = dynamic; } \ void freeAppendedLists() { } \ bool appendedListsDynamic() const { return m_dynamic; } ///use this if the class does not have a base class that also uses appended lists #define START_APPENDED_LISTS(container) \ unsigned int offsetBehindBase() const { return 0; } \ void freeDynamicData() { freeAppendedLists(); } ///Use this if one of the base-classes of the container also has the appended lists interfaces implemented. ///To reduce the probability of future problems, you should give the direct base class this one inherits from. ///@note: Multiple inheritance is not supported, however it will work ok if only one of the base-classes uses appended lists. #define START_APPENDED_LISTS_BASE(container, base) \ unsigned int offsetBehindBase() const { return base :: offsetBehindLastList(); } \ void freeDynamicData() { freeAppendedLists(); base::freeDynamicData(); } #define APPENDED_LIST_COMMON(container, type, name) \ uint name ## Data; \ unsigned int name ## Size() const { if((name ## Data & KDevelop::DynamicAppendedListRevertMask) == 0) return 0; if(!appendedListsDynamic()) return name ## Data; else return temporaryHash ## container ## name().getItem(name ## Data).size(); } \ KDevVarLengthArray& name ## List() { name ## NeedDynamicList(); return temporaryHash ## container ## name().getItem(name ## Data); }\ template bool name ## Equals(const T& rhs) const { unsigned int size = name ## Size(); if(size != rhs.name ## Size()) return false; for(uint a = 0; a < size; ++a) {if(!(name()[a] == rhs.name()[a])) return false;} return true; } \ template void name ## CopyFrom( const T& rhs ) { \ if(rhs.name ## Size() == 0 && (name ## Data & KDevelop::DynamicAppendedListRevertMask) == 0) return; \ if(appendedListsDynamic()) { \ name ## NeedDynamicList(); \ KDevVarLengthArray& item( temporaryHash ## container ## name().getItem(name ## Data) ); \ item.clear(); \ const type* otherCurr = rhs.name(); \ const type* otherEnd = otherCurr + rhs.name ## Size(); \ for(; otherCurr < otherEnd; ++otherCurr) \ item.append(*otherCurr); \ }else{ \ Q_ASSERT(name ## Data == 0); /* It is dangerous to overwrite the contents of non-dynamic lists(Most probably a mistake) */ \ name ## Data = rhs.name ## Size(); \ type* curr = const_cast(name()); type* end = curr + name ## Size(); \ const type* otherCurr = rhs.name(); \ for(; curr < end; ++curr, ++otherCurr) \ new (curr) type(*otherCurr); /* Call the copy constructors */ \ }\ } \ void name ## NeedDynamicList() { \ Q_ASSERT(appendedListsDynamic()); \ if((name ## Data & KDevelop::DynamicAppendedListRevertMask) == 0) {\ name ## Data = temporaryHash ## container ## name().alloc();\ Q_ASSERT(temporaryHash ## container ## name().getItem(name ## Data).isEmpty()); \ } \ } \ void name ## Initialize(bool dynamic) { name ## Data = (dynamic ? KDevelop::DynamicAppendedListMask : 0); } \ void name ## Free() { \ if(appendedListsDynamic()) { \ if(name ## Data & KDevelop::DynamicAppendedListRevertMask) temporaryHash ## container ## name().free(name ## Data);\ } else { \ type* curr = const_cast(name()); \ type* end = curr + name ## Size(); \ for(; curr < end; ++curr) curr->~type(); /*call destructors*/ \ } \ } \ ///@todo Make these things a bit faster(less recursion) #define APPENDED_LIST_FIRST(container, type, name) \ APPENDED_LIST_COMMON(container, type, name) \ const type* name() const { \ if((name ## Data & KDevelop::DynamicAppendedListRevertMask) == 0) return nullptr; \ if(!appendedListsDynamic()) return reinterpret_cast(reinterpret_cast(this) + classSize() + offsetBehindBase()); \ else return temporaryHash ## container ## name().getItem(name ## Data).data(); \ } \ unsigned int name ## OffsetBehind() const { return name ## Size() * sizeof(type) + offsetBehindBase(); } \ template bool name ## ListChainEquals( const T& rhs ) const { return name ## Equals(rhs); } \ template void name ## CopyAllFrom( const T& rhs ) { name ## CopyFrom(rhs); } \ void name ## InitializeChain(bool dynamic) { name ## Initialize(dynamic); } \ void name ## FreeChain() { name ## Free(); } #define APPENDED_LIST(container, type, name, predecessor) \ APPENDED_LIST_COMMON(container, type, name) \ const type* name() const {\ if((name ## Data & KDevelop::DynamicAppendedListRevertMask) == 0) return nullptr; \ if(!appendedListsDynamic()) return reinterpret_cast(reinterpret_cast(this) + classSize() + predecessor ## OffsetBehind()); \ else return temporaryHash ## container ## name().getItem(name ## Data).data(); \ } \ unsigned int name ## OffsetBehind() const { return name ## Size() * sizeof(type) + predecessor ## OffsetBehind(); } \ template bool name ## ListChainEquals( const T& rhs ) const { return name ## Equals(rhs) && predecessor ## ListChainEquals(rhs); } \ template void name ## CopyAllFrom( const T& rhs ) { predecessor ## CopyAllFrom(rhs); name ## CopyFrom(rhs); } \ void name ## InitializeChain(bool dynamic) { name ## Initialize(dynamic); predecessor ## InitializeChain(dynamic); } \ void name ## FreeChain() { name ## Free(); predecessor ## FreeChain(); } #define END_APPENDED_LISTS(container, predecessor) \ /* Returns the size of the object containing the appended lists, including them */ \ unsigned int completeSize() const { return classSize() + predecessor ## OffsetBehind(); } \ /* Compares all local appended lists(not from base classes) and returns true if they are equal */ \ template bool listsEqual(const T& rhs) const { return predecessor ## ListChainEquals(rhs); } \ /* Copies all the local appended lists(not from base classes) from the given item.*/ \ template void copyListsFrom(const T& rhs) { return predecessor ## CopyAllFrom(rhs); } \ void initializeAppendedLists(bool dynamic = appendedListDynamicDefault()) { \ predecessor ## Data = (dynamic ? KDevelop::DynamicAppendedListMask : 0); \ predecessor ## InitializeChain(dynamic); \ } \ void freeAppendedLists() { predecessor ## FreeChain(); } \ bool appendedListsDynamic() const { return predecessor ## Data & KDevelop::DynamicAppendedListMask; } \ unsigned int offsetBehindLastList() const { return predecessor ## OffsetBehind(); } \ uint dynamicSize() const { return offsetBehindLastList() + classSize(); } /** * This is a class that allows you easily putting instances of your class into an ItemRepository as seen in itemrepository.h. * All your class needs to do is: * - Be implemented using the APPENDED_LIST macros. * - Have a real copy-constructor that additionally takes a "bool dynamic = true" parameter, which should be given to initializeAppendedLists * - Except for these appended lists, only contain directly copyable data like indices(no pointers, no virtual functions) * - Implement operator==(..) which should compare everything, including the lists. @warning The default operator will not work! * - Implement a hash() function. The hash should equal for two instances when operator==(..) returns true. * - Should be completely functional without a constructor called, only the data copied * - Implement a "bool persistent() const" function, that should check the reference-count or other information to decide whether the item should stay in the repository * If those conditions are fulfilled, the data can easily be put into a repository using this request class. * */ template class AppendedListItemRequest { public: AppendedListItemRequest(const Type& item) : m_item(item) { } enum { AverageSize = sizeof(Type) + averageAppendedBytes }; unsigned int hash() const { return m_item.hash(); } uint itemSize() const { return m_item.dynamicSize(); } void createItem(Type* item) const { new (item) Type(m_item, false); } static void destroy(Type* item, KDevelop::AbstractItemRepository&) { item->~Type(); } static bool persistent(const Type* item) { return item->persistent(); } bool equals(const Type* item) const { return m_item == *item; } const Type& m_item; }; } ///This function is outside of the namespace, so it can always be found. It's used as default-parameter to initializeAppendedLists(..), ///and you can for example implement a function called like this in your local class hierarchy to override this default. inline bool appendedListDynamicDefault() { return true; } #endif diff --git a/language/duchain/duchain.cpp b/language/duchain/duchain.cpp index 785636067..ec801c27b 100644 --- a/language/duchain/duchain.cpp +++ b/language/duchain/duchain.cpp @@ -1,1751 +1,1751 @@ /* 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 "../interfaces/ilanguagesupport.h" #include "../interfaces/icodehighlighting.h" #include "../backgroundparser/backgroundparser.h" #include "util/debug.h" #include "language-features.h" #include "topducontext.h" #include "topducontextdata.h" #include "topducontextdynamicdata.h" #include "parsingenvironment.h" #include "declaration.h" #include "definitions.h" #include "duchainutils.h" #include "use.h" #include "uses.h" #include "abstractfunctiondeclaration.h" #include "duchainregister.h" #include "persistentsymboltable.h" #include "serialization/itemrepository.h" #include "waitforupdate.h" #include "importers.h" #if HAVE_MALLOC_TRIM #include "malloc.h" #endif namespace { //Additional "soft" cleanup steps that are done before the actual cleanup. //During "soft" cleanup, the consistency is not guaranteed. The repository is //marked to be updating during soft cleanup, so if kdevelop crashes, it will be cleared. //The big advantage of the soft cleanup steps is, that the duchain is always only locked for //short times, which leads to no lockup in the UI. const int SOFT_CLEANUP_STEPS = 1; const uint cleanupEverySeconds = 200; ///Approximate maximum count of top-contexts that are checked during final cleanup const uint maxFinalCleanupCheckContexts = 2000; const uint minimumFinalCleanupCheckContextsPercentage = 10; //Check at least n% of all top-contexts during cleanup //Set to true as soon as the duchain is deleted } namespace KDevelop { bool DUChain::m_deleted = false; ///Must be locked through KDevelop::SpinLock before using chainsByIndex ///This lock should be locked only for very short times QMutex DUChain::chainsByIndexLock; std::vector DUChain::chainsByIndex; //This thing is not actually used, but it's needed for compiling DEFINE_LIST_MEMBER_HASH(EnvironmentInformationListItem, items, uint) //An entry for the item-repository that holds some meta-data. Behind this entry, the actual ParsingEnvironmentFileData is stored. class EnvironmentInformationItem { public: EnvironmentInformationItem(uint topContext, uint size) : m_topContext(topContext), m_size(size) { } ~EnvironmentInformationItem() { } unsigned int hash() const { return m_topContext; } unsigned int itemSize() const { return sizeof(*this) + m_size; } uint m_topContext; uint m_size;//Size of the data behind, that holds the actual item }; struct ItemRepositoryIndexHash { uint operator()(unsigned int __x) const { return 173*(__x>>2) + 11 * (__x >> 16); } }; class EnvironmentInformationRequest { public: ///This constructor should only be used for lookup EnvironmentInformationRequest(uint topContextIndex) : m_file(nullptr), m_index(topContextIndex) { } EnvironmentInformationRequest(const ParsingEnvironmentFile* file) : m_file(file), m_index(file->indexedTopContext().index()) { } enum { AverageSize = 32 //This should be the approximate average size of an Item }; unsigned int hash() const { return m_index; } uint itemSize() const { return sizeof(EnvironmentInformationItem) + DUChainItemSystem::self().dynamicSize(*m_file->d_func()); } void createItem(EnvironmentInformationItem* item) const { new (item) EnvironmentInformationItem(m_index, DUChainItemSystem::self().dynamicSize(*m_file->d_func())); Q_ASSERT(m_file->d_func()->m_dynamic); DUChainBaseData* data = reinterpret_cast(reinterpret_cast(item) + sizeof(EnvironmentInformationItem)); DUChainItemSystem::self().copy(*m_file->d_func(), *data, true); Q_ASSERT(data->m_range == m_file->d_func()->m_range); Q_ASSERT(data->classId == m_file->d_func()->classId); Q_ASSERT(data->m_dynamic == false); } static void destroy(EnvironmentInformationItem* item, KDevelop::AbstractItemRepository&) { item->~EnvironmentInformationItem(); //We don't need to call the destructor, because that's done in DUChainBase::makeDynamic() //We just need to make sure that every environment-file is dynamic when it's deleted // DUChainItemSystem::self().callDestructor((DUChainBaseData*)(((char*)item) + sizeof(EnvironmentInformationItem))); } static bool persistent(const EnvironmentInformationItem* ) { //Cleanup done separately return true; } bool equals(const EnvironmentInformationItem* item) const { return m_index == item->m_topContext; } const ParsingEnvironmentFile* m_file; uint m_index; }; ///A list of environment-information/top-contexts mapped to a file-name class EnvironmentInformationListItem { public: EnvironmentInformationListItem() { initializeAppendedLists(true); } EnvironmentInformationListItem(const EnvironmentInformationListItem& rhs, bool dynamic = true) { initializeAppendedLists(dynamic); m_file = rhs.m_file; copyListsFrom(rhs); } ~EnvironmentInformationListItem() { freeAppendedLists(); } unsigned int hash() const { //We only compare the declaration. This allows us implementing a map, although the item-repository //originally represents a set. return m_file.hash(); } unsigned short int itemSize() const { return dynamicSize(); } IndexedString m_file; uint classSize() const { return sizeof(*this); } START_APPENDED_LISTS(EnvironmentInformationListItem); ///Contains the index of each contained environment-item APPENDED_LIST_FIRST(EnvironmentInformationListItem, uint, items); END_APPENDED_LISTS(EnvironmentInformationListItem, items); }; class EnvironmentInformationListRequest { public: ///This constructor should only be used for lookup EnvironmentInformationListRequest(const IndexedString& file) : m_file(file), m_item(nullptr) { } ///This is used to actually construct the information in the repository EnvironmentInformationListRequest(const IndexedString& file, const EnvironmentInformationListItem& item) : m_file(file), m_item(&item) { } enum { AverageSize = 160 //This should be the approximate average size of an Item }; unsigned int hash() const { return m_file.hash(); } uint itemSize() const { return m_item->itemSize(); } void createItem(EnvironmentInformationListItem* item) const { Q_ASSERT(m_item->m_file == m_file); new (item) EnvironmentInformationListItem(*m_item, false); } static void destroy(EnvironmentInformationListItem* item, KDevelop::AbstractItemRepository&) { item->~EnvironmentInformationListItem(); } static bool persistent(const EnvironmentInformationListItem*) { //Cleanup is done separately return true; } bool equals(const EnvironmentInformationListItem* item) const { return m_file == item->m_file; } IndexedString m_file; const EnvironmentInformationListItem* m_item; }; class DUChainPrivate; static DUChainPrivate* duChainPrivateSelf = nullptr; class DUChainPrivate { class CleanupThread : public QThread { public: - CleanupThread(DUChainPrivate* data) : m_stopRunning(false), m_data(data) { + explicit CleanupThread(DUChainPrivate* data) : m_stopRunning(false), m_data(data) { } void stopThread() { { QMutexLocker lock(&m_waitMutex); m_stopRunning = true; m_wait.wakeAll(); //Wakes the thread up, so it notices it should exit } wait(); } private: void run() override { while(1) { for(uint s = 0; s < cleanupEverySeconds; ++s) { if(m_stopRunning) break; QMutexLocker lock(&m_waitMutex); m_wait.wait(&m_waitMutex, 1000); } if(m_stopRunning) break; //Just to make sure the cache is cleared periodically ModificationRevisionSet::clearCache(); m_data->doMoreCleanup(SOFT_CLEANUP_STEPS, TryLock); if(m_stopRunning) break; } } bool m_stopRunning; QWaitCondition m_wait; QMutex m_waitMutex; DUChainPrivate* m_data; }; public: DUChainPrivate() : m_chainsMutex(QMutex::Recursive), m_cleanupMutex(QMutex::Recursive), instance(nullptr), m_cleanupDisabled(false), m_destroyed(false), m_environmentListInfo(QStringLiteral("Environment Lists")), m_environmentInfo(QStringLiteral("Environment Information")) { #if defined(TEST_NO_CLEANUP) m_cleanupDisabled = true; #endif duChainPrivateSelf = this; qRegisterMetaType("KDevelop::DUChainBasePointer"); qRegisterMetaType("KDevelop::DUContextPointer"); qRegisterMetaType("KDevelop::TopDUContextPointer"); qRegisterMetaType("KDevelop::DeclarationPointer"); qRegisterMetaType("KDevelop::FunctionDeclarationPointer"); qRegisterMetaType("KDevelop::IndexedString"); qRegisterMetaType("KDevelop::IndexedTopDUContext"); qRegisterMetaType("KDevelop::ReferencedTopDUContext"); instance = new DUChain(); m_cleanup = new CleanupThread(this); m_cleanup->start(); DUChain::m_deleted = false; ///Loading of some static data: { ///@todo Solve this more duchain-like QFile f(globalItemRepositoryRegistry().path() + "/parsing_environment_data"); bool opened = f.open(QIODevice::ReadOnly); ///FIXME: ugh, so ugly ParsingEnvironmentFile::m_staticData = reinterpret_cast( new char[sizeof(StaticParsingEnvironmentData)]); if(opened) { qCDebug(LANGUAGE) << "reading parsing-environment static data"; //Read f.read((char*)ParsingEnvironmentFile::m_staticData, sizeof(StaticParsingEnvironmentData)); }else{ qCDebug(LANGUAGE) << "creating new parsing-environment static data"; //Initialize new (ParsingEnvironmentFile::m_staticData) StaticParsingEnvironmentData(); } } ///Read in the list of available top-context indices { QFile f(globalItemRepositoryRegistry().path() + "/available_top_context_indices"); bool opened = f.open(QIODevice::ReadOnly); if(opened) { Q_ASSERT( (f.size() % sizeof(uint)) == 0); m_availableTopContextIndices.resize(f.size()/(int)sizeof(uint)); f.read((char*)m_availableTopContextIndices.data(), f.size()); } } } ~DUChainPrivate() { qCDebug(LANGUAGE) << "Destroying"; DUChain::m_deleted = true; m_cleanup->stopThread(); delete m_cleanup; delete instance; } void clear() { if(!m_cleanupDisabled) doMoreCleanup(); DUChainWriteLocker writeLock(DUChain::lock()); QMutexLocker l(&m_chainsMutex); foreach(TopDUContext* top, m_chainsByUrl) removeDocumentChainFromMemory(top); m_indexEnvironmentInformations.clear(); m_fileEnvironmentInformations.clear(); Q_ASSERT(m_fileEnvironmentInformations.isEmpty()); Q_ASSERT(m_chainsByUrl.isEmpty()); } ///DUChain must be write-locked ///Also removes from the environment-manager if the top-context is not on disk void removeDocumentChainFromMemory(TopDUContext* context) { QMutexLocker l(&m_chainsMutex); { QMutexLocker l(&m_referenceCountsMutex); if(m_referenceCounts.contains(context)) { //This happens during shutdown, since everything is unloaded qCDebug(LANGUAGE) << "removed a top-context that was reference-counted:" << context->url().str() << context->ownIndex(); m_referenceCounts.remove(context); } } uint index = context->ownIndex(); // qCDebug(LANGUAGE) << "duchain: removing document" << context->url().str(); Q_ASSERT(hasChainForIndex(index)); Q_ASSERT(m_chainsByUrl.contains(context->url(), context)); m_chainsByUrl.remove(context->url(), context); if(!context->isOnDisk()) instance->removeFromEnvironmentManager(context); l.unlock(); //DUChain is write-locked, so we can do whatever we want on the top-context, including deleting it context->deleteSelf(); l.relock(); Q_ASSERT(hasChainForIndex(index)); QMutexLocker lock(&DUChain::chainsByIndexLock); DUChain::chainsByIndex[index] = nullptr; } ///Must be locked before accessing content of this class. ///Should be released during expensive disk-operations and such. QMutex m_chainsMutex; QMutex m_cleanupMutex; CleanupThread* m_cleanup; DUChain* instance; DUChainLock lock; QMultiMap m_chainsByUrl; //Must be locked before accessing m_referenceCounts QMutex m_referenceCountsMutex; QHash m_referenceCounts; Definitions m_definitions; Uses m_uses; QSet m_loading; bool m_cleanupDisabled; //List of available top-context indices, protected by m_chainsMutex QVector m_availableTopContextIndices; ///Used to keep alive the top-context that belong to documents loaded in the editor QSet m_openDocumentContexts; bool m_destroyed; ///The item must not be stored yet ///m_chainsMutex should not be locked, since this can trigger I/O void addEnvironmentInformation(ParsingEnvironmentFilePointer info) { Q_ASSERT(!findInformation(info->indexedTopContext().index())); Q_ASSERT(m_environmentInfo.findIndex(info->indexedTopContext().index()) == 0); QMutexLocker lock(&m_chainsMutex); m_fileEnvironmentInformations.insert(info->url(), info); m_indexEnvironmentInformations.insert(info->indexedTopContext().index(), info); Q_ASSERT(info->d_func()->classId); } ///The item must be managed currently ///m_chainsMutex does not need to be locked void removeEnvironmentInformation(ParsingEnvironmentFilePointer info) { info->makeDynamic(); //By doing this, we make sure the data is actually being destroyed in the destructor bool removed = false; bool removed2 = false; { QMutexLocker lock(&m_chainsMutex); removed = m_fileEnvironmentInformations.remove(info->url(), info); removed2 = m_indexEnvironmentInformations.remove(info->indexedTopContext().index()); } { //Remove it from the environment information lists if it was there QMutexLocker lock(m_environmentListInfo.mutex()); uint index = m_environmentListInfo.findIndex(info->url()); if(index) { EnvironmentInformationListItem item(*m_environmentListInfo.itemFromIndex(index)); if(item.itemsList().removeOne(info->indexedTopContext().index())) { m_environmentListInfo.deleteItem(index); if(!item.itemsList().isEmpty()) m_environmentListInfo.index(EnvironmentInformationListRequest(info->url(), item)); } } } QMutexLocker lock(m_environmentInfo.mutex()); uint index = m_environmentInfo.findIndex(info->indexedTopContext().index()); if(index) { m_environmentInfo.deleteItem(index); } Q_UNUSED(removed); Q_UNUSED(removed2); Q_ASSERT(index || (removed && removed2)); Q_ASSERT(!findInformation(info->indexedTopContext().index())); } ///m_chainsMutex should _not_ be locked, because this may trigger I/O QList getEnvironmentInformation(IndexedString url) { QList ret; uint listIndex = m_environmentListInfo.findIndex(url); if(listIndex) { KDevVarLengthArray topContextIndices; { //First store all the possible intices into the KDevVarLengthArray, so we can unlock the mutex before processing them. QMutexLocker lock(m_environmentListInfo.mutex()); //Lock the mutex to make sure the item isn't changed while it's being iterated const EnvironmentInformationListItem* item = m_environmentListInfo.itemFromIndex(listIndex); FOREACH_FUNCTION(uint topContextIndex, item->items) topContextIndices << topContextIndex; } //Process the indices in a separate step after copying them from the array, so we don't need m_environmentListInfo.mutex locked, //and can call loadInformation(..) safely, which else might lead to a deadlock. foreach (uint topContextIndex, topContextIndices) { QExplicitlySharedDataPointer< ParsingEnvironmentFile > p = ParsingEnvironmentFilePointer(loadInformation(topContextIndex)); if(p) { ret << p; }else{ qCDebug(LANGUAGE) << "Failed to load enviromment-information for" << TopDUContextDynamicData::loadUrl(topContextIndex).str(); } } } QMutexLocker l(&m_chainsMutex); //Add those information that have not been added to the stored lists yet foreach(const ParsingEnvironmentFilePointer& file, m_fileEnvironmentInformations.values(url)) if(!ret.contains(file)) ret << file; return ret; } ///Must be called _without_ the chainsByIndex spin-lock locked static inline bool hasChainForIndex(uint index) { QMutexLocker lock(&DUChain::chainsByIndexLock); return (DUChain::chainsByIndex.size() > index) && DUChain::chainsByIndex[index]; } ///Must be called _without_ the chainsByIndex spin-lock locked. Returns the top-context if it is loaded. static inline TopDUContext* readChainForIndex(uint index) { QMutexLocker lock(&DUChain::chainsByIndexLock); if(DUChain::chainsByIndex.size() > index) return DUChain::chainsByIndex[index]; else return nullptr; } ///Makes sure that the chain with the given index is loaded ///@warning m_chainsMutex must NOT be locked when this is called void loadChain(uint index, QSet& loaded) { QMutexLocker l(&m_chainsMutex); if(!hasChainForIndex(index)) { if(m_loading.contains(index)) { //It's probably being loaded by another thread. So wait until the load is ready while(m_loading.contains(index)) { l.unlock(); qCDebug(LANGUAGE) << "waiting for another thread to load index" << index; QThread::usleep(50000); l.relock(); } loaded.insert(index); return; } m_loading.insert(index); loaded.insert(index); l.unlock(); qCDebug(LANGUAGE) << "loading top-context" << index; TopDUContext* chain = TopDUContextDynamicData::load(index); if(chain) { chain->setParsingEnvironmentFile(loadInformation(chain->ownIndex())); if(!chain->usingImportsCache()) { //Eventually also load all the imported chains, so the import-structure is built foreach(const DUContext::Import &import, chain->DUContext::importedParentContexts()) { if(!loaded.contains(import.topContextIndex())) { loadChain(import.topContextIndex(), loaded); } } } chain->rebuildDynamicImportStructure(); chain->setInDuChain(true); instance->addDocumentChain(chain); } l.relock(); m_loading.remove(index); } } ///Stores all environment-information ///Also makes sure that all information that stays is referenced, so it stays alive. ///@param atomic If this is false, the write-lock will be released time by time void storeAllInformation(bool atomic, DUChainWriteLocker& locker) { uint cnt = 0; QList urls; { QMutexLocker lock(&m_chainsMutex); urls += m_fileEnvironmentInformations.keys(); } foreach(const IndexedString &url, urls) { QList check; { QMutexLocker lock(&m_chainsMutex); check = m_fileEnvironmentInformations.values(url); } foreach(ParsingEnvironmentFilePointer file, check) { EnvironmentInformationRequest req(file.data()); QMutexLocker lock(m_environmentInfo.mutex()); uint index = m_environmentInfo.findIndex(req); if(file->d_func()->isDynamic()) { //This item has been changed, or isn't in the repository yet //Eventually remove an old entry if(index) m_environmentInfo.deleteItem(index); //Add the new entry to the item repository index = m_environmentInfo.index(req); Q_ASSERT(index); EnvironmentInformationItem* item = const_cast(m_environmentInfo.itemFromIndex(index)); DUChainBaseData* theData = reinterpret_cast(reinterpret_cast(item) + sizeof(EnvironmentInformationItem)); Q_ASSERT(theData->m_range == file->d_func()->m_range); Q_ASSERT(theData->m_dynamic == false); Q_ASSERT(theData->classId == file->d_func()->classId); file->setData( theData ); ++cnt; }else{ m_environmentInfo.itemFromIndex(index); //Prevent unloading of the data, by accessing the item } } ///We must not release the lock while holding a reference to a ParsingEnvironmentFilePointer, else we may miss the deletion of an ///information, and will get crashes. if(!atomic && (cnt % 100 == 0)) { //Release the lock on a regular basis locker.unlock(); locker.lock(); } storeInformationList(url); //Access the data in the repository, so the bucket isn't unloaded uint index = m_environmentListInfo.findIndex(EnvironmentInformationListRequest(url)); if(index) { m_environmentListInfo.itemFromIndex(index); }else{ QMutexLocker lock(&m_chainsMutex); qCDebug(LANGUAGE) << "Did not find stored item for" << url.str() << "count:" << m_fileEnvironmentInformations.values(url); } if(!atomic) { locker.unlock(); locker.lock(); } } } QMutex& cleanupMutex() { return m_cleanupMutex; } /// defines how we interact with the ongoing language parse jobs enum LockFlag { /// no locking required, only used when we locked previously NoLock = 0, /// lock all parse jobs and block until we succeeded. required at shutdown BlockingLock = 1, /// only try to lock and abort on failure, good for the intermittent cleanups TryLock = 2, }; ///@param retries When this is nonzero, then doMoreCleanup will do the specified amount of cycles ///doing the cleanup without permanently locking the du-chain. During these steps the consistency ///of the disk-storage is not guaranteed, but only few changes will be done during these steps, ///so the final step where the duchain is permanently locked is much faster. void doMoreCleanup(int retries = 0, LockFlag lockFlag = BlockingLock) { if(m_cleanupDisabled) return; //This mutex makes sure that there's never 2 threads at he same time trying to clean up QMutexLocker lockCleanupMutex(&cleanupMutex()); if(m_destroyed || m_cleanupDisabled) return; Q_ASSERT(!instance->lock()->currentThreadHasReadLock() && !instance->lock()->currentThreadHasWriteLock()); DUChainWriteLocker writeLock(instance->lock()); //This is used to stop all parsing before starting to do the cleanup. This way less happens during the //soft cleanups, and we have a good chance that during the "hard" cleanup only few data has to be written. QList locked; if (lockFlag != NoLock) { QList languages; if (ICore* core = ICore::self()) if (ILanguageController* lc = core->languageController()) languages = lc->loadedLanguages(); writeLock.unlock(); //Here we wait for all parsing-threads to stop their processing foreach(const auto language, languages) { if (lockFlag == TryLock) { if (!language->parseLock()->tryLockForWrite()) { qCDebug(LANGUAGE) << "Aborting cleanup because language plugin is still parsing:" << language->name(); // some language is still parsing, don't interfere with the cleanup foreach(auto* lock, locked) { lock->unlock(); } return; } } else { language->parseLock()->lockForWrite(); } locked << language->parseLock(); } writeLock.lock(); globalItemRepositoryRegistry().lockForWriting(); qCDebug(LANGUAGE) << "starting cleanup"; } QTime startTime = QTime::currentTime(); PersistentSymbolTable::self().clearCache(); storeAllInformation(!retries, writeLock); //Puts environment-information into a repository //We don't need to increase the reference-count, since the cleanup-mutex is locked QSet workOnContexts; { QMutexLocker l(&m_chainsMutex); workOnContexts.reserve(m_chainsByUrl.size()); foreach(TopDUContext* top, m_chainsByUrl) { workOnContexts << top; Q_ASSERT(hasChainForIndex(top->ownIndex())); } } foreach(TopDUContext* context, workOnContexts) { context->m_dynamicData->store(); if(retries) { //Eventually give other threads a chance to access the duchain writeLock.unlock(); //Sleep to give the other threads a realistic chance to get a read-lock in between QThread::usleep(500); writeLock.lock(); } } //Unload all top-contexts that don't have a reference-count and that are not imported by a referenced one QSet unloadedNames; bool unloadedOne = true; bool unloadAllUnreferenced = !retries; //Now unload contexts, but only ones that are not imported by any other currently loaded context //The complication: Since during the lock-break new references may be added, we must never keep //the du-chain in an invalid state. Thus we can only unload contexts that are not imported by any //currently loaded contexts. In case of loops, we have to unload everything at once. while(unloadedOne) { unloadedOne = false; int hadUnloadable = 0; unloadContexts: foreach(TopDUContext* unload, workOnContexts) { bool hasReference = false; { QMutexLocker l(&m_referenceCountsMutex); //Test if the context is imported by a referenced one for (auto it = m_referenceCounts.constBegin(), end = m_referenceCounts.constEnd(); it != end; ++it) { auto* context = it.key(); if(context == unload || context->imports(unload, CursorInRevision())) { workOnContexts.remove(unload); hasReference = true; } } } if(!hasReference) ++hadUnloadable; //We have found a context that is not referenced else continue; //This context is referenced bool isImportedByLoaded = !unload->loadedImporters().isEmpty(); //If we unload a context that is imported by other contexts, we create a bad loaded state if(isImportedByLoaded && !unloadAllUnreferenced) continue; unloadedNames.insert(unload->url()); //Since we've released the write-lock in between, we've got to call store() again to be sure that none of the data is dynamic //If nothing has changed, it is only a low-cost call. unload->m_dynamicData->store(); Q_ASSERT(!unload->d_func()->m_dynamic); removeDocumentChainFromMemory(unload); workOnContexts.remove(unload); unloadedOne = true; if(!unloadAllUnreferenced) { //Eventually give other threads a chance to access the duchain writeLock.unlock(); //Sleep to give the other threads a realistic chance to get a read-lock in between QThread::usleep(500); writeLock.lock(); } } if(hadUnloadable && !unloadedOne) { Q_ASSERT(!unloadAllUnreferenced); //This can happen in case of loops. We have o unload everything at one time. qCDebug(LANGUAGE) << "found" << hadUnloadable << "unloadable contexts, but could not unload separately. Unloading atomically."; unloadAllUnreferenced = true; hadUnloadable = 0; //Reset to 0, so we cannot loop forever goto unloadContexts; } } if(retries == 0) { QMutexLocker lock(&m_chainsMutex); //Do this atomically, since we must be sure that _everything_ is already saved for(QMultiMap::iterator it = m_fileEnvironmentInformations.begin(); it != m_fileEnvironmentInformations.end(); ) { ParsingEnvironmentFile* f = it->data(); Q_ASSERT(f->d_func()->classId); if(f->ref.load() == 1) { Q_ASSERT(!f->d_func()->isDynamic()); //It cannot be dynamic, since we have stored before //The ParsingEnvironmentFilePointer is only referenced once. This means that it does not belong to any //loaded top-context, so just remove it to save some memory and processing time. ///@todo use some kind of timeout before removing it = m_fileEnvironmentInformations.erase(it); }else{ ++it; } } } if(retries) writeLock.unlock(); //This must be the last step, due to the on-disk reference counting globalItemRepositoryRegistry().store(); //Stores all repositories { //Store the static parsing-environment file data ///@todo Solve this more elegantly, using a general mechanism to store static duchain-like data Q_ASSERT(ParsingEnvironmentFile::m_staticData); QFile f(globalItemRepositoryRegistry().path() + "/parsing_environment_data"); bool opened = f.open(QIODevice::WriteOnly); Q_ASSERT(opened); Q_UNUSED(opened); f.write((char*)ParsingEnvironmentFile::m_staticData, sizeof(StaticParsingEnvironmentData)); } ///Write out the list of available top-context indices { QMutexLocker lock(&m_chainsMutex); QFile f(globalItemRepositoryRegistry().path() + "/available_top_context_indices"); bool opened = f.open(QIODevice::WriteOnly); Q_ASSERT(opened); Q_UNUSED(opened); f.write((char*)m_availableTopContextIndices.data(), m_availableTopContextIndices.size() * sizeof(uint)); } if(retries) { doMoreCleanup(retries-1, NoLock); writeLock.lock(); } if(lockFlag != NoLock) { globalItemRepositoryRegistry().unlockForWriting(); int elapsedSeconds = startTime.secsTo(QTime::currentTime()); qCDebug(LANGUAGE) << "seconds spent doing cleanup: " << elapsedSeconds << "top-contexts still open:" << m_chainsByUrl.size(); } if(!retries) { int elapesedMilliSeconds = startTime.msecsTo(QTime::currentTime()); qCDebug(LANGUAGE) << "milliseconds spent doing cleanup with locked duchain: " << elapesedMilliSeconds; } foreach(QReadWriteLock* lock, locked) lock->unlock(); #if HAVE_MALLOC_TRIM // trim unused memory but keep a pad buffer of about 50 MB // this can greatly decrease the perceived memory consumption of kdevelop // see: https://sourceware.org/bugzilla/show_bug.cgi?id=14827 malloc_trim(50 * 1024 * 1024); #endif } ///Checks whether the information is already loaded. ParsingEnvironmentFile* findInformation(uint topContextIndex) { QMutexLocker lock(&m_chainsMutex); QHash::iterator it = m_indexEnvironmentInformations.find(topContextIndex); if(it != m_indexEnvironmentInformations.end()) return (*it).data(); return nullptr; } ///Loads/gets the environment-information for the given top-context index, or returns zero if none exists ///@warning m_chainsMutex should NOT be locked when this is called, because it triggers I/O ///@warning no other mutexes should be locked, as that may lead to a dedalock ParsingEnvironmentFile* loadInformation(uint topContextIndex) { ParsingEnvironmentFile* alreadyLoaded = findInformation(topContextIndex); if(alreadyLoaded) return alreadyLoaded; //Step two: Check if it is on disk, and if is, load it uint dataIndex = m_environmentInfo.findIndex(EnvironmentInformationRequest(topContextIndex)); if(!dataIndex) { //No environment-information stored for this top-context return nullptr; } const EnvironmentInformationItem& item(*m_environmentInfo.itemFromIndex(dataIndex)); QMutexLocker lock(&m_chainsMutex); //Due to multi-threading, we must do this check after locking the mutex, so we can be sure we don't create the same item twice at the same time alreadyLoaded = findInformation(topContextIndex); if(alreadyLoaded) return alreadyLoaded; ///FIXME: ugly, and remove const_cast ParsingEnvironmentFile* ret = dynamic_cast(DUChainItemSystem::self().create( const_cast(reinterpret_cast(reinterpret_cast(&item) + sizeof(EnvironmentInformationItem))) )); if(ret) { Q_ASSERT(ret->d_func()->classId); Q_ASSERT(ret->indexedTopContext().index() == topContextIndex); ParsingEnvironmentFilePointer retPtr(ret); m_fileEnvironmentInformations.insert(ret->url(), retPtr); Q_ASSERT(!m_indexEnvironmentInformations.contains(ret->indexedTopContext().index())); m_indexEnvironmentInformations.insert(ret->indexedTopContext().index(), retPtr); } return ret; } struct CleanupListVisitor { QList checkContexts; bool operator()(const EnvironmentInformationItem* item) { checkContexts << item->m_topContext; return true; } }; ///Will check a selection of all top-contexts for up-to-date ness, and remove them if out of date void cleanupTopContexts() { DUChainWriteLocker lock( DUChain::lock() ); qCDebug(LANGUAGE) << "cleaning top-contexts"; CleanupListVisitor visitor; uint startPos = 0; m_environmentInfo.visitAllItems(visitor); int checkContextsCount = maxFinalCleanupCheckContexts; int percentageOfContexts = (visitor.checkContexts.size() * 100) / minimumFinalCleanupCheckContextsPercentage; if(checkContextsCount < percentageOfContexts) checkContextsCount = percentageOfContexts; if(visitor.checkContexts.size() > (int)checkContextsCount) startPos = qrand() % (visitor.checkContexts.size() - checkContextsCount); int endPos = startPos + maxFinalCleanupCheckContexts; if(endPos > visitor.checkContexts.size()) endPos = visitor.checkContexts.size(); QSet< uint > check; for(int a = startPos; a < endPos && check.size() < checkContextsCount; ++a) if(check.size() < checkContextsCount) addContextsForRemoval(check, IndexedTopDUContext(visitor.checkContexts[a])); foreach(uint topIndex, check) { IndexedTopDUContext top(topIndex); if(top.data()) { qCDebug(LANGUAGE) << "removing top-context for" << top.data()->url().str() << "because it is out of date"; instance->removeDocumentChain(top.data()); } } qCDebug(LANGUAGE) << "check ready"; } private: void addContextsForRemoval(QSet& topContexts, IndexedTopDUContext top) { if(topContexts.contains(top.index())) return; QExplicitlySharedDataPointer info( instance->environmentFileForDocument(top) ); ///@todo Also check if the context is "useful"(Not a duplicate context, imported by a useful one, ...) if(info && info->needsUpdate()) { //This context will be removed }else{ return; } topContexts.insert(top.index()); if(info) { //Check whether importers need to be removed as well QList< QExplicitlySharedDataPointer > importers = info->importers(); QSet< QExplicitlySharedDataPointer > checkNext; //Do breadth first search, so less imports/importers have to be loaded, and a lower depth is reached for(QList< QExplicitlySharedDataPointer >::iterator it = importers.begin(); it != importers.end(); ++it) { IndexedTopDUContext c = (*it)->indexedTopContext(); if(!topContexts.contains(c.index())) { topContexts.insert(c.index()); //Prevent useless recursion checkNext.insert(*it); } } for(QSet< QExplicitlySharedDataPointer >::const_iterator it = checkNext.begin(); it != checkNext.end(); ++it) { topContexts.remove((*it)->indexedTopContext().index()); //Enable full check again addContextsForRemoval(topContexts, (*it)->indexedTopContext()); } } } ///Stores the environment-information for the given url void storeInformationList(IndexedString url) { QMutexLocker lock(m_environmentListInfo.mutex()); EnvironmentInformationListItem newItem; newItem.m_file = url; QSet newItems; { QMutexLocker lock(&m_chainsMutex); QMultiMap::iterator start = m_fileEnvironmentInformations.lowerBound(url); QMultiMap::iterator end = m_fileEnvironmentInformations.upperBound(url); for(QMultiMap::iterator it = start; it != end; ++it) { uint topContextIndex = (*it)->indexedTopContext().index(); newItems.insert(topContextIndex); newItem.itemsList().append(topContextIndex); } } uint index = m_environmentListInfo.findIndex(url); if(index) { //We only handle adding items here, since we can never be sure whether everything is loaded //Removal is handled directly in removeEnvironmentInformation const EnvironmentInformationListItem* item = m_environmentListInfo.itemFromIndex(index); QSet oldItems; FOREACH_FUNCTION(uint topContextIndex, item->items) { oldItems.insert(topContextIndex); if(!newItems.contains(topContextIndex)) { newItems.insert(topContextIndex); newItem.itemsList().append(topContextIndex); } } if(oldItems == newItems) return; ///Update/insert a new list m_environmentListInfo.deleteItem(index); //Remove the previous item } Q_ASSERT(m_environmentListInfo.findIndex(EnvironmentInformationListRequest(url)) == 0); //Insert the new item m_environmentListInfo.index(EnvironmentInformationListRequest(url, newItem)); Q_ASSERT(m_environmentListInfo.findIndex(EnvironmentInformationListRequest(url))); } //Loaded environment-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; }; Q_GLOBAL_STATIC(DUChainPrivate, sdDUChainPrivate) DUChain::DUChain() { Q_ASSERT(ICore::self()); connect(ICore::self()->documentController(), &IDocumentController::documentLoadedPrepare, this, &DUChain::documentLoadedPrepare); connect(ICore::self()->documentController(), &IDocumentController::documentUrlChanged, this, &DUChain::documentRenamed); connect(ICore::self()->documentController(), &IDocumentController::documentActivated, this, &DUChain::documentActivated); connect(ICore::self()->documentController(), &IDocumentController::documentClosed, this, &DUChain::documentClosed); } DUChain::~DUChain() { DUChain::m_deleted = true; } DUChain* DUChain::self() { return sdDUChainPrivate->instance; } extern void initModificationRevisionSetRepository(); extern void initDeclarationRepositories(); extern void initIdentifierRepository(); extern void initTypeRepository(); extern void initInstantiationInformationRepository(); void DUChain::initialize() { // Initialize the global item repository as first thing after loading the session Q_ASSERT(ICore::self()); Q_ASSERT(ICore::self()->activeSession()); ItemRepositoryRegistry::initialize(ICore::self()->activeSessionLock()); initReferenceCounting(); // This needs to be initialized here too as the function is not threadsafe, but can // sometimes be called from different threads. This results in the underlying QFile // being 0 and hence crashes at some point later when accessing the contents via // read. See https://bugs.kde.org/show_bug.cgi?id=250779 RecursiveImportRepository::repository(); RecursiveImportCacheRepository::repository(); // similar to above, see https://bugs.kde.org/show_bug.cgi?id=255323 initDeclarationRepositories(); initModificationRevisionSetRepository(); initIdentifierRepository(); initTypeRepository(); initInstantiationInformationRepository(); Importers::self(); globalImportIdentifier(); globalIndexedImportIdentifier(); globalAliasIdentifier(); globalIndexedAliasIdentifier(); } DUChainLock* DUChain::lock() { return &sdDUChainPrivate->lock; } QList DUChain::allChains() const { QMutexLocker l(&sdDUChainPrivate->m_chainsMutex); return sdDUChainPrivate->m_chainsByUrl.values(); } void DUChain::updateContextEnvironment( TopDUContext* context, ParsingEnvironmentFile* file ) { QMutexLocker l(&sdDUChainPrivate->m_chainsMutex); removeFromEnvironmentManager( context ); context->setParsingEnvironmentFile( file ); addToEnvironmentManager( context ); } void DUChain::removeDocumentChain( TopDUContext* context ) { ENSURE_CHAIN_WRITE_LOCKED; IndexedTopDUContext indexed(context->indexed()); Q_ASSERT(indexed.data() == context); ///This assertion fails if you call removeDocumentChain(..) on a document that has not been added to the du-chain context->m_dynamicData->deleteOnDisk(); Q_ASSERT(indexed.data() == context); sdDUChainPrivate->removeDocumentChainFromMemory(context); Q_ASSERT(!indexed.data()); Q_ASSERT(!environmentFileForDocument(indexed)); QMutexLocker lock(&sdDUChainPrivate->m_chainsMutex); sdDUChainPrivate->m_availableTopContextIndices.push_back(indexed.index()); } void DUChain::addDocumentChain( TopDUContext * chain ) { QMutexLocker l(&sdDUChainPrivate->m_chainsMutex); // qCDebug(LANGUAGE) << "duchain: adding document" << chain->url().str() << " " << chain; Q_ASSERT(chain); Q_ASSERT(!sdDUChainPrivate->hasChainForIndex(chain->ownIndex())); { QMutexLocker lock(&DUChain::chainsByIndexLock); if(DUChain::chainsByIndex.size() <= chain->ownIndex()) DUChain::chainsByIndex.resize(chain->ownIndex() + 100, nullptr); DUChain::chainsByIndex[chain->ownIndex()] = chain; } { Q_ASSERT(DUChain::chainsByIndex[chain->ownIndex()]); } Q_ASSERT(sdDUChainPrivate->hasChainForIndex(chain->ownIndex())); sdDUChainPrivate->m_chainsByUrl.insert(chain->url(), chain); Q_ASSERT(sdDUChainPrivate->hasChainForIndex(chain->ownIndex())); chain->setInDuChain(true); l.unlock(); addToEnvironmentManager(chain); // This function might be called during shutdown by stale parse jobs // Make sure we don't access null-pointers here if (ICore::self() && ICore::self()->languageController() && ICore::self()->languageController()->backgroundParser()->trackerForUrl(chain->url())) { //Make sure the context stays alive at least as long as the context is open ReferencedTopDUContext ctx(chain); sdDUChainPrivate->m_openDocumentContexts.insert(ctx); } } void DUChain::addToEnvironmentManager( TopDUContext * chain ) { ParsingEnvironmentFilePointer file = chain->parsingEnvironmentFile(); if( !file ) return; //We don't need to manage Q_ASSERT(file->indexedTopContext().index() == chain->ownIndex()); if(ParsingEnvironmentFile* alreadyHave = sdDUChainPrivate->findInformation(file->indexedTopContext().index())) { ///If this triggers, there has already been another environment-information registered for this top-context. ///removeFromEnvironmentManager should have been called before to remove the old environment-information. Q_ASSERT(alreadyHave == file.data()); Q_UNUSED(alreadyHave); return; } sdDUChainPrivate->addEnvironmentInformation(file); } void DUChain::removeFromEnvironmentManager( TopDUContext * chain ) { ParsingEnvironmentFilePointer file = chain->parsingEnvironmentFile(); if( !file ) return; //We don't need to manage sdDUChainPrivate->removeEnvironmentInformation(file); } TopDUContext* DUChain::chainForDocument(const QUrl& document, bool proxyContext) const { return chainForDocument(IndexedString(document), proxyContext); } bool DUChain::isInMemory(uint topContextIndex) const { return DUChainPrivate::hasChainForIndex(topContextIndex); } IndexedString DUChain::urlForIndex(uint index) const { { TopDUContext* chain = DUChainPrivate::readChainForIndex(index); if(chain) return chain->url(); } return TopDUContextDynamicData::loadUrl(index); } TopDUContext* DUChain::loadChain(uint index) { QSet loaded; sdDUChainPrivate->loadChain(index, loaded); { QMutexLocker lock(&chainsByIndexLock); if(chainsByIndex.size() > index) { TopDUContext* top = chainsByIndex[index]; if(top) return top; } } return nullptr; } TopDUContext* DUChain::chainForDocument(const KDevelop::IndexedString& document, bool proxyContext) const { ENSURE_CHAIN_READ_LOCKED; if(sdDUChainPrivate->m_destroyed) return nullptr; QList list = sdDUChainPrivate->getEnvironmentInformation(document); foreach(const ParsingEnvironmentFilePointer &file, list) if(isInMemory(file->indexedTopContext().index()) && file->isProxyContext() == proxyContext) { return file->topContext(); } foreach(const ParsingEnvironmentFilePointer &file, list) if(proxyContext == file->isProxyContext()) { return file->topContext(); } //Allow selecting a top-context even if there is no ParsingEnvironmentFile QList< TopDUContext* > ret = chainsForDocument(document); foreach(TopDUContext* ctx, ret) { if(!ctx->parsingEnvironmentFile() || (ctx->parsingEnvironmentFile()->isProxyContext() == proxyContext)) return ctx; } return nullptr; } QList DUChain::chainsForDocument(const QUrl& document) const { return chainsForDocument(IndexedString(document)); } QList DUChain::chainsForDocument(const IndexedString& document) const { QList chains; if(sdDUChainPrivate->m_destroyed) return chains; QMutexLocker l(&sdDUChainPrivate->m_chainsMutex); // Match all parsed versions of this document for (auto it = sdDUChainPrivate->m_chainsByUrl.lowerBound(document); it != sdDUChainPrivate->m_chainsByUrl.end(); ++it) { if (it.key() == document) chains << it.value(); else break; } return chains; } TopDUContext* DUChain::chainForDocument( const QUrl& document, const KDevelop::ParsingEnvironment* environment, bool proxyContext ) const { return chainForDocument( IndexedString(document), environment, proxyContext ); } ParsingEnvironmentFilePointer DUChain::environmentFileForDocument( const IndexedString& document, const ParsingEnvironment* environment, bool proxyContext ) const { ENSURE_CHAIN_READ_LOCKED; if(sdDUChainPrivate->m_destroyed) return ParsingEnvironmentFilePointer(); QList< ParsingEnvironmentFilePointer> list = sdDUChainPrivate->getEnvironmentInformation(document); // qCDebug(LANGUAGE) << document.str() << ": matching" << list.size() << (onlyProxyContexts ? "proxy-contexts" : (noProxyContexts ? "content-contexts" : "contexts")); auto it = list.constBegin(); while(it != list.constEnd()) { if(*it && ((*it)->isProxyContext() == proxyContext) && (*it)->matchEnvironment(environment) && // Verify that the environment-file and its top-context are "good": The top-context must exist, // and there must be a content-context associated to the proxy-context. (*it)->topContext() && (!proxyContext || DUChainUtils::contentContextFromProxyContext((*it)->topContext())) ) { return *it; } ++it; } return ParsingEnvironmentFilePointer(); } QList DUChain::allEnvironmentFiles(const IndexedString& document) { return sdDUChainPrivate->getEnvironmentInformation(document); } ParsingEnvironmentFilePointer DUChain::environmentFileForDocument(IndexedTopDUContext topContext) const { if(topContext.index() == 0) return ParsingEnvironmentFilePointer(); return ParsingEnvironmentFilePointer(sdDUChainPrivate->loadInformation(topContext.index())); } TopDUContext* DUChain::chainForDocument( const IndexedString& document, const ParsingEnvironment* environment, bool proxyContext ) const { if(sdDUChainPrivate->m_destroyed) return nullptr; ParsingEnvironmentFilePointer envFile = environmentFileForDocument(document, environment, proxyContext); if(envFile) { return envFile->topContext(); }else{ return nullptr; } } QList DUChain::documents() const { QMutexLocker l(&sdDUChainPrivate->m_chainsMutex); QList ret; ret.reserve(sdDUChainPrivate->m_chainsByUrl.count()); foreach(TopDUContext* top, sdDUChainPrivate->m_chainsByUrl) { ret << top->url().toUrl(); } return ret; } QList DUChain::indexedDocuments() const { QMutexLocker l(&sdDUChainPrivate->m_chainsMutex); QList ret; ret.reserve(sdDUChainPrivate->m_chainsByUrl.count()); foreach(TopDUContext* top, sdDUChainPrivate->m_chainsByUrl) { ret << top->url(); } return ret; } void DUChain::documentActivated(KDevelop::IDocument* doc) { if(sdDUChainPrivate->m_destroyed) return; DUChainReadLocker lock( DUChain::lock() ); QMutexLocker l(&sdDUChainPrivate->m_chainsMutex); auto backgroundParser = ICore::self()->languageController()->backgroundParser(); auto addWithHighPriority = [backgroundParser, doc]() { backgroundParser->addDocument(IndexedString(doc->url()), TopDUContext::VisibleDeclarationsAndContexts, BackgroundParser::BestPriority); }; TopDUContext* ctx = DUChainUtils::standardContextForUrl(doc->url(), true); //Check whether the document has an attached environment-manager, and whether that one thinks the document needs to be updated. //If yes, update it. if (ctx && ctx->parsingEnvironmentFile() && ctx->parsingEnvironmentFile()->needsUpdate()) { qCDebug(LANGUAGE) << "Document needs update, using best priority since it just got activated:" << doc->url(); addWithHighPriority(); } else if (backgroundParser->managedDocuments().contains(IndexedString(doc->url()))) { // increase priority if there's already parse job of this document in the queue qCDebug(LANGUAGE) << "Prioritizing activated document:" << doc->url(); addWithHighPriority(); } } void DUChain::documentClosed(IDocument* document) { if(sdDUChainPrivate->m_destroyed) return; IndexedString url(document->url()); foreach(const ReferencedTopDUContext &top, sdDUChainPrivate->m_openDocumentContexts) if(top->url() == url) sdDUChainPrivate->m_openDocumentContexts.remove(top); } void DUChain::documentLoadedPrepare(KDevelop::IDocument* doc) { if(sdDUChainPrivate->m_destroyed) return; const IndexedString url(doc->url()); DUChainWriteLocker lock( DUChain::lock() ); QMutexLocker l(&sdDUChainPrivate->m_chainsMutex); TopDUContext* standardContext = DUChainUtils::standardContextForUrl(doc->url()); QList chains = chainsForDocument(url); auto languages = ICore::self()->languageController()->languagesForUrl(doc->url()); if(standardContext) { Q_ASSERT(chains.contains(standardContext)); //We have just loaded it Q_ASSERT((standardContext->url() == url)); sdDUChainPrivate->m_openDocumentContexts.insert(standardContext); bool needsUpdate = standardContext->parsingEnvironmentFile() && standardContext->parsingEnvironmentFile()->needsUpdate(); if(!needsUpdate) { //Only apply the highlighting if we don't need to update, else we might highlight total crap //Do instant highlighting only if all imports are loaded, to make sure that we don't block the user-interface too long //Else the highlighting will be done in the background-thread //This is not exactly right, as the direct imports don't necessarily equal the real imports used by uses //but it approximates the correct behavior. bool allImportsLoaded = true; foreach(const DUContext::Import& import, standardContext->importedParentContexts()) if(!import.indexedContext().indexedTopContext().isLoaded()) allImportsLoaded = false; if(allImportsLoaded) { l.unlock(); lock.unlock(); foreach(const auto language, languages) { if(language->codeHighlighting()) { language->codeHighlighting()->highlightDUChain(standardContext); } } qCDebug(LANGUAGE) << "highlighted" << doc->url() << "in foreground"; return; } }else{ qCDebug(LANGUAGE) << "not highlighting the duchain because the documents needs an update"; } if(needsUpdate || !(standardContext->features() & TopDUContext::AllDeclarationsContextsAndUses)) { ICore::self()->languageController()->backgroundParser()->addDocument(IndexedString(doc->url()), (TopDUContext::Features)(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdate)); return; } } //Add for highlighting etc. ICore::self()->languageController()->backgroundParser()->addDocument(IndexedString(doc->url()), TopDUContext::AllDeclarationsContextsAndUses); } void DUChain::documentRenamed(KDevelop::IDocument* doc) { if(sdDUChainPrivate->m_destroyed) return; if(!doc->url().isValid()) { ///Maybe this happens when a file was deleted? qCWarning(LANGUAGE) << "Strange, url of renamed document is invalid!"; }else{ ICore::self()->languageController()->backgroundParser()->addDocument(IndexedString(doc->url()), (TopDUContext::Features)(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdate)); } } Uses* DUChain::uses() { return &sdDUChainPrivate->m_uses; } Definitions* DUChain::definitions() { return &sdDUChainPrivate->m_definitions; } static void finalCleanup() { DUChainWriteLocker writeLock(DUChain::lock()); qCDebug(LANGUAGE) << "doing final cleanup"; int cleaned = 0; while((cleaned = globalItemRepositoryRegistry().finalCleanup())) { qCDebug(LANGUAGE) << "cleaned" << cleaned << "B"; if(cleaned < 1000) { qCDebug(LANGUAGE) << "cleaned enough"; break; } } qCDebug(LANGUAGE) << "final cleanup ready"; } void DUChain::shutdown() { // if core is not shutting down, we can end up in deadlocks or crashes // since language plugins might still try to access static duchain stuff Q_ASSERT(!ICore::self() || ICore::self()->shuttingDown()); qCDebug(LANGUAGE) << "Cleaning up and shutting down DUChain"; QMutexLocker lock(&sdDUChainPrivate->cleanupMutex()); { //Acquire write-lock of the repository, so when kdevelop crashes in that process, the repository is discarded //Crashes here may happen in an inconsistent state, thus this makes sense, to protect the user from more crashes globalItemRepositoryRegistry().lockForWriting(); sdDUChainPrivate->cleanupTopContexts(); globalItemRepositoryRegistry().unlockForWriting(); } sdDUChainPrivate->doMoreCleanup(); //Must be done _before_ finalCleanup, else we may be deleting yet needed data sdDUChainPrivate->m_openDocumentContexts.clear(); sdDUChainPrivate->m_destroyed = true; sdDUChainPrivate->clear(); { //Acquire write-lock of the repository, so when kdevelop crashes in that process, the repository is discarded //Crashes here may happen in an inconsistent state, thus this makes sense, to protect the user from more crashes globalItemRepositoryRegistry().lockForWriting(); finalCleanup(); globalItemRepositoryRegistry().unlockForWriting(); } globalItemRepositoryRegistry().shutdown(); } uint DUChain::newTopContextIndex() { { QMutexLocker lock(&sdDUChainPrivate->m_chainsMutex); if(!sdDUChainPrivate->m_availableTopContextIndices.isEmpty()) { uint ret = sdDUChainPrivate->m_availableTopContextIndices.back(); sdDUChainPrivate->m_availableTopContextIndices.pop_back(); if(TopDUContextDynamicData::fileExists(ret)) { qCWarning(LANGUAGE) << "Problem in the management of availalbe top-context indices"; return newTopContextIndex(); } return ret; } } static QAtomicInt& currentId( globalItemRepositoryRegistry().getCustomCounter(QStringLiteral("Top-Context Counter"), 1) ); return currentId.fetchAndAddRelaxed(1); } void DUChain::refCountUp(TopDUContext* top) { QMutexLocker l(&sdDUChainPrivate->m_referenceCountsMutex); // note: value is default-constructed to zero if it does not exist ++sdDUChainPrivate->m_referenceCounts[top]; } bool DUChain::deleted() { return m_deleted; } void DUChain::refCountDown(TopDUContext* top) { QMutexLocker l(&sdDUChainPrivate->m_referenceCountsMutex); auto it = sdDUChainPrivate->m_referenceCounts.find(top); if (it == sdDUChainPrivate->m_referenceCounts.end()) { //qCWarning(LANGUAGE) << "tried to decrease reference-count for" << top->url().str() << "but this top-context is not referenced"; return; } auto& refCount = *it; --refCount; if (!refCount) { sdDUChainPrivate->m_referenceCounts.erase(it); } } void DUChain::emitDeclarationSelected(const DeclarationPointer& decl) { if(sdDUChainPrivate->m_destroyed) return; emit declarationSelected(decl); } void DUChain::emitUpdateReady(const IndexedString& url, const ReferencedTopDUContext& topContext) { if(sdDUChainPrivate->m_destroyed) return; emit updateReady(url, topContext); } KDevelop::ReferencedTopDUContext DUChain::waitForUpdate(const KDevelop::IndexedString& document, KDevelop::TopDUContext::Features minFeatures, bool proxyContext) { Q_ASSERT(!lock()->currentThreadHasReadLock() && !lock()->currentThreadHasWriteLock()); WaitForUpdate waiter; updateContextForUrl(document, minFeatures, &waiter); // waiter.m_waitMutex.lock(); // waiter.m_dataMutex.unlock(); while(!waiter.m_ready) { // we might have been shut down in the meanwhile if (!ICore::self()) { return nullptr; } QMetaObject::invokeMethod(ICore::self()->languageController()->backgroundParser(), "parseDocuments"); QApplication::processEvents(); QThread::usleep(1000); } if(!proxyContext) { DUChainReadLocker readLock(DUChain::lock()); return DUChainUtils::contentContextFromProxyContext(waiter.m_topContext); } return waiter.m_topContext; } void DUChain::updateContextForUrl(const IndexedString& document, TopDUContext::Features minFeatures, QObject* notifyReady, int priority) const { DUChainReadLocker lock( DUChain::lock() ); TopDUContext* standardContext = DUChainUtils::standardContextForUrl(document.toUrl()); if(standardContext && standardContext->parsingEnvironmentFile() && !standardContext->parsingEnvironmentFile()->needsUpdate() && standardContext->parsingEnvironmentFile()->featuresSatisfied(minFeatures)) { lock.unlock(); if(notifyReady) QMetaObject::invokeMethod(notifyReady, "updateReady", Qt::DirectConnection, Q_ARG(KDevelop::IndexedString, document), Q_ARG(KDevelop::ReferencedTopDUContext, ReferencedTopDUContext(standardContext))); }else{ ///Start a parse-job for the given document ICore::self()->languageController()->backgroundParser()->addDocument(document, minFeatures, priority, notifyReady); } } void DUChain::disablePersistentStorage(bool disable) { sdDUChainPrivate->m_cleanupDisabled = disable; } void DUChain::storeToDisk() { bool wasDisabled = sdDUChainPrivate->m_cleanupDisabled; sdDUChainPrivate->m_cleanupDisabled = false; sdDUChainPrivate->doMoreCleanup(); sdDUChainPrivate->m_cleanupDisabled = wasDisabled; } bool DUChain::compareToDisk() { DUChainWriteLocker writeLock(DUChain::lock()); ///Step 1: Compare the repositories return true; } } diff --git a/language/duchain/duchaindumper.cpp b/language/duchain/duchaindumper.cpp index f7b639647..23f9fdd4d 100644 --- a/language/duchain/duchaindumper.cpp +++ b/language/duchain/duchaindumper.cpp @@ -1,203 +1,203 @@ /* This file is part of KDevelop Copyright 2002-2005 Roberto Raggi Copyright 2006 Hamish Rodda Copyright 2010 Milian Wolff Copyright 2014 Kevin Funk This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "duchaindumper.h" #include #include #include "definitions.h" #include "ducontext.h" #include "topducontext.h" #include "declaration.h" #include "duchainpointer.h" #include "identifier.h" #include "use.h" #include "problem.h" #include #include "functiondefinition.h" #include using namespace KDevelop; namespace { QDebug fromTextStream(const QTextStream& out) { if (out.device()) return {out.device()}; return {out.string()}; } QString typeToString(DUContext::ContextType type) { switch(type) { case DUContext::Global: return QStringLiteral("Global"); case DUContext::Namespace: return QStringLiteral("Namespace"); case DUContext::Class: return QStringLiteral("Class"); case DUContext::Function: return QStringLiteral("Function"); case DUContext::Template: return QStringLiteral("Template"); case DUContext::Enum: return QStringLiteral("Enum"); case DUContext::Helper: return QStringLiteral("Helper"); case DUContext::Other: return QStringLiteral("Other"); } Q_ASSERT(false); return QString(); } } struct DUChainDumper::Private { Private() : m_indent(0) {} void dumpProblems(TopDUContext* top, QTextStream& out); void dump(DUContext* context, int allowedDepth, bool isFromImport, QTextStream& out); int m_indent; Features m_features; QSet m_visitedContexts; }; DUChainDumper::DUChainDumper(Features features) : d(new Private) { d->m_features = features; } DUChainDumper::~DUChainDumper( ) { } class Indent { public: - Indent(int level): m_level(level) {} + explicit Indent(int level): m_level(level) {} friend QDebug& operator<<(QDebug& debug, const Indent& ind) { for (int i=0; iproblems().isEmpty()) { qout << top->problems().size() << "problems encountered:" << endl; foreach(const ProblemPointer& p, top->problems()) { qout << Indent(m_indent * 2) << p->description() << p->explanation() << p->finalLocation() << endl; } qout << endl; } } void DUChainDumper::Private::dump(DUContext * context, int allowedDepth, bool isFromImport, QTextStream& out) { QDebug qout = fromTextStream(out); qout << Indent(m_indent * 2) << (isFromImport ? " ==import==>" : "") << (dynamic_cast(context) ? "Top-Context" : "Context") << typeToString(context->type()) << "(owner: " << context->owner() << ")" << context << context->localScopeIdentifier() << "[" << context->scopeIdentifier(true) << "]" << context->range().castToSimpleRange() << (dynamic_cast(context) ? static_cast(context)->url().byteArray(): "") << endl; if(m_visitedContexts.contains(context)) { qout << Indent((m_indent+2) * 2) << "(Skipping" << context->scopeIdentifier(true) << "because it was already printed)" << endl; return; } m_visitedContexts.insert(context); auto top = context->topContext(); if (allowedDepth >= 0) { foreach (Declaration* dec, context->localDeclarations(top)) { //IdentifiedType* idType = dynamic_cast(dec->abstractType().data()); qout << Indent((m_indent+2) * 2) << "Declaration:" << dec->toString() << "[" << dec->qualifiedIdentifier() << "]" << dec << "(internal ctx:" << dec->internalContext() << ")" << dec->range().castToSimpleRange() << "," << (dec->isDefinition() ? "defined, " : (FunctionDefinition::definition(dec) ? "" : "no definition, ")) << dec->uses().count() << "use(s)." << endl; if (FunctionDefinition::definition(dec)) { qout << Indent((m_indent+2) * 2 + 1) << "Definition:" << FunctionDefinition::definition(dec)->range().castToSimpleRange() << endl; } QMap > uses = dec->uses(); for(QMap >::const_iterator it = uses.constBegin(); it != uses.constEnd(); ++it) { qout << Indent((m_indent+3) * 2) << "File:" << it.key().str() << endl; foreach (const RangeInRevision range, *it) qout << Indent((m_indent+4) * 2) << "Use:" << range.castToSimpleRange() << endl; } } } else { qout << Indent((m_indent+2) * 2) << context->localDeclarations(top).count() << "Declarations," << context->childContexts().size() << "child-contexts" << endl; } ++m_indent; { /* foreach (const DUContext::Import &parent, context->importedParentContexts()) { DUContext* import = parent.context(top); if(!import) { qout << Indent((m_indent+2) * 2+1) << "Could not get parent, is it registered in the DUChain?" << endl; continue; } dump(import, allowedDepth-1, true, out); } */ foreach (DUContext* child, context->childContexts()) dump(child, allowedDepth-1, false, out); } --m_indent; } void DUChainDumper::dump(DUContext* context, int allowedDepth, QTextStream& out) { d->m_visitedContexts.clear(); if (!context) { out << "Error: Null context" << endl; return; } auto top = context->topContext(); if (d->m_features.testFlag(DumpProblems)) { d->dumpProblems(top, out); } if (d->m_features.testFlag(DumpContext)) { d->dump(context, allowedDepth, false, out); } } void DUChainDumper::dump(DUContext* context, int allowedDepth) { QTextStream out(stdout); dump(context, allowedDepth, out); } diff --git a/language/duchain/ducontext.h b/language/duchain/ducontext.h index 11f086cb4..f4d78f3c6 100644 --- a/language/duchain/ducontext.h +++ b/language/duchain/ducontext.h @@ -1,954 +1,954 @@ /* 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_DUCONTEXT_H #define KDEVPLATFORM_DUCONTEXT_H #include #include #include #include #include #include "identifier.h" #include "duchainbase.h" #include "types/abstracttype.h" #include "duchainpointer.h" #include "declarationid.h" #include "indexedducontext.h" #include "navigation/abstractnavigationwidget.h" class QWidget; namespace KDevelop { class Declaration; class DUChain; class Use; class TopDUContext; class DUContext; class DUContextData; class KDEVPLATFORMLANGUAGE_EXPORT DUChainVisitor { public: virtual void visit(DUContext* context) = 0; virtual void visit(Declaration* declaration) = 0; virtual ~DUChainVisitor(); }; typedef DUChainPointer DUContextPointer; /** * A single context in source code, represented as a node in a * directed acyclic graph. * * Access to context objects must be serialised by holding the * chain lock, ie. DUChain::lock(). * * NOTE: A du-context can be freely edited as long as it's parent-context is zero. * In the moment the parent-context is set, the context may only be edited when it * is allowed to edited it's top-level context(@see TopLevelContext::inDUChain() * * @todo change child relationships to a linked list within the context? */ class KDEVPLATFORMLANGUAGE_EXPORT DUContext : public DUChainBase { friend class Use; friend class Declaration; friend class DeclarationData; friend class DUContextData; friend class DUContextDynamicData; friend class Definition; friend class VisibleDeclarationIterator; public: /** * Constructor. No convenience methods, as the initialisation order is important, * * @param anonymous Whether the context should be added as an anonymous context to the parent. That way the context can never be found through any of the parent's member-functions. * * If the parent is in the symbol table and the context is not anonymous, it will also be added to the symbol table. You nead a write-lock to the DUChain then */ explicit DUContext(const RangeInRevision& range, DUContext* parent = nullptr, bool anonymous = false); explicit DUContext(DUContextData&); /** * Destructor. Will delete all child contexts which are defined within * the same file as this context. */ ~DUContext() override; enum ContextType : quint8 { Global /**< A context that declares functions, namespaces or classes */, Namespace /**< A context that declares namespace members */, Class /**< A context that declares class members */, Function /**< A context that declares function-arguments */, Template /**< A context that declares template-parameters */, Enum /**< A context that contains a list of enumerators */, Helper /**< A helper context. This context is treated specially during search: * when searching within the imports of a context, and that context's parent * is a context of type DUContext::Helper, then the upwards search is continued * into that helper(The decision happens in shouldSearchInParent) */, Other /**< Represents executable code, like for example within a compound-statement */ }; enum SearchFlag { NoSearchFlags = 0 /**< Searching for everything */, InImportedParentContext = 1 /**< Internal, do not use from outside */, OnlyContainerTypes = 2 /**< Not implemented yet */, DontSearchInParent = 4 /**< IF this flag is set, findDeclarations(..) will not search for the identifier in parent-contexts(which does not include imported parent-contexts) */, NoUndefinedTemplateParams = 8 /**< For languages that support templates(like C++). If this is set, the search should fail as soon as undefined template-parameters are involved. */, DirectQualifiedLookup = 16 /**< When this flag is used, the searched qualified identifier should NOT be split up into it's components and looked up one by one. Currently only plays a role in C++ specific parts. */, NoFiltering = 32 /**< Should be set when no filtering at all is wished, not even filtering that is natural for the underlying language(For example in C++, constructors are filtered out be default) */, OnlyFunctions = 64 /**< When this is given, only function-declarations are returned. In case of C++, this also means that constructors can be retrieved, while normally they are filtered out. */, NoImportsCheck = 128 /**< With this parameter, a global search will return all matching items, from all contexts, not only from imported ones. */, NoSelfLookUp = 256 /**< With this parameter, the special-treatment during search that allows finding the context-class by its name is disabled. */, DontResolveAliases = 512 /**< Disables the resolution of alias declarations in the returned list*/, LastSearchFlag = 1024 }; Q_DECLARE_FLAGS(SearchFlags, SearchFlag) ContextType type() const; void setType(ContextType type); /** * If this context was opened by a declaration or definition, this returns that item. * * The returned declaration/definition will have this context set as @c internalContext() */ Declaration* owner() const; /** * Sets the declaration/definition, and also updates it's internal context (they are strictly paired together). * * The declaration has to be part of the same top-context. */ void setOwner(Declaration* decl); /** * Calculate the depth of this context, from the top level context in the file. */ int depth() const; /** * Find the top context. */ TopDUContext* topContext() const override; /** * Visits all duchain objects in the whole duchain. * * Classes that hold a unique link to duchain objects like instantiations * have to pass the visitor over to those classes. * */ virtual void visit(DUChainVisitor& visitor); /** * Find the context which most specifically covers @p position. * * The search is recursive, so the most specific context is found. * * @param includeBorders When this is true, contexts will also be found that * have the position on their borders. * * @warning This uses the ranges in the local revision of the document (at last parsing time). * Use DUChainBase::transformToLocalRevision to transform the cursor into that revision first. */ DUContext* findContextAt(const CursorInRevision& position, bool includeBorders = false) const; /** * Find a child declaration that has a rang that covers the given @p position. * * The search is local, not recursive. * * @warning This uses the ranges in the local revision of the document (at last parsing time). * Use DUChainBase::transformToLocalRevision to transform the cursor into that revision first. */ Declaration* findDeclarationAt(const CursorInRevision& position) const; /** * Find the context which most specifically covers @a range. * * @warning This uses the ranges in the local revision of the document (at last parsing time). * Use DUChainBase::transformToLocalRevision to transform the cursor into that revision first. */ DUContext* findContextIncluding(const RangeInRevision& range) const; /** * Calculate the fully qualified scope identifier. */ QualifiedIdentifier scopeIdentifier(bool includeClasses = false) const; /** * Returns true if this context has the same scope identifier as the given one. * * @note This is much more efficient than computing the identifiers through @c scopeIdentifier(..) * and comparing them */ bool equalScopeIdentifier(const DUContext* rhs) const; /** * Scope identifier, used to qualify the identifiers occurring in each context. * * This is the part relative to the parent context. */ QualifiedIdentifier localScopeIdentifier() const; /** * Same as @c localScopeIdentifier(), but faster. */ IndexedQualifiedIdentifier indexedLocalScopeIdentifier() const; /** * Scope identifier, used to qualify the identifiers occurring in each context * This must not be called once this context has children. */ void setLocalScopeIdentifier(const QualifiedIdentifier& identifier); /** * Returns whether this context is listed in the symbol table (Namespaces and classes) */ bool inSymbolTable() const; /** * Move this object into/out of the symbol table. * * @note You need to have a duchain write lock, unless this is a TopDUContext. */ void setInSymbolTable(bool inSymbolTable); /** * Returns the immediate parent context of this context. */ DUContext* parentContext() const; /** * Represents an imported parent context. */ struct KDEVPLATFORMLANGUAGE_EXPORT Import { /** * @note DUChain must be read-locked when this is called */ Import(DUContext* context, const DUContext* importer, const CursorInRevision& position = CursorInRevision::invalid()); Import() : position(CursorInRevision::invalid()) { } - Import(const DeclarationId& id, const CursorInRevision& position = CursorInRevision::invalid()); + explicit Import(const DeclarationId& id, const CursorInRevision& position = CursorInRevision::invalid()); bool operator==(const Import& rhs) const { return m_context == rhs.m_context && m_declaration == rhs.m_declaration; } /** * @param topContext The top-context from where to start searching. * This is important to find the correct imports * in the case of templates or similar structures. */ DUContext* context(const TopDUContext* topContext, bool instantiateIfRequired = true) const; /** * Returns the top-context index, if this import is not a specialization import. */ uint topContextIndex() const { return m_context.topContextIndex(); } IndexedDUContext indexedContext() const { return m_context; } /** * Returns true if this import is direct. * * That is, the import is not referred to by its identifier, * but rather directly by its index. */ bool isDirect() const; /** * If this import is indirect, returns the imported declaration-id */ DeclarationId indirectDeclarationId() const { return m_declaration; } CursorInRevision position; private: //Either we store m_declaration, or m_context. That way we can resolve specialized contexts. ///@todo Compress using union DeclarationId m_declaration; IndexedDUContext m_context; }; /** * Returns the list of imported parent contexts for this context. * * @warning The list may contain objects that are not valid any more, * i.e. data() returns zero, @see addImportedParentContext) * @warning The import structure may contain loops if this is a TopDUContext, * so be careful when traversing the tree. * @note This is expensive. */ virtual QVector importedParentContexts() const; /** * If the given context is directly imported into this one, and * @c addImportedParentContext(..) was called with a valid cursor, * this will return that position. Otherwise an invalid cursor is returned. */ virtual CursorInRevision importPosition(const DUContext* target) const; /** * Returns true if this context imports @param origin at any depth, else false. */ virtual bool imports(const DUContext* origin, const CursorInRevision& position = CursorInRevision::invalid()) const; /** * Adds an imported context. * * @param anonymous If this is true, the import will not be registered at the imported context. * This allows du-chain contexts importing without having a write-lock. * @param position Position where the context is imported. This is mainly important in C++ with included files. * * If the context is already imported, only the position is updated. * * @note Be sure to have set the text location first, so that the chain is sorted correctly. */ virtual void addImportedParentContext(DUContext* context, const CursorInRevision& position = CursorInRevision::invalid(), bool anonymous = false, bool temporary = false); /** * Adds an imported context, which may be indirect. * * @warning This is only allowed if this context is _NOT_ a top-context. * @warning When using this mechanism, this context will not be registered as importer to the other one. * @warning The given import _must_ be indirect. * * @return true if the import was already imported before, else false. */ bool addIndirectImport(const DUContext::Import& import); /** * Removes a child context. */ virtual void removeImportedParentContext(DUContext* context); /** * Clear all imported parent contexts. */ virtual void clearImportedParentContexts(); /** * If this is set to true, all declarations that are added to this context * will also be visible in the parent-context. * * They will be visible in the parent using @c findDeclarations(...) and * @c findLocalDeclarations(...), but will not be in the list of @c localDeclarations(...). */ void setPropagateDeclarations(bool propagate); bool isPropagateDeclarations() const; /** * Returns the list of contexts importing this context. * * @note Very expensive, since the importers top-contexts need to be loaded. */ virtual QVector importers() const; /** * Returns the list of indexed importers. * * Cheap, because nothing needs to be loaded. */ KDevVarLengthArray indexedImporters() const; /** * Returns the list of immediate child contexts for this context. * * @note This is expensive. */ QVector childContexts() const; /** * Clears and deletes all child contexts recursively. * * This will not cross file boundaries. */ void deleteChildContextsRecursively(); /** * Resort the child contexts by their range. * * You must call this when you manually change the range of child contexts in a way * that could break the internal range sorting. */ void resortChildContexts(); /** * Returns true if this declaration is accessible through the du-chain, * and thus cannot be edited without a du-chain write lock */ virtual bool inDUChain() const; /** * Retrieve the context which is specialized with the given * @a specialization as seen from the given @a topContext. * * @param specialization the specialization index (see DeclarationId) * @param topContext the top context representing the perspective from which to specialize. * if @p topContext is zero, only already existing specializations are returned, * and if none exists, zero is returned. * @param upDistance upwards distance in the context-structure of the * given specialization-info. This allows specializing children. */ virtual DUContext* specialize(const IndexedInstantiationInformation& specialization, const TopDUContext* topContext, int upDistance = 0); /** * Searches for and returns a declaration with a given @p identifier in this context, which * is currently active at the given text @p position, with the given type @p dataType. * In fact, only items are returned that are declared BEFORE that position. * * @param identifier the identifier of the definition to search for * @param position the text position to search for * @param topContext the top-context from where a completion is triggered. * This is needed so delayed types (templates in C++) can be resolved in the correct context. * @param dataType the type to match, or null for no type matching. * * @returns the requested declaration if one was found, otherwise null. * * @warning this may return declarations which are not in this tree, you may need to lock them too... */ QList findDeclarations(const QualifiedIdentifier& identifier, const CursorInRevision& position = CursorInRevision::invalid(), const AbstractType::Ptr& dataType = AbstractType::Ptr(), const TopDUContext* topContext = nullptr, SearchFlags flags = NoSearchFlags) const; /** * Searches for and returns a declaration with a given @a identifier in this context, which * is currently active at the given text @a position. * * @param identifier the identifier of the definition to search for * @param topContext the top-context from where a completion is triggered. * This is needed so delayed types(templates in C++) can be resolved in the correct context. * @param position the text position to search for * * @returns the requested declaration if one was found, otherwise null. * * @warning this may return declarations which are not in this tree, you may need to lock them too... * * @overload */ QList findDeclarations(const IndexedIdentifier& identifier, const CursorInRevision& position = CursorInRevision::invalid(), const TopDUContext* topContext = nullptr, SearchFlags flags = NoSearchFlags) const; /** * Prefer the version above for speed reasons. */ QList findDeclarations(const Identifier& identifier, const CursorInRevision& position = CursorInRevision::invalid(), const TopDUContext* topContext = nullptr, SearchFlags flags = NoSearchFlags) const; /** * Returns the type of any @a identifier defined in this context, or * null if one is not found. * * Does not search imported parent-contexts(like base-classes). */ QList findLocalDeclarations(const IndexedIdentifier& identifier, const CursorInRevision& position = CursorInRevision::invalid(), const TopDUContext* topContext = nullptr, const AbstractType::Ptr& dataType = AbstractType::Ptr(), SearchFlags flags = NoSearchFlags) const; /** * Prefer the version above for speed reasons. */ QList findLocalDeclarations(const Identifier& identifier, const CursorInRevision& position = CursorInRevision::invalid(), const TopDUContext* topContext = nullptr, const AbstractType::Ptr& dataType = AbstractType::Ptr(), SearchFlags flags = NoSearchFlags) const; /** * Clears all local declarations. * * Does not delete the declaration; the caller assumes ownership. */ QVector clearLocalDeclarations(); /** * Clears all local declarations. * * Deletes these declarations, as the context has ownership. */ void deleteLocalDeclarations(); /** * Returns all local declarations * * @param source A source-context that is needed to instantiate template-declarations in some cases. * If it is zero, that signalizes that missing members should not be instantiated. */ virtual QVector localDeclarations(const TopDUContext* source = nullptr) const; /** * Resort the local declarations by their range. * * You must call this when you manually change the range of declarations in a way * that could break the internal range sorting. */ void resortLocalDeclarations(); /** * Searches for the most specific context for the given cursor @p position in the given @p url. * * @param position the text position to search for * @param parent the parent context to search from (this is mostly an internal detail, but if you only * want to search in a subbranch of the chain, you may specify the parent here) * * @returns the requested context if one was found, otherwise null. */ DUContext* findContext(const CursorInRevision& position, DUContext* parent = nullptr) const; /** * Iterates the tree to see if the provided @a context is a subcontext of this context. * * @returns true if @a context is a subcontext, otherwise false. */ bool parentContextOf(DUContext* context) const; /** * Return a list of all reachable declarations for a given cursor @p position in a given @p url. * * @param position the text position to search for * @param topContext the top-context from where a completion is triggered. * This is needed so delayed types(templates in C++) can be resolved * in the correct context. * @param searchInParents should declarations from parent-contexts be listed? * If false, only declarations from this and imported contexts will be returned. * * The returned declarations are paired together with their inheritance-depth, * which is the count of steps to into other contexts that were needed to find the declaration. * Declarations reached through a namespace- or global-context are offsetted by 1000. * * This also includes Declarations from sub-contexts that were propagated upwards * using @c setPropagateDeclarations(true). * * @returns the requested declarations, if any were active at that location. * Declarations propagated into this context(@c setPropagateDeclarations) are included. */ QList< QPair > allDeclarations(const CursorInRevision& position, const TopDUContext* topContext, bool searchInParents = true) const; /** * Delete and remove all slaves (uses, declarations, definitions, contexts) that are not in the given set. */ void cleanIfNotEncountered(const QSet& encountered); /** * Uses: * A "Use" represents any position in a document where a Declaration is used literally. * For efficiency, since there can be many many uses, they are managed efficiently by * TopDUContext and DUContext. In TopDUContext, the used declarations are registered * and assigned a "Declaration-Index" while calling TopDUContext::indexForUsedDeclaration. * From such a declaration-index, the declaration can be retrieved back by calling * @c TopDUContext::usedDeclarationForIndex. * * The actual uses are stored within DUContext, where each use consists of a range and * the declaration-index of the used declaration. * */ /** * Return a vector of all uses which occur in this context. * * To get the actual declarations, use @c TopDUContext::usedDeclarationForIndex(..) * with the declarationIndex. */ const Use* uses() const; /** * Returns the count of uses that can be accessed through @c uses() */ int usesCount() const; /** * Determines whether the given declaration has uses or not */ static bool declarationHasUses(Declaration* decl); /** * Find the use which encompasses @a position, if one exists. * @return The local index of the use, or -1 */ int findUseAt(const CursorInRevision& position) const; /** * @note The change must not break the ordering */ void changeUseRange(int useIndex, const RangeInRevision& range); /** * Assigns the declaration represented by @p declarationIndex * to the use with index @p useIndex. */ void setUseDeclaration(int useIndex, int declarationIndex); /** * Creates a new use of the declaration given through @p declarationIndex. * The index must be retrieved through @c TopDUContext::indexForUsedDeclaration(..). * * @param range The range of the use * @param insertBefore A hint where in the vector of uses to insert the use. * Must be correct so the order is preserved(ordered by position), * or -1 to automatically choose the position. * * @return Local index of the created use */ int createUse(int declarationIndex, const RangeInRevision& range, int insertBefore = -1); /** * Deletes the use number @p index. * * @param index is the position in the vector of uses, not a used declaration index. */ void deleteUse(int index); /** * Clear and delete all uses in this context. */ virtual void deleteUses(); /** * Recursively delete all uses in this context and all its child-contexts */ virtual void deleteUsesRecursively(); /** * Can be specialized by languages to create a navigation/information-widget. * * Ideally, the widget would be based on @c KDevelop::QuickOpenEmbeddedWidgetInterface * for user-interaction within the quickopen list. * * The returned widget will be owned by the caller. * * @param decl A member-declaration of this context the navigation-widget should be created for. * Zero to create a widget for this context. * @param topContext Top-context from where the navigation-widget is triggered. * In C++, this is needed to resolve forward-declarations. * @param htmlPrefix Html-formatted text that should be prepended before any information shown by this widget * @param htmlSuffix Html-formatted text that should be appended to any information shown by this widget * * Can return zero which disables the navigation widget. * * 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* createNavigationWidget(Declaration* decl = nullptr, TopDUContext* topContext = nullptr, const QString& htmlPrefix = QString(), const QString& htmlSuffix = QString(), AbstractNavigationWidget::DisplayHints hints = AbstractNavigationWidget::NoHints) const; enum { Identity = 2 }; /** * Represents multiple qualified identifiers in a way that is better * to manipulate and allows applying namespace-aliases or -imports easily. * * A SearchItem generally represents a tree of identifiers, and represents * all the qualified identifiers that can be constructed by walking * along the tree starting at an arbitrary root-node into the depth using the "next" pointers. * * The insertion order in the hierarchy determines the order of the represented list. */ struct KDEVPLATFORMLANGUAGE_EXPORT SearchItem : public QSharedData { typedef QExplicitlySharedDataPointer Ptr; typedef KDevVarLengthArray PtrList; /** * Constructs a representation of the given @p id qualified identifier, * starting at its index @p start. * * @param nextItem is set as next item to the last item in the chain */ - SearchItem(const QualifiedIdentifier& id, const Ptr& nextItem = Ptr(), int start = 0); + explicit SearchItem(const QualifiedIdentifier& id, const Ptr& nextItem = Ptr(), int start = 0); /** * Constructs a representation of the given @p id qualified identifier, * starting at its index @p start. * * @param nextItems is set as next item to the last item in the chain */ SearchItem(const QualifiedIdentifier& id, const PtrList& nextItems, int start = 0); SearchItem(bool explicitlyGlobal, const IndexedIdentifier& id, const PtrList& nextItems); SearchItem(bool explicitlyGlobal, const IndexedIdentifier& id, const Ptr& nextItem); bool isEmpty() const; bool hasNext() const; /** * Appends the given item to every item that can be reached from this item, * and not only to the end items. * * The effect to search is that the given item is searched with all prefixes * contained in this earch-item prepended. * * @warning This changes all contained sub-nodes, but they can be shared with * other SearchItem trees. You should not use this on SearchItem trees * that have shared nodes with other trees. * * @note These functions ignore explicitly global items. */ void addToEachNode(const Ptr& item); void addToEachNode(const PtrList& items); /** * Returns true if the given identifier matches one of the identifiers * represented by this SearchItem. Does not respect the explicitlyGlobal flag */ bool match(const QualifiedIdentifier& id, int offset = 0) const; /** * @note expensive */ QList toList(const QualifiedIdentifier& prefix = QualifiedIdentifier()) const; void addNext(const Ptr& other); bool isExplicitlyGlobal; IndexedIdentifier identifier; PtrList next; }; ///@todo Should be protected, moved here temporarily until I have figured ///out why the gcc 4.1.3 fails in cppducontext.h:212, which should work (within kdevelop) /// Declaration search implementation typedef QList DeclarationList; /** * This is a more complex interface to the declaration search engine. * * Always prefer @c findDeclarations(..) when possible. * * Advantage of this interface: * - You can search multiple identifiers at one time. * However, those should be aliased identifiers for one single item, because * search might stop as soon as one item is found. * - The source top-context is needed to correctly resolve template-parameters * * @param position A valid position, if in doubt use textRange().end() * * @warning @p position must be valid! * * @param depth Depth of the search in parents. This is used to prevent endless * recursions in endless import loops. * * * @return whether the search was successful. If it is false, it had to be stopped * for special reasons (like some flags) */ virtual bool findDeclarationsInternal(const SearchItem::PtrList& identifiers, const CursorInRevision& position, const AbstractType::Ptr& dataType, DeclarationList& ret, const TopDUContext* source, SearchFlags flags, uint depth ) const; /** * Returns the qualified identifier @p id with all aliases (for example namespace imports) applied * * @example: If the namespace 'Foo' is imported, and id is 'Bar', * then the returned list is 'Bar' and 'Foo::Bar' */ QList fullyApplyAliases(const QualifiedIdentifier& id, const TopDUContext* source) const; protected: /** * After one scope was searched, this function is asked whether more * results should be collected. Override it, for example to collect overloaded functions. * * The default-implementation returns true as soon as @p decls is not empty. */ virtual bool foundEnough( const DeclarationList& decls , SearchFlags flags ) const; /** * Merges definitions and their inheritance-depth up all branches of the * definition-use chain into one hash. * * This includes declarations propagated from sub-contexts. * * @param hadContexts is used to count together all contexts that already were * visited, so they are not visited again. */ virtual void mergeDeclarationsInternal(QList< QPair >& definitions, const CursorInRevision& position, QHash& hadContexts, const TopDUContext* source, bool searchInParents = true, int currentDepth = 0) const; void findLocalDeclarationsInternal(const Identifier& identifier, const CursorInRevision & position, const AbstractType::Ptr& dataType, DeclarationList& ret, const TopDUContext* source, SearchFlags flags ) const; virtual void findLocalDeclarationsInternal(const IndexedIdentifier& identifier, const CursorInRevision & position, const AbstractType::Ptr& dataType, DeclarationList& ret, const TopDUContext* source, SearchFlags flags ) const; /** * Applies namespace-imports and namespace-aliases and returns * possible absolute identifiers that need to be searched. * * @param targetIdentifiers will be filled with all identifiers that should * be searched for, instead of identifier. * @param onlyImports if this is true, namespace-aliases will not be respected, * but only imports. This is faster. */ void applyAliases(const SearchItem::PtrList& identifiers, SearchItem::PtrList& targetIdentifiers, const CursorInRevision& position, bool canBeNamespace, bool onlyImports = false) const; /** * Applies the aliases that need to be applied when moving the search * from this context up to the parent-context. * * The default-implementation adds a set of identifiers with the own local * identifier prefixed, if this is a namespace. * * For C++, this is needed when searching out of a namespace, so the item * can be found within that namespace in another place. */ virtual void applyUpwardsAliases(SearchItem::PtrList& identifiers, const TopDUContext* source) const; DUContext(DUContextData& dd, const RangeInRevision& range, DUContext* parent = nullptr, bool anonymous = false); /** * Just uses the data from the given context. Doesn't copy or change anything, * and the data will not be deleted on this contexts destruction. */ DUContext(DUContext& useDataFrom); /** * Whether this context, or any of its parent contexts, has been inserte * anonymously into the du-chain * * @see DUContext::DUContext */ bool isAnonymous() const; /** * This is called whenever the search needs to do the decision whether it * should be continued in the parent context. * * It is not called when the DontSearchInParent flag is set. Else this should * be overridden to do language-specific logic. * * The default implementation returns false if the flag InImportedParentContext is set. */ virtual bool shouldSearchInParent(SearchFlags flags) const; private: void rebuildDynamicData(DUContext* parent, uint ownIndex) override; friend class TopDUContext; friend class IndexedDUContext; friend class LocalIndexedDUContext; friend class TopDUContextDynamicData; DUCHAIN_DECLARE_DATA(DUContext) class DUContextDynamicData* m_dynamicData; }; /** * This is the identifier that can be used to search namespace-import declarations, * and should be used to store namespace-imports. * * It is stored statically for performance-reasons, so it doesn't need to be * constructed every time it is used. * * @see NamespaceAliasDeclaration. */ KDEVPLATFORMLANGUAGE_EXPORT const Identifier& globalImportIdentifier(); /** * This is the identifier that can be used to search namespace-alias declarations. * * It is stored statically for performance-reasons, so it doesn't need to be * constructed every time it is used. * * @see NamespaceAliasDeclaration. */ KDEVPLATFORMLANGUAGE_EXPORT const Identifier& globalAliasIdentifier(); /** * This is the identifier that can be used to search namespace-import declarations, * and should be used to store namespace-imports. * * It is stored statically for performance-reasons, so it doesn't need to be * constructed every time it is used. * * @see NamespaceAliasDeclaration. */ KDEVPLATFORMLANGUAGE_EXPORT const IndexedIdentifier& globalIndexedImportIdentifier(); /** * This is the identifier that can be used to search namespace-alias declarations. * * It is stored statically for performance-reasons, so it doesn't need to be * constructed every time it is used. * * @see NamespaceAliasDeclaration. */ KDEVPLATFORMLANGUAGE_EXPORT const IndexedIdentifier& globalIndexedAliasIdentifier(); /** * Collects all uses of the given @p declarationIndex */ KDEVPLATFORMLANGUAGE_EXPORT QList allUses(DUContext* context, int declarationIndex, bool noEmptyRanges = false); } Q_DECLARE_TYPEINFO(KDevelop::DUContext::Import, Q_MOVABLE_TYPE); KDEVPLATFORMLANGUAGE_EXPORT QDebug operator<<(QDebug dbg, const KDevelop::DUContext::Import& import); #endif // KDEVPLATFORM_DUCONTEXT_H diff --git a/language/duchain/ducontextdynamicdata.h b/language/duchain/ducontextdynamicdata.h index b4100a22e..4676889ad 100644 --- a/language/duchain/ducontextdynamicdata.h +++ b/language/duchain/ducontextdynamicdata.h @@ -1,169 +1,169 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2006 Hamish Rodda * * Copyright 2007-2008 David Nolden * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU 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 DUCONTEXTDYNAMICDATA_H #define DUCONTEXTDYNAMICDATA_H #include "ducontextdata.h" namespace KDevelop { ///This class contains data that is only runtime-dependant and does not need to be stored to disk class DUContextDynamicData { private: inline const DUContextData* d_func() const { return m_context->d_func(); } inline DUContextData* d_func_dynamic() { return m_context->d_func_dynamic(); } static inline const DUContextData* ctx_d_func(DUContext* ctx) { return ctx->d_func(); } static inline DUContextDynamicData* ctx_dynamicData(DUContext* ctx) { return ctx->m_dynamicData; } public: explicit DUContextDynamicData( DUContext* ); DUContextPointer m_parentContext; TopDUContext* m_topContext; uint m_indexInTopContext; //Index of this DUContext in the top-context DUContext* m_context; // cache of unserialized child contexts QVector m_childContexts; // cache of unserialized local declarations QVector m_localDeclarations; /** * Adds a child context. * * \note Be sure to have set the text location first, so that * the chain is sorted correctly. */ void addChildContext(DUContext* context); /**Removes the context from childContexts * @return Whether a context was removed * */ bool removeChildContext(DUContext* context); void addImportedChildContext( DUContext * context ); void removeImportedChildContext( DUContext * context ); void addDeclaration(Declaration* declaration); /**Removes the declaration from localDeclarations * @return Whether a declaration was removed * */ bool removeDeclaration(Declaration* declaration); //Files the scope identifier into target void scopeIdentifier(bool includeClasses, QualifiedIdentifier& target) const; //Iterates through all visible declarations within a given context, including the ones propagated from sub-contexts class VisibleDeclarationIterator { public: struct StackEntry { - StackEntry(const DUContextDynamicData* data = nullptr) + explicit StackEntry(const DUContextDynamicData* data = nullptr) : data(data) , index(0) , nextChild(0) { } const DUContextDynamicData* data; int index; uint nextChild; }; explicit VisibleDeclarationIterator(const DUContextDynamicData* data) : current(data) { toValidPosition(); } inline Declaration* operator*() const { return current.data ? current.data->m_localDeclarations.value(current.index) : nullptr; } inline VisibleDeclarationIterator& operator++() { ++current.index; toValidPosition(); return *this; } inline operator bool() const { return current.data && !current.data->m_localDeclarations.isEmpty(); } // Moves the cursor to the next valid position, from an invalid one void toValidPosition() { if (!current.data || current.index < current.data->m_localDeclarations.size()) { // still valid return; } do { // Check if we can proceed into a propagating child-context for (int a = current.nextChild; a < current.data->m_childContexts.size(); ++a) { DUContext* child = current.data->m_childContexts[a]; if(ctx_d_func(child)->m_propagateDeclarations) { current.nextChild = a+1; stack.append(current); current = StackEntry(ctx_dynamicData(child)); toValidPosition(); return; } } //Go up and into the next valid context if (stack.isEmpty()) { current = StackEntry(); return; } current = stack.back(); stack.pop_back(); } while(true); } StackEntry current; KDevVarLengthArray stack; }; /** * Returns true if this context is imported by the given one, on any level. * */ bool imports(const DUContext* context, const TopDUContext* source, QSet* recursionGuard) const; }; } Q_DECLARE_TYPEINFO(KDevelop::DUContextDynamicData::VisibleDeclarationIterator::StackEntry, Q_MOVABLE_TYPE); #endif diff --git a/language/duchain/identifier.cpp b/language/duchain/identifier.cpp index b53521d0c..48138620d 100644 --- a/language/duchain/identifier.cpp +++ b/language/duchain/identifier.cpp @@ -1,1604 +1,1604 @@ /* This file is part of KDevelop Copyright 2006 Hamish Rodda Copyright 2007-2008 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "identifier.h" #include #include "stringhelpers.h" #include "appendedlist_static.h" #include "serialization/itemrepository.h" #include "util/kdevhash.h" #include "util/debug.h" #include #include #define ifDebug(x) namespace KDevelop { template class IdentifierPrivate { public: IdentifierPrivate() : m_unique(0) , m_refCount(0) , m_hash(0) { } template - IdentifierPrivate(const IdentifierPrivate& rhs) + explicit IdentifierPrivate(const IdentifierPrivate& rhs) : m_unique(rhs.m_unique) , m_identifier(rhs.m_identifier) , m_refCount(0) , m_hash(rhs.m_hash) { copyListsFrom(rhs); } ~IdentifierPrivate() { templateIdentifiersList.free(const_cast(templateIdentifiers())); } //Flags the stored hash-value invalid void clearHash() { //This is always called on an object private to an Identifier, so there is no threading-problem. Q_ASSERT(dynamic); m_hash = 0; } uint hash() const { // Since this only needs reading and the data needs not to be private, this may be called by // multiple threads simultaneously, so computeHash() must be thread-safe. if( !m_hash && dynamic ) computeHash(); return m_hash; } int m_unique; IndexedString m_identifier; uint m_refCount; START_APPENDED_LISTS_STATIC(IdentifierPrivate) APPENDED_LIST_FIRST_STATIC(IndexedTypeIdentifier, templateIdentifiers) END_APPENDED_LISTS_STATIC(templateIdentifiers) uint itemSize() const { return sizeof(IdentifierPrivate) + lastOffsetBehind(); } void computeHash() const { Q_ASSERT(dynamic); //this must stay thread-safe(may be called by multiple threads at a time) //The thread-safety is given because all threads will have the same result, and it will only be written once at the end. KDevHash kdevhash; kdevhash << m_identifier.hash() << m_unique; FOREACH_FUNCTION_STATIC(const IndexedTypeIdentifier& templateIdentifier, templateIdentifiers) kdevhash << templateIdentifier.hash(); m_hash = kdevhash; } mutable uint m_hash; }; typedef IdentifierPrivate DynamicIdentifierPrivate; typedef IdentifierPrivate ConstantIdentifierPrivate; struct IdentifierItemRequest { IdentifierItemRequest(const DynamicIdentifierPrivate& identifier) : m_identifier(identifier) { identifier.hash(); //Make sure the hash is valid by calling this } enum { AverageSize = sizeof(IdentifierPrivate)+4 }; //Should return the hash-value associated with this request(For example the hash of a string) uint hash() const { return m_identifier.hash(); } //Should return the size of an item created with createItem uint itemSize() const { return m_identifier.itemSize(); } //Should create an item where the information of the requested item is permanently stored. The pointer //@param item equals an allocated range with the size of itemSize(). void createItem(ConstantIdentifierPrivate* item) const { new (item) ConstantIdentifierPrivate(m_identifier); } static bool persistent(const ConstantIdentifierPrivate* item) { return (bool)item->m_refCount; } static void destroy(ConstantIdentifierPrivate* item, AbstractItemRepository&) { item->~ConstantIdentifierPrivate(); } //Should return whether the here requested item equals the given item bool equals(const ConstantIdentifierPrivate* item) const { return item->m_hash == m_identifier.m_hash && item->m_unique == m_identifier.m_unique && item->m_identifier == m_identifier.m_identifier && m_identifier.listsEqual(*item); } const DynamicIdentifierPrivate& m_identifier; }; using IdentifierRepository = RepositoryManager< ItemRepository, false>; static IdentifierRepository& identifierRepository() { static IdentifierRepository identifierRepositoryObject(QStringLiteral("Identifier Repository")); return identifierRepositoryObject; } static uint emptyConstantIdentifierPrivateIndex() { static const uint index = identifierRepository()->index(DynamicIdentifierPrivate()); return index; } static const ConstantIdentifierPrivate* emptyConstantIdentifierPrivate() { static const ConstantIdentifierPrivate item; return &item; } bool IndexedIdentifier::isEmpty() const { return index == emptyConstantIdentifierPrivateIndex(); } /** * Before something is modified in QualifiedIdentifierPrivate, it must be made sure that * it is private to the QualifiedIdentifier it is used in(@see QualifiedIdentifier::prepareWrite) */ template class QualifiedIdentifierPrivate { public: QualifiedIdentifierPrivate() : m_explicitlyGlobal(false) , m_isExpression(false) , m_hash(0) , m_refCount(0) { } template - QualifiedIdentifierPrivate(const QualifiedIdentifierPrivate& rhs) + explicit QualifiedIdentifierPrivate(const QualifiedIdentifierPrivate& rhs) : m_explicitlyGlobal(rhs.m_explicitlyGlobal) , m_isExpression(rhs.m_isExpression) , m_hash(rhs.m_hash) , m_refCount(0) { copyListsFrom(rhs); } ~QualifiedIdentifierPrivate() { identifiersList.free(const_cast(identifiers())); } bool m_explicitlyGlobal:1; bool m_isExpression:1; mutable uint m_hash; uint m_refCount; START_APPENDED_LISTS_STATIC(QualifiedIdentifierPrivate) APPENDED_LIST_FIRST_STATIC(IndexedIdentifier, identifiers) END_APPENDED_LISTS_STATIC(identifiers) uint itemSize() const { return sizeof(QualifiedIdentifierPrivate) + lastOffsetBehind(); } //Constructs m_identifiers void splitIdentifiers( const QString& str, int start ) { Q_ASSERT(dynamic); uint currentStart = start; while( currentStart < (uint)str.length() ) { identifiersList.append(IndexedIdentifier(Identifier( str, currentStart, ¤tStart ))); while( currentStart < (uint)str.length() && (str[currentStart] == ' ' ) ) ++currentStart; currentStart += 2; //Skip "::" } } inline void clearHash() const { m_hash = 0; } uint hash() const { if( m_hash == 0 ) { KDevHash hash; quint32 bitfields = m_explicitlyGlobal | (m_isExpression << 1); hash << bitfields << identifiersSize(); FOREACH_FUNCTION_STATIC( const IndexedIdentifier& identifier, identifiers ) { hash << identifier.getIndex(); } m_hash = hash; } return m_hash; } }; typedef QualifiedIdentifierPrivate DynamicQualifiedIdentifierPrivate; typedef QualifiedIdentifierPrivate ConstantQualifiedIdentifierPrivate; struct QualifiedIdentifierItemRequest { QualifiedIdentifierItemRequest(const DynamicQualifiedIdentifierPrivate& identifier) : m_identifier(identifier) { identifier.hash(); //Make sure the hash is valid by calling this } enum { AverageSize = sizeof(QualifiedIdentifierPrivate)+8 }; //Should return the hash-value associated with this request(For example the hash of a string) uint hash() const { return m_identifier.hash(); } //Should return the size of an item created with createItem uint itemSize() const { return m_identifier.itemSize(); } /** * Should create an item where the information of the requested item is permanently stored. The pointer * @param item equals an allocated range with the size of itemSize(). */ void createItem(ConstantQualifiedIdentifierPrivate* item) const { Q_ASSERT(shouldDoDUChainReferenceCounting(item)); Q_ASSERT(shouldDoDUChainReferenceCounting(((char*)item) + (itemSize()-1))); new (item) ConstantQualifiedIdentifierPrivate(m_identifier); } static bool persistent(const ConstantQualifiedIdentifierPrivate* item) { return (bool)item->m_refCount; } static void destroy(ConstantQualifiedIdentifierPrivate* item, AbstractItemRepository&) { Q_ASSERT(shouldDoDUChainReferenceCounting(item)); item->~ConstantQualifiedIdentifierPrivate(); } //Should return whether the here requested item equals the given item bool equals(const ConstantQualifiedIdentifierPrivate* item) const { return item->m_explicitlyGlobal == m_identifier.m_explicitlyGlobal && item->m_isExpression == m_identifier.m_isExpression && item->m_hash == m_identifier.m_hash && m_identifier.listsEqual(*item); } const DynamicQualifiedIdentifierPrivate& m_identifier; }; using QualifiedIdentifierRepository = RepositoryManager< ItemRepository, false>; static QualifiedIdentifierRepository& qualifiedidentifierRepository() { static QualifiedIdentifierRepository repo(QStringLiteral("Qualified Identifier Repository"), 1, [] () -> AbstractRepositoryManager* { return &identifierRepository(); }); return repo; } static uint emptyConstantQualifiedIdentifierPrivateIndex() { static const uint index = qualifiedidentifierRepository()->index(DynamicQualifiedIdentifierPrivate()); return index; } static const ConstantQualifiedIdentifierPrivate* emptyConstantQualifiedIdentifierPrivate() { static const ConstantQualifiedIdentifierPrivate item; return &item; } Identifier::Identifier(const Identifier& rhs) { rhs.makeConstant(); cd = rhs.cd; m_index = rhs.m_index; } Identifier::Identifier(uint index) : m_index(index) { Q_ASSERT(m_index); cd = identifierRepository()->itemFromIndex(index); } Identifier::Identifier(const IndexedString& str) { if (str.isEmpty()) { m_index = emptyConstantIdentifierPrivateIndex(); cd = emptyConstantIdentifierPrivate(); } else { m_index = 0; dd = new IdentifierPrivate; dd->m_identifier = str; } } Identifier::Identifier(const QString& id, uint start, uint* takenRange) { if (id.isEmpty()) { m_index = emptyConstantIdentifierPrivateIndex(); cd = emptyConstantIdentifierPrivate(); return; } m_index = 0; dd = new IdentifierPrivate; ///Extract template-parameters ParamIterator paramIt(QStringLiteral("<>:"), id, start); dd->m_identifier = IndexedString(paramIt.prefix().trimmed()); while( paramIt ) { appendTemplateIdentifier( IndexedTypeIdentifier(IndexedQualifiedIdentifier(QualifiedIdentifier(*paramIt))) ); ++paramIt; } if( takenRange ) *takenRange = paramIt.position(); } Identifier::Identifier() : m_index(emptyConstantIdentifierPrivateIndex()) , cd(emptyConstantIdentifierPrivate()) { } Identifier& Identifier::operator=(const Identifier& rhs) { if(dd == rhs.dd && cd == rhs.cd) return *this; if(!m_index) delete dd; dd = nullptr; rhs.makeConstant(); cd = rhs.cd; m_index = rhs.m_index; Q_ASSERT(cd); return *this; } Identifier::Identifier(Identifier&& rhs) Q_DECL_NOEXCEPT : m_index(rhs.m_index) { if (m_index) { cd = rhs.cd; } else { dd = rhs.dd; } rhs.cd = emptyConstantIdentifierPrivate(); rhs.m_index = emptyConstantIdentifierPrivateIndex(); } Identifier& Identifier::operator=(Identifier&& rhs) Q_DECL_NOEXCEPT { if(dd == rhs.dd && cd == rhs.cd) return *this; if (!m_index) { delete dd; dd = nullptr; } m_index = rhs.m_index; if (m_index) { cd = rhs.cd; } else { dd = rhs.dd; } rhs.cd = emptyConstantIdentifierPrivate(); rhs.m_index = emptyConstantIdentifierPrivateIndex(); return *this; } Identifier::~Identifier() { if(!m_index) delete dd; } bool Identifier::nameEquals(const Identifier& rhs) const { return identifier() == rhs.identifier(); } uint Identifier::hash() const { if(!m_index) return dd->hash(); else return cd->hash(); } bool Identifier::isEmpty() const { if(!m_index) return dd->m_identifier.isEmpty() && dd->m_unique == 0 && dd->templateIdentifiersSize() == 0; else return cd->m_identifier.isEmpty() && cd->m_unique == 0 && cd->templateIdentifiersSize() == 0; } Identifier Identifier::unique(int token) { Identifier ret; ret.setUnique(token); return ret; } bool Identifier::isUnique() const { if(!m_index) return dd->m_unique; else return cd->m_unique; } int Identifier::uniqueToken() const { if(!m_index) return dd->m_unique; else return cd->m_unique; } void Identifier::setUnique(int token) { if (token != uniqueToken()) { prepareWrite(); dd->m_unique = token; } } const IndexedString Identifier::identifier() const { if(!m_index) return dd->m_identifier; else return cd->m_identifier; } void Identifier::setIdentifier(const QString& identifier) { IndexedString id(identifier); if (id != this->identifier()) { prepareWrite(); dd->m_identifier = std::move(id); } } void Identifier::setIdentifier(const IndexedString& identifier) { if (identifier != this->identifier()) { prepareWrite(); dd->m_identifier = identifier; } } IndexedTypeIdentifier Identifier::templateIdentifier(int num) const { if(!m_index) return dd->templateIdentifiers()[num]; else return cd->templateIdentifiers()[num]; } uint Identifier::templateIdentifiersCount() const { if(!m_index) return dd->templateIdentifiersSize(); else return cd->templateIdentifiersSize(); } void Identifier::appendTemplateIdentifier(const IndexedTypeIdentifier& identifier) { prepareWrite(); dd->templateIdentifiersList.append(identifier); } void Identifier::clearTemplateIdentifiers() { prepareWrite(); dd->templateIdentifiersList.clear(); } uint Identifier::index() const { makeConstant(); Q_ASSERT(m_index); return m_index; } bool Identifier::inRepository() const { return m_index; } void Identifier::setTemplateIdentifiers(const QList& templateIdentifiers) { prepareWrite(); dd->templateIdentifiersList.clear(); foreach(const IndexedTypeIdentifier& id, templateIdentifiers) dd->templateIdentifiersList.append(id); } QString Identifier::toString(IdentifierStringFormattingOptions options) const { QString ret = identifier().str(); if (!options.testFlag(RemoveTemplateInformation) && templateIdentifiersCount()) { ret.append("< "); for (uint i = 0; i < templateIdentifiersCount(); ++i) { ret.append(templateIdentifier(i).toString(options)); if (i != templateIdentifiersCount() - 1) ret.append(", "); } ret.append(" >"); } return ret; } bool Identifier::operator==(const Identifier& rhs) const { return index() == rhs.index(); } bool Identifier::operator!=(const Identifier& rhs) const { return !operator==(rhs); } uint QualifiedIdentifier::index() const { makeConstant(); Q_ASSERT(m_index); return m_index; } void Identifier::makeConstant() const { if(m_index) return; m_index = identifierRepository()->index( IdentifierItemRequest(*dd) ); delete dd; cd = identifierRepository()->itemFromIndex( m_index ); } void Identifier::prepareWrite() { if(m_index) { const IdentifierPrivate* oldCc = cd; dd = new IdentifierPrivate; dd->m_hash = oldCc->m_hash; dd->m_unique = oldCc->m_unique; dd->m_identifier = oldCc->m_identifier; dd->copyListsFrom(*oldCc); m_index = 0; } dd->clearHash(); } bool QualifiedIdentifier::inRepository() const { if(m_index) return true; else return (bool)qualifiedidentifierRepository()->findIndex( QualifiedIdentifierItemRequest(*dd) ); } QualifiedIdentifier::QualifiedIdentifier(uint index) : m_index(index) , cd( qualifiedidentifierRepository()->itemFromIndex(index) ) { } QualifiedIdentifier::QualifiedIdentifier(const QString& id, bool isExpression) { if (id.isEmpty()) { m_index = emptyConstantQualifiedIdentifierPrivateIndex(); cd = emptyConstantQualifiedIdentifierPrivate(); return; } m_index = 0; dd = new DynamicQualifiedIdentifierPrivate; if(isExpression) { setIsExpression(true); if(!id.isEmpty()) { //Prevent tokenization, since we may lose information there Identifier finishedId; finishedId.setIdentifier(id); push(finishedId); } }else{ if (id.startsWith(QStringLiteral("::"))) { dd->m_explicitlyGlobal = true; dd->splitIdentifiers(id, 2); } else { dd->m_explicitlyGlobal = false; dd->splitIdentifiers(id, 0); } } } QualifiedIdentifier::QualifiedIdentifier(const Identifier& id) { if (id.isEmpty()) { m_index = emptyConstantQualifiedIdentifierPrivateIndex(); cd = emptyConstantQualifiedIdentifierPrivate(); return; } m_index = 0; dd = new DynamicQualifiedIdentifierPrivate; if (id.dd->m_identifier.str().isEmpty()) { dd->m_explicitlyGlobal = true; } else { dd->m_explicitlyGlobal = false; dd->identifiersList.append(IndexedIdentifier(id)); } } QualifiedIdentifier::QualifiedIdentifier() : m_index(emptyConstantQualifiedIdentifierPrivateIndex()) , cd(emptyConstantQualifiedIdentifierPrivate()) { } QualifiedIdentifier::QualifiedIdentifier(const QualifiedIdentifier& id) { if(id.m_index) { m_index = id.m_index; cd = id.cd; }else{ m_index = 0; dd = new QualifiedIdentifierPrivate(*id.dd); } } QualifiedIdentifier::QualifiedIdentifier(QualifiedIdentifier&& rhs) Q_DECL_NOEXCEPT : m_index(rhs.m_index) { if (m_index) { cd = rhs.cd; } else { dd = rhs.dd; } rhs.m_index = emptyConstantQualifiedIdentifierPrivateIndex(); rhs.cd = emptyConstantQualifiedIdentifierPrivate(); } QualifiedIdentifier& QualifiedIdentifier::operator=(const QualifiedIdentifier& rhs) { if(dd == rhs.dd && cd == rhs.cd) return *this; if(!m_index) delete dd; rhs.makeConstant(); cd = rhs.cd; m_index = rhs.m_index; return *this; } QualifiedIdentifier& QualifiedIdentifier::operator=(QualifiedIdentifier&& rhs) Q_DECL_NOEXCEPT { if(!m_index) delete dd; m_index = rhs.m_index; if (m_index) { cd = rhs.cd; } else { dd = rhs.dd; } rhs.cd = emptyConstantQualifiedIdentifierPrivate(); rhs.m_index = emptyConstantQualifiedIdentifierPrivateIndex(); return *this; } QualifiedIdentifier::~QualifiedIdentifier() { if(!m_index) delete dd; } QStringList QualifiedIdentifier::toStringList(IdentifierStringFormattingOptions options) const { QStringList ret; ret.reserve(explicitlyGlobal() + count()); if (explicitlyGlobal()) ret.append(QString()); if(m_index) { FOREACH_FUNCTION_STATIC(const IndexedIdentifier& index, cd->identifiers) ret << index.identifier().toString(options); }else{ FOREACH_FUNCTION_STATIC(const IndexedIdentifier& index, dd->identifiers) ret << index.identifier().toString(options); } return ret; } QString QualifiedIdentifier::toString(IdentifierStringFormattingOptions options) const { const QString doubleColon = QStringLiteral("::"); QString ret; if( !options.testFlag(RemoveExplicitlyGlobalPrefix) && explicitlyGlobal() ) ret = doubleColon; bool first = true; if(m_index) { FOREACH_FUNCTION_STATIC(const IndexedIdentifier& index, cd->identifiers) { if( !first ) ret += doubleColon; else first = false; ret += index.identifier().toString(options); } }else{ FOREACH_FUNCTION_STATIC(const IndexedIdentifier& index, dd->identifiers) { if( !first ) ret += doubleColon; else first = false; ret += index.identifier().toString(options); } } return ret; } QualifiedIdentifier QualifiedIdentifier::merge(const QualifiedIdentifier& base) const { QualifiedIdentifier ret(base); ret.push(*this); return ret; } QualifiedIdentifier QualifiedIdentifier::operator+(const QualifiedIdentifier& rhs) const { return rhs.merge(*this); } QualifiedIdentifier& QualifiedIdentifier::operator+=(const QualifiedIdentifier& rhs) { push(rhs); return *this; } QualifiedIdentifier QualifiedIdentifier::operator+(const Identifier& rhs) const { QualifiedIdentifier ret(*this); ret.push(rhs); return ret; } QualifiedIdentifier& QualifiedIdentifier::operator+=(const Identifier& rhs) { push(rhs); return *this; } QualifiedIdentifier QualifiedIdentifier::operator+(const IndexedIdentifier& rhs) const { QualifiedIdentifier ret(*this); ret.push(rhs); return ret; } QualifiedIdentifier& QualifiedIdentifier::operator+=(const IndexedIdentifier& rhs) { push(rhs); return *this; } bool QualifiedIdentifier::isExpression() const { if(m_index) return cd->m_isExpression; else return dd->m_isExpression; } void QualifiedIdentifier::setIsExpression(bool is) { if (is != isExpression()) { prepareWrite(); dd->m_isExpression = is; } } bool QualifiedIdentifier::explicitlyGlobal() const { // True if started with "::" if(m_index) return cd->m_explicitlyGlobal; else return dd->m_explicitlyGlobal; } void QualifiedIdentifier::setExplicitlyGlobal(bool eg) { if (eg != explicitlyGlobal()) { prepareWrite(); dd->m_explicitlyGlobal = eg; } } bool QualifiedIdentifier::sameIdentifiers(const QualifiedIdentifier& rhs) const { if(m_index && rhs.m_index) return cd->listsEqual(*rhs.cd); else if(m_index && !rhs.m_index) return cd->listsEqual(*rhs.dd); else if(!m_index && !rhs.m_index) return dd->listsEqual(*rhs.dd); else return dd->listsEqual(*rhs.cd); } bool QualifiedIdentifier::operator==(const QualifiedIdentifier& rhs) const { if( cd == rhs.cd ) return true; return hash() == rhs.hash() && sameIdentifiers(rhs); } bool QualifiedIdentifier::operator!=(const QualifiedIdentifier& rhs) const { return !operator==(rhs); } bool QualifiedIdentifier::beginsWith(const QualifiedIdentifier& other) const { uint c = count(); uint oc = other.count(); for (uint i = 0; i < c && i < oc; ++i) if (at(i) == other.at(i)) { continue; } else { return false; } return true; } struct Visitor { Visitor(KDevVarLengthArray& target, uint hash) : target(target) , hash(hash) { } bool operator()(const ConstantQualifiedIdentifierPrivate* item, uint index) const { if(item->m_hash == hash) target.append(QualifiedIdentifier(index)); return true; } KDevVarLengthArray& target; const uint hash; }; uint QualifiedIdentifier::hash() const { if(m_index) return cd->hash(); else return dd->hash(); } uint qHash(const IndexedTypeIdentifier& id) { return id.hash(); } uint qHash(const QualifiedIdentifier& id) { return id.hash(); } uint qHash(const Identifier& id) { return id.hash(); } bool QualifiedIdentifier::isQualified() const { return count() > 1 || explicitlyGlobal(); } void QualifiedIdentifier::push(const Identifier& id) { if(id.isEmpty()) return; push(IndexedIdentifier(id)); } void QualifiedIdentifier::push(const IndexedIdentifier& id) { if (id.isEmpty()) { return; } prepareWrite(); dd->identifiersList.append(id); } void QualifiedIdentifier::push(const QualifiedIdentifier& id) { if (id.isEmpty()) { return; } prepareWrite(); if (id.m_index) { dd->identifiersList.append(id.cd->identifiers(), id.cd->identifiersSize()); } else { dd->identifiersList.append(id.dd->identifiers(), id.dd->identifiersSize()); } if (id.explicitlyGlobal()) { setExplicitlyGlobal(true); } } void QualifiedIdentifier::pop() { prepareWrite(); if(!dd->identifiersSize()) return; dd->identifiersList.resize(dd->identifiersList.size()-1); } void QualifiedIdentifier::clear() { prepareWrite(); dd->identifiersList.clear(); dd->m_explicitlyGlobal = false; dd->m_isExpression = false; } bool QualifiedIdentifier::isEmpty() const { if(m_index) return cd->identifiersSize() == 0; else return dd->identifiersSize() == 0; } int QualifiedIdentifier::count() const { if(m_index) return cd->identifiersSize(); else return dd->identifiersSize(); } Identifier QualifiedIdentifier::first() const { return indexedFirst().identifier(); } IndexedIdentifier QualifiedIdentifier::indexedFirst() const { if( (m_index && cd->identifiersSize() == 0) || (!m_index && dd->identifiersSize() == 0) ) return IndexedIdentifier(); else return indexedAt(0); } Identifier QualifiedIdentifier::last() const { return indexedLast().identifier(); } IndexedIdentifier QualifiedIdentifier::indexedLast() const { uint c = count(); if(c) return indexedAt(c-1); else return IndexedIdentifier(); } Identifier QualifiedIdentifier::top() const { return last(); } QualifiedIdentifier QualifiedIdentifier::mid(int pos, int len) const { QualifiedIdentifier ret; if( pos == 0 ) ret.setExplicitlyGlobal(explicitlyGlobal()); int cnt = (int)count(); if( len == -1 ) len = cnt - pos; if( pos+len > cnt ) len -= cnt - (pos+len); for( int a = pos; a < pos+len; a++ ) ret.push(at(a)); return ret; } Identifier QualifiedIdentifier::at(int i) const { return indexedAt(i).identifier(); } IndexedIdentifier QualifiedIdentifier::indexedAt(int i) const { if (m_index) { Q_ASSERT(i >= 0 && i < (int)cd->identifiersSize()); return cd->identifiers()[i]; } else { Q_ASSERT(i >= 0 && i < (int)dd->identifiersSize()); return dd->identifiers()[i]; } } void QualifiedIdentifier::makeConstant() const { if(m_index) return; m_index = qualifiedidentifierRepository()->index( QualifiedIdentifierItemRequest(*dd) ); delete dd; cd = qualifiedidentifierRepository()->itemFromIndex( m_index ); } void QualifiedIdentifier::prepareWrite() { if(m_index) { const QualifiedIdentifierPrivate* oldCc = cd; dd = new QualifiedIdentifierPrivate; dd->m_explicitlyGlobal = oldCc->m_explicitlyGlobal; dd->m_isExpression = oldCc->m_isExpression; dd->m_hash = oldCc->m_hash; dd->copyListsFrom(*oldCc); m_index = 0; } dd->clearHash(); } uint IndexedTypeIdentifier::hash() const { quint32 bitfields = m_isConstant | (m_isReference << 1) | (m_isRValue << 2) | (m_isVolatile << 3) | (m_pointerDepth << 4) | (m_pointerConstMask << 9); return KDevHash() << m_identifier.getIndex() << bitfields; } bool IndexedTypeIdentifier::operator==(const IndexedTypeIdentifier& rhs) const { return m_identifier == rhs.m_identifier && m_isConstant == rhs.m_isConstant && m_isReference == rhs.m_isReference && m_isRValue == rhs.m_isRValue && m_isVolatile == rhs.m_isVolatile && m_pointerConstMask == rhs.m_pointerConstMask && m_pointerDepth == rhs.m_pointerDepth; } bool IndexedTypeIdentifier::operator!=(const IndexedTypeIdentifier& rhs) const { return !operator==(rhs); } bool IndexedTypeIdentifier::isReference() const { return m_isReference; } void IndexedTypeIdentifier::setIsReference(bool isRef) { m_isReference = isRef; } bool IndexedTypeIdentifier::isRValue() const { return m_isRValue; } void IndexedTypeIdentifier::setIsRValue(bool isRVal) { m_isRValue = isRVal; } bool IndexedTypeIdentifier::isConstant() const { return m_isConstant; } void IndexedTypeIdentifier::setIsConstant(bool isConst) { m_isConstant = isConst; } bool IndexedTypeIdentifier::isVolatile() const { return m_isVolatile; } void IndexedTypeIdentifier::setIsVolatile(bool isVolatile) { m_isVolatile = isVolatile; } int IndexedTypeIdentifier::pointerDepth() const { return m_pointerDepth; } void IndexedTypeIdentifier::setPointerDepth(int depth) { Q_ASSERT(depth <= 23 && depth >= 0); ///Clear the mask in removed fields for(int s = depth; s < (int)m_pointerDepth; ++s) setIsConstPointer(s, false); m_pointerDepth = depth; } bool IndexedTypeIdentifier::isConstPointer(int depthNumber) const { return m_pointerConstMask & (1 << depthNumber); } void IndexedTypeIdentifier::setIsConstPointer(int depthNumber, bool constant) { if(constant) m_pointerConstMask |= (1 << depthNumber); else m_pointerConstMask &= (~(1 << depthNumber)); } QString IndexedTypeIdentifier::toString(IdentifierStringFormattingOptions options) const { QString ret; if(isConstant()) ret += QLatin1String("const "); if(isVolatile()) ret += QLatin1String("volatile "); ret += m_identifier.identifier().toString(options); for(int a = 0; a < pointerDepth(); ++a) { ret += '*'; if( isConstPointer(a) ) ret += QLatin1String("const"); } if(isRValue()) ret += QLatin1String("&&"); else if(isReference()) ret += '&'; return ret; } IndexedTypeIdentifier::IndexedTypeIdentifier(const IndexedQualifiedIdentifier& identifier) : m_identifier(identifier) , m_isConstant(false) , m_isReference(false) , m_isRValue(false) , m_isVolatile(false) , m_pointerDepth(0) , m_pointerConstMask(0) { } IndexedTypeIdentifier::IndexedTypeIdentifier(const QString& identifier, bool isExpression) : m_identifier(QualifiedIdentifier(identifier, isExpression)) , m_isConstant(false) , m_isReference(false) , m_isRValue(false) , m_isVolatile(false) , m_pointerDepth(0) , m_pointerConstMask(0) { } IndexedIdentifier::IndexedIdentifier() : index(emptyConstantIdentifierPrivateIndex()) { if(shouldDoDUChainReferenceCounting(this)) { QMutexLocker lock(identifierRepository()->mutex()); increase(identifierRepository()->dynamicItemFromIndexSimple(index)->m_refCount, index); } } IndexedIdentifier::IndexedIdentifier(const Identifier& id) : index(id.index()) { if(shouldDoDUChainReferenceCounting(this)) { QMutexLocker lock(identifierRepository()->mutex()); increase(identifierRepository()->dynamicItemFromIndexSimple(index)->m_refCount, index); } } IndexedIdentifier::IndexedIdentifier(const IndexedIdentifier& rhs) : index(rhs.index) { if(shouldDoDUChainReferenceCounting(this)) { QMutexLocker lock(identifierRepository()->mutex()); increase(identifierRepository()->dynamicItemFromIndexSimple(index)->m_refCount, index); } } IndexedIdentifier::IndexedIdentifier(IndexedIdentifier&& rhs) Q_DECL_NOEXCEPT : index(rhs.index) { rhs.index = emptyConstantIdentifierPrivateIndex(); } IndexedIdentifier::~IndexedIdentifier() { if(shouldDoDUChainReferenceCounting(this)) { QMutexLocker lock(identifierRepository()->mutex()); decrease(identifierRepository()->dynamicItemFromIndexSimple(index)->m_refCount, index); } } IndexedIdentifier& IndexedIdentifier::operator=(const Identifier& id) { if(shouldDoDUChainReferenceCounting(this)) { QMutexLocker lock(identifierRepository()->mutex()); decrease(identifierRepository()->dynamicItemFromIndexSimple(index)->m_refCount, index); } index = id.index(); if(shouldDoDUChainReferenceCounting(this)) { QMutexLocker lock(identifierRepository()->mutex()); increase(identifierRepository()->dynamicItemFromIndexSimple(index)->m_refCount, index); } return *this; } IndexedIdentifier& IndexedIdentifier::operator=(IndexedIdentifier&& rhs) Q_DECL_NOEXCEPT { if(shouldDoDUChainReferenceCounting(this)) { QMutexLocker lock(identifierRepository()->mutex()); ifDebug( qCDebug(LANGUAGE) << "decreasing"; ) decrease(identifierRepository()->dynamicItemFromIndexSimple(index)->m_refCount, index); } else if (shouldDoDUChainReferenceCounting(&rhs)) { QMutexLocker lock(identifierRepository()->mutex()); ifDebug( qCDebug(LANGUAGE) << "decreasing"; ) decrease(identifierRepository()->dynamicItemFromIndexSimple(rhs.index)->m_refCount, rhs.index); } index = rhs.index; rhs.index = emptyConstantIdentifierPrivateIndex(); if(shouldDoDUChainReferenceCounting(this) && !(shouldDoDUChainReferenceCounting(&rhs))) { QMutexLocker lock(identifierRepository()->mutex()); ifDebug( qCDebug(LANGUAGE) << "increasing"; ) increase(identifierRepository()->dynamicItemFromIndexSimple(index)->m_refCount, index); } return *this; } IndexedIdentifier& IndexedIdentifier::operator=(const IndexedIdentifier& id) { if(shouldDoDUChainReferenceCounting(this)) { QMutexLocker lock(identifierRepository()->mutex()); decrease(identifierRepository()->dynamicItemFromIndexSimple(index)->m_refCount, index); } index = id.index; if(shouldDoDUChainReferenceCounting(this)) { QMutexLocker lock(identifierRepository()->mutex()); increase(identifierRepository()->dynamicItemFromIndexSimple(index)->m_refCount, index); } return *this; } bool IndexedIdentifier::operator==(const IndexedIdentifier& rhs) const { return index == rhs.index; } bool IndexedIdentifier::operator!=(const IndexedIdentifier& rhs) const { return index != rhs.index; } bool IndexedIdentifier::operator==(const Identifier& id) const { return index == id.index(); } Identifier IndexedIdentifier::identifier() const { return Identifier(index); } IndexedIdentifier::operator Identifier() const { return Identifier(index); } bool IndexedQualifiedIdentifier::isValid() const { return index != emptyConstantQualifiedIdentifierPrivateIndex(); } bool IndexedQualifiedIdentifier::isEmpty() const { return index == emptyConstantQualifiedIdentifierPrivateIndex(); } int cnt = 0; IndexedQualifiedIdentifier IndexedTypeIdentifier::identifier() const { return m_identifier; } void IndexedTypeIdentifier::setIdentifier(const IndexedQualifiedIdentifier& id) { m_identifier = id; } IndexedQualifiedIdentifier::IndexedQualifiedIdentifier() : index(emptyConstantQualifiedIdentifierPrivateIndex()) { ifDebug( qCDebug(LANGUAGE) << "(" << ++cnt << ")" << identifier().toString() << index; ) if(shouldDoDUChainReferenceCounting(this)) { ifDebug( qCDebug(LANGUAGE) << "increasing"; ) //qCDebug(LANGUAGE) << "(" << ++cnt << ")" << this << identifier().toString() << "inc" << index; QMutexLocker lock(qualifiedidentifierRepository()->mutex()); increase(qualifiedidentifierRepository()->dynamicItemFromIndexSimple(index)->m_refCount, index); } } IndexedQualifiedIdentifier::IndexedQualifiedIdentifier(const QualifiedIdentifier& id) : index(id.index()) { ifDebug( qCDebug(LANGUAGE) << "(" << ++cnt << ")" << identifier().toString() << index; ) if(shouldDoDUChainReferenceCounting(this)) { ifDebug( qCDebug(LANGUAGE) << "increasing"; ) QMutexLocker lock(qualifiedidentifierRepository()->mutex()); increase(qualifiedidentifierRepository()->dynamicItemFromIndexSimple(index)->m_refCount, index); } } IndexedQualifiedIdentifier::IndexedQualifiedIdentifier(const IndexedQualifiedIdentifier& id) : index(id.index) { ifDebug( qCDebug(LANGUAGE) << "(" << ++cnt << ")" << identifier().toString() << index; ) if(shouldDoDUChainReferenceCounting(this)) { ifDebug( qCDebug(LANGUAGE) << "increasing"; ) QMutexLocker lock(qualifiedidentifierRepository()->mutex()); increase(qualifiedidentifierRepository()->dynamicItemFromIndexSimple(index)->m_refCount, index); } } IndexedQualifiedIdentifier::IndexedQualifiedIdentifier(IndexedQualifiedIdentifier&& rhs) Q_DECL_NOEXCEPT : index(rhs.index) { rhs.index = emptyConstantQualifiedIdentifierPrivateIndex(); } IndexedQualifiedIdentifier& IndexedQualifiedIdentifier::operator=(const QualifiedIdentifier& id) { ifDebug( qCDebug(LANGUAGE) << "(" << ++cnt << ")" << identifier().toString() << index; ) if(shouldDoDUChainReferenceCounting(this)) { QMutexLocker lock(qualifiedidentifierRepository()->mutex()); ifDebug( qCDebug(LANGUAGE) << "decreasing"; ) decrease(qualifiedidentifierRepository()->dynamicItemFromIndexSimple(index)->m_refCount, index); index = id.index(); ifDebug( qCDebug(LANGUAGE) << index << "increasing"; ) increase(qualifiedidentifierRepository()->dynamicItemFromIndexSimple(index)->m_refCount, index); } else { index = id.index(); } return *this; } IndexedQualifiedIdentifier& IndexedQualifiedIdentifier::operator=(const IndexedQualifiedIdentifier& rhs) { ifDebug( qCDebug(LANGUAGE) << "(" << ++cnt << ")" << identifier().toString() << index; ) if(shouldDoDUChainReferenceCounting(this)) { QMutexLocker lock(qualifiedidentifierRepository()->mutex()); ifDebug( qCDebug(LANGUAGE) << "decreasing"; ) decrease(qualifiedidentifierRepository()->dynamicItemFromIndexSimple(index)->m_refCount, index); index = rhs.index; ifDebug( qCDebug(LANGUAGE) << index << "increasing"; ) increase(qualifiedidentifierRepository()->dynamicItemFromIndexSimple(index)->m_refCount, index); } else { index = rhs.index; } return *this; } IndexedQualifiedIdentifier& IndexedQualifiedIdentifier::operator=(IndexedQualifiedIdentifier&& rhs) Q_DECL_NOEXCEPT { if(shouldDoDUChainReferenceCounting(this)) { QMutexLocker lock(qualifiedidentifierRepository()->mutex()); ifDebug( qCDebug(LANGUAGE) << "decreasing"; ) decrease(qualifiedidentifierRepository()->dynamicItemFromIndexSimple(index)->m_refCount, index); } else if (shouldDoDUChainReferenceCounting(&rhs)) { QMutexLocker lock(qualifiedidentifierRepository()->mutex()); ifDebug( qCDebug(LANGUAGE) << "decreasing"; ) decrease(qualifiedidentifierRepository()->dynamicItemFromIndexSimple(rhs.index)->m_refCount, rhs.index); } index = rhs.index; rhs.index = emptyConstantQualifiedIdentifierPrivateIndex(); if(shouldDoDUChainReferenceCounting(this) && !(shouldDoDUChainReferenceCounting(&rhs))) { QMutexLocker lock(qualifiedidentifierRepository()->mutex()); ifDebug( qCDebug(LANGUAGE) << "increasing"; ) increase(qualifiedidentifierRepository()->dynamicItemFromIndexSimple(index)->m_refCount, index); } return *this; } IndexedQualifiedIdentifier::~IndexedQualifiedIdentifier() { ifDebug( qCDebug(LANGUAGE) << "(" << ++cnt << ")" << identifier().toString() << index; ) if(shouldDoDUChainReferenceCounting(this)) { ifDebug( qCDebug(LANGUAGE) << index << "decreasing"; ) QMutexLocker lock(qualifiedidentifierRepository()->mutex()); decrease(qualifiedidentifierRepository()->dynamicItemFromIndexSimple(index)->m_refCount, index); } } bool IndexedQualifiedIdentifier::operator==(const IndexedQualifiedIdentifier& rhs) const { return index == rhs.index; } bool IndexedQualifiedIdentifier::operator==(const QualifiedIdentifier& id) const { return index == id.index(); } QualifiedIdentifier IndexedQualifiedIdentifier::identifier() const { return QualifiedIdentifier(index); } IndexedQualifiedIdentifier::operator QualifiedIdentifier() const { return QualifiedIdentifier(index); } void initIdentifierRepository() { emptyConstantIdentifierPrivateIndex(); emptyConstantIdentifierPrivate(); emptyConstantQualifiedIdentifierPrivateIndex(); emptyConstantQualifiedIdentifierPrivate(); } } QDebug operator<<(QDebug s, const KDevelop::Identifier& identifier) { s.nospace() << identifier.toString(); return s.space(); } QDebug operator<<(QDebug s, const KDevelop::QualifiedIdentifier& identifier) { s.nospace() << identifier.toString(); return s.space(); } diff --git a/language/duchain/navigation/abstractnavigationcontext.h b/language/duchain/navigation/abstractnavigationcontext.h index 97b4ee6b9..30cad6f94 100644 --- a/language/duchain/navigation/abstractnavigationcontext.h +++ b/language/duchain/navigation/abstractnavigationcontext.h @@ -1,195 +1,195 @@ /* 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_ABSTRACTNAVIGATIONCONTEXT_H #define KDEVPLATFORM_ABSTRACTNAVIGATIONCONTEXT_H #include #include #include "../indexeddeclaration.h" #include "navigationaction.h" namespace KDevelop { /** A helper-class for elegant colorization of html-strings . * * Initialize it with a html-color like "990000". and colorize strings * using operator() */ struct KDEVPLATFORMLANGUAGE_EXPORT Colorizer { enum FormattingFlag { Nothing = 0x0, Bold = 0x1, Italic = 0x2, Fixed = 0x4 }; Q_DECLARE_FLAGS(Formatting, FormattingFlag) - Colorizer(const QString& color, Formatting formatting = Nothing) + explicit Colorizer(const QString& color, Formatting formatting = Nothing) : m_color(color), m_formatting(formatting) { } QString operator()(const QString& str) const; QString m_color; Formatting m_formatting; }; class AbstractNavigationContext; typedef QExplicitlySharedDataPointer NavigationContextPointer; class KDEVPLATFORMLANGUAGE_EXPORT AbstractNavigationContext : public QObject, public QSharedData { Q_OBJECT public: explicit AbstractNavigationContext( KDevelop::TopDUContextPointer topContext = KDevelop::TopDUContextPointer(), AbstractNavigationContext* previousContext = nullptr ); ~AbstractNavigationContext() override { } void nextLink(); void previousLink(); int linkCount() const; void up(); void down(); void setPrefixSuffix( const QString& prefix, const QString& suffix ); NavigationContextPointer accept(); NavigationContextPointer back(); NavigationContextPointer accept(IndexedDeclaration decl); NavigationContextPointer acceptLink(const QString& link); NavigationAction currentAction() const; virtual QString name() const = 0; ///Here the context can return html to be displayed. ///NOTE: The DUChain must be locked while this is called. virtual QString html(bool shorten = false); ///Here the context can return a widget to be displayed. ///The widget stays owned by this navigation-context. ///The widget may have a signal "navigateDeclaration(KDevelop::IndexedDeclaration)". ///If that signal is emitted, the new declaration is navigated in the navigation-wdiget. virtual QWidget* widget() const; ///Whether the widget returned by widget() should take the maximum possible spsace. ///The default implementation returns true. virtual bool isWidgetMaximized() const; ///Returns whether this context's string has already been computed, and is up to date. ///After clear() was called, this returns false again. bool alreadyComputed() const; void setTopContext(TopDUContextPointer context); TopDUContextPointer topContext() const; NavigationContextPointer executeLink(QString link); NavigationContextPointer execute(const NavigationAction& action); Q_SIGNALS: void contentsChanged(); protected: /// Returns the html font-size prefix (aka. <small> or similar) for the given mode QString fontSizePrefix(bool shorten) const; /// Returns the html font-size suffix (aka. <small> or similar) for the given mode QString fontSizeSuffix(bool shorten) const; virtual void setPreviousContext(AbstractNavigationContext* previous); struct TextHandler { - TextHandler(AbstractNavigationContext* c) : context(c) { + explicit TextHandler(AbstractNavigationContext* c) : context(c) { } void operator+=(const QString& str) const { context->addHtml(str); } AbstractNavigationContext* context; }; ///Override this to execute own key-actions using NavigationAction virtual NavigationContextPointer executeKeyAction(QString key); ///Adds given the text to currentHtml() void addHtml(QString html); ///Returns the html text being built in its current state QString currentHtml() const; ///Returns a convenience object that allows writing "modifyHtml() += "Hallo";" TextHandler modifyHtml() { return TextHandler(this); } //Clears the computed html and links void clear(); void addExternalHtml( const QString& text ); ///Creates and registers a link to the given declaration, labeled by the given name virtual void makeLink( const QString& name, DeclarationPointer declaration, NavigationAction::Type actionType ); ///Creates a link that executes the given action and adds it to the current context void makeLink( const QString& name, QString targetId, const NavigationAction& action); ///Creates a link that executes the given action and returns it QString createLink(const QString& name, QString targetId, const NavigationAction& action); int m_selectedLink; //The link currently selected NavigationAction m_selectedLinkAction; //Target of the currently selected link NavigationContextPointer registerChild(DeclarationPointer /*declaration*/); NavigationContextPointer registerChild( AbstractNavigationContext* context ); QList m_children; //Useed to keep alive all children until this is deleted bool m_shorten; int m_currentLine; //A counter used while building the html-code to count the used links. int m_linkCount; //Something else than -1 if the current position is represented by a line-number, not a link. int m_currentPositionLine; QMap m_links; QMap m_linkLines; //Holds the line for each link QMap m_intLinks; AbstractNavigationContext* m_previousContext; QString m_prefix, m_suffix; KDevelop::TopDUContextPointer m_topContext; virtual QString declarationKind(DeclarationPointer decl); static const Colorizer typeHighlight; static const Colorizer errorHighlight; static const Colorizer labelHighlight; static const Colorizer codeHighlight; static const Colorizer propertyHighlight; static const Colorizer navigationHighlight; static const Colorizer importantHighlight; static const Colorizer commentHighlight; static const Colorizer nameHighlight; private: QString m_currentText; //Here the text is built }; } Q_DECLARE_OPERATORS_FOR_FLAGS(KDevelop::Colorizer::Formatting) #endif diff --git a/language/duchain/navigation/navigationaction.h b/language/duchain/navigation/navigationaction.h index 18aba56fa..824428196 100644 --- a/language/duchain/navigation/navigationaction.h +++ b/language/duchain/navigation/navigationaction.h @@ -1,72 +1,72 @@ /* 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_NAVIGATIONACTION_H #define KDEVPLATFORM_NAVIGATIONACTION_H #include #include #include "../duchainpointer.h" namespace KDevelop { class AbstractNavigationContext; struct NavigationAction { enum Type { None, NavigateDeclaration, NavigateUses, ShowUses, JumpToSource, //If this is set, the action jumps to document and cursor if they are valid, else to the declaration-position of decl ExecuteKey, //This is used to do changes within one single navigation-context. executeKey(key) will be called in the current context, //and the context has the chance to react in an arbitrary way. ShowDocumentation }; ///When executed, this navigation-action calls the "executeKeyAction(QString) function in its navigation-context - NavigationAction(QString _key) : targetContext(nullptr), type(ExecuteKey), key(_key) { + explicit NavigationAction(QString _key) : targetContext(nullptr), type(ExecuteKey), key(_key) { } NavigationAction() : targetContext(nullptr), type(None) { } NavigationAction( DeclarationPointer decl_, Type type_ ) : targetContext(nullptr), decl(decl_), type(type_) { } NavigationAction( const QUrl& _document, const KTextEditor::Cursor& _cursor) : targetContext(nullptr), type(JumpToSource), document(_document), cursor(_cursor) { } - NavigationAction(AbstractNavigationContext* _targetContext) : targetContext(_targetContext) { + explicit NavigationAction(AbstractNavigationContext* _targetContext) : targetContext(_targetContext) { } AbstractNavigationContext* targetContext; //If this is set, this action does nothing else than jumping to that context DeclarationPointer decl; Type type; QUrl document; KTextEditor::Cursor cursor; QString key; }; } #endif diff --git a/language/duchain/navigation/usescollector.cpp b/language/duchain/navigation/usescollector.cpp index 41cb42de9..76a0cacf7 100644 --- a/language/duchain/navigation/usescollector.cpp +++ b/language/duchain/navigation/usescollector.cpp @@ -1,427 +1,427 @@ /* Copyright 2008 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "usescollector.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "../classmemberdeclaration.h" #include "../abstractfunctiondeclaration.h" #include "../functiondefinition.h" #include "util/debug.h" #include #include #include using namespace KDevelop; ///@todo make this language-neutral static Identifier destructorForName(Identifier name) { QString str = name.identifier().str(); if(str.startsWith('~')) return Identifier(str); return Identifier('~'+str); } ///@todo Only collect uses within currently loaded projects template void collectImporters(ImportanceChecker& checker, ParsingEnvironmentFile* current, QSet& visited, QSet& collected) { //Ignore proxy-contexts while collecting. Those build a parallel and much more complicated structure. if(current->isProxyContext()) return; if(visited.contains(current)) return; visited.insert(current); if(checker(current)) collected.insert(current); foreach(const ParsingEnvironmentFilePointer& importer, current->importers()) if(importer.data()) collectImporters(checker, importer.data(), visited, collected); else qCDebug(LANGUAGE) << "missing environment-file, strange"; } ///The returned set does not include the file itself ///@parm visited should be empty on each call, used to prevent endless recursion void allImportedFiles(ParsingEnvironmentFilePointer file, QSet& set, QSet& visited) { foreach(const ParsingEnvironmentFilePointer &import, file->imports()) { if(!import) { qCDebug(LANGUAGE) << "warning: missing import"; continue; } if(!visited.contains(import)) { visited.insert(import); set.insert(import->url()); allImportedFiles(import, set, visited); } } } void UsesCollector::setCollectConstructors(bool process) { m_collectConstructors = process; } void UsesCollector::setProcessDeclarations(bool process) { m_processDeclarations = process; } void UsesCollector::setCollectOverloads(bool collect) { m_collectOverloads = collect; } void UsesCollector::setCollectDefinitions(bool collect) { m_collectDefinitions = collect; } QList UsesCollector::declarations() { return m_declarations; } bool UsesCollector::isReady() const { return m_waitForUpdate.size() == m_updateReady.size(); } bool UsesCollector::shouldRespectFile(IndexedString document) { return (bool)ICore::self()->projectController()->findProjectForUrl(document.toUrl()) || (bool)ICore::self()->documentController()->documentForUrl(document.toUrl()); } struct ImportanceChecker { - ImportanceChecker(UsesCollector& collector) : m_collector(collector) { + explicit ImportanceChecker(UsesCollector& collector) : m_collector(collector) { } bool operator ()(ParsingEnvironmentFile* file) { return m_collector.shouldRespectFile(file->url()); } UsesCollector& m_collector; }; void UsesCollector::startCollecting() { DUChainReadLocker lock(DUChain::lock()); if(Declaration* decl = m_declaration.data()) { if(m_collectDefinitions) { if(FunctionDefinition* def = dynamic_cast(decl)) { //Jump from definition to declaration Declaration* declaration = def->declaration(); if(declaration) decl = declaration; } } ///Collect all overloads into "decls" QList decls; if(m_collectOverloads && decl->context()->owner() && decl->context()->type() == DUContext::Class) { //First find the overridden base, and then all overriders of that base. while(Declaration* overridden = DUChainUtils::getOverridden(decl)) decl = overridden; uint maxAllowedSteps = 10000; decls += DUChainUtils::getOverriders( decl->context()->owner(), decl, maxAllowedSteps ); if(maxAllowedSteps == 10000) { ///@todo Fail! } } decls << decl; ///Collect all "parsed versions" or forward-declarations etc. here, into allDeclarations QSet allDeclarations; foreach(Declaration* overload, decls) { m_declarations = DUChainUtils::collectAllVersions(overload); foreach(const IndexedDeclaration &d, m_declarations) { if(!d.data() || d.data()->id() != overload->id()) continue; allDeclarations.insert(d); if(m_collectConstructors && d.data() && d.data()->internalContext() && d.data()->internalContext()->type() == DUContext::Class) { QList constructors = d.data()->internalContext()->findLocalDeclarations(d.data()->identifier(), CursorInRevision::invalid(), nullptr, AbstractType::Ptr(), DUContext::OnlyFunctions); foreach(Declaration* constructor, constructors) { ClassFunctionDeclaration* classFun = dynamic_cast(constructor); if(classFun && classFun->isConstructor()) allDeclarations.insert(IndexedDeclaration(constructor)); } Identifier destructorId = destructorForName(d.data()->identifier()); QList destructors = d.data()->internalContext()->findLocalDeclarations(destructorId, CursorInRevision::invalid(), nullptr, AbstractType::Ptr(), DUContext::OnlyFunctions); foreach(Declaration* destructor, destructors) { ClassFunctionDeclaration* classFun = dynamic_cast(destructor); if(classFun && classFun->isDestructor()) allDeclarations.insert(IndexedDeclaration(destructor)); } } } } ///Collect definitions for declarations if(m_collectDefinitions) { foreach(const IndexedDeclaration d, allDeclarations) { Declaration* definition = FunctionDefinition::definition(d.data()); if(definition) { qCDebug(LANGUAGE) << "adding definition"; allDeclarations.insert(IndexedDeclaration(definition)); } } } m_declarations.clear(); ///Step 4: Copy allDeclarations into m_declarations, build top-context list, etc. QList candidateTopContexts; foreach(const IndexedDeclaration d, allDeclarations) { m_declarations << d; m_declarationTopContexts.insert(d.indexedTopContext()); //We only collect declarations with the same type here.. candidateTopContexts << d.indexedTopContext().data(); } ImportanceChecker checker(*this); QSet visited; QSet collected; qCDebug(LANGUAGE) << "count of source candidate top-contexts:" << candidateTopContexts.size(); ///We use ParsingEnvironmentFile to collect all the relevant importers, because loading those is very cheap, compared ///to loading a whole TopDUContext. if(decl->inSymbolTable()) { //The declaration can only be used from other contexts if it is in the symbol table foreach(const ReferencedTopDUContext &top, candidateTopContexts) { if(top->parsingEnvironmentFile()) { collectImporters(checker, top->parsingEnvironmentFile().data(), visited, collected); //In C++, visibility is not handled strictly through the import-structure. //It may happen that an object is visible because of an earlier include. //We can not perfectly handle that, but we can at least handle it if the header includes //the header that contains the declaration. That header may be parsed empty due to header-guards, //but we still need to pick it up here. QList allVersions = DUChain::self()->allEnvironmentFiles(top->url()); foreach(const ParsingEnvironmentFilePointer& version, allVersions) collectImporters(checker, version.data(), visited, collected); } } } KDevelop::ParsingEnvironmentFile* file=decl->topContext()->parsingEnvironmentFile().data(); if(!file) return; if(checker(file)) collected.insert(file); { QSet filteredCollected; QMap grepCache; // Filter the collected files by performing a grep foreach(ParsingEnvironmentFile* file, collected) { IndexedString url = file->url(); QMap< IndexedString, bool >::iterator grepCacheIt = grepCache.find(url); if(grepCacheIt == grepCache.end()) { CodeRepresentation::Ptr repr = KDevelop::createCodeRepresentation( url ); if(repr) { QVector found = repr->grep(decl->identifier().identifier().str()); grepCacheIt = grepCache.insert(url, !found.isEmpty()); } } if(grepCacheIt.value()) filteredCollected << file; } qCDebug(LANGUAGE) << "Collected contexts for full re-parse, before filtering: " << collected.size() << " after filtering: " << filteredCollected.size(); collected = filteredCollected; } ///We have all importers now. However since we can tell parse-jobs to also update all their importers, we only need to ///update the "root" top-contexts that open the whole set with their imports. QSet rootFiles; QSet allFiles; foreach(ParsingEnvironmentFile* importer, collected) { QSet allImports; QSet visited; allImportedFiles(ParsingEnvironmentFilePointer(importer), allImports, visited); //Remove all files from the "root" set that are imported by this one ///@todo more intelligent rootFiles -= allImports; allFiles += allImports; allFiles.insert(importer->url()); rootFiles.insert(importer->url()); } emit maximumProgressSignal(rootFiles.size()); maximumProgress(rootFiles.size()); //If we used the AllDeclarationsContextsAndUsesRecursive flag here, we would compute way too much. This way we only //set the minimum-features selectively on the files we really require them on. foreach(ParsingEnvironmentFile* file, collected) m_staticFeaturesManipulated.insert(file->url()); m_staticFeaturesManipulated.insert(decl->url()); foreach(const IndexedString &file, m_staticFeaturesManipulated) ParseJob::setStaticMinimumFeatures(file, TopDUContext::AllDeclarationsContextsAndUses); m_waitForUpdate = rootFiles; foreach(const IndexedString &file, rootFiles) { qCDebug(LANGUAGE) << "updating root file:" << file.str(); DUChain::self()->updateContextForUrl(file, TopDUContext::AllDeclarationsContextsAndUses, this); } }else{ emit maximumProgressSignal(0); maximumProgress(0); } } void UsesCollector::maximumProgress(uint max) { Q_UNUSED(max); } UsesCollector::UsesCollector(IndexedDeclaration declaration) : m_declaration(declaration), m_collectOverloads(true), m_collectDefinitions(true), m_collectConstructors(false), m_processDeclarations(true) { } UsesCollector::~UsesCollector() { ICore::self()->languageController()->backgroundParser()->revertAllRequests(this); foreach(const IndexedString &file, m_staticFeaturesManipulated) ParseJob::unsetStaticMinimumFeatures(file, TopDUContext::AllDeclarationsContextsAndUses); } void UsesCollector::progress(uint processed, uint total) { Q_UNUSED(processed); Q_UNUSED(total); } void UsesCollector::updateReady(KDevelop::IndexedString url, KDevelop::ReferencedTopDUContext topContext) { DUChainReadLocker lock(DUChain::lock()); if(!topContext) { qCDebug(LANGUAGE) << "failed updating" << url.str(); }else{ if(topContext->parsingEnvironmentFile() && topContext->parsingEnvironmentFile()->isProxyContext()) { ///Use the attached content-context instead foreach(const DUContext::Import &import, topContext->importedParentContexts()) { if(import.context(nullptr) && import.context(nullptr)->topContext()->parsingEnvironmentFile() && !import.context(nullptr)->topContext()->parsingEnvironmentFile()->isProxyContext()) { if((import.context(nullptr)->topContext()->features() & TopDUContext::AllDeclarationsContextsAndUses)) { ReferencedTopDUContext newTop(import.context(nullptr)->topContext()); topContext = newTop; break; } } } if(topContext->parsingEnvironmentFile() && topContext->parsingEnvironmentFile()->isProxyContext()) { qCDebug(LANGUAGE) << "got bad proxy-context for" << url.str(); topContext = nullptr; } } } if(m_waitForUpdate.contains(url) && !m_updateReady.contains(url)) { m_updateReady << url; m_checked.clear(); emit progressSignal(m_updateReady.size(), m_waitForUpdate.size()); progress(m_updateReady.size(), m_waitForUpdate.size()); } if(!topContext || !topContext->parsingEnvironmentFile()) { qCDebug(LANGUAGE) << "bad top-context"; return; } if(!m_staticFeaturesManipulated.contains(url)) return; //Not interesting if(!(topContext->features() & TopDUContext::AllDeclarationsContextsAndUses)) { ///@todo With simplified environment-matching, the same file may have been imported multiple times, ///while only one of those was updated. We have to check here whether this file is just such an import, ///or whether we work on with it. ///@todo We will lose files that were edited right after their update here. qCWarning(LANGUAGE) << "WARNING: context" << topContext->url().str() << "does not have the required features!!"; ICore::self()->uiController()->showErrorMessage("Updating " + ICore::self()->projectController()->prettyFileName(topContext->url().toUrl(), KDevelop::IProjectController::FormatPlain) + " failed!", 5); return; } if(topContext->parsingEnvironmentFile()->needsUpdate()) { qCWarning(LANGUAGE) << "WARNING: context" << topContext->url().str() << "is not up to date!"; ICore::self()->uiController()->showErrorMessage(i18n("%1 still needs an update!", ICore::self()->projectController()->prettyFileName(topContext->url().toUrl(), KDevelop::IProjectController::FormatPlain)), 5); // return; } IndexedTopDUContext indexed(topContext.data()); if(m_checked.contains(indexed)) return; if(!topContext.data()) { qCDebug(LANGUAGE) << "updated top-context is zero:" << url.str(); return; } m_checked.insert(indexed); if(m_declaration.data() && ((m_processDeclarations && m_declarationTopContexts.contains(indexed)) || DUChainUtils::contextHasUse(topContext.data(), m_declaration.data()))) { if(!m_processed.contains(topContext->url())) { m_processed.insert(topContext->url()); lock.unlock(); emit processUsesSignal(topContext); processUses(topContext); lock.lock(); } } else { if(!m_declaration.data()) { qCDebug(LANGUAGE) << "declaration has become invalid"; } } QList imports; foreach(const DUContext::Import &imported, topContext->importedParentContexts()) if(imported.context(nullptr) && imported.context(nullptr)->topContext()) imports << KDevelop::ReferencedTopDUContext(imported.context(nullptr)->topContext()); foreach(const KDevelop::ReferencedTopDUContext &import, imports) { IndexedString url = import->url(); lock.unlock(); updateReady(url, import); lock.lock(); } } IndexedDeclaration UsesCollector::declaration() const { return m_declaration; } diff --git a/language/duchain/navigation/useswidget.h b/language/duchain/navigation/useswidget.h index 38c78a0e1..e67eed6d9 100644 --- a/language/duchain/navigation/useswidget.h +++ b/language/duchain/navigation/useswidget.h @@ -1,160 +1,160 @@ /* 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 #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, KTextEditor::Range range, const CodeRepresentation& code); ~OneUseWidget() override; private: void mousePressEvent(QMouseEvent * event) override; void resizeEvent ( QResizeEvent * event ) override; 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: explicit NavigatableWidgetList(bool allowScrolling = false, uint maxHeight = 0, bool vertical = true); ~NavigatableWidgetList() override; void addItem(QWidget* widget, int pos = -1); void addHeaderItem(QWidget* widget, Qt::Alignment alignment = nullptr); ///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; 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); + explicit UsesWidgetCollector(IndexedDeclaration decl); void processUses(KDevelop::ReferencedTopDUContext topContext) override; void maximumProgress(uint max) override; void progress(uint processed, uint total) override; UsesWidget* m_widget; }; QSize sizeHint () const override; ///@param customCollector allows specifying an own subclass of UsesWidgetCollector. explicit UsesWidget(const IndexedDeclaration& declaration, QSharedPointer customCollector = {}); ~UsesWidget() override; void setAllExpanded(bool expanded); unsigned int countAllUses() const; Q_SIGNALS: void navigateDeclaration(KDevelop::IndexedDeclaration); private: const QString headerLineText() const; QLabel* m_headerLine; QSharedPointer m_collector; QProgressBar* m_progressBar; public slots: void headerLinkActivated(QString linkName); void redrawHeaderLine(); }; } #endif diff --git a/language/duchain/persistentsymboltable.cpp b/language/duchain/persistentsymboltable.cpp index 4cfe822f0..3fe926657 100644 --- a/language/duchain/persistentsymboltable.cpp +++ b/language/duchain/persistentsymboltable.cpp @@ -1,422 +1,422 @@ /* This file is part of KDevelop Copyright 2008 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "persistentsymboltable.h" #include #include #include "declaration.h" #include "declarationid.h" #include "appendedlist.h" #include "serialization/itemrepository.h" #include "identifier.h" #include "ducontext.h" #include "topducontext.h" #include "duchain.h" #include "duchainlock.h" #include //For now, just _always_ use the cache const uint MinimumCountForCache = 1; namespace { QDebug fromTextStream(const QTextStream& out) { if (out.device()) return {out.device()}; return {out.string()}; } } namespace KDevelop { Utils::BasicSetRepository* RecursiveImportCacheRepository::repository() { static Utils::BasicSetRepository recursiveImportCacheRepositoryObject(QStringLiteral("Recursive Imports Cache"), nullptr, false); return &recursiveImportCacheRepositoryObject; } DEFINE_LIST_MEMBER_HASH(PersistentSymbolTableItem, declarations, IndexedDeclaration) class PersistentSymbolTableItem { public: PersistentSymbolTableItem() : centralFreeItem(-1) { initializeAppendedLists(); } PersistentSymbolTableItem(const PersistentSymbolTableItem& rhs, bool dynamic = true) : id(rhs.id), centralFreeItem(rhs.centralFreeItem) { initializeAppendedLists(dynamic); copyListsFrom(rhs); } ~PersistentSymbolTableItem() { freeAppendedLists(); } inline unsigned int hash() const { //We only compare the declaration. This allows us implementing a map, although the item-repository //originally represents a set. return id.getIndex(); } unsigned int itemSize() const { return dynamicSize(); } uint classSize() const { return sizeof(PersistentSymbolTableItem); } IndexedQualifiedIdentifier id; int centralFreeItem; START_APPENDED_LISTS(PersistentSymbolTableItem); APPENDED_LIST_FIRST(PersistentSymbolTableItem, IndexedDeclaration, declarations); END_APPENDED_LISTS(PersistentSymbolTableItem, declarations); }; class PersistentSymbolTableRequestItem { public: PersistentSymbolTableRequestItem(const PersistentSymbolTableItem& item) : m_item(item) { } enum { AverageSize = 30 //This should be the approximate average size of an Item }; unsigned int hash() const { return m_item.hash(); } uint itemSize() const { return m_item.itemSize(); } void createItem(PersistentSymbolTableItem* item) const { new (item) PersistentSymbolTableItem(m_item, false); } static void destroy(PersistentSymbolTableItem* item, KDevelop::AbstractItemRepository&) { item->~PersistentSymbolTableItem(); } static bool persistent(const PersistentSymbolTableItem*) { return true; //Nothing to do } bool equals(const PersistentSymbolTableItem* item) const { return m_item.id == item->id; } const PersistentSymbolTableItem& m_item; }; template struct CacheEntry { typedef KDevVarLengthArray Data; typedef QHash DataHash; DataHash m_hash; }; class PersistentSymbolTablePrivate { public: PersistentSymbolTablePrivate() : m_declarations(QStringLiteral("Persistent Declaration Table")) { } //Maps declaration-ids to declarations ItemRepository m_declarations; QHash > m_declarationsCache; //We cache the imports so the currently used nodes are very close in memory, which leads to much better CPU cache utilization QHash m_importsCache; }; void PersistentSymbolTable::clearCache() { ENSURE_CHAIN_WRITE_LOCKED { QMutexLocker lock(d->m_declarations.mutex()); d->m_importsCache.clear(); d->m_declarationsCache.clear(); } } PersistentSymbolTable::PersistentSymbolTable() : d(new PersistentSymbolTablePrivate()) { } PersistentSymbolTable::~PersistentSymbolTable() { //Workaround for a strange destruction-order related crash duing shutdown //We just let the data leak. This doesn't hurt, as there is no meaningful destructors. // delete d; } void PersistentSymbolTable::addDeclaration(const IndexedQualifiedIdentifier& id, const IndexedDeclaration& declaration) { QMutexLocker lock(d->m_declarations.mutex()); ENSURE_CHAIN_WRITE_LOCKED d->m_declarationsCache.remove(id); PersistentSymbolTableItem item; item.id = id; PersistentSymbolTableRequestItem request(item); uint index = d->m_declarations.findIndex(item); if(index) { //Check whether the item is already in the mapped list, else copy the list into the new created item const PersistentSymbolTableItem* oldItem = d->m_declarations.itemFromIndex(index); EmbeddedTreeAlgorithms alg(oldItem->declarations(), oldItem->declarationsSize(), oldItem->centralFreeItem); if(alg.indexOf(declaration) != -1) return; DynamicItem editableItem = d->m_declarations.dynamicItemFromIndex(index); EmbeddedTreeAddItem add(const_cast(editableItem->declarations()), editableItem->declarationsSize(), editableItem->centralFreeItem, declaration); uint newSize = add.newItemCount(); if(newSize != editableItem->declarationsSize()) { //We need to resize. Update and fill the new item, and delete the old item. item.declarationsList().resize(newSize); add.transferData(item.declarationsList().data(), newSize, &item.centralFreeItem); d->m_declarations.deleteItem(index); Q_ASSERT(!d->m_declarations.findIndex(request)); }else{ //We're fine, the item could be added to the existing list return; } }else{ item.declarationsList().append(declaration); } //This inserts the changed item d->m_declarations.index(request); } void PersistentSymbolTable::removeDeclaration(const IndexedQualifiedIdentifier& id, const IndexedDeclaration& declaration) { QMutexLocker lock(d->m_declarations.mutex()); ENSURE_CHAIN_WRITE_LOCKED d->m_declarationsCache.remove(id); Q_ASSERT(!d->m_declarationsCache.contains(id)); PersistentSymbolTableItem item; item.id = id; PersistentSymbolTableRequestItem request(item); uint index = d->m_declarations.findIndex(item); if(index) { //Check whether the item is already in the mapped list, else copy the list into the new created item const PersistentSymbolTableItem* oldItem = d->m_declarations.itemFromIndex(index); EmbeddedTreeAlgorithms alg(oldItem->declarations(), oldItem->declarationsSize(), oldItem->centralFreeItem); if(alg.indexOf(declaration) == -1) return; DynamicItem editableItem = d->m_declarations.dynamicItemFromIndex(index); EmbeddedTreeRemoveItem remove(const_cast(editableItem->declarations()), editableItem->declarationsSize(), editableItem->centralFreeItem, declaration); uint newSize = remove.newItemCount(); if(newSize != editableItem->declarationsSize()) { //We need to resize. Update and fill the new item, and delete the old item. item.declarationsList().resize(newSize); remove.transferData(item.declarationsList().data(), newSize, &item.centralFreeItem); d->m_declarations.deleteItem(index); Q_ASSERT(!d->m_declarations.findIndex(request)); }else{ //We're fine, the item could be added to the existing list return; } } //This inserts the changed item if(item.declarationsSize()) d->m_declarations.index(request); } struct DeclarationCacheVisitor { - DeclarationCacheVisitor(KDevVarLengthArray& _cache) : cache(_cache) { + explicit DeclarationCacheVisitor(KDevVarLengthArray& _cache) : cache(_cache) { } bool operator()(const IndexedDeclaration& decl) const { cache.append(decl); return true; } KDevVarLengthArray& cache; }; PersistentSymbolTable::FilteredDeclarationIterator PersistentSymbolTable::getFilteredDeclarations(const IndexedQualifiedIdentifier& id, const TopDUContext::IndexedRecursiveImports& visibility) const { QMutexLocker lock(d->m_declarations.mutex()); ENSURE_CHAIN_READ_LOCKED Declarations decls = getDeclarations(id).iterator(); CachedIndexedRecursiveImports cachedImports; QHash::const_iterator it = d->m_importsCache.constFind(visibility); if(it != d->m_importsCache.constEnd()) { cachedImports = *it; }else{ cachedImports = CachedIndexedRecursiveImports(visibility.set().stdSet()); d->m_importsCache.insert(visibility, cachedImports); } if(decls.dataSize() > MinimumCountForCache) { //Do visibility caching CacheEntry& cached(d->m_declarationsCache[id]); CacheEntry::DataHash::const_iterator cacheIt = cached.m_hash.constFind(visibility); if(cacheIt != cached.m_hash.constEnd()) return FilteredDeclarationIterator(Declarations::Iterator(cacheIt->constData(), cacheIt->size(), -1), cachedImports); CacheEntry::DataHash::iterator insertIt = cached.m_hash.insert(visibility, KDevVarLengthArray()); KDevVarLengthArray& cache(*insertIt); { typedef ConvenientEmbeddedSetTreeFilterVisitor FilteredDeclarationCacheVisitor; //The visitor visits all the declarations from within its constructor DeclarationCacheVisitor v(cache); FilteredDeclarationCacheVisitor visitor(v, decls.iterator(), cachedImports); } return FilteredDeclarationIterator(Declarations::Iterator(cache.constData(), cache.size(), -1), cachedImports, true); }else{ return FilteredDeclarationIterator(decls.iterator(), cachedImports); } } PersistentSymbolTable::Declarations PersistentSymbolTable::getDeclarations(const IndexedQualifiedIdentifier& id) const { QMutexLocker lock(d->m_declarations.mutex()); ENSURE_CHAIN_READ_LOCKED PersistentSymbolTableItem item; item.id = id; uint index = d->m_declarations.findIndex(item); if(index) { const PersistentSymbolTableItem* repositoryItem = d->m_declarations.itemFromIndex(index); return PersistentSymbolTable::Declarations(repositoryItem->declarations(), repositoryItem->declarationsSize(), repositoryItem->centralFreeItem); }else{ return PersistentSymbolTable::Declarations(); } } void PersistentSymbolTable::declarations(const IndexedQualifiedIdentifier& id, uint& countTarget, const IndexedDeclaration*& declarationsTarget) const { QMutexLocker lock(d->m_declarations.mutex()); ENSURE_CHAIN_READ_LOCKED PersistentSymbolTableItem item; item.id = id; uint index = d->m_declarations.findIndex(item); if(index) { const PersistentSymbolTableItem* repositoryItem = d->m_declarations.itemFromIndex(index); countTarget = repositoryItem->declarationsSize(); declarationsTarget = repositoryItem->declarations(); }else{ countTarget = 0; declarationsTarget = nullptr; } } struct DebugVisitor { - DebugVisitor(const QTextStream& _out) + explicit DebugVisitor(const QTextStream& _out) : out(_out) { } bool operator() (const PersistentSymbolTableItem* item) { QDebug qout = fromTextStream(out); QualifiedIdentifier id(item->id.identifier()); if(identifiers.contains(id)) { qout << "identifier" << id.toString() << "appears for" << identifiers[id] << "th time"; } ++identifiers[id]; for(uint a = 0; a < item->declarationsSize(); ++a) { IndexedDeclaration decl(item->declarations()[a]); if(!decl.isDummy()) { if(declarations.contains(decl)) { qout << "declaration found for multiple identifiers. Previous identifier:" << declarations[decl].toString() << "current identifier:" << id.toString() << endl; }else{ declarations.insert(decl, id); } } if(decl.data() && decl.data()->qualifiedIdentifier() != item->id.identifier()) { qout << decl.data()->url().str() << "declaration" << decl.data()->qualifiedIdentifier() << "is registered as" << item->id.identifier() << endl; } const QString url = IndexedTopDUContext(decl.topContextIndex()).url().str(); if(!decl.data() && !decl.isDummy()) { qout << "Item in symbol-table is invalid:" << id.toString() << "- localIndex:" << decl.localIndex() << "- url:" << url << endl; } else { qout << "Item in symbol-table:" << id.toString() << "- localIndex:" << decl.localIndex() << "- url:" << url; if (auto d = decl.data()) { qout << "- range:" << d->range(); } else { qout << "- null declaration"; } qout << endl; } } return true; } const QTextStream& out; QHash identifiers; QHash declarations; }; void PersistentSymbolTable::dump(const QTextStream& out) { { QMutexLocker lock(d->m_declarations.mutex()); QDebug qout = fromTextStream(out); DebugVisitor v(out); d->m_declarations.visitAllItems(v); qout << "Statistics:" << endl; qout << d->m_declarations.statistics() << endl; } } PersistentSymbolTable& PersistentSymbolTable::self() { static PersistentSymbolTable ret; return ret; } } diff --git a/language/duchain/safetycounter.h b/language/duchain/safetycounter.h index 1b52e36cc..0a6b4da59 100644 --- a/language/duchain/safetycounter.h +++ b/language/duchain/safetycounter.h @@ -1,57 +1,57 @@ /*************************************************************************** Copyright 2006 David Nolden ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef __SAFETYCOUNTER_H__ #define __SAFETYCOUNTER_H__ #include struct SafetyCounter { int safetyCounter; const int maxSafetyCounter; - SafetyCounter( int max = 40000 ) : safetyCounter(0), maxSafetyCounter(max) { + explicit SafetyCounter( int max = 40000 ) : safetyCounter(0), maxSafetyCounter(max) { } void init() { safetyCounter = 0; } SafetyCounter& operator ++() { safetyCounter++; return *this; } ///Returns whether the counter is ok, but without increasing it bool ok() const { return safetyCounter < maxSafetyCounter; } operator bool() { safetyCounter++; bool ret = safetyCounter < maxSafetyCounter; if( !ret ) { if( safetyCounter == maxSafetyCounter ) { #ifdef DEPTHBACKTRACE qDebug() << "WARNING: Safety-counter reached count > " << maxSafetyCounter << ", operation stopped"; #endif } } return ret; } }; #endif diff --git a/language/duchain/topducontextdynamicdata.h b/language/duchain/topducontextdynamicdata.h index 92c0558e3..bd86cf37a 100644 --- a/language/duchain/topducontextdynamicdata.h +++ b/language/duchain/topducontextdynamicdata.h @@ -1,189 +1,189 @@ /* 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" class QFile; namespace KDevelop { class TopDUContext; class DUContext; class Declaration; class IndexedString; class IndexedDUContext; class DUChainBaseData; ///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: explicit TopDUContextDynamicData(TopDUContext* topContext); ~TopDUContextDynamicData(); void clear(); /** * Allocates an index for the given declaration in this top-context. * The returned index is never zero. * @param temporary 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 temporary 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(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 { uint dataOffset; /// Offset of the data uint parentContext; /// Parent context of the data (0 means the global context) }; struct ArrayWithPosition { QByteArray array; uint position; }; 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); + explicit DUChainItemStorage(TopDUContextDynamicData* data); ~DUChainItemStorage(); void clearItems(); bool itemsHaveChanged() const; void storeData(uint& currentDataOffset, const QVector& oldData); Item getItemForIndex(uint index) const; void clearItemIndex(const Item& item, const uint index); 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 offsets; QVector temporaryItems; TopDUContextDynamicData* const data; }; DUChainItemStorage m_contexts; DUChainItemStorage m_declarations; DUChainItemStorage m_problems; //For temporary declarations that will not be stored to disk, like template instantiations mutable QVector m_data; mutable QVector 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_PRIMITIVE_TYPE); #endif diff --git a/language/duchain/types/identifiedtype.h b/language/duchain/types/identifiedtype.h index b14a0804f..a233fd0fa 100644 --- a/language/duchain/types/identifiedtype.h +++ b/language/duchain/types/identifiedtype.h @@ -1,174 +1,174 @@ /* This file is part of KDevelop Copyright 2002-2005 Roberto Raggi Copyright 2006 Adam Treat Copyright 2006-2008 Hamish Rodda Copyright 2007-2008 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_IDENTIFIEDTYPE_H #define KDEVPLATFORM_IDENTIFIEDTYPE_H #include "../identifier.h" #include "../declarationid.h" namespace KDevelop { class DUContext; class Declaration; class DeclarationId; class TopDUContext; class AbstractType; /** * Data structure for identified types. */ class IdentifiedTypeData { public: DeclarationId m_id; }; /** * \short An IdentifiedType is a type that has a declaration. * * Example of an identified type: * - A class type * * Example of types which are not identified: * - Pointer types (they can point to identified types, but by themselves have no declaration) * - Reference types (the same) * */ class KDEVPLATFORMLANGUAGE_EXPORT IdentifiedType { public: /// Destructor virtual ~IdentifiedType(); /** * Test for equivalence with another type. * * \param rhs other type to check for equivalence * \returns true if equal, otherwise false. */ bool equals(const IdentifiedType* rhs) const; /// Clear the identifier void clear(); /** * Determine the qualified identifier for this identified type. * * \note This is relatively expensive. Use declarationId() instead when possible! * \returns the type's qualified identifier */ QualifiedIdentifier qualifiedIdentifier() const; /// Determine this identified type's hash value. \returns the hash value uint hash() const; /** * Access the identified type's declaration id, which is a unique global identifier for the * type even when the same identifier represents a different type in a different context * or source file. * * \returns the declaration identifier. * \sa DeclarationId */ DeclarationId declarationId() const; /** * Set the globally unique declaration \a id. * \param id new identifier */ void setDeclarationId(const DeclarationId& id); /** * Look up the declaration of this type in the given \a top context. * * \param top Top context to search for the declaration within * \returns the declaration corresponding to this identified type */ Declaration* declaration(const TopDUContext* top) const; /** * \param top Top context to search for the declaration within * Convenience function that returns the internal context of the attached declaration, * or zero if no declaration is found, or if it does not have an internal context. */ DUContext* internalContext(const TopDUContext* top) const; /** * Set the declaration which created this type. * * \note You should be careful when setting this, because it also changes the meaning of the declaration. * * The logic is: * If a declaration has a set abstractType(), and that abstractType() has set the same declaration as declaration(), * then the declaration declares the type(thus it is a type-declaration, see Declaration::kind()) * * \param declaration Declaration which created the type * */ void setDeclaration(Declaration* declaration); /// Allow IdentifiedType to access its data. virtual IdentifiedTypeData* idData() = 0; /// Allow IdentifiedType to access its data. virtual const IdentifiedTypeData* idData() const = 0; }; ///Implements everything necessary to merge the given Parent class with IdentifiedType ///Your used Data class must be based on the Data member class template class MergeIdentifiedType : public Parent, public IdentifiedType { public: class Data : public Parent::Data, public IdentifiedTypeData { }; MergeIdentifiedType() { } - MergeIdentifiedType(Data& data) : Parent(data) { + explicit MergeIdentifiedType(Data& data) : Parent(data) { } virtual IdentifiedTypeData* idData() { return static_cast(this->d_func_dynamic()); } virtual const IdentifiedTypeData* idData() const { return static_cast(this->d_func()); } virtual bool equals(const KDevelop::AbstractType* rhs) const { if (!Parent::equals(rhs)) return false; const IdentifiedType* rhsId = dynamic_cast(rhs); Q_ASSERT(rhsId); return IdentifiedType::equals(static_cast(rhsId)); } private: MergeIdentifiedType(const MergeIdentifiedType& rhs); }; } #endif diff --git a/language/highlighting/codehighlighting.h b/language/highlighting/codehighlighting.h index 38ba80213..ff8af99e4 100644 --- a/language/highlighting/codehighlighting.h +++ b/language/highlighting/codehighlighting.h @@ -1,221 +1,221 @@ /* * This file is part of KDevelop * * Copyright 2007-2010 David Nolden * Copyright 2006 Hamish Rodda * Copyright 2009 Milian Wolff * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_CODEHIGHLIGHTING_H #define KDEVPLATFORM_CODEHIGHLIGHTING_H #include #include #include #include #include #include #include #include #include namespace KDevelop { class DUContext; class Declaration; typedef QVector ColorMap; class CodeHighlighting; struct HighlightingEnumContainer { enum Types { UnknownType, //Primary highlighting: LocalClassMemberType, InheritedClassMemberType, LocalVariableType, //Other highlighting: ClassType, FunctionType, ForwardDeclarationType, EnumType, EnumeratorType, TypeAliasType, MacroType, /// Declaration of a macro such as "#define FOO" MacroFunctionLikeType, /// Declaration of a function like macro such as "#define FOO()" //If none of the above match: MemberVariableType, NamespaceVariableType, GlobalVariableType, //Most of these are currently not used: ArgumentType, CodeType, FileType, NamespaceType, ScopeType, TemplateType, TemplateParameterType, FunctionVariableType, ErrorVariableType }; enum Contexts { DefinitionContext, DeclarationContext, ReferenceContext }; }; struct HighlightedRange { RangeInRevision range; KTextEditor::Attribute::Ptr attribute; bool operator<(const HighlightedRange& rhs) const { return range.start < rhs.range.start; } }; /** * Code highlighting instance that is used to apply code highlighting to one specific top context * */ class KDEVPLATFORMLANGUAGE_EXPORT CodeHighlightingInstance : public HighlightingEnumContainer { public: - CodeHighlightingInstance(const CodeHighlighting* highlighting) : m_useClassCache(false), m_highlighting(highlighting) { + explicit CodeHighlightingInstance(const CodeHighlighting* highlighting) : m_useClassCache(false), m_highlighting(highlighting) { } virtual ~CodeHighlightingInstance() { } virtual void highlightDeclaration(KDevelop::Declaration* declaration, const QColor &color); virtual void highlightUse(KDevelop::DUContext* context, int index, const QColor &color); virtual void highlightUses(KDevelop::DUContext* context); void highlightDUChain(KDevelop::TopDUContext* context); void highlightDUChain(KDevelop::DUContext* context, QHash colorsForDeclarations, ColorMap); KDevelop::Declaration* localClassFromCodeContext(KDevelop::DUContext* context) const; /** * @param context Should be the context from where the declaration is used, if a use is highlighted. * */ virtual Types typeForDeclaration(KDevelop::Declaration* dec, KDevelop::DUContext* context) const; /** * Decides whether to apply auto-generated rainbow colors to @p dec. * Default implementation only applies that to local variables in functions. */ virtual bool useRainbowColor(KDevelop::Declaration* dec) const; //A temporary hash for speedup mutable QHash m_contextClasses; //Here the colors of function context are stored until they are merged into the function body mutable QMap > m_functionColorsForDeclarations; mutable QMap m_functionDeclarationsForColors; mutable bool m_useClassCache; const CodeHighlighting* m_highlighting; QVector m_highlight; }; /** * General class representing the code highlighting for one language * */ class KDEVPLATFORMLANGUAGE_EXPORT CodeHighlighting : public QObject, public KDevelop::ICodeHighlighting, public HighlightingEnumContainer { Q_OBJECT Q_INTERFACES(KDevelop::ICodeHighlighting) public: explicit CodeHighlighting(QObject* parent); ~CodeHighlighting() override; /// This function is thread-safe /// @warning The duchain must not be locked when this is called (->possible deadlock) void highlightDUChain(ReferencedTopDUContext context) override; //color should be zero when undecided KTextEditor::Attribute::Ptr attributeForType(Types type, Contexts context, const QColor &color) const; KTextEditor::Attribute::Ptr attributeForDepth(int depth) const; /// This function is thread-safe /// Returns whether a highlighting is already given for the given url bool hasHighlighting(IndexedString url) const override; private: //Returns whether the given attribute was set by the code highlighting, and not by something else //Always returns true when the attribute is zero bool isCodeHighlight(KTextEditor::Attribute::Ptr attr) const; protected: //Can be overridden to create an own instance type virtual CodeHighlightingInstance* createInstance() const; private: /// Highlighting of one specific document struct DocumentHighlighting { IndexedString m_document; qint64 m_waitingRevision; // The ranges are sorted by range start, so they can easily be matched QVector m_waiting; QVector m_highlightedRanges; }; QMap m_highlights; friend class CodeHighlightingInstance; mutable QHash m_definitionAttributes; mutable QHash m_declarationAttributes; mutable QHash m_referenceAttributes; mutable QList m_depthAttributes; // Should be used to enable/disable the colorization of local variables and their uses bool m_localColorization; // Should be used to enable/disable the colorization of global types and their uses bool m_globalColorization; mutable QMutex m_dataMutex; private Q_SLOTS: void clearHighlightingForDocument(KDevelop::IndexedString document); void applyHighlighting(void* highlighting); void trackerDestroyed(QObject* object); /// when the colors change we must invalidate our local caches void adaptToColorChanges(); void aboutToInvalidateMovingInterfaceContent(KTextEditor::Document*); void aboutToRemoveText(const KTextEditor::Range&); }; } #endif // kate: space-indent on; indent-width 2; remove-trailing-spaces all; show-tabs on; tab-indents on; tab-width 2; diff --git a/language/highlighting/colorcache.h b/language/highlighting/colorcache.h index 263676dcc..118a1e24e 100644 --- a/language/highlighting/colorcache.h +++ b/language/highlighting/colorcache.h @@ -1,182 +1,182 @@ /* * This file is part of KDevelop * * Copyright 2009 Milian Wolff * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_COLORCACHE_H #define KDEVPLATFORM_COLORCACHE_H #include #include #include #include #include namespace KTextEditor { class Document; class View; } namespace KDevelop { class CodeHighlightingColors; class IDocument; /** * A singleton which holds the global default colors, adapted to the current color scheme */ class KDEVPLATFORMLANGUAGE_EXPORT ColorCache : public QObject { Q_OBJECT public: ~ColorCache() override; /// access the global color cache static ColorCache* self(); /// adapt a given foreground color to the current color scheme /// @p ratio between 0 and 255 where 0 gives @see m_foregroundColor /// and 255 gives @p color /// /// @note if you are looking for a background color, simply setting an alpha /// value should work. QColor blend(QColor color, uchar ratio) const; /// adapt a given background color to the current color scheme /// @p ratio between 0 and 255 where 0 gives @see m_foregroundColor /// and 255 gives @p color /// /// @note if you are looking for a background color, simply setting an alpha /// value should work. QColor blendBackground(QColor color, uchar ratio) const; /// blend a color for local colorization according to the user settings /// @see blend() QColor blendLocalColor(QColor color) const; /// blend a color for global colorization according to the user settings /// @see blend() QColor blendGlobalColor(QColor color) const; /// access the default colors CodeHighlightingColors* defaultColors() const; /** * @returns a primary color if @p num less primaryColorCount and a supplementary color if @p num >= primaryColorCount and < validColorCount * @see validColorCount() * @see primaryColorCount() */ QColor generatedColor(uint num) const; /** * @returns the number of primary and supplementary colors * * @see generatedColor() * @see primaryColorCount() */ uint validColorCount() const; /** * @returns number of primary colors * * When you run out of primary colors use supplementary colors */ uint primaryColorCount() const; /// access the foreground color QColor foregroundColor() const; signals: /// will be emitted whenever the colors got changed /// @see update() void colorsGotChanged(); private slots: /// if necessary, adapt to the colors of this document void slotDocumentActivated(); /// settings got changed, update to the settings of the sender void slotViewSettingsChanged(); /// will regenerate colors from global KDE color scheme void updateColorsFromScheme(); /// will regenerate colors with the proper intensity settings void updateColorsFromSettings(); /// regenerate colors and emits @p colorsGotChanged() /// and finally triggers a rehighlight of the opened documents void updateInternal(); bool tryActiveDocument(); private: - ColorCache(QObject *parent = nullptr); + explicit ColorCache(QObject *parent = nullptr); static ColorCache* m_self; /// get @p totalGeneratedColors colors from the color wheel and adapt them to the current color scheme void generateColors(); /// calls @c updateInternal() delayed to prevent double loading of language plugins. void update(); /// try to access the KatePart settings for the given doc or fallback to the global KDE scheme /// and update the colors if necessary /// @see generateColors(), updateColorsFromScheme() void updateColorsFromView(KTextEditor::View* view); /// the default colors for the different types CodeHighlightingColors* m_defaultColors; /// the generated colors QList m_colors; uint m_validColorCount; uint m_primaryColorCount; /// Maybe make this configurable: An offset where to start stepping through the color wheel uint m_colorOffset; /// the text color for the current color scheme QColor m_foregroundColor; /// the editor background color color for the current color scheme QColor m_backgroundColor; /// How generated colors for local variables should be mixed with the foreground color. /// Between 0 and 255, where 255 means only foreground color, and 0 only the chosen color. uchar m_localColorRatio; /// How global colors (i.e. for types, uses, etc.) should be mixed with the foreground color. /// Between 0 and 255, where 255 means only foreground color, and 0 only the chosen color. uchar m_globalColorRatio; /// Whether declarations have to be rendered with a bold style or not. bool m_boldDeclarations; /// The view we are listening to for setting changes. QPointer m_view; }; } #endif // KDEVPLATFORM_COLORCACHE_H // kate: space-indent on; indent-width 2; remove-trailing-spaces all; show-tabs on; tab-indents on; tab-width 2; diff --git a/language/interfaces/codecontext.cpp b/language/interfaces/codecontext.cpp index 3768a1d1a..8005d27a2 100644 --- a/language/interfaces/codecontext.cpp +++ b/language/interfaces/codecontext.cpp @@ -1,136 +1,136 @@ /* 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 #include namespace KDevelop { class DUContextContext::Private { public: - Private( const IndexedDUContext& item ) : m_item( item ) + explicit 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; } QList DUContextContext::urls() const { DUChainReadLocker lock; if (auto context = d->m_item.context()) { return {context->url().toUrl()}; } return {}; } 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, const KTextEditor::Cursor& position) : DUContextContext(IndexedDUContext()) { const QUrl& url = view->document()->url(); DUChainReadLocker lock; DUChainUtils::ItemUnderCursor item = DUChainUtils::itemUnderCursor(url, position); DocumentRange useRange = DocumentRange(IndexedString(url), item.range); Declaration* declaration = item.declaration; IndexedDeclaration indexed; if ( declaration ) { indexed = IndexedDeclaration(declaration); } d = new Private(declaration, useRange); setContext(IndexedDUContext(item.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/util/basicsetrepository.h b/language/util/basicsetrepository.h index 5ab6b1511..12994e25c 100644 --- a/language/util/basicsetrepository.h +++ b/language/util/basicsetrepository.h @@ -1,354 +1,354 @@ /*************************************************************************** Copyright 2007 David Nolden ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef KDEVPLATFORM_BASICSETREPOSITORY_H #define KDEVPLATFORM_BASICSETREPOSITORY_H #include #include #include #include #include /** * This file provides a set system that can be used to efficiently manage sub-sets of a set of global objects. * Each global object must be mapped to a natural number. * * The efficiency comes from: * 1. The sets are represented by binary trees, where every single tree node can be shared across multiple sets. * For that reason, intersecting sets will share large parts of their internal data structures, which leads to * extremely low memory-usage compared to using for example std::set. * * The more common intersections between the sets exist, the more efficient the system is. * This will have the biggest advantage when items that were added contiguously are commonly * used within the same sets, and work fastest if the intersections between different sets are contiguously long. * * That makes it perfect for representing sets that are inherited across tree-like structures, like for example in C++: * - Macros defined in files(Macros are inherited from included files) * - Strings contained in files(Strings are inherited from included files) * - Set of all included files * * Measurements(see in kdevelop languages/cpp/cppduchain/tests/duchaintest) show that even in worst case(with totally random sets) * these set-repositories are 2 times faster than std::set, and 4 times faster than QSet. * * The main disadvantages are that a global repository needs to be managed, and needs to be secured from simultaneous write-access * during multi-threading. This is done internally if the doLocking flag is set while constructing. * */ class QString; namespace Utils { enum { delayedDeletionByDefault = 0 }; class SetNode; class BasicSetRepository; class SetNodeDataRequest; ///Internal node representation, exported here for performance reason. struct KDEVPLATFORMLANGUAGE_EXPORT SetNodeData { private: //Rule: start < end uint m_start, m_end; //This set-node bounds all indices starting at start until end, not including end. //Child nodes //Rule: left->start == start, right->end == end //Rule: (left != 0 && right != 0) || (left == 0 && right == 0) uint m_leftNode, m_rightNode; // cached hash of this node data - it only includes the first four data members, // i.e. m_start, m_end, m_leftNode and m_rightNode uint m_hash; public: uint m_refCount; - inline SetNodeData(uint start = 1, uint end = 1, uint leftNode = 0, uint rightNode = 0) + inline explicit SetNodeData(uint start = 1, uint end = 1, uint leftNode = 0, uint rightNode = 0) : m_start(start) , m_end(end) , m_leftNode(leftNode) , m_rightNode(rightNode) , m_hash(calculateHash()) , m_refCount(0) { } inline uint hash() const { return m_hash; } uint calculateHash() const { return KDevHash() << m_start << m_end << m_leftNode << m_rightNode; } inline short unsigned int itemSize() const { return sizeof(SetNodeData); } inline bool contiguous() const { return !m_leftNode; } inline bool hasSlaves() const { return (bool)m_leftNode; } inline uint start() const { return m_start; } inline uint end() const { return m_end; } inline uint leftNode() const { return m_leftNode; } inline uint rightNode() const { return m_rightNode; } }; typedef KDevelop::ItemRepository SetDataRepositoryBase; struct SetDataRepository; class SetNodeDataRequest { public: enum { AverageSize = sizeof(SetNodeData) }; //This constructor creates a request that finds or creates a node that equals the given node //The m_hash must be up to date, and the node must be split correctly around its splitPosition inline SetNodeDataRequest(const SetNodeData* _data, SetDataRepository& _repository, BasicSetRepository* _setRepository); ~SetNodeDataRequest(); typedef unsigned int HashType; //Should return the m_hash-value associated with this request(For example the m_hash of a string) inline HashType hash() const { return m_hash; } //Should return the size of an item created with createItem inline uint itemSize() const { return sizeof(SetNodeData); } //Should create an item where the information of the requested item is permanently stored. The pointer //@param item equals an allocated range with the size of itemSize(). void createItem(SetNodeData* item) const; static void destroy(SetNodeData* data, KDevelop::AbstractItemRepository& _repository); static bool persistent(const SetNodeData* item) { return (bool)item->m_refCount; } //Should return whether the here requested item equals the given item inline bool equals(const SetNodeData* item) const; SetNodeData data; uint m_hash; SetDataRepository& repository; mutable BasicSetRepository* setRepository; //May be zero when no notifications are wanted mutable bool m_created; }; struct KDEVPLATFORMLANGUAGE_EXPORT SetDataRepository : public SetDataRepositoryBase { SetDataRepository(BasicSetRepository* _setRepository, QString name, KDevelop::ItemRepositoryRegistry* registry) : SetDataRepositoryBase(name, registry), setRepository(_setRepository) { } BasicSetRepository* setRepository; }; /** * This object is copyable. It represents a set, and allows iterating through the represented indices. * */ class KDEVPLATFORMLANGUAGE_EXPORT Set { public: class Iterator; typedef unsigned int Index; Set(); //Internal constructor Set(uint treeNode, BasicSetRepository* repository); ~Set(); Set(const Set& rhs); Set& operator=(const Set& rhs); QString dumpDotGraph() const; //Returns an itrator that can be used to iterate over the contained indices Iterator iterator() const; //Returns this set converted to a standard set that contains all indices contained by this set. std::set stdSet() const; ///Returns the count of items in the set unsigned int count() const; bool contains(Index index) const; ///@warning: The following operations can change the global repository, and thus need to be serialized /// using mutexes in case of multi-threading. ///Set union Set operator +(const Set& rhs) const; Set& operator +=(const Set& rhs); ///Set intersection Set operator &(const Set& rhs) const; Set& operator &=(const Set& rhs); ///Set subtraction Set operator -(const Set& rhs) const; Set& operator -=(const Set& rhs); uint setIndex() const { return m_tree; } ///Increase the static reference-count of this set by one. The initial reference-count of newly created sets is zero. void staticRef(); ///Decrease the static reference-count of this set by one. This set must have a reference-count >= 1. ///If this set reaches the reference-count zero, it will be deleted, and all sub-nodes that also reach the reference-count zero ///will be deleted as well. @warning Either protect ALL your sets by using reference-counting, or don't use it at all. void staticUnref(); ///Returns a pointer to the repository this set belongs to. Returns zero when this set is not initialized yet. BasicSetRepository* repository() const; private: void unrefNode(uint); friend class BasicSetRepository; uint m_tree; mutable BasicSetRepository* m_repository; }; /** * This is a repository that can be used to efficiently manage generic sets * that are represented by interweaved binary trees. * * All strings are based on items that are contained in one master-repository, * starting at one. * * An index of zero is interpreted as invalid. * */ class KDEVPLATFORMLANGUAGE_EXPORT BasicSetRepository { public: ///@param name The name must be unique, and is used for loading and storing the data ///@param registry Where the repository should be registered. If you give zero, it won't be registered, and thus won't be saved to disk. explicit BasicSetRepository(QString name, KDevelop::ItemRepositoryRegistry* registry = &KDevelop::globalItemRepositoryRegistry(), bool delayedDeletion = delayedDeletionByDefault); virtual ~BasicSetRepository(); typedef unsigned int Index; /** * Takes a sorted list indices, returns a set representing them * */ Set createSetFromIndices(const std::vector& indices); /** * Takes a simple set of indices * */ Set createSet(const std::set& indices); /** * Creates a set that only contains that single index. * For better performance, you should create bigger sets than this. * */ Set createSet(Index i); void printStatistics() const; ///Is called when this index is not part of any set any more virtual void itemRemovedFromSets(uint index); ///Is called when this index is added to one of the contained sets for the first time virtual void itemAddedToSets(uint index); inline const SetNodeData* nodeFromIndex(uint index) const { if(index) return dataRepository.itemFromIndex(index); else return nullptr; } inline QMutex* mutex() const { return m_mutex; } ///Only public to get statistics and such const SetDataRepository& getDataRepository() const { return dataRepository; } ///Set whether set-nodes with reference-count zero should be deleted only after a delay ///The default is true. ///This may be faster when the structure is large anyway and many temporary sets ///are created, but leads to a sparse structure in memory, which is bad for cache. void setDelayedDeletion(bool delayed) { m_delayedDeletion = delayed; } inline bool delayedDeletion() const { return m_delayedDeletion; } private: friend class Set; friend class Set::Iterator; class Private; Private* d; SetDataRepository dataRepository; QMutex* m_mutex; bool m_delayedDeletion; // SetNode }; /** * Use this to iterate over the indices contained in a set * */ class KDEVPLATFORMLANGUAGE_EXPORT Set::Iterator { public: Iterator(); Iterator(const Iterator& rhs); Iterator& operator=(const Iterator& rhs); ~Iterator(); operator bool() const; Iterator& operator++(); BasicSetRepository::Index operator*() const; private: friend class Set; friend class IteratorPrivate; static inline BasicSetRepository::Private *getPrivatePtr(BasicSetRepository *repo) { return repo->d; } static inline SetDataRepository &getDataRepository(BasicSetRepository *repo) { return repo->dataRepository; } class IteratorPrivate; IteratorPrivate* d; }; } #endif diff --git a/language/util/setrepository.cpp b/language/util/setrepository.cpp index 503ccc25f..d89609108 100644 --- a/language/util/setrepository.cpp +++ b/language/util/setrepository.cpp @@ -1,1121 +1,1121 @@ /*************************************************************************** Copyright 2007 David Nolden ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "setrepository.h" #include "util/debug.h" #include #include #include #include #include #include #include #include //#define DEBUG_SETREPOSITORY #ifdef DEBUG_SETREPOSITORY #define ifDebug(X) X #else #define ifDebug(x) #undef Q_ASSERT #define Q_ASSERT(x) #endif #ifndef DEBUG_SETREPOSITORY #define CHECK_SPLIT_POSITION(Node) #else #define CHECK_SPLIT_POSITION(node) Q_ASSERT(!(node).leftNode || (getLeftNode(&node)->end() <= splitPositionForRange((node).start, (node).end) && getRightNode(&node)->start() >= splitPositionForRange((node).start, (node).end))) #endif namespace Utils { /** * To achieve a maximum re-usage of nodes, we make sure that sub-nodes of a node always split at specific boundaries. * For each range we can compute a position where that range should be split into its child-nodes. * When creating a new node with 2 sub-nodes, we re-create those child-nodes if their boundaries don't represent those split-positions. * * We pick the split-positions deterministically, they are in order of priority: * ((1<<31)*n, n = [0,...] * ((1<<30)*n, n = [0,...] * ((1<<29)*n, n = [0,...] * ((1<<...)*n, n = [0,...] * ... * */ typedef BasicSetRepository::Index Index; ///The returned split position shall be the end of the first sub-range, and the start of the second ///@param splitBit should be initialized with 31, unless you know better. The value can then be used on while computing child split positions. ///In the end, it will contain the bit used to split the range. It will also contain zero if no split-position exists(length 1) uint splitPositionForRange(uint start, uint end, uchar& splitBit) { if(end-start == 1) { splitBit = 0; return 0; } while(true) { uint position = ((end-1) >> splitBit) << splitBit; //Round to the split-position in this interval that is smaller than end if(position > start && position < end) return position; Q_ASSERT(splitBit != 0); --splitBit; } return 0; } uint splitPositionForRange(uint start, uint end) { uchar splitBit = 31; return splitPositionForRange(start, end, splitBit); } class SetNodeDataRequest; #define getLeftNode(node) repository.itemFromIndex(node->leftNode()) #define getRightNode(node) repository.itemFromIndex(node->rightNode()) #define nodeFromIndex(index) repository.itemFromIndex(index) struct SetRepositoryAlgorithms { SetRepositoryAlgorithms(SetDataRepository& _repository, BasicSetRepository* _setRepository) : repository(_repository), setRepository(_setRepository) { } ///Expensive Index count(const SetNodeData* node) const; void localCheck(const SetNodeData* node); void check(uint node); void check(const SetNodeData* node); QString shortLabel(const SetNodeData& node) const; uint set_union(uint firstNode, uint secondNode, const SetNodeData* first, const SetNodeData* second, uchar splitBit = 31); uint createSetFromNodes(uint leftNode, uint rightNode, const SetNodeData* left = nullptr, const SetNodeData* right = nullptr); uint computeSetFromNodes(uint leftNode, uint rightNode, const SetNodeData* left, const SetNodeData* right, uchar splitBit); uint set_intersect(uint firstNode, uint secondNode, const SetNodeData* first, const SetNodeData* second, uchar splitBit = 31); bool set_contains(const SetNodeData* node, Index index); uint set_subtract(uint firstNode, uint secondNode, const SetNodeData* first, const SetNodeData* second, uchar splitBit = 31); //Required both nodes to be split correctly bool set_equals(const SetNodeData* lhs, const SetNodeData* rhs); QString dumpDotGraph(uint node) const; ///Finds or inserts the given ranges into the repository, and returns the set-index that represents them uint setForIndices(std::vector::const_iterator begin, std::vector::const_iterator end, uchar splitBit = 31) { Q_ASSERT(begin != end); uint startIndex = *begin; uint endIndex = *(end-1)+1; if(endIndex == startIndex+1) { SetNodeData data(startIndex, endIndex); return repository.index( SetNodeDataRequest(&data, repository, setRepository) ); } uint split = splitPositionForRange(startIndex, endIndex, splitBit); Q_ASSERT(split); std::vector::const_iterator splitIterator = std::lower_bound(begin, end, split); Q_ASSERT(*splitIterator >= split); Q_ASSERT(splitIterator > begin); Q_ASSERT(*(splitIterator-1) < split); return createSetFromNodes(setForIndices(begin, splitIterator, splitBit), setForIndices(splitIterator, end, splitBit)); } private: QString dumpDotGraphInternal(uint node, bool master=false) const; SetDataRepository& repository; BasicSetRepository* setRepository; }; void SetNodeDataRequest::destroy(SetNodeData* data, KDevelop::AbstractItemRepository& _repository) { SetDataRepository& repository(static_cast(_repository)); if(repository.setRepository->delayedDeletion()) { if(data->leftNode()){ SetDataRepositoryBase::MyDynamicItem left = repository.dynamicItemFromIndex(data->leftNode()); SetDataRepositoryBase::MyDynamicItem right = repository.dynamicItemFromIndex(data->rightNode()); Q_ASSERT(left->m_refCount > 0); --left->m_refCount; Q_ASSERT(right->m_refCount > 0); --right->m_refCount; }else { //Deleting a leaf Q_ASSERT(data->end() - data->start() == 1); repository.setRepository->itemRemovedFromSets(data->start()); } } } SetNodeDataRequest::SetNodeDataRequest(const SetNodeData* _data, SetDataRepository& _repository, BasicSetRepository* _setRepository) : data(*_data), m_hash(_data->hash()), repository(_repository), setRepository(_setRepository), m_created(false) { ifDebug( SetRepositoryAlgorithms alg(repository); alg.check(_data) ); } SetNodeDataRequest::~SetNodeDataRequest() { //Eventually increase the reference-count of direct children if(m_created) { if(data.leftNode()) ++repository.dynamicItemFromIndex(data.leftNode())->m_refCount; if(data.rightNode()) ++repository.dynamicItemFromIndex(data.rightNode())->m_refCount; } } //Should create an item where the information of the requested item is permanently stored. The pointer //@param item equals an allocated range with the size of itemSize(). void SetNodeDataRequest::createItem(SetNodeData* item) const { Q_ASSERT((data.rightNode() && data.leftNode()) || (!data.rightNode() && !data.leftNode())); m_created = true; *item = data; Q_ASSERT((item->rightNode() && item->leftNode()) || (!item->rightNode() && !item->leftNode())); #ifdef DEBUG_SETREPOSITORY //Make sure we split at the correct split position if(item->hasSlaves()) { uint split = splitPositionForRange(data.start, data.end); const SetNodeData* left = repository.itemFromIndex(item->leftNode()); const SetNodeData* right = repository.itemFromIndex(item->rightNode()); Q_ASSERT(split >= left->end() && split <= right->start()); } #endif if(!data.leftNode() && setRepository) { for(uint a = item->start(); a < item->end(); ++a) setRepository->itemAddedToSets(a); } } bool SetNodeDataRequest::equals(const SetNodeData* item) const { Q_ASSERT((item->rightNode() && item->leftNode()) || (!item->rightNode() && !item->leftNode())); //Just compare child nodes, since data must be correctly split, this is perfectly ok //Since this happens in very tight loops, we don't call an additional function here, but just do the check. return item->leftNode() == data.leftNode() && item->rightNode() == data.rightNode() && item->start() == data.start() && item->end() == data.end(); } class BasicSetRepository::Private { public: - Private(QString _name) : name(_name) { + explicit Private(QString _name) : name(_name) { } ~Private() { } QString name; private: }; Set::Set() : m_tree(0), m_repository(nullptr) { } Set::~Set() { } unsigned int Set::count() const { if(!m_repository || !m_tree) return 0; QMutexLocker lock(m_repository->m_mutex); SetRepositoryAlgorithms alg(m_repository->dataRepository, m_repository); return alg.count(m_repository->dataRepository.itemFromIndex(m_tree)); } Set::Set(uint treeNode, BasicSetRepository* repository) : m_tree(treeNode), m_repository(repository) { } Set::Set(const Set& rhs) { m_repository = rhs.m_repository; m_tree = rhs.m_tree; } Set& Set::operator=(const Set& rhs) { m_repository = rhs.m_repository; m_tree = rhs.m_tree; return *this; } QString Set::dumpDotGraph() const { if(!m_repository || !m_tree) return QString(); QMutexLocker lock(m_repository->m_mutex); SetRepositoryAlgorithms alg(m_repository->dataRepository, m_repository); return alg.dumpDotGraph(m_tree); } Index SetRepositoryAlgorithms::count(const SetNodeData* node) const { if(node->leftNode() && node->rightNode()) return count(getLeftNode(node)) + count(getRightNode(node)); else return node->end() - node->start(); } void SetRepositoryAlgorithms::localCheck(const SetNodeData* ifDebug(node) ) { // Q_ASSERT(node->start() > 0); Q_ASSERT(node->start() < node->end()); Q_ASSERT((node->leftNode() && node->rightNode()) || (!node->leftNode() && !node->rightNode())); Q_ASSERT(!node->leftNode() || (getLeftNode(node())->start() == node->start() && getRightNode(node)->end() == node->end())); Q_ASSERT(!node->leftNode() || (getLeftNode(node())->end() <= getRightNode(node)->start())); } void SetRepositoryAlgorithms::check(uint node) { if(!node) return; check(nodeFromIndex(node)); } void SetRepositoryAlgorithms::check(const SetNodeData* node) { localCheck(node); if(node->leftNode()) check(getLeftNode(node)); if(node->rightNode()) check(getRightNode(node)); // CHECK_SPLIT_POSITION(*node); Re-enable this } QString SetRepositoryAlgorithms::shortLabel(const SetNodeData& node) const { return QStringLiteral("n%1_%2").arg(node.start()).arg(node.end()); } QString SetRepositoryAlgorithms::dumpDotGraphInternal(uint nodeIndex, bool master) const { if(!nodeIndex) return QStringLiteral("empty node"); const SetNodeData& node(*repository.itemFromIndex(nodeIndex)); QString color = QStringLiteral("blue"); if(master) color = QStringLiteral("red"); QString label = QStringLiteral("%1 -> %2").arg(node.start()).arg(node.end()); if(!node.contiguous()) label += QLatin1String(", with gaps"); QString ret = QStringLiteral("%1[label=\"%2\", color=\"%3\"];\n").arg(shortLabel(node), label, color); if(node.leftNode()) { const SetNodeData& left(*repository.itemFromIndex(node.leftNode())); const SetNodeData& right(*repository.itemFromIndex(node.rightNode())); Q_ASSERT(node.rightNode()); ret += QStringLiteral("%1 -> %2;\n").arg(shortLabel(node), shortLabel(left)); ret += QStringLiteral("%1 -> %2;\n").arg(shortLabel(node), shortLabel(right)); ret += dumpDotGraphInternal(node.leftNode()); ret += dumpDotGraphInternal(node.rightNode()); } return ret; } QString SetRepositoryAlgorithms::dumpDotGraph(uint nodeIndex) const { QString ret = QStringLiteral("digraph Repository {\n"); ret += dumpDotGraphInternal(nodeIndex, true); ret += QLatin1String("}\n"); return ret; } const int nodeStackAlloc = 500; class Set::Iterator::IteratorPrivate { public: IteratorPrivate() : nodeStackSize(0), currentIndex(0), repository(nullptr) { nodeStackData.resize(nodeStackAlloc); nodeStack = nodeStackData.data(); } IteratorPrivate(const IteratorPrivate& rhs) : nodeStackData(rhs.nodeStackData), nodeStackSize(rhs.nodeStackSize), currentIndex(rhs.currentIndex), repository(rhs.repository) { nodeStack = nodeStackData.data(); } void resizeNodeStack() { nodeStackData.resize(nodeStackSize + 1); nodeStack = nodeStackData.data(); } KDevVarLengthArray nodeStackData; const SetNodeData** nodeStack; int nodeStackSize; Index currentIndex; BasicSetRepository* repository; /** * Pushes the noed on top of the stack, changes currentIndex, and goes as deep as necessary for iteration. * */ void startAtNode(const SetNodeData* node) { Q_ASSERT(node->start() != node->end()); currentIndex = node->start(); do { nodeStack[nodeStackSize++] = node; if(nodeStackSize >= nodeStackAlloc) resizeNodeStack(); if(node->contiguous()) break; //We need no finer granularity, because the range is contiguous node = Set::Iterator::getDataRepository(repository).itemFromIndex(node->leftNode()); } while(node); Q_ASSERT(currentIndex >= nodeStack[0]->start()); } }; std::set Set::stdSet() const { Set::Iterator it = iterator(); std::set ret; while(it) { Q_ASSERT(ret.find(*it) == ret.end()); ret.insert(*it); ++it; } return ret; } Set::Iterator::Iterator(const Iterator& rhs) : d(new IteratorPrivate(*rhs.d)) { } Set::Iterator& Set::Iterator::operator=(const Iterator& rhs) { delete d; d = new IteratorPrivate(*rhs.d); return *this; } Set::Iterator::Iterator() : d(new IteratorPrivate) { } Set::Iterator::~Iterator() { delete d; } Set::Iterator::operator bool() const { return d->nodeStackSize; } Set::Iterator& Set::Iterator::operator++() { Q_ASSERT(d->nodeStackSize); if(d->repository->m_mutex) d->repository->m_mutex->lock(); ++d->currentIndex; //const SetNodeData** currentNode = &d->nodeStack[d->nodeStackSize - 1]; if(d->currentIndex >= d->nodeStack[d->nodeStackSize - 1]->end()) { //Advance to the next node while(d->nodeStackSize && d->currentIndex >= d->nodeStack[d->nodeStackSize - 1]->end()) { --d->nodeStackSize; } if(!d->nodeStackSize) { //ready }else{ //++d->nodeStackSize; //We were iterating the left slave of the node, now continue with the right. ifDebug( const SetNodeData& left = *d->repository->dataRepository.itemFromIndex(d->nodeStack[d->nodeStackSize - 1]->leftNode()); Q_ASSERT(left.end == d->currentIndex); ) const SetNodeData& right = *d->repository->dataRepository.itemFromIndex(d->nodeStack[d->nodeStackSize - 1]->rightNode()); d->startAtNode(&right); } } Q_ASSERT(d->nodeStackSize == 0 || d->currentIndex < d->nodeStack[0]->end()); if(d->repository->m_mutex) d->repository->m_mutex->unlock(); return *this; } BasicSetRepository::Index Set::Iterator::operator*() const { return d->currentIndex; } Set::Iterator Set::iterator() const { if(!m_tree || !m_repository) return Iterator(); QMutexLocker lock(m_repository->m_mutex); Iterator ret; ret.d->repository = m_repository; if(m_tree) ret.d->startAtNode(m_repository->dataRepository.itemFromIndex(m_tree)); return ret; } //Creates a set item with the given children., they must be valid, and they must be split around their split-position. uint SetRepositoryAlgorithms::createSetFromNodes(uint leftNode, uint rightNode, const SetNodeData* left, const SetNodeData* right) { if(!left) left = nodeFromIndex(leftNode); if(!right) right = nodeFromIndex(rightNode); Q_ASSERT(left->end() <= right->start()); SetNodeData set(left->start(), right->end(), leftNode, rightNode); Q_ASSERT(set.start() < set.end()); uint ret = repository.index(SetNodeDataRequest(&set, repository, setRepository)); Q_ASSERT(set.leftNode() >= 0x10000); Q_ASSERT(set.rightNode() >= 0x10000); Q_ASSERT(ret == repository.findIndex(SetNodeDataRequest(&set, repository, setRepository))); ifDebug( check(ret) ); return ret; } //Constructs a set node from the given two sub-nodes. Those must be valid, they must not intersect, and they must have a correct split-hierarchy. //The do not need to be split around their computed split-position. uint SetRepositoryAlgorithms::computeSetFromNodes(uint leftNode, uint rightNode, const SetNodeData* left, const SetNodeData* right, uchar splitBit) { Q_ASSERT(left->end() <= right->start()); uint splitPosition = splitPositionForRange(left->start(), right->end(), splitBit); Q_ASSERT(splitPosition); if(splitPosition < left->end()) { //The split-position intersects the left node uint leftLeftNode = left->leftNode(); uint leftRightNode = left->rightNode(); const SetNodeData* leftLeft = this->getLeftNode(left); const SetNodeData* leftRight = this->getRightNode(left); Q_ASSERT(splitPosition >= leftLeft->end() && splitPosition <= leftRight->start()); //Create a new set from leftLeft, and from leftRight + right. That set will have the correct split-position. uint newRightNode = computeSetFromNodes(leftRightNode, rightNode, leftRight, right, splitBit); return createSetFromNodes(leftLeftNode, newRightNode, leftLeft); }else if(splitPosition > right->start()) { //The split-position intersects the right node uint rightLeftNode = right->leftNode(); uint rightRightNode = right->rightNode(); const SetNodeData* rightLeft = this->getLeftNode(right); const SetNodeData* rightRight = this->getRightNode(right); Q_ASSERT(splitPosition >= rightLeft->end() && splitPosition <= rightRight->start()); //Create a new set from left + rightLeft, and from rightRight. That set will have the correct split-position. uint newLeftNode = computeSetFromNodes(leftNode, rightLeftNode, left, rightLeft, splitBit); return createSetFromNodes(newLeftNode, rightRightNode, nullptr, rightRight); }else{ return createSetFromNodes(leftNode, rightNode, left, right); } } uint SetRepositoryAlgorithms::set_union(uint firstNode, uint secondNode, const SetNodeData* first, const SetNodeData* second, uchar splitBit) { if(firstNode == secondNode) return firstNode; uint firstStart = first->start(), secondEnd = second->end(); if(firstStart >= secondEnd) return computeSetFromNodes(secondNode, firstNode, second, first, splitBit); uint firstEnd = first->end(), secondStart = second->start(); if(secondStart >= firstEnd) return computeSetFromNodes(firstNode, secondNode, first, second, splitBit); //The ranges of first and second do intersect uint newStart = firstStart < secondStart ? firstStart : secondStart; uint newEnd = firstEnd > secondEnd ? firstEnd : secondEnd; //Compute the split-position for the resulting merged node uint splitPosition = splitPositionForRange(newStart, newEnd, splitBit); //Since the ranges overlap, we can be sure that either first or second contain splitPosition. //The node that contains it, will also be split by it. if(splitPosition > firstStart && splitPosition < firstEnd && splitPosition > secondStart && splitPosition < secondEnd) { //The split-position intersect with both first and second. Continue the union on both sides of the split-position, and merge it. uint firstLeftNode = first->leftNode(); uint firstRightNode = first->rightNode(); uint secondLeftNode = second->leftNode(); uint secondRightNode = second->rightNode(); const SetNodeData* firstLeft = repository.itemFromIndex(firstLeftNode); const SetNodeData* firstRight = repository.itemFromIndex(firstRightNode); const SetNodeData* secondLeft = repository.itemFromIndex(secondLeftNode); const SetNodeData* secondRight = repository.itemFromIndex(secondRightNode); Q_ASSERT(splitPosition >= firstLeft->end() && splitPosition <= firstRight->start()); Q_ASSERT(splitPosition >= secondLeft->end() && splitPosition <= secondRight->start()); return createSetFromNodes( set_union(firstLeftNode, secondLeftNode, firstLeft, secondLeft, splitBit), set_union(firstRightNode, secondRightNode, firstRight, secondRight, splitBit) ); }else if(splitPosition > firstStart && splitPosition < firstEnd) { uint firstLeftNode = first->leftNode(); uint firstRightNode = first->rightNode(); const SetNodeData* firstLeft = repository.itemFromIndex(firstLeftNode); const SetNodeData* firstRight = repository.itemFromIndex(firstRightNode); Q_ASSERT(splitPosition >= firstLeft->end() && splitPosition <= firstRight->start()); //splitPosition does not intersect second. That means that second is completely on one side of it. //So we only need to union that side of first with second. if(secondEnd <= splitPosition) { return createSetFromNodes( set_union(firstLeftNode, secondNode, firstLeft, second, splitBit), firstRightNode, nullptr, firstRight ); }else{ Q_ASSERT(secondStart >= splitPosition); return createSetFromNodes( firstLeftNode, set_union(firstRightNode, secondNode, firstRight, second, splitBit), firstLeft ); } }else if(splitPosition > secondStart && splitPosition < secondEnd) { uint secondLeftNode = second->leftNode(); uint secondRightNode = second->rightNode(); const SetNodeData* secondLeft = repository.itemFromIndex(secondLeftNode); const SetNodeData* secondRight = repository.itemFromIndex(secondRightNode); Q_ASSERT(splitPosition >= secondLeft->end() && splitPosition <= secondRight->start()); if(firstEnd <= splitPosition) { return createSetFromNodes( set_union(secondLeftNode, firstNode, secondLeft, first, splitBit), secondRightNode, nullptr, secondRight ); }else{ Q_ASSERT(firstStart >= splitPosition); return createSetFromNodes( secondLeftNode, set_union(secondRightNode, firstNode, secondRight, first, splitBit), secondLeft ); } }else{ //We would have stopped earlier of first and second don't intersect ifDebug( uint test = repository.findIndex(SetNodeDataRequest(first, repository, setRepository)); qCDebug(LANGUAGE) << "found index:" << test; ) Q_ASSERT(0); return 0; } } bool SetRepositoryAlgorithms::set_equals(const SetNodeData* lhs, const SetNodeData* rhs) { if(lhs->leftNode() != rhs->leftNode() || lhs->rightNode() != rhs->rightNode()) return false; else return true; } uint SetRepositoryAlgorithms::set_intersect(uint firstNode, uint secondNode, const SetNodeData* first, const SetNodeData* second, uchar splitBit) { if(firstNode == secondNode) return firstNode; if(first->start() >= second->end()) return 0; if(second->start() >= first->end()) return 0; //The ranges of first and second do intersect uint firstStart = first->start(), firstEnd = first->end(), secondStart = second->start(), secondEnd = second->end(); uint newStart = firstStart < secondStart ? firstStart : secondStart; uint newEnd = firstEnd > secondEnd ? firstEnd : secondEnd; //Compute the split-position for the resulting merged node uint splitPosition = splitPositionForRange(newStart, newEnd, splitBit); //Since the ranges overlap, we can be sure that either first or second contain splitPosition. //The node that contains it, will also be split by it. if(splitPosition > firstStart && splitPosition < firstEnd && splitPosition > secondStart && splitPosition < secondEnd) { //The split-position intersect with both first and second. Continue the intersection on both sides uint firstLeftNode = first->leftNode(); uint firstRightNode = first->rightNode(); uint secondLeftNode = second->leftNode(); uint secondRightNode = second->rightNode(); const SetNodeData* firstLeft = repository.itemFromIndex(firstLeftNode); const SetNodeData* firstRight = repository.itemFromIndex(firstRightNode); const SetNodeData* secondLeft = repository.itemFromIndex(secondLeftNode); const SetNodeData* secondRight = repository.itemFromIndex(secondRightNode); Q_ASSERT(splitPosition >= firstLeft->end() && splitPosition <= firstRight->start()); Q_ASSERT(splitPosition >= secondLeft->end() && splitPosition <= secondRight->start()); uint newLeftNode = set_intersect(firstLeftNode, secondLeftNode, firstLeft, secondLeft, splitBit); uint newRightNode = set_intersect(firstRightNode, secondRightNode, firstRight, secondRight, splitBit); if(newLeftNode && newRightNode) return createSetFromNodes( newLeftNode, newRightNode ); else if(newLeftNode) return newLeftNode; else return newRightNode; }else if(splitPosition > firstStart && splitPosition < firstEnd) { uint firstLeftNode = first->leftNode(); uint firstRightNode = first->rightNode(); const SetNodeData* firstLeft = repository.itemFromIndex(firstLeftNode); const SetNodeData* firstRight = repository.itemFromIndex(firstRightNode); Q_ASSERT(splitPosition >= firstLeft->end() && splitPosition <= firstRight->start()); //splitPosition does not intersect second. That means that second is completely on one side of it. //So we can completely ignore the other side of first. if(secondEnd <= splitPosition) { return set_intersect(firstLeftNode, secondNode, firstLeft, second, splitBit); }else{ Q_ASSERT(secondStart >= splitPosition); return set_intersect(firstRightNode, secondNode, firstRight, second, splitBit); } }else if(splitPosition > secondStart && splitPosition < secondEnd) { uint secondLeftNode = second->leftNode(); uint secondRightNode = second->rightNode(); const SetNodeData* secondLeft = repository.itemFromIndex(secondLeftNode); const SetNodeData* secondRight = repository.itemFromIndex(secondRightNode); Q_ASSERT(splitPosition >= secondLeft->end() && splitPosition <= secondRight->start()); if(firstEnd <= splitPosition) { return set_intersect(secondLeftNode, firstNode, secondLeft, first, splitBit); }else{ Q_ASSERT(firstStart >= splitPosition); return set_intersect(secondRightNode, firstNode, secondRight, first, splitBit); } }else{ //We would have stopped earlier of first and second don't intersect Q_ASSERT(0); return 0; } Q_ASSERT(0); } bool SetRepositoryAlgorithms::set_contains(const SetNodeData* node, Index index) { while(true) { if(node->start() > index || node->end() <= index) return false; if(node->contiguous()) return true; const SetNodeData* leftNode = nodeFromIndex(node->leftNode()); if(index < leftNode->end()) node = leftNode; else { const SetNodeData* rightNode = nodeFromIndex(node->rightNode()); node = rightNode; } } return false; } uint SetRepositoryAlgorithms::set_subtract(uint firstNode, uint secondNode, const SetNodeData* first, const SetNodeData* second, uchar splitBit) { if(firstNode == secondNode) return 0; if(first->start() >= second->end() || second->start() >= first->end()) return firstNode; //The ranges of first and second do intersect uint firstStart = first->start(), firstEnd = first->end(), secondStart = second->start(), secondEnd = second->end(); uint newStart = firstStart < secondStart ? firstStart : secondStart; uint newEnd = firstEnd > secondEnd ? firstEnd : secondEnd; //Compute the split-position for the resulting merged node uint splitPosition = splitPositionForRange(newStart, newEnd, splitBit); //Since the ranges overlap, we can be sure that either first or second contain splitPosition. //The node that contains it, will also be split by it. if(splitPosition > firstStart && splitPosition < firstEnd && splitPosition > secondStart && splitPosition < secondEnd) { //The split-position intersect with both first and second. Continue the subtract on both sides of the split-position, and merge it. uint firstLeftNode = first->leftNode(); uint firstRightNode = first->rightNode(); uint secondLeftNode = second->leftNode(); uint secondRightNode = second->rightNode(); const SetNodeData* firstLeft = repository.itemFromIndex(firstLeftNode); const SetNodeData* firstRight = repository.itemFromIndex(firstRightNode); const SetNodeData* secondLeft = repository.itemFromIndex(secondLeftNode); const SetNodeData* secondRight = repository.itemFromIndex(secondRightNode); Q_ASSERT(splitPosition >= firstLeft->end() && splitPosition <= firstRight->start()); Q_ASSERT(splitPosition >= secondLeft->end() && splitPosition <= secondRight->start()); uint newLeftNode = set_subtract(firstLeftNode, secondLeftNode, firstLeft, secondLeft, splitBit); uint newRightNode = set_subtract(firstRightNode, secondRightNode, firstRight, secondRight, splitBit); if(newLeftNode && newRightNode) return createSetFromNodes(newLeftNode, newRightNode); else if(newLeftNode) return newLeftNode; else return newRightNode; }else if(splitPosition > firstStart && splitPosition < firstEnd) { // Q_ASSERT(splitPosition >= firstLeft->end() && splitPosition <= firstRight->start()); uint firstLeftNode = first->leftNode(); uint firstRightNode = first->rightNode(); const SetNodeData* firstLeft = repository.itemFromIndex(firstLeftNode); const SetNodeData* firstRight = repository.itemFromIndex(firstRightNode); //splitPosition does not intersect second. That means that second is completely on one side of it. //So we only need to subtract that side of first with second. uint newLeftNode = firstLeftNode, newRightNode = firstRightNode; if(secondEnd <= splitPosition) { newLeftNode = set_subtract(firstLeftNode, secondNode, firstLeft, second, splitBit); }else{ Q_ASSERT(secondStart >= splitPosition); newRightNode = set_subtract(firstRightNode, secondNode, firstRight, second, splitBit); } if(newLeftNode && newRightNode) return createSetFromNodes(newLeftNode, newRightNode); else if(newLeftNode) return newLeftNode; else return newRightNode; }else if(splitPosition > secondStart && splitPosition < secondEnd) { uint secondLeftNode = second->leftNode(); uint secondRightNode = second->rightNode(); const SetNodeData* secondLeft = repository.itemFromIndex(secondLeftNode); const SetNodeData* secondRight = repository.itemFromIndex(secondRightNode); Q_ASSERT(splitPosition >= secondLeft->end() && splitPosition <= secondRight->start()); if(firstEnd <= splitPosition) { return set_subtract(firstNode, secondLeftNode, first, secondLeft, splitBit); }else{ Q_ASSERT(firstStart >= splitPosition); return set_subtract(firstNode, secondRightNode, first, secondRight, splitBit); } }else{ //We would have stopped earlier of first and second don't intersect Q_ASSERT(0); return 0; } Q_ASSERT(0); } Set BasicSetRepository::createSetFromIndices(const std::vector& indices) { QMutexLocker lock(m_mutex); if(indices.empty()) return Set(); SetRepositoryAlgorithms alg(dataRepository, this); return Set(alg.setForIndices(indices.begin(), indices.end()), this); } Set BasicSetRepository::createSet(Index i) { QMutexLocker lock(m_mutex); SetNodeData data(i, i+1); return Set(dataRepository.index( SetNodeDataRequest(&data, dataRepository, this) ), this); } Set BasicSetRepository::createSet(const std::set& indices) { if(indices.empty()) return Set(); QMutexLocker lock(m_mutex); std::vector indicesVector; indicesVector.reserve(indices.size()); for( std::set::const_iterator it = indices.begin(); it != indices.end(); ++it ) indicesVector.push_back(*it); return createSetFromIndices(indicesVector); } BasicSetRepository::BasicSetRepository(QString name, KDevelop::ItemRepositoryRegistry* registry, bool delayedDeletion) : d(new Private(name)), dataRepository(this, name, registry), m_mutex(nullptr), m_delayedDeletion(delayedDeletion) { m_mutex = dataRepository.mutex(); } struct StatisticsVisitor { - StatisticsVisitor(const SetDataRepository& _rep) : nodeCount(0), badSplitNodeCount(0), zeroRefCountNodes(0), rep(_rep) { + explicit StatisticsVisitor(const SetDataRepository& _rep) : nodeCount(0), badSplitNodeCount(0), zeroRefCountNodes(0), rep(_rep) { } bool operator() (const SetNodeData* item) { if(item->m_refCount == 0) ++zeroRefCountNodes; ++nodeCount; uint split = splitPositionForRange(item->start(), item->end()); if(item->hasSlaves()) if(split < rep.itemFromIndex(item->leftNode())->end() || split > rep.itemFromIndex(item->rightNode())->start()) ++badSplitNodeCount; return true; } uint nodeCount; uint badSplitNodeCount; uint zeroRefCountNodes; const SetDataRepository& rep; }; void BasicSetRepository::printStatistics() const { StatisticsVisitor stats(dataRepository); dataRepository.visitAllItems(stats); qCDebug(LANGUAGE) << "count of nodes:" << stats.nodeCount << "count of nodes with bad split:" << stats.badSplitNodeCount << "count of nodes with zero reference-count:" << stats.zeroRefCountNodes; } BasicSetRepository::~BasicSetRepository() { delete d; } void BasicSetRepository::itemRemovedFromSets(uint /*index*/) { } void BasicSetRepository::itemAddedToSets(uint /*index*/) { } ////////////Set convenience functions////////////////// bool Set::contains(Index index) const { if(!m_tree || !m_repository) return false; QMutexLocker lock(m_repository->m_mutex); SetRepositoryAlgorithms alg(m_repository->dataRepository, m_repository); return alg.set_contains(m_repository->dataRepository.itemFromIndex(m_tree), index); } Set Set::operator +(const Set& first) const { if(!first.m_tree) return *this; else if(!m_tree || !m_repository) return first; Q_ASSERT(m_repository == first.m_repository); QMutexLocker lock(m_repository->m_mutex); SetRepositoryAlgorithms alg(m_repository->dataRepository, m_repository); uint retNode = alg.set_union(m_tree, first.m_tree, m_repository->dataRepository.itemFromIndex(m_tree), m_repository->dataRepository.itemFromIndex(first.m_tree)); ifDebug(alg.check(retNode)); return Set(retNode, m_repository); } Set& Set::operator +=(const Set& first) { if(!first.m_tree) return *this; else if(!m_tree || !m_repository) { m_tree = first.m_tree; m_repository = first.m_repository; return *this; } QMutexLocker lock(m_repository->m_mutex); SetRepositoryAlgorithms alg(m_repository->dataRepository, m_repository); m_tree = alg.set_union(m_tree, first.m_tree, m_repository->dataRepository.itemFromIndex(m_tree), m_repository->dataRepository.itemFromIndex(first.m_tree)); ifDebug(alg.check(m_tree)); return *this; } Set Set::operator &(const Set& first) const { if(!first.m_tree || !m_tree) return Set(); Q_ASSERT(m_repository); QMutexLocker lock(m_repository->m_mutex); SetRepositoryAlgorithms alg(m_repository->dataRepository, m_repository); Set ret( alg.set_intersect(m_tree, first.m_tree, m_repository->dataRepository.itemFromIndex(m_tree), m_repository->dataRepository.itemFromIndex(first.m_tree)), m_repository ); ifDebug(alg.check(ret.m_tree)); return ret; } Set& Set::operator &=(const Set& first) { if(!first.m_tree || !m_tree) { m_tree = 0; return *this; } Q_ASSERT(m_repository); QMutexLocker lock(m_repository->m_mutex); SetRepositoryAlgorithms alg(m_repository->dataRepository, m_repository); m_tree = alg.set_intersect(m_tree, first.m_tree, m_repository->dataRepository.itemFromIndex(m_tree), m_repository->dataRepository.itemFromIndex(first.m_tree)); ifDebug(alg.check(m_tree)); return *this; } Set Set::operator -(const Set& rhs) const { if(!m_tree || !rhs.m_tree) return *this; Q_ASSERT(m_repository); QMutexLocker lock(m_repository->m_mutex); SetRepositoryAlgorithms alg(m_repository->dataRepository, m_repository); Set ret( alg.set_subtract(m_tree, rhs.m_tree, m_repository->dataRepository.itemFromIndex(m_tree), m_repository->dataRepository.itemFromIndex(rhs.m_tree)), m_repository ); ifDebug( alg.check(ret.m_tree) ); return ret; } Set& Set::operator -=(const Set& rhs) { if(!m_tree || !rhs.m_tree) return *this; Q_ASSERT(m_repository); QMutexLocker lock(m_repository->m_mutex); SetRepositoryAlgorithms alg(m_repository->dataRepository, m_repository); m_tree = alg.set_subtract(m_tree, rhs.m_tree, m_repository->dataRepository.itemFromIndex(m_tree), m_repository->dataRepository.itemFromIndex(rhs.m_tree)); ifDebug(alg.check(m_tree)); return *this; } BasicSetRepository* Set::repository() const { return m_repository; } void Set::staticRef() { if(!m_tree) return; QMutexLocker lock(m_repository->m_mutex); SetNodeData* data = m_repository->dataRepository.dynamicItemFromIndexSimple(m_tree); ++data->m_refCount; } ///Mutex must be locked void Set::unrefNode(uint current) { SetNodeData* data = m_repository->dataRepository.dynamicItemFromIndexSimple(current); Q_ASSERT(data->m_refCount); --data->m_refCount; if(!m_repository->delayedDeletion()) { if(data->m_refCount == 0) { if(data->leftNode()){ Q_ASSERT(data->rightNode()); unrefNode(data->rightNode()); unrefNode(data->leftNode()); }else { //Deleting a leaf Q_ASSERT(data->end() - data->start() == 1); m_repository->itemRemovedFromSets(data->start()); } m_repository->dataRepository.deleteItem(current); } } } ///Decrease the static reference-count of this set by one. This set must have a reference-count > 1. ///If this set reaches the reference-count zero, it will be deleted, and all sub-nodes that also reach the reference-count zero ///will be deleted as well. @warning Either protect ALL your sets by using reference-counting, or don't use it at all. void Set::staticUnref() { if(!m_tree) return; QMutexLocker lock(m_repository->m_mutex); unrefNode(m_tree); } StringSetRepository::StringSetRepository(QString name) : Utils::BasicSetRepository(name) { } void StringSetRepository::itemRemovedFromSets(uint index) { ///Call the IndexedString destructor with enabled reference-counting KDevelop::IndexedString string = KDevelop::IndexedString::fromIndex(index); KDevelop::enableDUChainReferenceCounting(&string, sizeof(KDevelop::IndexedString)); string.~IndexedString(); //Call destructor with enabled reference-counting KDevelop::disableDUChainReferenceCounting(&string); } void StringSetRepository::itemAddedToSets(uint index) { ///Call the IndexedString constructor with enabled reference-counting KDevelop::IndexedString string = KDevelop::IndexedString::fromIndex(index); char data[sizeof(KDevelop::IndexedString)]; KDevelop::enableDUChainReferenceCounting(data, sizeof(KDevelop::IndexedString)); new (data) KDevelop::IndexedString(string); //Call constructor with enabled reference-counting KDevelop::disableDUChainReferenceCounting(data); } } diff --git a/language/util/setrepository.h b/language/util/setrepository.h index 303e0b133..c278658b6 100644 --- a/language/util/setrepository.h +++ b/language/util/setrepository.h @@ -1,478 +1,483 @@ /*************************************************************************** Copyright 2007 David Nolden ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef KDEVPLATFORM_SETREPOSITORY_H #define KDEVPLATFORM_SETREPOSITORY_H #include "basicsetrepository.h" #include #include #include /** * This header defines convenience-class that allow handling set-repositories using the represented higher-level objects instead * of indices to them. * */ namespace Utils { /** * Use this class to conveniently iterate over the items in a set. * @tparam T The type the indices will be converted to * @tparam Conversion Should be a class that has a toIndex member function that takes an object of type T as parameter, and returns an index, * and a toItem member function that takes an index, and returns an item of type T. * */ template class ConvenientIterator : public Conversion { public: - ConvenientIterator(Set::Iterator it=Set::Iterator()) : m_it(it) { + explicit ConvenientIterator(Set::Iterator it=Set::Iterator()) : m_it(it) { } - ConvenientIterator(const Set& set) : m_it(set.iterator()) { + explicit ConvenientIterator(const Set& set) : m_it(set.iterator()) { } operator bool() const { return m_it; } ConvenientIterator& operator++() { ++m_it; return *this; } T operator*() const { return Conversion::toItem(*m_it); } uint index() const { return *m_it; } private: Set::Iterator m_it; }; struct DummyLocker { }; template struct IdentityConversion { static T toIndex(const T& t) { return t; } static T toItem(const T& t) { return t; } }; ///This is a virtual set-node that allows conveniently iterating through the tree-structure, ///accessing the contained items directly, and accessing the ranges. template -class VirtualSetNode { +class VirtualSetNode +{ + private: + using ClassType = VirtualSetNode; + public: - inline VirtualSetNode(const SetNodeData* data = 0) : m_data(data) { + inline explicit VirtualSetNode(const SetNodeData* data = nullptr) : m_data(data) { } inline bool isValid() const { return (bool)m_data; } ///If this returns false, a left and a right node are available. ///If this returns true, this node represents a single item, that can be retrieved by calling item() or operator*. inline bool isFinalNode() const { return m_data->leftNode() == 0; } inline T firstItem() const { return Conversion::toItem(start()); } inline T lastItem() const { return Conversion::toItem(end()-1); } inline T operator*() const { return Conversion::toItem(start()); } - inline VirtualSetNode leftChild() const { + inline ClassType leftChild() const { if(m_data->leftNode()) - return StaticRepository::repository()->nodeFromIndex(m_data->leftNode()); + return ClassType(StaticRepository::repository()->nodeFromIndex(m_data->leftNode())); else - return nullptr; + return ClassType(nullptr); } - inline VirtualSetNode rightChild() const { + inline ClassType rightChild() const { if(m_data->rightNode()) - return StaticRepository::repository()->nodeFromIndex(m_data->rightNode()); + return ClassType(StaticRepository::repository()->nodeFromIndex(m_data->rightNode())); else - return nullptr; + return ClassType(nullptr); } ///Returns the start of this node's range. If this is a final node, the length of the range is 1. inline uint start() const { return m_data->start(); } ///Returns the end of this node's range. inline uint end() const { return m_data->end(); } private: + const SetNodeData* m_data; }; template class StorableSet : public Conversion { public: typedef VirtualSetNode Node; StorableSet(const StorableSet& rhs) : m_setIndex(rhs.m_setIndex) { StaticAccessLocker lock; Q_UNUSED(lock); if(doReferenceCounting) set().staticRef(); } - StorableSet(const std::set& indices) { + explicit StorableSet(const std::set& indices) { StaticAccessLocker lock; Q_UNUSED(lock); m_setIndex = StaticRepository::repository()->createSet(indices).setIndex(); if(doReferenceCounting) set().staticRef(); } StorableSet() : m_setIndex(0) { } ~StorableSet() { StaticAccessLocker lock; Q_UNUSED(lock); if(doReferenceCounting) set().staticUnref(); } void insert(const T& t) { insertIndex(Conversion::toIndex(t)); } bool isEmpty() const { return m_setIndex == 0; } uint count() const { return set().count(); } void insertIndex(uint index) { StaticAccessLocker lock; Q_UNUSED(lock); Set set(m_setIndex, StaticRepository::repository()); Set oldSet(set); Set addedSet = StaticRepository::repository()->createSet(index); if(doReferenceCounting) addedSet.staticRef(); set += addedSet; m_setIndex = set.setIndex(); if(doReferenceCounting) { set.staticRef(); oldSet.staticUnref(); addedSet.staticUnref(); } } void remove(const T& t) { removeIndex(Conversion::toIndex(t)); } void removeIndex(uint index) { StaticAccessLocker lock; Q_UNUSED(lock); Set set(m_setIndex, StaticRepository::repository()); Set oldSet(set); Set removedSet = StaticRepository::repository()->createSet(index); if(doReferenceCounting) { removedSet.staticRef(); } set -= removedSet; m_setIndex = set.setIndex(); if(doReferenceCounting) { set.staticRef(); oldSet.staticUnref(); removedSet.staticUnref(); } } Set set() const { return Set(m_setIndex, StaticRepository::repository()); } bool contains(const T& item) const { return containsIndex(Conversion::toIndex(item)); } bool containsIndex(uint index) const { StaticAccessLocker lock; Q_UNUSED(lock); Set set(m_setIndex, StaticRepository::repository()); return set.contains(index); } StorableSet& operator +=(const StorableSet& rhs) { StaticAccessLocker lock; Q_UNUSED(lock); Set set(m_setIndex, StaticRepository::repository()); Set oldSet(set); Set otherSet(rhs.m_setIndex, StaticRepository::repository()); set += otherSet; m_setIndex = set.setIndex(); if(doReferenceCounting) { set.staticRef(); oldSet.staticUnref(); } return *this; } StorableSet& operator -=(const StorableSet& rhs) { StaticAccessLocker lock; Q_UNUSED(lock); Set set(m_setIndex, StaticRepository::repository()); Set oldSet(set); Set otherSet(rhs.m_setIndex, StaticRepository::repository()); set -= otherSet; m_setIndex = set.setIndex(); if(doReferenceCounting) { set.staticRef(); oldSet.staticUnref(); } return *this; } StorableSet& operator &=(const StorableSet& rhs) { StaticAccessLocker lock; Q_UNUSED(lock); Set set(m_setIndex, StaticRepository::repository()); Set oldSet(set); Set otherSet(rhs.m_setIndex, StaticRepository::repository()); set &= otherSet; m_setIndex = set.setIndex(); if(doReferenceCounting) { set.staticRef(); oldSet.staticUnref(); } return *this; } StorableSet& operator=(const StorableSet& rhs) { StaticAccessLocker lock; Q_UNUSED(lock); if(doReferenceCounting) set().staticUnref(); m_setIndex = rhs.m_setIndex; if(doReferenceCounting) set().staticRef(); return *this; } StorableSet operator +(const StorableSet& rhs) const { StorableSet ret(*this); ret += rhs; return ret; } StorableSet operator -(const StorableSet& rhs) const { StorableSet ret(*this); ret -= rhs; return ret; } StorableSet operator &(const StorableSet& rhs) const { StorableSet ret(*this); ret &= rhs; return ret; } bool operator==(const StorableSet& rhs) const { return m_setIndex == rhs.m_setIndex; } typedef ConvenientIterator Iterator; Iterator iterator() const { return ConvenientIterator(set()); } Node node() const { return Node(StaticRepository::repository()->nodeFromIndex(m_setIndex)); } uint setIndex() const { return m_setIndex; } private: uint m_setIndex; }; template uint qHash(const StorableSet& set) { return set.setIndex(); } /** This is a helper-class that helps inserting a bunch of items into a set without caring about grouping them together. * * It creates a much better tree-structure if many items are inserted at one time, and this class helps doing that in * cases where there is no better choice then storing a temporary list of items and inserting them all at once. * * This set will then care about really inserting them into the repository once the real set is requested. * * @todo eventually make this unnecessary * * @tparam T Should be the type that should be dealt * @tparam Conversion Should be a class that has a toIndex member function that takes an object of type T as parameter, and returns an index, * and a toItem member function that takes an index, and returns an item of type T. **/ template class LazySet : public Conversion { public: /** @param rep The repository the set should belong/belongs to * @param lockBeforeAccess If this is nonzero, the given mutex will be locked before each modification to the repository. * @param basicSet If this is explicitly given, the given set will be used as base. However it will not be changed. * * @warning Watch for deadlocks, never use this class while the mutex given through lockBeforeAccess is locked */ - LazySet(BasicSetRepository* rep, QMutex* lockBeforeAccess = 0, const Set& basicSet = Set()) : m_rep(rep), m_set(basicSet), m_lockBeforeAccess(lockBeforeAccess) { + explicit LazySet(BasicSetRepository* rep, QMutex* lockBeforeAccess = 0, const Set& basicSet = Set()) : m_rep(rep), m_set(basicSet), m_lockBeforeAccess(lockBeforeAccess) { } void insert(const T& t) { if(!m_temporaryRemoveIndices.empty()) apply(); m_temporaryIndices.insert(Conversion::toIndex(t)); } void insertIndex(uint index) { if(!m_temporaryRemoveIndices.empty()) apply(); m_temporaryIndices.insert(index); } void remove(const T& t) { if(!m_temporaryIndices.empty()) apply(); m_temporaryRemoveIndices.insert(Conversion::toIndex(t)); } ///Returns the set this LazySet represents. When this is called, the set is constructed in the repository. Set set() const { apply(); return m_set; } ///@warning this is expensive, because the set is constructed bool contains(const T& item) const { QMutexLocker l(m_lockBeforeAccess); uint index = Conversion::toIndex(item); if( m_temporaryRemoveIndices.empty() ) { //Simplification without creating the set if(m_temporaryIndices.find(index) != m_temporaryIndices.end()) return true; return m_set.contains(index); } return set().contains(index); } LazySet& operator +=(const Set& set) { if(!m_temporaryRemoveIndices.empty()) apply(); QMutexLocker l(m_lockBeforeAccess); m_set += set; return *this; } LazySet& operator -=(const Set& set) { if(!m_temporaryIndices.empty()) apply(); QMutexLocker l(m_lockBeforeAccess); m_set -= set; return *this; } LazySet operator +(const Set& set) const { apply(); QMutexLocker l(m_lockBeforeAccess); Set ret = m_set + set; return LazySet(m_rep, m_lockBeforeAccess, ret); } LazySet operator -(const Set& set) const { apply(); QMutexLocker l(m_lockBeforeAccess); Set ret = m_set - set; return LazySet(m_rep, m_lockBeforeAccess, ret); } void clear() { QMutexLocker l(m_lockBeforeAccess); m_set = Set(); m_temporaryIndices.clear(); m_temporaryRemoveIndices.clear(); } ConvenientIterator iterator() const { apply(); return ConvenientIterator(set()); } private: void apply() const { if(!m_temporaryIndices.empty()) { QMutexLocker l(m_lockBeforeAccess); Set tempSet = m_rep->createSet(m_temporaryIndices); m_temporaryIndices.clear(); m_set += tempSet; } if(!m_temporaryRemoveIndices.empty()) { QMutexLocker l(m_lockBeforeAccess); Set tempSet = m_rep->createSet(m_temporaryRemoveIndices); m_temporaryRemoveIndices.clear(); m_set -= tempSet; } } BasicSetRepository* m_rep; mutable Set m_set; QMutex* m_lockBeforeAccess; typedef std::set IndexList; mutable IndexList m_temporaryIndices; mutable IndexList m_temporaryRemoveIndices; }; ///Persistent repository that manages string-sets, also correctly increasing the string reference-counts as needed struct KDEVPLATFORMLANGUAGE_EXPORT StringSetRepository : public Utils::BasicSetRepository { - StringSetRepository(QString name); + explicit StringSetRepository(QString name); void itemRemovedFromSets(uint index) override; void itemAddedToSets(uint index) override; }; } #endif diff --git a/outputview/outputexecutejob.cpp b/outputview/outputexecutejob.cpp index dbbe8a718..7bd0f7241 100644 --- a/outputview/outputexecutejob.cpp +++ b/outputview/outputexecutejob.cpp @@ -1,541 +1,541 @@ /* This file is part of KDevelop Copyright 2012 Ivan Shapovalov This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "outputexecutejob.h" #include "outputmodel.h" #include "outputdelegate.h" #include "debug.h" #include #include #include #include #include #include #include namespace KDevelop { class OutputExecuteJobPrivate { public: - OutputExecuteJobPrivate( KDevelop::OutputExecuteJob* owner ); + explicit OutputExecuteJobPrivate( KDevelop::OutputExecuteJob* owner ); void childProcessStdout(); void childProcessStderr(); void emitProgress(const IFilterStrategy::Progress& progress); QString joinCommandLine() const; QString getJobName(); template< typename T > static void mergeEnvironment( QProcessEnvironment& dest, const T& src ); QProcessEnvironment effectiveEnvironment(const QUrl& workingDirectory) const; QStringList effectiveCommandLine() const; OutputExecuteJob* m_owner; KProcess* m_process; ProcessLineMaker* m_lineMaker; OutputExecuteJob::JobStatus m_status; OutputExecuteJob::JobProperties m_properties; OutputModel::OutputFilterStrategy m_filteringStrategy; QScopedPointer m_filteringStrategyPtr; QStringList m_arguments; QStringList m_privilegedExecutionCommand; QUrl m_workingDirectory; QString m_environmentProfile; QHash m_environmentOverrides; QString m_jobName; bool m_outputStarted; }; OutputExecuteJobPrivate::OutputExecuteJobPrivate( OutputExecuteJob* owner ) : m_owner( owner ), m_process( new KProcess( m_owner ) ), m_lineMaker( new ProcessLineMaker( m_owner ) ), // do not assign process to the line maker as we'll feed it data ourselves m_status( OutputExecuteJob::JobNotStarted ), m_properties( OutputExecuteJob::DisplayStdout ), m_filteringStrategy( OutputModel::NoFilter ), m_outputStarted( false ) { } OutputExecuteJob::OutputExecuteJob( QObject* parent, OutputJob::OutputJobVerbosity verbosity ): OutputJob( parent, verbosity ), d( new OutputExecuteJobPrivate( this ) ) { d->m_process->setOutputChannelMode( KProcess::SeparateChannels ); connect( d->m_process, static_cast(&KProcess::finished), this, &OutputExecuteJob::childProcessExited ); connect( d->m_process, static_cast(&KProcess::error), this, &OutputExecuteJob::childProcessError ); connect( d->m_process, &KProcess::readyReadStandardOutput, this, [=] { d->childProcessStdout(); } ); connect( d->m_process, &KProcess::readyReadStandardError, this, [=] { d->childProcessStderr(); } ); } OutputExecuteJob::~OutputExecuteJob() { // indicates if process is running and survives kill, then we cannot do anything bool killSuccessful = d->m_process->state() == QProcess::NotRunning; if( !killSuccessful ) { killSuccessful = doKill(); } Q_ASSERT( d->m_process->state() == QProcess::NotRunning || !killSuccessful ); delete d; } OutputExecuteJob::JobStatus OutputExecuteJob::status() const { return d->m_status; } OutputModel* OutputExecuteJob::model() const { return dynamic_cast ( OutputJob::model() ); } QStringList OutputExecuteJob::commandLine() const { return d->m_arguments; } OutputExecuteJob& OutputExecuteJob::operator<<( const QString& argument ) { d->m_arguments << argument; return *this; } OutputExecuteJob& OutputExecuteJob::operator<<( const QStringList& arguments ) { d->m_arguments << arguments; return *this; } QStringList OutputExecuteJob::privilegedExecutionCommand() const { return d->m_privilegedExecutionCommand; } void OutputExecuteJob::setPrivilegedExecutionCommand( const QStringList& command ) { d->m_privilegedExecutionCommand = command; } void OutputExecuteJob::setJobName( const QString& name ) { d->m_jobName = name; QString jobName = d->getJobName(); setObjectName( jobName ); setTitle( jobName ); } QUrl OutputExecuteJob::workingDirectory() const { return d->m_workingDirectory; } void OutputExecuteJob::setWorkingDirectory( const QUrl& url ) { d->m_workingDirectory = url; } void OutputExecuteJob::start() { Q_ASSERT( d->m_status == JobNotStarted ); d->m_status = JobRunning; const bool isBuilder = d->m_properties.testFlag( IsBuilderHint ); const QUrl effectiveWorkingDirectory = workingDirectory(); if( effectiveWorkingDirectory.isEmpty() ) { if( d->m_properties.testFlag( NeedWorkingDirectory ) ) { // A directory is not given, but we need it. setError( InvalidWorkingDirectoryError ); if( isBuilder ) { setErrorText( i18n( "No build directory specified for a builder job." ) ); } else { setErrorText( i18n( "No working directory specified for a process." ) ); } return emitResult(); } setModel( new OutputModel ); } else { // Basic sanity checks. if( !effectiveWorkingDirectory.isValid() ) { setError( InvalidWorkingDirectoryError ); if( isBuilder ) { setErrorText( i18n( "Invalid build directory '%1'", effectiveWorkingDirectory.toDisplayString(QUrl::PreferLocalFile) ) ); } else { setErrorText( i18n( "Invalid working directory '%1'", effectiveWorkingDirectory.toDisplayString(QUrl::PreferLocalFile) ) ); } return emitResult(); } else if( !effectiveWorkingDirectory.isLocalFile() ) { setError( InvalidWorkingDirectoryError ); if( isBuilder ) { setErrorText( i18n( "Build directory '%1' is not a local path", effectiveWorkingDirectory.toDisplayString(QUrl::PreferLocalFile) ) ); } else { setErrorText( i18n( "Working directory '%1' is not a local path", effectiveWorkingDirectory.toDisplayString(QUrl::PreferLocalFile) ) ); } return emitResult(); } QFileInfo workingDirInfo( effectiveWorkingDirectory.toLocalFile() ); if( !workingDirInfo.isDir() ) { // If a working directory does not actually exist, either bail out or create it empty, // depending on what we need by properties. // We use a dedicated bool variable since !isDir() may also mean that it exists, // but is not a directory, or a symlink to an inexistent object. bool successfullyCreated = false; if( !d->m_properties.testFlag( CheckWorkingDirectory ) ) { successfullyCreated = QDir().mkdir( effectiveWorkingDirectory.toLocalFile() ); } if( !successfullyCreated ) { setError( InvalidWorkingDirectoryError ); if( isBuilder ) { setErrorText( i18n( "Build directory '%1' does not exist or is not a directory", effectiveWorkingDirectory.toDisplayString(QUrl::PreferLocalFile) ) ); } else { setErrorText( i18n( "Working directory '%1' does not exist or is not a directory", effectiveWorkingDirectory.toDisplayString(QUrl::PreferLocalFile) ) ); } return emitResult(); } } setModel( new OutputModel( effectiveWorkingDirectory ) ); } Q_ASSERT( model() ); if (d->m_filteringStrategyPtr) { model()->setFilteringStrategy(d->m_filteringStrategyPtr.take()); } else { model()->setFilteringStrategy(d->m_filteringStrategy); } setDelegate( new OutputDelegate ); connect(model(), &OutputModel::progress, this, [&](const IFilterStrategy::Progress& progress) { d->emitProgress(progress); }); // Slots hasRawStdout() and hasRawStderr() are responsible // for feeding raw data to the line maker; so property-based channel filtering is implemented there. if( d->m_properties.testFlag( PostProcessOutput ) ) { connect( d->m_lineMaker, &ProcessLineMaker::receivedStdoutLines, this, &OutputExecuteJob::postProcessStdout ); connect( d->m_lineMaker, &ProcessLineMaker::receivedStderrLines, this, &OutputExecuteJob::postProcessStderr ); } else { connect( d->m_lineMaker, &ProcessLineMaker::receivedStdoutLines, model(), &OutputModel::appendLines ); connect( d->m_lineMaker, &ProcessLineMaker::receivedStderrLines, model(), &OutputModel::appendLines ); } if( !d->m_properties.testFlag( NoSilentOutput ) || verbosity() != Silent ) { d->m_outputStarted = true; startOutput(); } const QString joinedCommandLine = d->joinCommandLine(); QString headerLine; if( !effectiveWorkingDirectory.isEmpty() ) { headerLine = effectiveWorkingDirectory.toString( QUrl::PreferLocalFile | QUrl::StripTrailingSlash ) + "> " + joinedCommandLine; } else { headerLine = joinedCommandLine; } model()->appendLine( headerLine ); if( !effectiveWorkingDirectory.isEmpty() ) { d->m_process->setWorkingDirectory( effectiveWorkingDirectory.toLocalFile() ); } d->m_process->setProcessEnvironment( d->effectiveEnvironment(effectiveWorkingDirectory) ); if (!d->effectiveCommandLine().isEmpty()) { d->m_process->setProgram( d->effectiveCommandLine() ); // there is no way to input data in the output view so redirect stdin to the null device d->m_process->setStandardInputFile(QProcess::nullDevice()); qCDebug(OUTPUTVIEW) << "Starting:" << d->m_process->program().join(QStringLiteral(" ")) << "in" << d->m_process->workingDirectory(); d->m_process->start(); } else { QString errorMessage = i18n("Failed to specify program to start"); model()->appendLine( i18n( "*** %1 ***", errorMessage) ); setErrorText(errorMessage); setError( FailedShownError ); emitResult(); return; } } bool OutputExecuteJob::doKill() { const int terminateKillTimeout = 1000; // msecs if( d->m_status != JobRunning ) { return true; } d->m_status = JobCanceled; d->m_process->terminate(); bool terminated = d->m_process->waitForFinished( terminateKillTimeout ); if( !terminated ) { d->m_process->kill(); terminated = d->m_process->waitForFinished( terminateKillTimeout ); } d->m_lineMaker->flushBuffers(); if( terminated ) { model()->appendLine( i18n( "*** Killed process ***" ) ); } else { // It survived SIGKILL, leave it alone... qCWarning(OUTPUTVIEW) << "Could not kill the running process:" << d->m_process->error(); model()->appendLine( i18n( "*** Warning: could not kill the process ***" ) ); return false; } return true; } void OutputExecuteJob::childProcessError( QProcess::ProcessError processError ) { // This can be called twice: one time via an error() signal, and second - from childProcessExited(). // Avoid doing things in second time. if( d->m_status != OutputExecuteJob::JobRunning ) return; d->m_status = OutputExecuteJob::JobFailed; QString errorValue; switch( processError ) { case QProcess::FailedToStart: errorValue = i18n("%1 has failed to start", commandLine().at(0)); break; case QProcess::Crashed: errorValue = i18n("%1 has crashed", commandLine().at(0)); break; case QProcess::ReadError: errorValue = i18n("Read error"); break; case QProcess::WriteError: errorValue = i18n("Write error"); break; case QProcess::Timedout: errorValue = i18n("Waiting for the process has timed out"); break; default: case QProcess::UnknownError: errorValue = i18n("Exit code %1", d->m_process->exitCode()); break; } // Show the toolview if it's hidden for the user to be able to diagnose errors. if( !d->m_outputStarted ) { d->m_outputStarted = true; startOutput(); } setError( FailedShownError ); setErrorText( errorValue ); d->m_lineMaker->flushBuffers(); model()->appendLine( i18n("*** Failure: %1 ***", errorValue) ); emitResult(); } void OutputExecuteJob::childProcessExited( int exitCode, QProcess::ExitStatus exitStatus ) { if( d->m_status != JobRunning ) return; if( exitStatus == QProcess::CrashExit ) { childProcessError( QProcess::Crashed ); } else if ( exitCode != 0 ) { childProcessError( QProcess::UnknownError ); } else { d->m_status = JobSucceeded; d->m_lineMaker->flushBuffers(); model()->appendLine( i18n("*** Finished ***") ); emitResult(); } } void OutputExecuteJobPrivate::childProcessStdout() { QByteArray out = m_process->readAllStandardOutput(); if( m_properties.testFlag( OutputExecuteJob::DisplayStdout ) ) { m_lineMaker->slotReceivedStdout( out ); } } void OutputExecuteJobPrivate::childProcessStderr() { QByteArray err = m_process->readAllStandardError(); if( m_properties.testFlag( OutputExecuteJob::DisplayStderr ) ) { m_lineMaker->slotReceivedStderr( err ); } } void OutputExecuteJobPrivate::emitProgress(const IFilterStrategy::Progress& progress) { if (progress.percent != -1) { m_owner->emitPercent(progress.percent, 100); } if (!progress.status.isEmpty()) { m_owner->infoMessage(m_owner, progress.status); } } void OutputExecuteJob::postProcessStdout( const QStringList& lines ) { model()->appendLines( lines ); } void OutputExecuteJob::postProcessStderr( const QStringList& lines ) { model()->appendLines( lines ); } void OutputExecuteJob::setFilteringStrategy( OutputModel::OutputFilterStrategy strategy ) { d->m_filteringStrategy = strategy; // clear the other d->m_filteringStrategyPtr.reset(nullptr); } void OutputExecuteJob::setFilteringStrategy(IFilterStrategy* filterStrategy) { d->m_filteringStrategyPtr.reset(filterStrategy); // clear the other d->m_filteringStrategy = OutputModel::NoFilter; } OutputExecuteJob::JobProperties OutputExecuteJob::properties() const { return d->m_properties; } void OutputExecuteJob::setProperties( OutputExecuteJob::JobProperties properties, bool override ) { if( override ) { d->m_properties = properties; } else { d->m_properties |= properties; } } void OutputExecuteJob::unsetProperties( OutputExecuteJob::JobProperties properties ) { d->m_properties &= ~properties; } QString OutputExecuteJob::environmentProfile() const { return d->m_environmentProfile; } void OutputExecuteJob::setEnvironmentProfile( const QString& profile ) { d->m_environmentProfile = profile; } void OutputExecuteJob::addEnvironmentOverride( const QString& name, const QString& value ) { d->m_environmentOverrides[name] = value; } void OutputExecuteJob::removeEnvironmentOverride( const QString& name ) { d->m_environmentOverrides.remove( name ); } template< typename T > void OutputExecuteJobPrivate::mergeEnvironment( QProcessEnvironment& dest, const T& src ) { for( typename T::const_iterator it = src.begin(); it != src.end(); ++it ) { dest.insert( it.key(), it.value() ); } } QProcessEnvironment OutputExecuteJobPrivate::effectiveEnvironment(const QUrl& workingDirectory) const { const EnvironmentGroupList environmentGroup( KSharedConfig::openConfig() ); QString environmentProfile = m_owner->environmentProfile(); if( environmentProfile.isEmpty() ) { environmentProfile = environmentGroup.defaultGroup(); } QProcessEnvironment environment = QProcessEnvironment::systemEnvironment(); auto userEnv = environmentGroup.variables(environmentProfile); expandVariables(userEnv, environment); OutputExecuteJobPrivate::mergeEnvironment( environment, userEnv ); OutputExecuteJobPrivate::mergeEnvironment( environment, m_environmentOverrides ); if( m_properties.testFlag( OutputExecuteJob::PortableMessages ) ) { environment.remove( QStringLiteral( "LC_ALL" ) ); environment.insert( QStringLiteral( "LC_MESSAGES" ), QStringLiteral( "C" ) ); } if (!workingDirectory.isEmpty() && environment.contains(QStringLiteral("PWD"))) { // also update the environment variable for the cwd, otherwise scripts can break easily environment.insert(QStringLiteral("PWD"), workingDirectory.toLocalFile()); } return environment; } QString OutputExecuteJobPrivate::joinCommandLine() const { return KShell::joinArgs( effectiveCommandLine() ); } QStringList OutputExecuteJobPrivate::effectiveCommandLine() const { // If we need to use a su-like helper, invoke it as // "helper -- our command line". QStringList privilegedCommand = m_owner->privilegedExecutionCommand(); if( !privilegedCommand.isEmpty() ) { return QStringList() << m_owner->privilegedExecutionCommand() << QStringLiteral("--") << m_owner->commandLine(); } else { return m_owner->commandLine(); } } QString OutputExecuteJobPrivate::getJobName() { const QString joinedCommandLine = joinCommandLine(); if( m_properties.testFlag( OutputExecuteJob::AppendProcessString ) ) { if( !m_jobName.isEmpty() ) { return m_jobName + ": " + joinedCommandLine; } else { return joinedCommandLine; } } else { return m_jobName; } } } // namespace KDevelop #include "moc_outputexecutejob.cpp" diff --git a/outputview/outputexecutejob.h b/outputview/outputexecutejob.h index b861e63ad..2cda8c157 100644 --- a/outputview/outputexecutejob.h +++ b/outputview/outputexecutejob.h @@ -1,256 +1,256 @@ /* This file is part of KDevelop C opyright 2012 Ivan Shapoval*ov 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_OUTPUTEXECUTEJOB_H #define KDEVPLATFORM_OUTPUTEXECUTEJOB_H #include "outputjob.h" #include "outputmodel.h" #include #include class KProcess; namespace KDevelop { class ProcessLineMaker; class OutputExecuteJobPrivate; class KDEVPLATFORMOUTPUTVIEW_EXPORT OutputExecuteJob : public OutputJob { Q_OBJECT public: enum JobStatus { JobRunning = 0, /**< The job is running */ JobSucceeded = 1, /**< The job has succeeded */ JobCanceled = 2, /**< The job has been cancelled */ JobFailed = 3, /**< The job has failed */ JobNotStarted = 4 /**< The job hasn't been started so far */ }; enum { InvalidWorkingDirectoryError = OutputJob::UserDefinedError, UserDefinedError }; enum JobProperty { AppendProcessString = 0x001, /**< Whether to append a process string to the user-specified job name */ NeedWorkingDirectory = 0x002, /**< Whether to require a non-empty working directory to be provided */ CheckWorkingDirectory = 0x004, /**< Whether to check that the working directory actually exists (and not to create it if needed) */ PortableMessages = 0x008, /**< Whether to set LC_MESSAGES=C in the process' environment */ DisplayStdout = 0x010, /**< Whether to pass process' stdout to the output model */ DisplayStderr = 0x020, /**< Whether to pass process' stderr to the output model */ NoSilentOutput = 0x040, /**< Whether to call \ref startOutput() only if verbosity is \ref OutputJob::Verbose */ PostProcessOutput = 0x080, /**< Whether to connect line maker's signals to \ref postProcessStdout() and \ref postProcessStderr() */ IsBuilderHint = 0x100, /**< Whether to use builder-specific messages to talk to user (e. g. "build directory" instead of "working directory" */ }; Q_FLAGS(JobProperty JobProperties) Q_DECLARE_FLAGS(JobProperties, JobProperty) - OutputExecuteJob( QObject* parent = nullptr, OutputJobVerbosity verbosity = OutputJob::Verbose ); + explicit OutputExecuteJob( QObject* parent = nullptr, OutputJobVerbosity verbosity = OutputJob::Verbose ); ~OutputExecuteJob() override; /** * Get the job's status (associated with the process). * * @returns The job's status. * @see JobStatus */ JobStatus status() const; /** * Get the job's output model. * * @returns The job's output model, downcasted to \ref OutputModel */ OutputModel* model() const; /** * Returns a working directory for the job's process. * * @returns URL which has been set through \ref setWorkingDirectory(); empty URL if unset. */ virtual QUrl workingDirectory() const; /** * Set a working directory for the job's process. * Effective if \ref workingDirectory() hasn't been overridden. * * @param directory a valid local directory URL, or an empty URL to unset. */ void setWorkingDirectory( const QUrl& directory ); /** * Get process' command line. * * @returns The command line for the process, with first element in list being the program path. */ virtual QStringList commandLine() const; /** * Append an element to the command line argument list for this process. * If no executable is set yet, it will be set instead. * Effective if \ref commandLine() hasn't been overridden. * * @param argument the argument to add */ OutputExecuteJob& operator<<( const QString& argument ); /** * Append a list of elements to the command line argument list for this process. * If no executable is set yet, it will be set from the first argument in given list. * Effective if \ref commandLine() hasn't been overridden. * * @param arguments the arguments to add */ OutputExecuteJob& operator<<( const QStringList& arguments ); /** * Get the privilege escalation command ("su", "sudo", etc.) used for the job's process. * * @returns The privilege escalation command name and arguments; empty list if not set. */ virtual QStringList privilegedExecutionCommand() const; /** * Set the privilege escalation command ("su", "sudo", etc.) which will be used for the job's process. * Effective if \ref privilegedExecutionCommand() hasn't been overridden. * * @param command The privilege escalation command's name and arguments; empty list to unset. * @see privilegedCommand */ void setPrivilegedExecutionCommand( const QStringList& command ); /** * A convenience function to set the job name. * * Calls \ref setTitle() and \ref setObjectName(). * * @note If you need the command-line to be appended to the job name, * make sure that it is already configured upon calling this function. * * @param name The name to set; empty string to use default (process string). */ void setJobName( const QString& name ); /** * Set one of the standard filtering strategies for the output model. */ void setFilteringStrategy( OutputModel::OutputFilterStrategy strategy ); /** * Set the filtering strategy for the output model. */ void setFilteringStrategy(IFilterStrategy* filterStrategy); /** * Get the current properties of the job. * * @note Default-set properties are: \ref DisplayStdout. */ virtual JobProperties properties() const; /** * Set properties of the job. * Effective if \ref properties() hasn't been overridden. * * @param properties Which flags to add to the job. * @param override Whether to assign instead of doing bitwise OR. * @see JobProperties, properties(), unsetProperties() */ void setProperties( JobProperties properties, bool override = false ); /** * Unset properties of the job. * * @param properties Which flags to remove from the job * @see JobProperties, properties(), setProperties() */ void unsetProperties( JobProperties properties ); /** * Add a variable to the job's process environment. * * The variables added with this method override ones from the system environment and * the global environment profile, but are overridden by "PortableMessages" property. * * @param name The name of a variable to add * @param value The value of a variable to add; empty string to unset. */ void addEnvironmentOverride( const QString& name, const QString& value ); /** * Remove a variable from the override set. * * @param name The name of a variable to remove. * @note This does not force a variable to empty value; this is to undo the overriding itself. */ void removeEnvironmentOverride( const QString& name ); /** * Get the global environment profile name for the job's process. * * @returns The environment profile name to use in the job's process; empty if unset. */ virtual QString environmentProfile() const; /** * Set the environment profile name for the job's process. * Effective if \ref environmentProfile() hasn't been overridden. * * @param profile The name of profile to set. */ void setEnvironmentProfile( const QString& profile ); void start() override; protected: bool doKill() override; protected slots: // Redefine these functions if you want to post-process the output somehow // before it hits the output model. // Default implementations for either function call "model()->appendLines( lines );". // Do the same if you need the output to be visible. virtual void postProcessStdout( const QStringList& lines ); virtual void postProcessStderr( const QStringList& lines ); // Redefine these functions if you want to handle process' exit codes in a special manner. // One possible usage is in "cvs diff" job which returns 1 on success. virtual void childProcessExited( int exitCode, QProcess::ExitStatus exitStatus ); virtual void childProcessError( QProcess::ProcessError processError ); private: friend class OutputExecuteJobPrivate; OutputExecuteJobPrivate* d; Q_PRIVATE_SLOT(d, void childProcessStdout()); Q_PRIVATE_SLOT(d, void childProcessStderr()); }; } // namespace KDevelop Q_DECLARE_OPERATORS_FOR_FLAGS(KDevelop::OutputExecuteJob::JobProperties); #endif // KDEVPLATFORM_OUTPUTEXECUTEJOB_H diff --git a/outputview/outputfilteringstrategies.cpp b/outputview/outputfilteringstrategies.cpp index a085c4917..8889a18d5 100644 --- a/outputview/outputfilteringstrategies.cpp +++ b/outputview/outputfilteringstrategies.cpp @@ -1,457 +1,457 @@ /* 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 { void initializeFilteredItem(FilteredItem& item, const ErrorFormat& filter, const QRegularExpressionMatch& match) { item.lineNo = match.captured( filter.lineGroup ).toInt() - 1; item.columnNo = filter.columnNumber(match); } template FilteredItem match(const ErrorFormats& errorFormats, const QString& line) { FilteredItem item(line); for( const ErrorFormat& curErrFilter : errorFormats ) { const auto match = curErrFilter.expression.match(line); if( match.hasMatch() ) { initializeFilteredItem(item, curErrFilter, match); item.url = QUrl::fromUserInput(match.captured( curErrFilter.fileGroup )); 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 QUrl& buildDir); + explicit CompilerFilterStrategyPrivate(const QUrl& buildDir); Path pathForFile( const QString& ) const; bool isMultiLineCase(ErrorFormat curErrFilter) const; void putDirAtEnd(const Path& pathToInsert); QVector m_currentDirs; Path m_buildDir; using PositionMap = QHash; PositionMap m_positionInCurrentDirs; }; CompilerFilterStrategyPrivate::CompilerFilterStrategyPrivate(const QUrl& buildDir) : m_buildDir(buildDir) { } Path CompilerFilterStrategyPrivate::pathForFile(const QString& filename) const { QFileInfo fi( filename ); Path currentPath; if( fi.isRelative() ) { if( m_currentDirs.isEmpty() ) { return Path(m_buildDir, filename ); } auto it = m_currentDirs.constEnd() - 1; do { currentPath = Path(*it, filename); } while( (it-- != m_currentDirs.constBegin()) && !QFileInfo::exists(currentPath.toLocalFile()) ); return currentPath; } else { currentPath = Path(filename); } return currentPath; } bool CompilerFilterStrategyPrivate::isMultiLineCase(KDevelop::ErrorFormat curErrFilter) const { if(curErrFilter.compiler == QLatin1String("gfortran") || curErrFilter.compiler == QLatin1String("cmake")) { return true; } return false; } void CompilerFilterStrategyPrivate::putDirAtEnd(const Path& pathToInsert) { CompilerFilterStrategyPrivate::PositionMap::iterator it = m_positionInCurrentDirs.find( pathToInsert ); // Encountered new build directory? if (it == m_positionInCurrentDirs.end()) { m_currentDirs.push_back( pathToInsert ); m_positionInCurrentDirs.insert( pathToInsert, 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 pathForFile) 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 QUrl& buildDir) : d(new CompilerFilterStrategyPrivate( buildDir )) { } CompilerFilterStrategy::~CompilerFilterStrategy() { delete d; } QVector CompilerFilterStrategy::getCurrentDirs() { QVector ret; ret.reserve(d->m_currentDirs.size()); foreach (const auto& path, d->m_currentDirs) { ret << path.pathOrUrl(); } return ret; } FilteredItem CompilerFilterStrategy::actionInLine(const QString& line) { // A list of filters for possible compiler, linker, and make actions static const ActionFormat ACTION_FILTERS[] = { ActionFormat( 2, QStringLiteral("(?:^|[^=])\\b(gcc|CC|cc|distcc|c\\+\\+|g\\+\\+|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( 2, QStringLiteral("/(moc|uic)\\b.*\\s-o\\s([^\\s;]+)")), //libtool linking ActionFormat( QStringLiteral("libtool"), QStringLiteral("/bin/sh\\s.*libtool.*--mode=link\\s.*\\s-o\\s([^\\s;]+)"), 1 ), //unsermake ActionFormat( 1, QStringLiteral("^compiling (.*)") ), ActionFormat( 2, QStringLiteral("^generating (.*)") ), ActionFormat( 2, QStringLiteral("(gcc|cc|c\\+\\+|g\\+\\+|clang(?:\\+\\+)|mpicc|icc|icpc)\\S* (?:\\S* )*-o ([^\\s;]+)")), ActionFormat( 2, QStringLiteral("^linking (.*)") ), //cmake ActionFormat( 1, QStringLiteral("\\[.+%\\] Built target (.*)") ), ActionFormat( QStringLiteral("cmake"), QStringLiteral("\\[.+%\\] Building .* object (.*)"), 1 ), ActionFormat( 1, QStringLiteral("\\[.+%\\] Generating (.*)") ), ActionFormat( 1, QStringLiteral("^Linking (.*)") ), ActionFormat( QStringLiteral("cmake"), QStringLiteral("(-- Configuring (done|incomplete)|-- Found|-- Adding|-- Enabling)"), -1 ), ActionFormat( 1, QStringLiteral("-- Installing (.*)") ), //libtool install ActionFormat( {}, QStringLiteral("/(?:bin/sh\\s.*mkinstalldirs).*\\s([^\\s;]+)"), 1 ), ActionFormat( {}, QStringLiteral("/(?:usr/bin/install|bin/sh\\s.*mkinstalldirs|bin/sh\\s.*libtool.*--mode=install).*\\s([^\\s;]+)"), 1 ), //dcop ActionFormat( QStringLiteral("dcopidl"), QStringLiteral("dcopidl .* > ([^\\s;]+)"), 1 ), ActionFormat( QStringLiteral("dcopidl2cpp"), QStringLiteral("dcopidl2cpp (?:\\S* )*([^\\s;]+)"), 1 ), // match against Entering directory to update current build dir ActionFormat( QStringLiteral("cd"), QStringLiteral("make\\[\\d+\\]: Entering directory (\\`|\\')(.+)'"), 2), // waf and scons use the same basic convention as make ActionFormat( QStringLiteral("cd"), QStringLiteral("(Waf|scons): Entering directory (\\`|\\')(.+)'"), 3) }; FilteredItem item(line); for (const auto& curActFilter : ACTION_FILTERS) { const auto match = curActFilter.expression.match(line); if( match.hasMatch() ) { item.type = FilteredItem::ActionItem; if( curActFilter.tool == QLatin1String("cd") ) { const Path path(match.captured(curActFilter.fileGroup)); d->m_currentDirs.push_back( path ); d->m_positionInCurrentDirs.insert( path , 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 pathForFile to find source files corresponding to // compiler errors. // Note: CMake objectfile has the format: "/path/to/four/CMakeFiles/file.o" if ( curActFilter.fileGroup != -1 && curActFilter.tool == QLatin1String("cmake") && line.contains(QStringLiteral("Building"))) { const auto objectFile = match.captured(curActFilter.fileGroup); const auto dir = objectFile.section(QStringLiteral("CMakeFiles/"), 0, 0); d->putDirAtEnd(Path(d->m_buildDir, dir)); } break; } } return item; } FilteredItem CompilerFilterStrategy::errorInLine(const QString& line) { // All the possible string that indicate an error if we via Regex have been able to // extract file and linenumber from a given outputline // TODO: This seems clumsy -- and requires another scan of the line. // Merge this information into ErrorFormat? --Kevin using Indicator = QPair; static const Indicator INDICATORS[] = { // ld Indicator(QStringLiteral("undefined reference"), FilteredItem::ErrorItem), Indicator(QStringLiteral("undefined symbol"), FilteredItem::ErrorItem), Indicator(QStringLiteral("ld: cannot find"), FilteredItem::ErrorItem), Indicator(QStringLiteral("no such file"), FilteredItem::ErrorItem), // gcc Indicator(QStringLiteral("error"), FilteredItem::ErrorItem), // generic Indicator(QStringLiteral("warning"), FilteredItem::WarningItem), Indicator(QStringLiteral("info"), FilteredItem::InformationItem), Indicator(QStringLiteral("note"), FilteredItem::InformationItem), }; // A list of filters for possible compiler, linker, and make errors static const ErrorFormat ERROR_FILTERS[] = { #ifdef Q_OS_WIN // MSVC ErrorFormat( QStringLiteral("^([a-zA-Z]:\\\\.+)\\(([1-9][0-9]*)\\): ((?:error|warning) .+\\:).*$"), 1, 2, 3 ), #endif // GCC - another case, eg. for #include "pixmap.xpm" which does not exists ErrorFormat( QStringLiteral("^([^:\t]+):([0-9]+):([0-9]+):([^0-9]+)"), 1, 2, 4, 3 ), // ant ErrorFormat( QStringLiteral("\\[javac\\][\\s]+([^:\t]+):([0-9]+): (warning: .*|error: .*)"), 1, 2, 3, QStringLiteral("javac")), // GCC ErrorFormat( QStringLiteral("^([^:\t]+):([0-9]+):([^0-9]+)"), 1, 2, 3 ), // GCC ErrorFormat( QStringLiteral("^(In file included from |[ ]+from )([^: \\t]+):([0-9]+)(:|,)(|[0-9]+)"), 2, 3, 5 ), // ICC ErrorFormat( QStringLiteral("^([^: \\t]+)\\(([0-9]+)\\):([^0-9]+)"), 1, 2, 3, QStringLiteral("intel") ), //libtool link ErrorFormat( QStringLiteral("^(libtool):( link):( warning): "), 0, 0, 0 ), // make ErrorFormat( QStringLiteral("No rule to make target"), 0, 0, 0 ), // cmake ErrorFormat( QStringLiteral("^([^: \\t]+):([0-9]+):"), 1, 2, 0, QStringLiteral("cmake") ), // cmake ErrorFormat( QStringLiteral("CMake (Error|Warning) (|\\([a-zA-Z]+\\) )(in|at) ([^:]+):($|[0-9]+)"), 4, 5, 1, QStringLiteral("cmake") ), // cmake/automoc // example: AUTOMOC: error: /foo/bar.cpp The file includes (...), // example: AUTOMOC: error: /foo/bar.cpp: The file includes (...) // note: ':' after file name isn't always appended, see http://cmake.org/gitweb?p=cmake.git;a=commitdiff;h=317d8498aa02c9f486bf5071963bb2034777cdd6 // example: AUTOGEN: error: /foo/bar.cpp: The file includes (...) // note: AUTOMOC got renamed to AUTOGEN at some point ErrorFormat( QStringLiteral("^(AUTOMOC|AUTOGEN): error: ([^:]+):? (The file .*)$"), 2, 0, 0 ), // via qt4_automoc // example: automoc4: The file "/foo/bar.cpp" includes the moc file "bar1.moc", but ... ErrorFormat( QStringLiteral("^automoc4: The file \"([^\"]+)\" includes the moc file"), 1, 0, 0 ), // Fortran ErrorFormat( QStringLiteral("\"(.*)\", line ([0-9]+):(.*)"), 1, 2, 3 ), // GFortran ErrorFormat( QStringLiteral("^(.*):([0-9]+)\\.([0-9]+):(.*)"), 1, 2, 4, QStringLiteral("gfortran"), 3 ), // Jade ErrorFormat( QStringLiteral("^[a-zA-Z]+:([^: \t]+):([0-9]+):[0-9]+:[a-zA-Z]:(.*)"), 1, 2, 3 ), // ifort ErrorFormat( QStringLiteral("^fortcom: (.*): (.*), line ([0-9]+):(.*)"), 2, 3, 1, QStringLiteral("intel") ), // PGI ErrorFormat( QStringLiteral("PGF9(.*)-(.*)-(.*)-(.*) \\((.*): ([0-9]+)\\)"), 5, 6, 4, QStringLiteral("pgi") ), // PGI (2) ErrorFormat( QStringLiteral("PGF9(.*)-(.*)-(.*)-Symbol, (.*) \\((.*)\\)"), 5, 5, 4, QStringLiteral("pgi") ), }; FilteredItem item(line); for (const auto& curErrFilter : ERROR_FILTERS) { const auto match = curErrFilter.expression.match(line); if( match.hasMatch() && !( line.contains( QLatin1String("Each undeclared identifier is reported only once") ) || line.contains( QLatin1String("for each function it appears in.") ) ) ) { if(curErrFilter.fileGroup > 0) { if( curErrFilter.compiler == QLatin1String("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.parent() ); } } item.url = d->pathForFile( match.captured( curErrFilter.fileGroup ) ).toUrl(); } initializeFilteredItem(item, curErrFilter, match); const QString txt = match.captured(curErrFilter.textGroup); // Find the indicator which happens most early. int earliestIndicatorIdx = txt.length(); for (const auto& 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 --- ScriptErrorFilterStrategy::ScriptErrorFilterStrategy() { } FilteredItem ScriptErrorFilterStrategy::actionInLine(const QString& line) { return FilteredItem(line); } FilteredItem ScriptErrorFilterStrategy::errorInLine(const QString& line) { // A list of filters for possible Python and PHP errors static const ErrorFormat SCRIPT_ERROR_FILTERS[] = { ErrorFormat( QStringLiteral("^ File \"(.*)\", line ([0-9]+)(.*$|, in(.*)$)"), 1, 2, -1 ), ErrorFormat( QStringLiteral("^.*(/.*):([0-9]+).*$"), 1, 2, -1 ), ErrorFormat( QStringLiteral("^.* in (/.*) on line ([0-9]+).*$"), 1, 2, -1 ) }; return match(SCRIPT_ERROR_FILTERS, line); } /// --- Native application error filter strategy --- NativeAppErrorFilterStrategy::NativeAppErrorFilterStrategy() { } FilteredItem NativeAppErrorFilterStrategy::actionInLine(const QString& line) { return FilteredItem(line); } FilteredItem NativeAppErrorFilterStrategy::errorInLine(const QString& line) { static const ErrorFormat NATIVE_APPLICATION_ERROR_FILTERS[] = { // BEGIN: C++ // a.out: test.cpp:5: int main(): Assertion `false' failed. ErrorFormat(QStringLiteral("^.+: (.+):([1-9][0-9]*): .*: Assertion `.*' failed\\.$"), 1, 2, -1), // END: C++ // BEGIN: Qt // Note: Scan the whole line for catching Qt runtime warnings (-> no ^$) // Reasoning: With QT_MESSAGE_PATTERN you can configure the output to your desire, // which means the output could be arbitrarily prefixed or suffixed with other content // QObject::connect related errors, also see err_method_notfound() in qobject.cpp // QObject::connect: No such slot Foo::bar() in /foo/bar.cpp:313 ErrorFormat(QStringLiteral("QObject::connect: (?:No such|Parentheses expected,) (?:slot|signal) [^ ]* in (.*):([0-9]+)"), 1, 2, -1), // ASSERT: "errors().isEmpty()" in file /foo/bar.cpp, line 49 ErrorFormat(QStringLiteral("ASSERT: \"(.*)\" in file (.*), line ([0-9]+)"), 2, 3, -1), // Catch: // FAIL! : FooTest::testBar() Compared pointers are not the same // Actual ... // Expected ... // Loc: [/foo/bar.cpp(33)] // // Do *not* catch: // ... // Loc: [Unknown file(0)] ErrorFormat(QStringLiteral(" Loc: \\[(.*)\\(([1-9][0-9]*)\\)\\]"), 1, 2, -1), // file:///path/to/foo.qml:7:1: Bar is not a type // file:///path/to/foo.qml:49:5: QML Row: Binding loop detected for property "height" ErrorFormat(QStringLiteral("(file:\\/\\/(?:[^:]+)):([1-9][0-9]*):([1-9][0-9]*): (.*) (?:is not a type|is ambiguous|is instantiated recursively|Binding loop detected)"), 1, 2, -1, 3), // file:///path/to/foo.qml:52: TypeError: Cannot read property 'height' of null ErrorFormat(QStringLiteral("(file:\\/\\/(?:[^:]+)):([1-9][0-9]*): ([a-zA-Z]+)Error"), 1, 2, -1), // END: Qt }; return match(NATIVE_APPLICATION_ERROR_FILTERS, line); } /// --- Static Analysis filter strategy --- StaticAnalysisFilterStrategy::StaticAnalysisFilterStrategy() { } FilteredItem StaticAnalysisFilterStrategy::actionInLine(const QString& line) { return FilteredItem(line); } FilteredItem StaticAnalysisFilterStrategy::errorInLine(const QString& line) { // A list of filters for static analysis tools (krazy2, cppcheck) static const ErrorFormat STATIC_ANALYSIS_FILTERS[] = { // CppCheck ErrorFormat( QStringLiteral("^\\[(.*):([0-9]+)\\]:(.*)"), 1, 2, 3 ), // krazy2 ErrorFormat( QStringLiteral("^\\t([^:]+).*line#([0-9]+).*"), 1, 2, -1 ), // krazy2 without line info ErrorFormat( QStringLiteral("^\\t(.*): missing license"), 1, -1, -1 ) }; return match(STATIC_ANALYSIS_FILTERS, line); } } diff --git a/outputview/outputfilteringstrategies.h b/outputview/outputfilteringstrategies.h index 4dca61df8..fb5e545e1 100644 --- a/outputview/outputfilteringstrategies.h +++ b/outputview/outputfilteringstrategies.h @@ -1,125 +1,125 @@ /* 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 #include #include #include #include namespace KDevelop { 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(); FilteredItem errorInLine(const QString& line) override; FilteredItem actionInLine(const QString& line) override; }; /** * 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(const QUrl& buildDir); + explicit CompilerFilterStrategy(const QUrl& buildDir); virtual ~CompilerFilterStrategy(); FilteredItem errorInLine(const QString& line) override; FilteredItem actionInLine(const QString& line) override; 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(); FilteredItem errorInLine(const QString& line) override; FilteredItem actionInLine(const QString& line) override; }; /** * 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(); FilteredItem errorInLine(const QString& line) override; FilteredItem actionInLine(const QString& line) override; }; /** * This filter stategy filters out errors (no actions) from Static code analysis tools (Cppcheck,) **/ class KDEVPLATFORMOUTPUTVIEW_EXPORT StaticAnalysisFilterStrategy : public IFilterStrategy { public: StaticAnalysisFilterStrategy(); FilteredItem errorInLine(const QString& line) override; FilteredItem actionInLine(const QString& line) override; }; } // namespace KDevelop #endif // KDEVPLATFORM_OUTPUTFILTERINGSTRATEGIES_H diff --git a/outputview/outputjob.h b/outputview/outputjob.h index 5c9524c3a..a1c86cbbf 100644 --- a/outputview/outputjob.h +++ b/outputview/outputjob.h @@ -1,109 +1,109 @@ /* This file is part of KDevelop Copyright 2007-2008 Hamish Rodda This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License 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_OUTPUTJOB_H #define KDEVPLATFORM_OUTPUTJOB_H #include #include #include #include #include class QStandardItemModel; class QItemDelegate; namespace KDevelop { class KDEVPLATFORMOUTPUTVIEW_EXPORT OutputJob : public KJob { Q_OBJECT public: enum { FailedShownError = UserDefinedError + 100 //job failed and failure is shown in OutputView }; enum OutputJobVerbosity { Silent, Verbose }; - OutputJob(QObject* parent = nullptr, OutputJobVerbosity verbosity = OutputJob::Verbose); + explicit OutputJob(QObject* parent = nullptr, OutputJobVerbosity verbosity = OutputJob::Verbose); void startOutput(); OutputJobVerbosity verbosity() const; void setVerbosity(OutputJobVerbosity verbosity); QAbstractItemModel* model() const; /// Set the \a title for this job's output tab. If not set, will default to the job's objectName(). void setTitle(const QString& title); protected: void setStandardToolView(IOutputView::StandardToolView standard); void setToolTitle(const QString& title); void setToolIcon(const QIcon& icon); void setViewType(IOutputView::ViewType type); void setBehaviours(IOutputView::Behaviours behaviours); void setKillJobOnOutputClose(bool killJobOnOutputClose); /** * Sets the model for the view that shows this jobs output. * * The view takes ownership of the model, but it is safe to * use the model while the job is running. * * NOTE: Do not reuse the same model for different jobs. */ void setModel(QAbstractItemModel* model); /** * Sets the delegate for the view that shows this jobs output. * * The view takes ownership of the delegate, but it is safe to * use the delegate while the job is running. * * NOTE: Do not reuse the same delegate for different jobs. */ void setDelegate(QAbstractItemDelegate* delegate); int outputId() const; private Q_SLOTS: void outputViewRemoved(int , int id); private: int m_standardToolView; QString m_title, m_toolTitle; QIcon m_toolIcon; IOutputView::ViewType m_type; IOutputView::Behaviours m_behaviours; bool m_killJobOnOutputClose; OutputJobVerbosity m_verbosity; int m_outputId; QPointer m_outputModel; QAbstractItemDelegate* m_outputDelegate; }; } #endif diff --git a/outputview/outputmodel.cpp b/outputview/outputmodel.cpp index 50fbfce6d..a5f9d4eec 100644 --- a/outputview/outputmodel.cpp +++ b/outputview/outputmodel.cpp @@ -1,468 +1,468 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2007 Andreas Pakulat * * Copyright 2010 Aleix Pol Gonzalez * * Copyright (C) 2012 Morten Danielsen Volden mvolden2@gmail.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "outputmodel.h" #include "filtereditem.h" #include "outputfilteringstrategies.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include #include Q_DECLARE_METATYPE(QVector) namespace KDevelop { /** * Number of lines that are processed in one go before we notify the GUI thread * about the result. It is generally faster to add multiple items to a model * in one go compared to adding each item independently. */ static const int BATCH_SIZE = 50; /** * Time in ms that we wait in the parse worker for new incoming lines before * actually processing them. If we already have enough for one batch though * we process immediately. */ static const int BATCH_AGGREGATE_TIME_DELAY = 50; class ParseWorker : public QObject { Q_OBJECT public: ParseWorker() : QObject(nullptr) , m_filter(new NoFilterStrategy) , m_timer(new QTimer(this)) { m_timer->setInterval(BATCH_AGGREGATE_TIME_DELAY); m_timer->setSingleShot(true); connect(m_timer, &QTimer::timeout, this, &ParseWorker::process); } public slots: void changeFilterStrategy( KDevelop::IFilterStrategy* newFilterStrategy ) { m_filter = QSharedPointer( newFilterStrategy ); } void addLines( const QStringList& lines ) { m_cachedLines << lines; if (m_cachedLines.size() >= BATCH_SIZE) { // if enough lines were added, process immediately m_timer->stop(); process(); } else if (!m_timer->isActive()) { m_timer->start(); } } void flushBuffers() { m_timer->stop(); process(); emit allDone(); } signals: void parsedBatch(const QVector& filteredItems); void progress(const KDevelop::IFilterStrategy::Progress& progress); void allDone(); private slots: /** * Process *all* cached lines, emit parsedBatch for each batch */ void process() { QVector filteredItems; filteredItems.reserve(qMin(BATCH_SIZE, m_cachedLines.size())); // apply pre-filtering functions std::transform(m_cachedLines.constBegin(), m_cachedLines.constEnd(), m_cachedLines.begin(), &KDevelop::stripAnsiSequences); // apply filtering strategy foreach(const QString& line, m_cachedLines) { FilteredItem item = m_filter->errorInLine(line); if( item.type == FilteredItem::InvalidItem ) { item = m_filter->actionInLine(line); } filteredItems << item; auto progress = m_filter->progressInLine(line); if (progress.percent >= 0 && m_progress.percent != progress.percent) { m_progress = progress; emit this->progress(m_progress); } if( filteredItems.size() == BATCH_SIZE ) { emit parsedBatch(filteredItems); filteredItems.clear(); filteredItems.reserve(qMin(BATCH_SIZE, m_cachedLines.size())); } } // Make sure to emit the rest as well if( !filteredItems.isEmpty() ) { emit parsedBatch(filteredItems); } m_cachedLines.clear(); } private: QSharedPointer m_filter; QStringList m_cachedLines; QTimer* m_timer; IFilterStrategy::Progress m_progress; }; class ParsingThread { public: ParsingThread() { m_thread.setObjectName(QStringLiteral("OutputFilterThread")); } virtual ~ParsingThread() { if (m_thread.isRunning()) { m_thread.quit(); m_thread.wait(); } } void addWorker(ParseWorker* worker) { if (!m_thread.isRunning()) { m_thread.start(); } worker->moveToThread(&m_thread); } private: QThread m_thread; }; Q_GLOBAL_STATIC(ParsingThread, s_parsingThread); struct OutputModelPrivate { - OutputModelPrivate( OutputModel* model, const QUrl& builddir = QUrl() ); + explicit OutputModelPrivate( OutputModel* model, const QUrl& builddir = QUrl() ); ~OutputModelPrivate(); bool isValidIndex( const QModelIndex&, int currentRowCount ) const; OutputModel* model; ParseWorker* worker; QVector m_filteredItems; // We use std::set because that is ordered std::set m_errorItems; // Indices of all items that we want to move to using previous and next QUrl m_buildDir; void linesParsed(const QVector& items) { model->beginInsertRows( QModelIndex(), model->rowCount(), model->rowCount() + items.size() - 1); foreach( const FilteredItem& item, items ) { if( item.type == FilteredItem::ErrorItem ) { m_errorItems.insert(m_filteredItems.size()); } m_filteredItems << item; } model->endInsertRows(); } }; OutputModelPrivate::OutputModelPrivate( OutputModel* model_, const QUrl& builddir) : model(model_) , worker(new ParseWorker ) , m_buildDir( builddir ) { qRegisterMetaType >(); qRegisterMetaType(); qRegisterMetaType(); s_parsingThread->addWorker(worker); model->connect(worker, &ParseWorker::parsedBatch, model, [=] (const QVector& items) { linesParsed(items); }); model->connect(worker, &ParseWorker::allDone, model, &OutputModel::allDone); model->connect(worker, &ParseWorker::progress, model, &OutputModel::progress); } bool OutputModelPrivate::isValidIndex( const QModelIndex& idx, int currentRowCount ) const { return ( idx.isValid() && idx.row() >= 0 && idx.row() < currentRowCount && idx.column() == 0 ); } OutputModelPrivate::~OutputModelPrivate() { worker->deleteLater(); } OutputModel::OutputModel( const QUrl& builddir, QObject* parent ) : QAbstractListModel(parent) , d( new OutputModelPrivate( this, builddir ) ) { } OutputModel::OutputModel( QObject* parent ) : QAbstractListModel(parent) , d( new OutputModelPrivate( this ) ) { } OutputModel::~OutputModel() { 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() ).originalLine; break; case OutputModel::OutputItemTypeRole: return static_cast(d->m_filteredItems.at( idx.row() ).type); break; case Qt::FontRole: return QFontDatabase::systemFont(QFontDatabase::FixedFont); break; default: break; } } return QVariant(); } int OutputModel::rowCount( const QModelIndex& parent ) const { if( !parent.isValid() ) return d->m_filteredItems.count(); return 0; } QVariant OutputModel::headerData( int, Qt::Orientation, int ) const { return QVariant(); } void OutputModel::activate( const QModelIndex& index ) { if( index.model() != this || !d->isValidIndex(index, rowCount()) ) { return; } qCDebug(OUTPUTVIEW) << "Model activated" << index.row(); FilteredItem item = d->m_filteredItems.at( index.row() ); if( item.isActivatable ) { qCDebug(OUTPUTVIEW) << "activating:" << item.lineNo << item.url; KTextEditor::Cursor range( item.lineNo, item.columnNo ); KDevelop::IDocumentController *docCtrl = KDevelop::ICore::self()->documentController(); QUrl url = item.url; if (item.url.isEmpty()) { qWarning() << "trying to open empty url"; return; } if(url.isRelative()) { url = d->m_buildDir.resolved(url); } Q_ASSERT(!url.isRelative()); docCtrl->openDocument( url, range ); } else { qCDebug(OUTPUTVIEW) << "not an activateable item"; } } QModelIndex OutputModel::firstHighlightIndex() { if( !d->m_errorItems.empty() ) { return index( *d->m_errorItems.begin(), 0, QModelIndex() ); } for( int row = 0; row < rowCount(); ++row ) { if( d->m_filteredItems.at( row ).isActivatable ) { return index( row, 0, QModelIndex() ); } } return QModelIndex(); } QModelIndex OutputModel::nextHighlightIndex( const QModelIndex ¤tIdx ) { int startrow = d->isValidIndex(currentIdx, rowCount()) ? currentIdx.row() + 1 : 0; if( !d->m_errorItems.empty() ) { qCDebug(OUTPUTVIEW) << "searching next error"; // Jump to the next error item std::set< int >::const_iterator next = d->m_errorItems.lower_bound( startrow ); if( next == d->m_errorItems.end() ) next = d->m_errorItems.begin(); return index( *next, 0, QModelIndex() ); } for( int row = 0; row < rowCount(); ++row ) { int currow = (startrow + row) % rowCount(); if( d->m_filteredItems.at( currow ).isActivatable ) { return index( currow, 0, QModelIndex() ); } } return QModelIndex(); } QModelIndex OutputModel::previousHighlightIndex( const QModelIndex ¤tIdx ) { //We have to ensure that startrow is >= rowCount - 1 to get a positive value from the % operation. int startrow = rowCount() + (d->isValidIndex(currentIdx, rowCount()) ? currentIdx.row() : rowCount()) - 1; if(!d->m_errorItems.empty()) { qCDebug(OUTPUTVIEW) << "searching previous error"; // Jump to the previous error item std::set< int >::const_iterator previous = d->m_errorItems.lower_bound( currentIdx.row() ); if( previous == d->m_errorItems.begin() ) previous = d->m_errorItems.end(); --previous; return index( *previous, 0, QModelIndex() ); } for ( int row = 0; row < rowCount(); ++row ) { int currow = (startrow - row) % rowCount(); if( d->m_filteredItems.at( currow ).isActivatable ) { return index( currow, 0, QModelIndex() ); } } return QModelIndex(); } QModelIndex OutputModel::lastHighlightIndex() { if( !d->m_errorItems.empty() ) { return index( *d->m_errorItems.rbegin(), 0, QModelIndex() ); } for( int row = rowCount()-1; row >=0; --row ) { if( d->m_filteredItems.at( row ).isActivatable ) { return index( row, 0, QModelIndex() ); } } return QModelIndex(); } void OutputModel::setFilteringStrategy(const OutputFilterStrategy& currentStrategy) { // TODO: Turn into factory, decouple from OutputModel IFilterStrategy* filter = nullptr; switch( currentStrategy ) { case NoFilter: filter = new NoFilterStrategy; break; case CompilerFilter: filter = new CompilerFilterStrategy( d->m_buildDir ); break; case ScriptErrorFilter: filter = new ScriptErrorFilterStrategy; break; case NativeAppErrorFilter: filter = new NativeAppErrorFilterStrategy; break; case StaticAnalysisFilter: filter = new StaticAnalysisFilterStrategy; break; default: // assert(false); filter = new NoFilterStrategy; break; } Q_ASSERT(filter); QMetaObject::invokeMethod(d->worker, "changeFilterStrategy", Q_ARG(KDevelop::IFilterStrategy*, filter)); } void OutputModel::setFilteringStrategy(IFilterStrategy* filterStrategy) { QMetaObject::invokeMethod(d->worker, "changeFilterStrategy", Q_ARG(KDevelop::IFilterStrategy*, filterStrategy)); } void OutputModel::appendLines( const QStringList& lines ) { if( lines.isEmpty() ) return; QMetaObject::invokeMethod(d->worker, "addLines", Q_ARG(QStringList, lines)); } void OutputModel::appendLine( const QString& l ) { appendLines( QStringList() << l ); } void OutputModel::ensureAllDone() { QMetaObject::invokeMethod(d->worker, "flushBuffers"); } } #include "outputmodel.moc" #include "moc_outputmodel.cpp" diff --git a/plugins/classbrowser/classbrowserplugin.cpp b/plugins/classbrowser/classbrowserplugin.cpp index 2993f720c..c3df772cc 100644 --- a/plugins/classbrowser/classbrowserplugin.cpp +++ b/plugins/classbrowser/classbrowserplugin.cpp @@ -1,187 +1,187 @@ /* * This file is part of KDevelop * * Copyright 2006 Adam Treat * Copyright 2006-2008 Hamish Rodda * Copyright 2009 Lior Mualem * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "classbrowserplugin.h" #include #include #include #include "interfaces/icore.h" #include "interfaces/iuicontroller.h" #include "interfaces/idocumentcontroller.h" #include "interfaces/contextmenuextension.h" #include "language/interfaces/codecontext.h" #include "language/duchain/duchainbase.h" #include "language/duchain/duchain.h" #include "language/duchain/duchainlock.h" #include "language/duchain/declaration.h" #include #include "debug.h" #include "language/classmodel/classmodel.h" #include "classtree.h" #include "classwidget.h" #include #include #include #include #include #include Q_LOGGING_CATEGORY(PLUGIN_CLASSBROWSER, "kdevplatform.plugins.classbrowser") K_PLUGIN_FACTORY_WITH_JSON(KDevClassBrowserFactory, "kdevclassbrowser.json", registerPlugin(); ) using namespace KDevelop; class ClassBrowserFactory: public KDevelop::IToolViewFactory { public: - ClassBrowserFactory(ClassBrowserPlugin *plugin): m_plugin(plugin) {} + explicit ClassBrowserFactory(ClassBrowserPlugin *plugin): m_plugin(plugin) {} QWidget* create(QWidget *parent = nullptr) override { return new ClassWidget(parent, m_plugin); } Qt::DockWidgetArea defaultPosition() override { return Qt::LeftDockWidgetArea; } QString id() const override { return QStringLiteral("org.kdevelop.ClassBrowserView"); } private: ClassBrowserPlugin *m_plugin; }; ClassBrowserPlugin::ClassBrowserPlugin(QObject *parent, const QVariantList&) : KDevelop::IPlugin(QStringLiteral("kdevclassbrowser"), parent) , m_factory(new ClassBrowserFactory(this)) , m_activeClassTree(nullptr) { core()->uiController()->addToolView(i18n("Classes"), m_factory); setXMLFile( QStringLiteral("kdevclassbrowser.rc") ); m_findInBrowser = new QAction(i18n("Find in &Class Browser"), this); connect(m_findInBrowser, &QAction::triggered, this, &ClassBrowserPlugin::findInClassBrowser); } ClassBrowserPlugin::~ClassBrowserPlugin() { } void ClassBrowserPlugin::unload() { core()->uiController()->removeToolView(m_factory); } KDevelop::ContextMenuExtension ClassBrowserPlugin::contextMenuExtension( KDevelop::Context* context) { KDevelop::ContextMenuExtension menuExt = KDevelop::IPlugin::contextMenuExtension( context ); // No context menu if we don't have a class browser at hand. if ( m_activeClassTree == nullptr ) return menuExt; KDevelop::DeclarationContext *codeContext = dynamic_cast(context); if (!codeContext) return menuExt; DUChainReadLocker readLock(DUChain::lock()); Declaration* decl(codeContext->declaration().data()); if (decl) { if(decl->inSymbolTable()) { if(!ClassTree::populatingClassBrowserContextMenu() && ICore::self()->projectController()->findProjectForUrl(decl->url().toUrl()) && decl->kind() == Declaration::Type && decl->internalContext() && decl->internalContext()->type() == DUContext::Class) { //Currently "Find in Class Browser" seems to only work for classes, so only show it in that case m_findInBrowser->setData(QVariant::fromValue(DUChainBasePointer(decl))); menuExt.addAction( KDevelop::ContextMenuExtension::ExtensionGroup, m_findInBrowser); } } } return menuExt; } void ClassBrowserPlugin::findInClassBrowser() { ICore::self()->uiController()->findToolView(i18n("Classes"), m_factory, KDevelop::IUiController::CreateAndRaise); Q_ASSERT(qobject_cast(sender())); if ( m_activeClassTree == nullptr ) return; DUChainReadLocker readLock(DUChain::lock()); QAction* a = static_cast(sender()); Q_ASSERT(a->data().canConvert()); DeclarationPointer decl = qvariant_cast(a->data()).dynamicCast(); if (decl) m_activeClassTree->highlightIdentifier(decl->qualifiedIdentifier()); } void ClassBrowserPlugin::showDefinition(DeclarationPointer declaration) { DUChainReadLocker readLock(DUChain::lock()); if ( !declaration ) return; Declaration* decl = declaration.data(); // If it's a function, find the function definition to go to the actual declaration. if ( decl && decl->isFunctionDeclaration() ) { FunctionDefinition* funcDefinition = dynamic_cast(decl); if ( funcDefinition == nullptr ) funcDefinition = FunctionDefinition::definition(decl); if ( funcDefinition ) decl = funcDefinition; } if (decl) { QUrl url = decl->url().toUrl(); KTextEditor::Range range = decl->rangeInCurrentRevision(); readLock.unlock(); ICore::self()->documentController()->openDocument(url, range.start()); } } #include "classbrowserplugin.moc" // kate: space-indent on; indent-width 2; tab-width 4; replace-tabs on; auto-insert-doxygen on diff --git a/plugins/contextbrowser/browsemanager.cpp b/plugins/contextbrowser/browsemanager.cpp index 7ebeadf0e..3b68397b8 100644 --- a/plugins/contextbrowser/browsemanager.cpp +++ b/plugins/contextbrowser/browsemanager.cpp @@ -1,342 +1,342 @@ /* * 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 #include #include "contextbrowserview.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "contextbrowser.h" #include "debug.h" using namespace KDevelop; using namespace KTextEditor; EditorViewWatcher::EditorViewWatcher(QObject* parent) : QObject(parent) { connect(ICore::self()->documentController(), &IDocumentController::textDocumentCreated, this, &EditorViewWatcher::documentCreated); 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, &Document::viewCreated, this, &EditorViewWatcher::viewCreated); 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, &View::destroyed, this, &EditorViewWatcher::viewDestroyed); } 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() { avoidMenuAltFocus(); if(m_browsingByKey == Qt::Key_Alt && m_browsingStartedInView) emit startDelayedBrowsing(m_browsingStartedInView); } 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, &QTimer::timeout, this, &BrowseManager::eventuallyStartDelayedBrowsing); foreach(KTextEditor::View* view, m_watcher.allViews()) viewAdded(view); } KTextEditor::View* viewFromWidget(QWidget* widget) { if(!widget) return nullptr; KTextEditor::View* view = qobject_cast(widget); if(view) return view; else return viewFromWidget(widget->parentWidget()); } BrowseManager::JumpLocation BrowseManager::determineJumpLoc(KTextEditor::Cursor textCursor, const QUrl& viewUrl) const { // @todo find out why this is needed, fix the code in kate if (textCursor.column() > 0) { textCursor.setColumn(textCursor.column() - 1); } // Step 1: Look for a special language object(Macro, included header, etc.) foreach (const auto& language, ICore::self()->languageController()->languagesForUrl(viewUrl)) { auto jumpTo = language->specialLanguageObjectJumpCursor(viewUrl, textCursor); if (jumpTo.first.isValid() && jumpTo.second.isValid()) { - return {jumpTo}; + return {jumpTo.first, jumpTo.second}; } } // Step 2: Look for a declaration/use DUChainReadLocker lock; // Jump to definition by default, unless a definition itself was selected, // in which case jump to declaration. if (auto selectedDeclaration = DUChainUtils::itemUnderCursor(viewUrl, textCursor).declaration) { auto jumpDestination = selectedDeclaration; if (selectedDeclaration->isDefinition()) { // A definition was clicked directly - jump to declaration instead. if (auto declaration = DUChainUtils::declarationForDefinition(selectedDeclaration)) { jumpDestination = declaration; } } else if (selectedDeclaration == DUChainUtils::declarationForDefinition(selectedDeclaration)) { // Clicked the declaration - jump to definition if (auto definition = FunctionDefinition::definition(selectedDeclaration)) { jumpDestination = definition; } } - return {{jumpDestination->url().toUrl(), jumpDestination->rangeInCurrentRevision().start()}}; + return {jumpDestination->url().toUrl(), jumpDestination->rangeInCurrentRevision().start()}; } return {}; } bool BrowseManager::eventFilter(QObject * watched, QEvent * event) { QWidget* widget = qobject_cast(watched); Q_ASSERT(widget); QKeyEvent* keyEvent = dynamic_cast(event); const int browseKey = Qt::Key_Control; const int magicModifier = Qt::Key_Alt; KTextEditor::View* view = viewFromWidget(widget); //Eventually start key-browsing if(keyEvent && (keyEvent->key() == browseKey || keyEvent->key() == magicModifier) && !m_browsingByKey && keyEvent->type() == QEvent::KeyPress) { m_delayedBrowsingTimer->start(300); // always start the timer, to get consistent behavior regarding the ALT key and the menu activation m_browsingByKey = keyEvent->key(); if(!view) { return false; } if(keyEvent->key() == magicModifier) { if(dynamic_cast(view) && dynamic_cast(view)->isCompletionActive()) { //Completion is active. avoidMenuAltFocus(); m_delayedBrowsingTimer->stop(); }else{ m_browsingStartedInView = view; } } } if (keyEvent && m_browsingByKey && m_browsingStartedInView && keyEvent->type() == QEvent::KeyPress) { if (keyEvent->key() >= Qt::Key_1 && keyEvent->key() <= Qt::Key_9) { // user wants to trigger an action in the code browser const int index = keyEvent->key() - Qt::Key_1; emit invokeAction(index); stopDelayedBrowsing(); return true; } } if(!view) { return false; } QFocusEvent* focusEvent = dynamic_cast(event); //Eventually stop key-browsing if((keyEvent && m_browsingByKey && keyEvent->key() == m_browsingByKey && keyEvent->type() == QEvent::KeyRelease) || (focusEvent && focusEvent->lostFocus()) || event->type() == QEvent::WindowDeactivate) { 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; } if(mouseEvent) { QPoint coordinatesInView = widget->mapTo(view, mouseEvent->pos()); KTextEditor::Cursor textCursor = view->coordinatesToCursor(coordinatesInView); if (textCursor.isValid()) { JumpLocation jumpTo = determineJumpLoc(textCursor, view->document()->url()); if (jumpTo.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.url, jumpTo.cursor); // 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::avoidMenuAltFocus() { // send an invalid key event to the main menu bar. The menu bar will // stop listening when observing another key than ALT between the press // and the release. QKeyEvent event1(QEvent::KeyPress, 0, Qt::NoModifier); QApplication::sendEvent(ICore::self()->uiController()->activeMainWindow()->menuBar(), &event1); QKeyEvent event2(QEvent::KeyRelease, 0, Qt::NoModifier); QApplication::sendEvent(ICore::self()->uiController()->activeMainWindow()->menuBar(), &event2); } 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 // can't use new signal/slot syntax here, these signals are only defined in KateView // TODO: should we really depend on kate internals here? 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 Watcher::viewAdded(KTextEditor::View* view) { m_manager->viewAdded(view); } void BrowseManager::setBrowsing(bool enabled) { if(enabled == m_browsing) return; m_browsing = enabled; //This collects all the views if(enabled) { qCDebug(PLUGIN_CONTEXTBROWSER) << "Enabled browsing-mode"; }else{ qCDebug(PLUGIN_CONTEXTBROWSER) << "Disabled browsing-mode"; resetChangedCursor(); } } Watcher::Watcher(BrowseManager* manager) : EditorViewWatcher(manager), m_manager(manager) { foreach(KTextEditor::View* view, allViews()) m_manager->applyEventFilter(view, true); } diff --git a/plugins/contextbrowser/browsemanager.h b/plugins/contextbrowser/browsemanager.h index 072d97a7d..0c9f480db 100644 --- a/plugins/contextbrowser/browsemanager.h +++ b/plugins/contextbrowser/browsemanager.h @@ -1,125 +1,123 @@ /* * 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. */ #ifndef KDEVPLATFORM_PLUGIN_BROWSEMANAGER_H #define KDEVPLATFORM_PLUGIN_BROWSEMANAGER_H #include #include #include #include #include #include #include class QWidget; namespace KTextEditor { class View; class Document; } namespace KDevelop { class IDocument; } class EditorViewWatcher : public QObject { Q_OBJECT public: explicit EditorViewWatcher(QObject* parent = nullptr); QList allViews(); private: ///Called for every added view. Reimplement this to catch them. virtual void viewAdded(KTextEditor::View*); private slots: void viewDestroyed(QObject* view); void viewCreated(KTextEditor::Document*, KTextEditor::View*); void documentCreated( KDevelop::IDocument* document ); private: void addViewInternal(KTextEditor::View* view); QList m_views; }; class ContextBrowserPlugin; class BrowseManager; class Watcher : public EditorViewWatcher { Q_OBJECT public: explicit Watcher(BrowseManager* manager); void viewAdded(KTextEditor::View*) override; private: BrowseManager* m_manager; }; /** * Integrates the context-browser with the editor views, by listening for navigation events, and implementing html-like source browsing */ class BrowseManager : public QObject { Q_OBJECT public: explicit BrowseManager(ContextBrowserPlugin* controller); void viewAdded(KTextEditor::View* view); ///Installs/uninstalls the event-filter void applyEventFilter(QWidget* object, bool install); Q_SIGNALS: ///Emitted when browsing was started using the magic-modifier void startDelayedBrowsing(KTextEditor::View* view); void stopDelayedBrowsing(); void invokeAction(int index); public slots: ///Enabled/disables the browsing mode void setBrowsing(bool); private slots: void eventuallyStartDelayedBrowsing(); private: struct JumpLocation { QUrl url; KTextEditor::Cursor cursor; - JumpLocation(const QPair& pair = {}) - : url(pair.first) - , cursor(pair.second) - {} + bool isValid() const { return url.isValid() && cursor.isValid(); } }; + void resetChangedCursor(); JumpLocation determineJumpLoc(KTextEditor::Cursor textCursor, const QUrl& viewUrl) const; void setHandCursor(QWidget* widget); void avoidMenuAltFocus(); bool eventFilter(QObject * watched, QEvent * event) override ; ContextBrowserPlugin* m_plugin; bool m_browsing; int m_browsingByKey; //Whether the browsing was started because of a key Watcher m_watcher; //Maps widgets to their previously set cursors QMap, QCursor> m_oldCursors; QTimer* m_delayedBrowsingTimer; QPointer m_browsingStartedInView; KTextEditor::Cursor m_buttonPressPosition; }; #endif diff --git a/plugins/contextbrowser/contextbrowser.cpp b/plugins/contextbrowser/contextbrowser.cpp index 4ded5c16a..7296a87cb 100644 --- a/plugins/contextbrowser/contextbrowser.cpp +++ b/plugins/contextbrowser/contextbrowser.cpp @@ -1,1495 +1,1495 @@ /* * This file is part of KDevelop * * Copyright 2007 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "contextbrowser.h" #include "contextbrowserview.h" #include "browsemanager.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include Q_LOGGING_CATEGORY(PLUGIN_CONTEXTBROWSER, "kdevplatform.plugins.contextbrowser") using KTextEditor::Attribute; using KTextEditor::View; // Helper that follows the QObject::parent() chain, and returns the highest widget that has no parent. QWidget* masterWidget(QWidget* w) { while(w && w->parent() && qobject_cast(w->parent())) w = qobject_cast(w->parent()); return w; } namespace { const unsigned int highlightingTimeout = 150; const float highlightingZDepth = -5000; const int maxHistoryLength = 30; // Helper that determines the context to use for highlighting at a specific position DUContext* contextForHighlightingAt(const KTextEditor::Cursor& position, TopDUContext* topContext) { DUContext* ctx = topContext->findContextAt(topContext->transformToLocalRevision(position)); while(ctx && ctx->parentContext() && (ctx->type() == DUContext::Template || ctx->type() == DUContext::Helper || ctx->localScopeIdentifier().isEmpty())) { ctx = ctx->parentContext(); } return ctx; } ///Duchain must be locked DUContext* getContextAt(const QUrl& url, KTextEditor::Cursor cursor) { TopDUContext* topContext = DUChainUtils::standardContextForUrl(url); if (!topContext) return nullptr; return contextForHighlightingAt(KTextEditor::Cursor(cursor), topContext); } DeclarationPointer cursorDeclaration() { KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView(); if (!view) { return DeclarationPointer(); } DUChainReadLocker lock; Declaration *decl = DUChainUtils::declarationForDefinition(DUChainUtils::itemUnderCursor(view->document()->url(), KTextEditor::Cursor(view->cursorPosition())).declaration); return DeclarationPointer(decl); } } class ContextBrowserViewFactory: public KDevelop::IToolViewFactory { public: - ContextBrowserViewFactory(ContextBrowserPlugin *plugin): m_plugin(plugin) {} + explicit ContextBrowserViewFactory(ContextBrowserPlugin *plugin): m_plugin(plugin) {} QWidget* create(QWidget *parent = nullptr) override { ContextBrowserView* ret = new ContextBrowserView(m_plugin, parent); return ret; } Qt::DockWidgetArea defaultPosition() override { return Qt::BottomDockWidgetArea; } QString id() const override { return QStringLiteral("org.kdevelop.ContextBrowser"); } private: ContextBrowserPlugin *m_plugin; }; KXMLGUIClient* ContextBrowserPlugin::createGUIForMainWindow( Sublime::MainWindow* window ) { m_browseManager = new BrowseManager(this); KXMLGUIClient* ret = KDevelop::IPlugin::createGUIForMainWindow(window); connect(ICore::self()->documentController(), &IDocumentController::documentJumpPerformed, this, &ContextBrowserPlugin::documentJumpPerformed); m_previousButton = new QToolButton(); m_previousButton->setToolTip(i18n("Go back in context history")); m_previousButton->setPopupMode(QToolButton::MenuButtonPopup); m_previousButton->setIcon(QIcon::fromTheme(QStringLiteral("go-previous"))); m_previousButton->setEnabled(false); m_previousButton->setFocusPolicy(Qt::NoFocus); m_previousMenu = new QMenu(); m_previousButton->setMenu(m_previousMenu); connect(m_previousButton.data(), &QToolButton::clicked, this, &ContextBrowserPlugin::historyPrevious); connect(m_previousMenu.data(), &QMenu::aboutToShow, this, &ContextBrowserPlugin::previousMenuAboutToShow); m_nextButton = new QToolButton(); m_nextButton->setToolTip(i18n("Go forward in context history")); m_nextButton->setPopupMode(QToolButton::MenuButtonPopup); m_nextButton->setIcon(QIcon::fromTheme(QStringLiteral("go-next"))); m_nextButton->setEnabled(false); m_nextButton->setFocusPolicy(Qt::NoFocus); m_nextMenu = new QMenu(); m_nextButton->setMenu(m_nextMenu); connect(m_nextButton.data(), &QToolButton::clicked, this, &ContextBrowserPlugin::historyNext); connect(m_nextMenu.data(), &QMenu::aboutToShow, this, &ContextBrowserPlugin::nextMenuAboutToShow); IQuickOpen* quickOpen = KDevelop::ICore::self()->pluginController()->extensionForPlugin(QStringLiteral("org.kdevelop.IQuickOpen")); if(quickOpen) { m_outlineLine = quickOpen->createQuickOpenLine(QStringList(), QStringList() << i18n("Outline"), IQuickOpen::Outline); m_outlineLine->setDefaultText(i18n("Outline...")); m_outlineLine->setToolTip(i18n("Navigate outline of active document, click to browse.")); } connect(m_browseManager, &BrowseManager::startDelayedBrowsing, this, &ContextBrowserPlugin::startDelayedBrowsing); connect(m_browseManager, &BrowseManager::stopDelayedBrowsing, this, &ContextBrowserPlugin::stopDelayedBrowsing); connect(m_browseManager, &BrowseManager::invokeAction, this, &ContextBrowserPlugin::invokeAction); m_toolbarWidget = toolbarWidgetForMainWindow(window); m_toolbarWidgetLayout = new QHBoxLayout; m_toolbarWidgetLayout->setSizeConstraint(QLayout::SetMaximumSize); m_previousButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); m_nextButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); m_toolbarWidgetLayout->setMargin(0); m_toolbarWidgetLayout->addWidget(m_previousButton); if (m_outlineLine) { m_toolbarWidgetLayout->addWidget(m_outlineLine); m_outlineLine->setMaximumWidth(600); connect(ICore::self()->documentController(), &IDocumentController::documentClosed, m_outlineLine.data(), &IQuickOpenLine::clear); } m_toolbarWidgetLayout->addWidget(m_nextButton); if(m_toolbarWidget->children().isEmpty()) m_toolbarWidget->setLayout(m_toolbarWidgetLayout); connect(ICore::self()->documentController(), &IDocumentController::documentActivated, this, &ContextBrowserPlugin::documentActivated); return ret; } void ContextBrowserPlugin::createActionsForMainWindow(Sublime::MainWindow* window, QString& xmlFile, KActionCollection& actions) { xmlFile = QStringLiteral("kdevcontextbrowser.rc"); QAction* sourceBrowseMode = actions.addAction(QStringLiteral("source_browse_mode")); sourceBrowseMode->setText( i18n("Source &Browse Mode") ); sourceBrowseMode->setIcon( QIcon::fromTheme(QStringLiteral("arrow-up")) ); sourceBrowseMode->setCheckable(true); connect(sourceBrowseMode, &QAction::triggered, m_browseManager, &BrowseManager::setBrowsing); QAction* previousContext = actions.addAction(QStringLiteral("previous_context")); previousContext->setText( i18n("&Previous Visited Context") ); previousContext->setIcon( QIcon::fromTheme(QStringLiteral("go-previous-context") ) ); actions.setDefaultShortcut( previousContext, Qt::META | Qt::Key_Left ); QObject::connect(previousContext, &QAction::triggered, this, &ContextBrowserPlugin::previousContextShortcut); QAction* nextContext = actions.addAction(QStringLiteral("next_context")); nextContext->setText( i18n("&Next Visited Context") ); nextContext->setIcon( QIcon::fromTheme(QStringLiteral("go-next-context") ) ); actions.setDefaultShortcut( nextContext, Qt::META | Qt::Key_Right ); QObject::connect(nextContext, &QAction::triggered, this, &ContextBrowserPlugin::nextContextShortcut); QAction* previousUse = actions.addAction(QStringLiteral("previous_use")); previousUse->setText( i18n("&Previous Use") ); previousUse->setIcon( QIcon::fromTheme(QStringLiteral("go-previous-use")) ); actions.setDefaultShortcut( previousUse, Qt::META | Qt::SHIFT | Qt::Key_Left ); QObject::connect(previousUse, &QAction::triggered, this, &ContextBrowserPlugin::previousUseShortcut); QAction* nextUse = actions.addAction(QStringLiteral("next_use")); nextUse->setText( i18n("&Next Use") ); nextUse->setIcon( QIcon::fromTheme(QStringLiteral("go-next-use")) ); actions.setDefaultShortcut( nextUse, Qt::META | Qt::SHIFT | Qt::Key_Right ); QObject::connect(nextUse, &QAction::triggered, this, &ContextBrowserPlugin::nextUseShortcut); QWidgetAction* outline = new QWidgetAction(this); outline->setText(i18n("Context Browser")); QWidget* w = toolbarWidgetForMainWindow(window); w->setHidden(false); outline->setDefaultWidget(w); actions.addAction(QStringLiteral("outline_line"), outline); // Add to the actioncollection so one can set global shortcuts for the action actions.addAction(QStringLiteral("find_uses"), m_findUses); } void ContextBrowserPlugin::nextContextShortcut() { // TODO: cleanup historyNext(); } void ContextBrowserPlugin::previousContextShortcut() { // TODO: cleanup historyPrevious(); } K_PLUGIN_FACTORY_WITH_JSON(ContextBrowserFactory, "kdevcontextbrowser.json", registerPlugin();) ContextBrowserPlugin::ContextBrowserPlugin(QObject *parent, const QVariantList&) : KDevelop::IPlugin(QStringLiteral("kdevcontextbrowser"), parent) , m_viewFactory(new ContextBrowserViewFactory(this)) , m_nextHistoryIndex(0) , m_textHintProvider(this) { core()->uiController()->addToolView(i18n("Code Browser"), m_viewFactory); connect( core()->documentController(), &IDocumentController::textDocumentCreated, this, &ContextBrowserPlugin::textDocumentCreated ); connect( DUChain::self(), &DUChain::updateReady, this, &ContextBrowserPlugin::updateReady); connect( ColorCache::self(), &ColorCache::colorsGotChanged, this, &ContextBrowserPlugin::colorSetupChanged ); connect( DUChain::self(), &DUChain::declarationSelected, this, &ContextBrowserPlugin::declarationSelectedInUI ); m_updateTimer = new QTimer(this); m_updateTimer->setSingleShot(true); connect( m_updateTimer, &QTimer::timeout, this, &ContextBrowserPlugin::updateViews ); //Needed global action for the context-menu extensions m_findUses = new QAction(i18n("Find Uses"), this); connect(m_findUses, &QAction::triggered, this, &ContextBrowserPlugin::findUses); } ContextBrowserPlugin::~ContextBrowserPlugin() { ///TODO: QObject inheritance should suffice? delete m_nextMenu; delete m_previousMenu; delete m_toolbarWidgetLayout; delete m_previousButton; delete m_outlineLine; delete m_nextButton; } void ContextBrowserPlugin::unload() { core()->uiController()->removeToolView(m_viewFactory); } KDevelop::ContextMenuExtension ContextBrowserPlugin::contextMenuExtension(KDevelop::Context* context) { KDevelop::ContextMenuExtension menuExt = KDevelop::IPlugin::contextMenuExtension( context ); KDevelop::DeclarationContext *codeContext = dynamic_cast(context); if (!codeContext) return menuExt; DUChainReadLocker lock(DUChain::lock()); if(!codeContext->declaration().data()) return menuExt; qRegisterMetaType("KDevelop::IndexedDeclaration"); menuExt.addAction(KDevelop::ContextMenuExtension::ExtensionGroup, m_findUses); return menuExt; } void ContextBrowserPlugin::showUses(const DeclarationPointer& declaration) { QMetaObject::invokeMethod(this, "showUsesDelayed", Qt::QueuedConnection, Q_ARG(KDevelop::DeclarationPointer, declaration)); } void ContextBrowserPlugin::showUsesDelayed(const DeclarationPointer& declaration) { DUChainReadLocker lock; Declaration* decl = declaration.data(); if(!decl) { return; } QWidget* toolView = ICore::self()->uiController()->findToolView(i18n("Code Browser"), m_viewFactory, KDevelop::IUiController::CreateAndRaise); if(!toolView) { return; } ContextBrowserView* view = dynamic_cast(toolView); Q_ASSERT(view); view->allowLockedUpdate(); view->setDeclaration(decl, decl->topContext(), true); //We may get deleted while the call to acceptLink, so make sure we don't crash in that case QPointer widget = dynamic_cast(view->navigationWidget()); if(widget && widget->context()) { NavigationContextPointer nextContext = widget->context()->execute( NavigationAction(declaration, KDevelop::NavigationAction::ShowUses)); if(widget) { widget->setContext( nextContext ); } } } void ContextBrowserPlugin::findUses() { showUses(cursorDeclaration()); } ContextBrowserHintProvider::ContextBrowserHintProvider(ContextBrowserPlugin* plugin) : m_plugin(plugin) { } QString ContextBrowserHintProvider::textHint(View* view, const KTextEditor::Cursor& cursor) { m_plugin->m_mouseHoverCursor = KTextEditor::Cursor(cursor); if(!view) { qWarning() << "could not cast to view"; }else{ m_plugin->m_mouseHoverDocument = view->document()->url(); m_plugin->m_updateViews << view; } m_plugin->m_updateTimer->start(1); // triggers updateViews() m_plugin->showToolTip(view, cursor); return QString(); } void ContextBrowserPlugin::stopDelayedBrowsing() { hideToolTip(); } void ContextBrowserPlugin::invokeAction(int index) { if (!m_currentNavigationWidget) return; auto navigationWidget = qobject_cast(m_currentNavigationWidget); if (!navigationWidget) return; // TODO: Add API in AbstractNavigation{Widget,Context}? QMetaObject::invokeMethod(navigationWidget->context().data(), "executeAction", Q_ARG(int, index)); } void ContextBrowserPlugin::startDelayedBrowsing(KTextEditor::View* view) { if(!m_currentToolTip) { showToolTip(view, view->cursorPosition()); } } void ContextBrowserPlugin::hideToolTip() { if(m_currentToolTip) { m_currentToolTip->deleteLater(); m_currentToolTip = nullptr; m_currentNavigationWidget = nullptr; m_currentToolTipProblems.clear(); m_currentToolTipDeclaration = {}; } } static QVector findProblemsUnderCursor(TopDUContext* topContext, KTextEditor::Cursor position) { QVector problems; auto modelsData = ICore::self()->languageController()->problemModelSet()->models(); foreach (auto modelData, modelsData) { foreach (auto problem, modelData.model->problems(topContext->url())) { DocumentRange problemRange = problem->finalLocation(); if (problemRange.contains(position) || (problemRange.isEmpty() && problemRange.boundaryAtCursor(position))) problems += problem; } } return problems; } static QVector findProblemsCloseToCursor(TopDUContext* topContext, KTextEditor::Cursor position, KTextEditor::View* view) { QVector allProblems; auto modelsData = ICore::self()->languageController()->problemModelSet()->models(); foreach (auto modelData, modelsData) { foreach (auto problem, modelData.model->problems(topContext->url())) { allProblems += problem; } } if (allProblems.isEmpty()) return allProblems; std::sort(allProblems.begin(), allProblems.end(), [position](const KDevelop::IProblem::Ptr a, const KDevelop::IProblem::Ptr b) { const auto aRange = a->finalLocation(); const auto bRange = b->finalLocation(); const auto aLineDistance = qMin(qAbs(aRange.start().line() - position.line()), qAbs(aRange.end().line() - position.line())); const auto bLineDistance = qMin(qAbs(bRange.start().line() - position.line()), qAbs(bRange.end().line() - position.line())); if (aLineDistance != bLineDistance) { return aLineDistance < bLineDistance; } if (aRange.start().line() == bRange.start().line()) { return qAbs(aRange.start().column() - position.column()) < qAbs(bRange.start().column() - position.column()); } return qAbs(aRange.end().column() - position.column()) < qAbs(bRange.end().column() - position.column()); }); QVector closestProblems; // Show problems, located on the same line foreach (auto problem, allProblems) { auto r = problem->finalLocation(); if (r.onSingleLine() && r.start().line() == position.line()) closestProblems += problem; else break; } // If not, only show it in case there's only whitespace // between the current cursor position and the problem line if (closestProblems.isEmpty()) { foreach (auto problem, allProblems) { auto r = problem->finalLocation(); KTextEditor::Range dist; KTextEditor::Cursor bound(r.start().line(), 0); if (position < r.start()) dist = KTextEditor::Range(position, bound); else { bound.setLine(r.end().line() + 1); dist = KTextEditor::Range(bound, position); } if (view->document()->text(dist).trimmed().isEmpty()) closestProblems += problem; else break; } } return closestProblems; } QWidget* ContextBrowserPlugin::navigationWidgetForPosition(KTextEditor::View* view, KTextEditor::Cursor position) { QUrl viewUrl = view->document()->url(); auto languages = ICore::self()->languageController()->languagesForUrl(viewUrl); DUChainReadLocker lock(DUChain::lock()); foreach (const auto language, languages) { auto widget = language->specialLanguageObjectNavigationWidget(viewUrl, KTextEditor::Cursor(position)); auto navigationWidget = qobject_cast(widget); if(navigationWidget) return navigationWidget; } TopDUContext* topContext = DUChainUtils::standardContextForUrl(view->document()->url()); if (topContext) { // first pass: find problems under the cursor const auto problems = findProblemsUnderCursor(topContext, position); if (!problems.isEmpty()) { if (problems == m_currentToolTipProblems && m_currentToolTip) { return nullptr; } m_currentToolTipProblems = problems; auto widget = new AbstractNavigationWidget; auto context = new ProblemNavigationContext(problems); context->setTopContext(TopDUContextPointer(topContext)); widget->setContext(NavigationContextPointer(context)); return widget; } } auto declUnderCursor = DUChainUtils::itemUnderCursor(viewUrl, position).declaration; Declaration* decl = DUChainUtils::declarationForDefinition(declUnderCursor); if (decl && decl->kind() == Declaration::Alias) { AliasDeclaration* alias = dynamic_cast(decl); Q_ASSERT(alias); DUChainReadLocker lock; decl = alias->aliasedDeclaration().declaration(); } if(decl) { if(m_currentToolTipDeclaration == IndexedDeclaration(decl) && m_currentToolTip) return nullptr; m_currentToolTipDeclaration = IndexedDeclaration(decl); return decl->context()->createNavigationWidget(decl, DUChainUtils::standardContextForUrl(viewUrl)); } if (topContext) { // second pass: find closest problem to the cursor const auto problems = findProblemsCloseToCursor(topContext, position, view); if (!problems.isEmpty()) { if (problems == m_currentToolTipProblems && m_currentToolTip) { return nullptr; } m_currentToolTipProblems = problems; auto widget = new AbstractNavigationWidget; // since the problem is not under cursor: show location widget->setContext(NavigationContextPointer(new ProblemNavigationContext(problems, ProblemNavigationContext::ShowLocation))); return widget; } } return nullptr; } void ContextBrowserPlugin::showToolTip(KTextEditor::View* view, KTextEditor::Cursor position) { ContextBrowserView* contextView = browserViewForWidget(view); if(contextView && contextView->isVisible() && !contextView->isLocked()) return; // If the context-browser view is visible, it will care about updating by itself auto navigationWidget = navigationWidgetForPosition(view, position); if(navigationWidget) { // If we have an invisible context-view, assign the tooltip navigation-widget to it. // If the user makes the context-view visible, it will instantly contain the correct widget. if(contextView && !contextView->isLocked()) contextView->setNavigationWidget(navigationWidget); if(m_currentToolTip) { m_currentToolTip->deleteLater(); m_currentToolTip = nullptr; m_currentNavigationWidget = nullptr; } KDevelop::NavigationToolTip* tooltip = new KDevelop::NavigationToolTip(view, view->mapToGlobal(view->cursorToCoordinate(position)) + QPoint(20, 40), navigationWidget); KTextEditor::Range itemRange; { DUChainReadLocker lock; auto viewUrl = view->document()->url(); itemRange = DUChainUtils::itemUnderCursor(viewUrl, position).range; } tooltip->setHandleRect(KTextEditorHelpers::getItemBoundingRect(view, itemRange)); tooltip->resize( navigationWidget->sizeHint() + QSize(10, 10) ); QObject::connect( view, &KTextEditor::View::verticalScrollPositionChanged, this, &ContextBrowserPlugin::hideToolTip ); QObject::connect( view, &KTextEditor::View::horizontalScrollPositionChanged, this, &ContextBrowserPlugin::hideToolTip ); qCDebug(PLUGIN_CONTEXTBROWSER) << "tooltip size" << tooltip->size(); m_currentToolTip = tooltip; m_currentNavigationWidget = navigationWidget; ActiveToolTip::showToolTip(tooltip); if ( ! navigationWidget->property("DoNotCloseOnCursorMove").toBool() ) { connect(view, &View::cursorPositionChanged, this, &ContextBrowserPlugin::hideToolTip, Qt::UniqueConnection); } else { disconnect(view, &View::cursorPositionChanged, this, &ContextBrowserPlugin::hideToolTip); } }else{ qCDebug(PLUGIN_CONTEXTBROWSER) << "not showing tooltip, no navigation-widget"; } } void ContextBrowserPlugin::clearMouseHover() { m_mouseHoverCursor = KTextEditor::Cursor::invalid(); m_mouseHoverDocument.clear(); } Attribute::Ptr ContextBrowserPlugin::highlightedUseAttribute(KTextEditor::View* view) const { if( !m_highlightAttribute ) { m_highlightAttribute = Attribute::Ptr( new Attribute() ); m_highlightAttribute->setDefaultStyle(KTextEditor::dsNormal); m_highlightAttribute->setForeground(m_highlightAttribute->selectedForeground()); m_highlightAttribute->setBackgroundFillWhitespace(true); auto iface = qobject_cast(view); auto background = iface->configValue(QStringLiteral("search-highlight-color")).value(); m_highlightAttribute->setBackground(background); } return m_highlightAttribute; } void ContextBrowserPlugin::colorSetupChanged() { m_highlightAttribute = Attribute::Ptr(); } Attribute::Ptr ContextBrowserPlugin::highlightedSpecialObjectAttribute(KTextEditor::View* view) const { return highlightedUseAttribute(view); } void ContextBrowserPlugin::addHighlight( View* view, KDevelop::Declaration* decl ) { if( !view || !decl ) { qCDebug(PLUGIN_CONTEXTBROWSER) << "invalid view/declaration"; return; } ViewHighlights& highlights(m_highlightedRanges[view]); KDevelop::DUChainReadLocker lock; // Highlight the declaration highlights.highlights << decl->createRangeMoving(); highlights.highlights.back()->setAttribute(highlightedUseAttribute(view)); highlights.highlights.back()->setZDepth(highlightingZDepth); // Highlight uses { QMap< IndexedString, QList< KTextEditor::Range > > currentRevisionUses = decl->usesCurrentRevision(); for(QMap< IndexedString, QList< KTextEditor::Range > >::iterator fileIt = currentRevisionUses.begin(); fileIt != currentRevisionUses.end(); ++fileIt) { for(QList< KTextEditor::Range >::const_iterator useIt = (*fileIt).constBegin(); useIt != (*fileIt).constEnd(); ++useIt) { highlights.highlights << PersistentMovingRange::Ptr(new PersistentMovingRange(*useIt, fileIt.key())); highlights.highlights.back()->setAttribute(highlightedUseAttribute(view)); highlights.highlights.back()->setZDepth(highlightingZDepth); } } } if( FunctionDefinition* def = FunctionDefinition::definition(decl) ) { highlights.highlights << def->createRangeMoving(); highlights.highlights.back()->setAttribute(highlightedUseAttribute(view)); highlights.highlights.back()->setZDepth(highlightingZDepth); } } Declaration* ContextBrowserPlugin::findDeclaration(View* view, const KTextEditor::Cursor& position, bool mouseHighlight) { Q_UNUSED(mouseHighlight); Declaration* foundDeclaration = nullptr; if(m_useDeclaration.data()) { foundDeclaration = m_useDeclaration.data(); }else{ //If we haven't found a special language object, search for a use/declaration and eventually highlight it foundDeclaration = DUChainUtils::declarationForDefinition( DUChainUtils::itemUnderCursor(view->document()->url(), position).declaration ); if (foundDeclaration && foundDeclaration->kind() == Declaration::Alias) { AliasDeclaration* alias = dynamic_cast(foundDeclaration); Q_ASSERT(alias); DUChainReadLocker lock; foundDeclaration = alias->aliasedDeclaration().declaration(); } } return foundDeclaration; } ContextBrowserView* ContextBrowserPlugin::browserViewForWidget(QWidget* widget) { foreach(ContextBrowserView* contextView, m_views) { if(masterWidget(contextView) == masterWidget(widget)) { return contextView; } } return nullptr; } void ContextBrowserPlugin::updateForView(View* view) { bool allowHighlight = true; if(view->selection()) { // If something is selected, we unhighlight everything, so that we don't conflict with the // kate plugin that highlights occurrences of the selected string, and also to reduce the // overall amount of concurrent highlighting. allowHighlight = false; } if(m_highlightedRanges[view].keep) { m_highlightedRanges[view].keep = false; return; } // Clear all highlighting m_highlightedRanges.clear(); // Re-highlight ViewHighlights& highlights = m_highlightedRanges[view]; QUrl url = view->document()->url(); IDocument* activeDoc = core()->documentController()->activeDocument(); bool mouseHighlight = (url == m_mouseHoverDocument) && (m_mouseHoverCursor.isValid()); bool shouldUpdateBrowser = (mouseHighlight || (view == ICore::self()->documentController()->activeTextDocumentView() && activeDoc && activeDoc->textDocument() == view->document())); KTextEditor::Cursor highlightPosition; if (mouseHighlight) highlightPosition = m_mouseHoverCursor; else highlightPosition = KTextEditor::Cursor(view->cursorPosition()); ///Pick a language ILanguageSupport* language = nullptr; if(ICore::self()->languageController()->languagesForUrl(url).isEmpty()) { qCDebug(PLUGIN_CONTEXTBROWSER) << "found no language for document" << url; return; }else{ language = ICore::self()->languageController()->languagesForUrl(url).front(); } ///Check whether there is a special language object to highlight (for example a macro) KTextEditor::Range specialRange = language->specialLanguageObjectRange(url, highlightPosition); ContextBrowserView* updateBrowserView = shouldUpdateBrowser ? browserViewForWidget(view) : nullptr; if(specialRange.isValid()) { // Highlight a special language object if(allowHighlight) { highlights.highlights << PersistentMovingRange::Ptr(new PersistentMovingRange(specialRange, IndexedString(url))); highlights.highlights.back()->setAttribute(highlightedSpecialObjectAttribute(view)); highlights.highlights.back()->setZDepth(highlightingZDepth); } if(updateBrowserView) updateBrowserView->setSpecialNavigationWidget(language->specialLanguageObjectNavigationWidget(url, highlightPosition)); }else{ KDevelop::DUChainReadLocker lock( DUChain::lock(), 100 ); if(!lock.locked()) { qCDebug(PLUGIN_CONTEXTBROWSER) << "Failed to lock du-chain in time"; return; } TopDUContext* topContext = DUChainUtils::standardContextForUrl(view->document()->url()); if (!topContext) return; DUContext* ctx = contextForHighlightingAt(highlightPosition, topContext); if (!ctx) return; //Only update the history if this context is around the text cursor if(core()->documentController()->activeDocument() && highlightPosition == KTextEditor::Cursor(view->cursorPosition()) && view->document() == core()->documentController()->activeDocument()->textDocument()) { updateHistory(ctx, highlightPosition); } Declaration* foundDeclaration = findDeclaration(view, highlightPosition, mouseHighlight); if( foundDeclaration ) { m_lastHighlightedDeclaration = highlights.declaration = IndexedDeclaration(foundDeclaration); if(allowHighlight) addHighlight( view, foundDeclaration ); if(updateBrowserView) updateBrowserView->setDeclaration(foundDeclaration, topContext); }else{ if(updateBrowserView) updateBrowserView->setContext(ctx); } } } void ContextBrowserPlugin::updateViews() { foreach( View* view, m_updateViews ) { updateForView(view); } m_updateViews.clear(); m_useDeclaration = IndexedDeclaration(); } void ContextBrowserPlugin::declarationSelectedInUI(const DeclarationPointer& decl) { m_useDeclaration = IndexedDeclaration(decl.data()); KTextEditor::View* view = core()->documentController()->activeTextDocumentView(); if(view) m_updateViews << view; if(!m_updateViews.isEmpty()) m_updateTimer->start(highlightingTimeout); // triggers updateViews() } void ContextBrowserPlugin::updateReady(const IndexedString& file, const ReferencedTopDUContext& /*topContext*/) { const auto url = file.toUrl(); for(QMap< View*, ViewHighlights >::iterator it = m_highlightedRanges.begin(); it != m_highlightedRanges.end(); ++it) { if(it.key()->document()->url() == url) { if(!m_updateViews.contains(it.key())) { qCDebug(PLUGIN_CONTEXTBROWSER) << "adding view for update"; m_updateViews << it.key(); // Don't change the highlighted declaration after finished parse-jobs (*it).keep = true; } } } if(!m_updateViews.isEmpty()) m_updateTimer->start(highlightingTimeout); } void ContextBrowserPlugin::textDocumentCreated( KDevelop::IDocument* document ) { Q_ASSERT(document->textDocument()); connect( document->textDocument(), &KTextEditor::Document::viewCreated, this, &ContextBrowserPlugin::viewCreated ); foreach( View* view, document->textDocument()->views() ) viewCreated( document->textDocument(), view ); } void ContextBrowserPlugin::documentActivated( IDocument* doc ) { if (m_outlineLine) m_outlineLine->clear(); if (View* view = doc->activeTextView()) { cursorPositionChanged(view, view->cursorPosition()); } } void ContextBrowserPlugin::viewDestroyed( QObject* obj ) { m_highlightedRanges.remove(static_cast(obj)); m_updateViews.remove(static_cast(obj)); } void ContextBrowserPlugin::selectionChanged( View* view ) { clearMouseHover(); m_updateViews.insert(view); m_updateTimer->start(highlightingTimeout/2); // triggers updateViews() } void ContextBrowserPlugin::cursorPositionChanged( View* view, const KTextEditor::Cursor& newPosition ) { if(view->document() == m_lastInsertionDocument && newPosition == m_lastInsertionPos) { //Do not update the highlighting while typing m_lastInsertionDocument = nullptr; m_lastInsertionPos = KTextEditor::Cursor(); if(m_highlightedRanges.contains(view)) m_highlightedRanges[view].keep = true; }else{ if(m_highlightedRanges.contains(view)) m_highlightedRanges[view].keep = false; } clearMouseHover(); m_updateViews.insert(view); m_updateTimer->start(highlightingTimeout/2); // triggers updateViews() } void ContextBrowserPlugin::textInserted(KTextEditor::Document* doc, const KTextEditor::Cursor& cursor, const QString& text) { m_lastInsertionDocument = doc; m_lastInsertionPos = cursor + KTextEditor::Cursor(0, text.size()); } void ContextBrowserPlugin::viewCreated( KTextEditor::Document* , View* v ) { disconnect( v, &View::cursorPositionChanged, this, &ContextBrowserPlugin::cursorPositionChanged ); ///Just to make sure that multiple connections don't happen connect( v, &View::cursorPositionChanged, this, &ContextBrowserPlugin::cursorPositionChanged ); connect( v, &View::destroyed, this, &ContextBrowserPlugin::viewDestroyed ); disconnect( v->document(), &KTextEditor::Document::textInserted, this, &ContextBrowserPlugin::textInserted); connect(v->document(), &KTextEditor::Document::textInserted, this, &ContextBrowserPlugin::textInserted); disconnect(v, &View::selectionChanged, this, &ContextBrowserPlugin::selectionChanged); KTextEditor::TextHintInterface *iface = dynamic_cast(v); if( !iface ) return; iface->setTextHintDelay(highlightingTimeout); iface->registerTextHintProvider(&m_textHintProvider); } void ContextBrowserPlugin::registerToolView(ContextBrowserView* view) { m_views << view; } void ContextBrowserPlugin::previousUseShortcut() { switchUse(false); } void ContextBrowserPlugin::nextUseShortcut() { switchUse(true); } KTextEditor::Range cursorToRange(KTextEditor::Cursor cursor) { return KTextEditor::Range(cursor, cursor); } void ContextBrowserPlugin::switchUse(bool forward) { View* view = core()->documentController()->activeTextDocumentView(); if(view) { KTextEditor::Document* doc = view->document(); KDevelop::DUChainReadLocker lock( DUChain::lock() ); KDevelop::TopDUContext* chosen = DUChainUtils::standardContextForUrl(doc->url()); if( chosen ) { KTextEditor::Cursor cCurrent(view->cursorPosition()); KDevelop::CursorInRevision c = chosen->transformToLocalRevision(cCurrent); Declaration* decl = nullptr; //If we have a locked declaration, use that for jumping foreach(ContextBrowserView* view, m_views) { decl = view->lockedDeclaration().data(); ///@todo Somehow match the correct context-browser view if there is multiple if(decl) break; } if(!decl) //Try finding a declaration under the cursor decl = DUChainUtils::itemUnderCursor(doc->url(), cCurrent).declaration; if (decl && decl->kind() == Declaration::Alias) { AliasDeclaration* alias = dynamic_cast(decl); Q_ASSERT(alias); DUChainReadLocker lock; decl = alias->aliasedDeclaration().declaration(); } if(decl) { Declaration* target = nullptr; if(forward) //Try jumping from definition to declaration target = DUChainUtils::declarationForDefinition(decl, chosen); else if(decl->url().toUrl() == doc->url() && decl->range().contains(c)) //Try jumping from declaration to definition target = FunctionDefinition::definition(decl); if(target && target != decl) { KTextEditor::Cursor jumpTo = target->rangeInCurrentRevision().start(); QUrl document = target->url().toUrl(); lock.unlock(); core()->documentController()->openDocument( document, cursorToRange(jumpTo) ); return; }else{ //Always work with the declaration instead of the definition decl = DUChainUtils::declarationForDefinition(decl, chosen); } } if(!decl) { //Pick the last use we have highlighted decl = m_lastHighlightedDeclaration.data(); } if(decl) { KDevVarLengthArray usingFiles = DUChain::uses()->uses(decl->id()); if(DUChainUtils::contextHasUse(decl->topContext(), decl) && usingFiles.indexOf(decl->topContext()) == -1) usingFiles.insert(0, decl->topContext()); if(decl->range().contains(c) && decl->url() == chosen->url()) { //The cursor is directly on the declaration. Jump to the first or last use. if(!usingFiles.isEmpty()) { TopDUContext* top = (forward ? usingFiles[0] : usingFiles.back()).data(); if(top) { QList useRanges = allUses(top, decl, true); std::sort(useRanges.begin(), useRanges.end()); if(!useRanges.isEmpty()) { QUrl url = top->url().toUrl(); KTextEditor::Range selectUse = chosen->transformFromLocalRevision(forward ? useRanges.first() : useRanges.back()); lock.unlock(); core()->documentController()->openDocument(url, cursorToRange(selectUse.start())); } } } return; } //Check whether we are within a use QList localUses = allUses(chosen, decl, true); std::sort(localUses.begin(), localUses.end()); for(int a = 0; a < localUses.size(); ++a) { int nextUse = (forward ? a+1 : a-1); bool pick = localUses[a].contains(c); if(!pick && forward && a+1 < localUses.size() && localUses[a].end <= c && localUses[a+1].start > c) { //Special case: We aren't on a use, but we are jumping forward, and are behind this and the next use pick = true; } if(!pick && !forward && a-1 >= 0 && c < localUses[a].start && c >= localUses[a-1].end) { //Special case: We aren't on a use, but we are jumping backward, and are in front of this use, but behind the previous one pick = true; } if(!pick && a == 0 && c < localUses[a].start) { if(!forward) { //Will automatically jump to previous file }else{ nextUse = 0; //We are before the first use, so jump to it. } pick = true; } if(!pick && a == localUses.size()-1 && c >= localUses[a].end) { if(forward) { //Will automatically jump to next file }else{ //We are behind the last use, but moving backward. So pick the last use. nextUse = a; } pick = true; } if(pick) { //Make sure we end up behind the use if(nextUse != a) while(forward && nextUse < localUses.size() && (localUses[nextUse].start <= localUses[a].end || localUses[nextUse].isEmpty())) ++nextUse; //Make sure we end up before the use if(nextUse != a) while(!forward && nextUse >= 0 && (localUses[nextUse].start >= localUses[a].start || localUses[nextUse].isEmpty())) --nextUse; //Jump to the next use qCDebug(PLUGIN_CONTEXTBROWSER) << "count of uses:" << localUses.size() << "nextUse" << nextUse; if(nextUse < 0 || nextUse == localUses.size()) { qCDebug(PLUGIN_CONTEXTBROWSER) << "jumping to next file"; //Jump to the first use in the next using top-context int indexInFiles = usingFiles.indexOf(chosen); if(indexInFiles != -1) { int nextFile = (forward ? indexInFiles+1 : indexInFiles-1); qCDebug(PLUGIN_CONTEXTBROWSER) << "current file" << indexInFiles << "nextFile" << nextFile; if(nextFile < 0 || nextFile >= usingFiles.size()) { //Open the declaration, or the definition if(nextFile >= usingFiles.size()) { Declaration* definition = FunctionDefinition::definition(decl); if(definition) decl = definition; } QUrl u = decl->url().toUrl(); KTextEditor::Range range = decl->rangeInCurrentRevision(); range.setEnd(range.start()); lock.unlock(); core()->documentController()->openDocument(u, range); return; }else{ TopDUContext* nextTop = usingFiles[nextFile].data(); QUrl u = nextTop->url().toUrl(); QList nextTopUses = allUses(nextTop, decl, true); std::sort(nextTopUses.begin(), nextTopUses.end()); if(!nextTopUses.isEmpty()) { KTextEditor::Range range = chosen->transformFromLocalRevision(forward ? nextTopUses.front() : nextTopUses.back()); range.setEnd(range.start()); lock.unlock(); core()->documentController()->openDocument(u, range); } return; } }else{ qCDebug(PLUGIN_CONTEXTBROWSER) << "not found own file in use list"; } }else{ QUrl url = chosen->url().toUrl(); KTextEditor::Range range = chosen->transformFromLocalRevision(localUses[nextUse]); range.setEnd(range.start()); lock.unlock(); core()->documentController()->openDocument(url, range); return; } } } } } } } void ContextBrowserPlugin::unRegisterToolView(ContextBrowserView* view) { m_views.removeAll(view); } // history browsing QWidget* ContextBrowserPlugin::toolbarWidgetForMainWindow( Sublime::MainWindow* window ) { //TODO: support multiple windows (if that ever gets revived) if (!m_toolbarWidget) { m_toolbarWidget = new QWidget(window); } return m_toolbarWidget; } void ContextBrowserPlugin::documentJumpPerformed( KDevelop::IDocument* newDocument, const KTextEditor::Cursor& newCursor, KDevelop::IDocument* previousDocument, const KTextEditor::Cursor& previousCursor) { DUChainReadLocker lock(DUChain::lock()); /*TODO: support multiple windows if that ever gets revived if(newDocument && newDocument->textDocument() && newDocument->textDocument()->activeView() && masterWidget(newDocument->textDocument()->activeView()) != masterWidget(this)) return; */ if(previousDocument && previousCursor.isValid()) { qCDebug(PLUGIN_CONTEXTBROWSER) << "updating jump source"; DUContext* context = getContextAt(previousDocument->url(), previousCursor); if(context) { updateHistory(context, KTextEditor::Cursor(previousCursor), true); }else{ //We just want this place in the history m_history.resize(m_nextHistoryIndex); // discard forward history m_history.append(HistoryEntry(DocumentCursor(IndexedString(previousDocument->url()), KTextEditor::Cursor(previousCursor)))); ++m_nextHistoryIndex; } } qCDebug(PLUGIN_CONTEXTBROWSER) << "new doc: " << newDocument << " new cursor: " << newCursor; if(newDocument && newCursor.isValid()) { qCDebug(PLUGIN_CONTEXTBROWSER) << "updating jump target"; DUContext* context = getContextAt(newDocument->url(), newCursor); if(context) { updateHistory(context, KTextEditor::Cursor(newCursor), true); }else{ //We just want this place in the history m_history.resize(m_nextHistoryIndex); // discard forward history m_history.append(HistoryEntry(DocumentCursor(IndexedString(newDocument->url()), KTextEditor::Cursor(newCursor)))); ++m_nextHistoryIndex; if (m_outlineLine) m_outlineLine->clear(); } } } void ContextBrowserPlugin::updateButtonState() { m_nextButton->setEnabled( m_nextHistoryIndex < m_history.size() ); m_previousButton->setEnabled( m_nextHistoryIndex >= 2 ); } void ContextBrowserPlugin::historyNext() { if(m_nextHistoryIndex >= m_history.size()) { return; } openDocument(m_nextHistoryIndex); // opening the document at given position // will update the widget for us ++m_nextHistoryIndex; updateButtonState(); } void ContextBrowserPlugin::openDocument(int historyIndex) { Q_ASSERT_X(historyIndex >= 0, "openDocument", "negative history index"); Q_ASSERT_X(historyIndex < m_history.size(), "openDocument", "history index out of range"); DocumentCursor c = m_history[historyIndex].computePosition(); if (c.isValid() && !c.document.str().isEmpty()) { disconnect(ICore::self()->documentController(), &IDocumentController::documentJumpPerformed, this, &ContextBrowserPlugin::documentJumpPerformed); ICore::self()->documentController()->openDocument(c.document.toUrl(), c); connect(ICore::self()->documentController(), &IDocumentController::documentJumpPerformed, this, &ContextBrowserPlugin::documentJumpPerformed); KDevelop::DUChainReadLocker lock( KDevelop::DUChain::lock() ); updateDeclarationListBox(m_history[historyIndex].context.data()); } } void ContextBrowserPlugin::historyPrevious() { if(m_nextHistoryIndex < 2) { return; } --m_nextHistoryIndex; openDocument(m_nextHistoryIndex-1); // opening the document at given position // will update the widget for us updateButtonState(); } QString ContextBrowserPlugin::actionTextFor(int historyIndex) const { const HistoryEntry& entry = m_history.at(historyIndex); QString actionText = entry.context.data() ? entry.context.data()->scopeIdentifier(true).toString() : QString(); if(actionText.isEmpty()) actionText = entry.alternativeString; if(actionText.isEmpty()) actionText = QStringLiteral(""); actionText += QLatin1String(" @ "); QString fileName = entry.absoluteCursorPosition.document.toUrl().fileName(); actionText += QStringLiteral("%1:%2").arg(fileName).arg(entry.absoluteCursorPosition.line()+1); return actionText; } /* inline QDebug operator<<(QDebug debug, const ContextBrowserPlugin::HistoryEntry &he) { DocumentCursor c = he.computePosition(); debug << "\n\tHistoryEntry " << c.line << " " << c.document.str(); return debug; } */ void ContextBrowserPlugin::nextMenuAboutToShow() { QList indices; for(int a = m_nextHistoryIndex; a < m_history.size(); ++a) { indices << a; } fillHistoryPopup(m_nextMenu, indices); } void ContextBrowserPlugin::previousMenuAboutToShow() { QList indices; for(int a = m_nextHistoryIndex-2; a >= 0; --a) { indices << a; } fillHistoryPopup(m_previousMenu, indices); } void ContextBrowserPlugin::fillHistoryPopup(QMenu* menu, const QList& historyIndices) { menu->clear(); KDevelop::DUChainReadLocker lock( KDevelop::DUChain::lock() ); foreach(int index, historyIndices) { QAction* action = new QAction(actionTextFor(index), menu); action->setData(index); menu->addAction(action); connect(action, &QAction::triggered, this, &ContextBrowserPlugin::actionTriggered); } } bool ContextBrowserPlugin::isPreviousEntry(KDevelop::DUContext* context, const KTextEditor::Cursor& /*position*/) const { if (m_nextHistoryIndex == 0) return false; Q_ASSERT(m_nextHistoryIndex <= m_history.count()); const HistoryEntry& he = m_history.at(m_nextHistoryIndex-1); KDevelop::DUChainReadLocker lock( KDevelop::DUChain::lock() ); // is this necessary?? Q_ASSERT(context); return IndexedDUContext(context) == he.context; } void ContextBrowserPlugin::updateHistory(KDevelop::DUContext* context, const KTextEditor::Cursor& position, bool force) { qCDebug(PLUGIN_CONTEXTBROWSER) << "updating history"; if(m_outlineLine && m_outlineLine->isVisible()) updateDeclarationListBox(context); if(!context || (!context->owner() && !force)) { return; //Only add history-entries for contexts that have owners, which in practice should be functions and classes //This keeps the history cleaner } if (isPreviousEntry(context, position)) { if(m_nextHistoryIndex) { HistoryEntry& he = m_history[m_nextHistoryIndex-1]; he.setCursorPosition(position); } return; } else { // Append new history entry m_history.resize(m_nextHistoryIndex); // discard forward history m_history.append(HistoryEntry(IndexedDUContext(context), position)); ++m_nextHistoryIndex; updateButtonState(); if(m_history.size() > (maxHistoryLength + 5)) { m_history = m_history.mid(m_history.size() - maxHistoryLength); m_nextHistoryIndex = m_history.size(); } } } void ContextBrowserPlugin::updateDeclarationListBox(DUContext* context) { if(!context || !context->owner()) { qCDebug(PLUGIN_CONTEXTBROWSER) << "not updating box"; m_listUrl = IndexedString(); ///@todo Compute the context in the document here if (m_outlineLine) m_outlineLine->clear(); return; } Declaration* decl = context->owner(); m_listUrl = context->url(); Declaration* specialDecl = SpecializationStore::self().applySpecialization(decl, decl->topContext()); FunctionType::Ptr function = specialDecl->type(); QString text = specialDecl->qualifiedIdentifier().toString(); if(function) text += function->partToString(KDevelop::FunctionType::SignatureArguments); if(m_outlineLine && !m_outlineLine->hasFocus()) { m_outlineLine->setText(text); m_outlineLine->setCursorPosition(0); } qCDebug(PLUGIN_CONTEXTBROWSER) << "updated" << text; } void ContextBrowserPlugin::actionTriggered() { QAction* action = qobject_cast(sender()); Q_ASSERT(action); Q_ASSERT(action->data().type() == QVariant::Int); int historyPosition = action->data().toInt(); // qCDebug(PLUGIN_CONTEXTBROWSER) << "history pos" << historyPosition << m_history.size() << m_history; if(historyPosition >= 0 && historyPosition < m_history.size()) { m_nextHistoryIndex = historyPosition + 1; openDocument(historyPosition); updateButtonState(); } } void ContextBrowserPlugin::doNavigate(NavigationActionType action) { KTextEditor::View* view = qobject_cast(sender()); if(!view) { qWarning() << "sender is not a view"; return; } KTextEditor::CodeCompletionInterface* iface = qobject_cast(view); if(!iface || iface->isCompletionActive()) return; // If code completion is active, the actions should be handled by the completion widget QWidget* widget = m_currentNavigationWidget.data(); if(!widget || !widget->isVisible()) { ContextBrowserView* contextView = browserViewForWidget(view); if(contextView) widget = contextView->navigationWidget(); } if(widget) { AbstractNavigationWidget* navWidget = qobject_cast(widget); if (navWidget) { switch(action) { case Accept: navWidget->accept(); break; case Back: navWidget->back(); break; case Left: navWidget->previous(); break; case Right: navWidget->next(); break; case Up: navWidget->up(); break; case Down: navWidget->down(); break; } } } } void ContextBrowserPlugin::navigateAccept() { doNavigate(Accept); } void ContextBrowserPlugin::navigateBack() { doNavigate(Back); } void ContextBrowserPlugin::navigateDown() { doNavigate(Down); } void ContextBrowserPlugin::navigateLeft() { doNavigate(Left); } void ContextBrowserPlugin::navigateRight() { doNavigate(Right); } void ContextBrowserPlugin::navigateUp() { doNavigate(Up); } //BEGIN HistoryEntry ContextBrowserPlugin::HistoryEntry::HistoryEntry(KDevelop::DocumentCursor pos) : absoluteCursorPosition(pos) { } ContextBrowserPlugin::HistoryEntry::HistoryEntry(IndexedDUContext ctx, const KTextEditor::Cursor& cursorPosition) : context(ctx) { //Use a position relative to the context setCursorPosition(cursorPosition); if(ctx.data()) alternativeString = ctx.data()->scopeIdentifier(true).toString(); if(!alternativeString.isEmpty()) alternativeString += i18n("(changed)"); //This is used when the context was deleted in between } DocumentCursor ContextBrowserPlugin::HistoryEntry::computePosition() const { KDevelop::DUChainReadLocker lock( KDevelop::DUChain::lock() ); DocumentCursor ret; if(context.data()) { ret = DocumentCursor(context.data()->url(), relativeCursorPosition); ret.setLine(ret.line() + context.data()->range().start.line); }else{ ret = absoluteCursorPosition; } return ret; } void ContextBrowserPlugin::HistoryEntry::setCursorPosition(const KTextEditor::Cursor& cursorPosition) { KDevelop::DUChainReadLocker lock( KDevelop::DUChain::lock() ); if(context.data()) { absoluteCursorPosition = DocumentCursor(context.data()->url(), cursorPosition); relativeCursorPosition = cursorPosition; relativeCursorPosition.setLine(relativeCursorPosition.line() - context.data()->range().start.line); } } // kate: space-indent on; indent-width 2; tab-width 4; replace-tabs on; auto-insert-doxygen on #include "contextbrowser.moc" diff --git a/plugins/contextbrowser/contextbrowser.h b/plugins/contextbrowser/contextbrowser.h index c0b7f7d1f..25ab2d4ad 100644 --- a/plugins/contextbrowser/contextbrowser.h +++ b/plugins/contextbrowser/contextbrowser.h @@ -1,282 +1,282 @@ /* * 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 #include class QHBoxLayout; class QMenu; class QToolButton; namespace Sublime { class MainWindow; } namespace KDevelop { class IDocument; class DUContext; class TopDUContext; class ReferencedTopDUContext; class DUChainBase; class AbstractNavigationWidget; } namespace KTextEditor { class Document; class View; } class ContextBrowserViewFactory; class ContextBrowserView; class ContextBrowserPlugin; class BrowseManager; class ContextBrowserHintProvider : public KTextEditor::TextHintProvider { public: explicit ContextBrowserHintProvider(ContextBrowserPlugin* plugin); QString textHint(KTextEditor::View* view, const KTextEditor::Cursor& position) override; private: ContextBrowserPlugin* m_plugin; }; 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: explicit ContextBrowserPlugin(QObject *parent, const QVariantList & = QVariantList() ); ~ContextBrowserPlugin() override; void unload() override; void registerToolView(ContextBrowserView* view); void unRegisterToolView(ContextBrowserView* view); KDevelop::ContextMenuExtension contextMenuExtension(KDevelop::Context*) override; KXMLGUIClient* createGUIForMainWindow( Sublime::MainWindow* window ) override; ///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 KTextEditor::Cursor& cursorPosition, bool force = false); void updateDeclarationListBox(KDevelop::DUContext* context); void showUses(const KDevelop::DeclarationPointer& declaration) override; KTextEditor::Attribute::Ptr highlightedUseAttribute(KTextEditor::View* view) const; KTextEditor::Attribute::Ptr highlightedSpecialObjectAttribute(KTextEditor::View* view) const; public Q_SLOTS: void showUsesDelayed(const KDevelop::DeclarationPointer& declaration); void previousContextShortcut(); void nextContextShortcut(); void startDelayedBrowsing(KTextEditor::View* view); void stopDelayedBrowsing(); void invokeAction(int index); void previousUseShortcut(); void nextUseShortcut(); void declarationSelectedInUI(const KDevelop::DeclarationPointer& decl); void updateReady(const KDevelop::IndexedString& url, const KDevelop::ReferencedTopDUContext& topContext); 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* doc, const KTextEditor::Cursor& cursor, const QString& text); void selectionChanged(KTextEditor::View*); void historyNext(); void historyPrevious(); void colorSetupChanged(); private slots: // history browsing void documentJumpPerformed( KDevelop::IDocument* newDocument, const KTextEditor::Cursor& newCursor, KDevelop::IDocument* previousDocument, const KTextEditor::Cursor& previousCursor); void nextMenuAboutToShow(); void previousMenuAboutToShow(); void actionTriggered(); void navigateLeft(); void navigateRight(); void navigateUp(); void navigateDown(); void navigateAccept(); void navigateBack(); private: QWidget* toolbarWidgetForMainWindow(Sublime::MainWindow* window); void createActionsForMainWindow(Sublime::MainWindow* window, QString& xmlFile, KActionCollection& actions) override; QWidget* navigationWidgetForPosition(KTextEditor::View* view, KTextEditor::Cursor position); 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 KTextEditor::Cursor&, bool mouseHighlight); void updateForView(KTextEditor::View* view); // history browsing bool isPreviousEntry(KDevelop::DUContext*, const KTextEditor::Cursor& 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; QUrl m_mouseHoverDocument; KTextEditor::Cursor m_mouseHoverCursor; ContextBrowserViewFactory* m_viewFactory; QPointer m_currentToolTip; QPointer m_currentNavigationWidget; KDevelop::IndexedDeclaration m_currentToolTipDeclaration; QVector m_currentToolTipProblems; 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 KTextEditor::Cursor& cursorPosition = KTextEditor::Cursor()); - HistoryEntry(KDevelop::DocumentCursor pos); + explicit HistoryEntry(KDevelop::IndexedDUContext ctx = KDevelop::IndexedDUContext(), const KTextEditor::Cursor& cursorPosition = KTextEditor::Cursor()); + explicit HistoryEntry(KDevelop::DocumentCursor pos); //Duchain must be locked void setCursorPosition(const KTextEditor::Cursor& cursorPosition); //Duchain does not need to be locked KDevelop::DocumentCursor computePosition() const; KDevelop::IndexedDUContext context; KDevelop::DocumentCursor absoluteCursorPosition; KTextEditor::Cursor 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; 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; mutable KTextEditor::Attribute::Ptr m_highlightAttribute; friend class ContextBrowserHintProvider; ContextBrowserHintProvider m_textHintProvider; }; #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/cvs/cvsplugin.cpp b/plugins/cvs/cvsplugin.cpp index 988ebc090..0f99da4ac 100644 --- a/plugins/cvs/cvsplugin.cpp +++ b/plugins/cvs/cvsplugin.cpp @@ -1,486 +1,486 @@ /*************************************************************************** * Copyright 2007 Robert Gruber * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "cvsplugin.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cvsmainview.h" #include "cvsproxy.h" #include "cvsjob.h" #include "editorsview.h" #include "commitdialog.h" #include "cvsgenericoutputview.h" #include "checkoutdialog.h" #include "importdialog.h" #include "importmetadatawidget.h" #include "debug.h" #include #include #include Q_LOGGING_CATEGORY(PLUGIN_CVS, "kdevplatform.plugins.cvs") K_PLUGIN_FACTORY(KDevCvsFactory, registerPlugin();) // K_EXPORT_PLUGIN(KDevCvsFactory(KAboutData("kdevcvs", "kdevcvs", ki18n("CVS"), "0.1", ki18n("Support for CVS version control system"), KAboutData::License_GPL))) class KDevCvsViewFactory: public KDevelop::IToolViewFactory { public: - KDevCvsViewFactory(CvsPlugin *plugin): m_plugin(plugin) {} + explicit KDevCvsViewFactory(CvsPlugin *plugin): m_plugin(plugin) {} QWidget* create(QWidget *parent = nullptr) override { return new CvsMainView(m_plugin, parent); } Qt::DockWidgetArea defaultPosition() override { return Qt::BottomDockWidgetArea; } QString id() const override { return QStringLiteral("org.kdevelop.CVSView"); } private: CvsPlugin *m_plugin; }; class CvsPluginPrivate { public: explicit CvsPluginPrivate(CvsPlugin *pThis) : m_factory(new KDevCvsViewFactory(pThis)) , m_proxy(new CvsProxy(pThis)) , m_common(new KDevelop::VcsPluginHelper(pThis, pThis)) {} KDevCvsViewFactory* m_factory; QPointer m_proxy; QScopedPointer m_common; }; CvsPlugin::CvsPlugin(QObject *parent, const QVariantList &) : KDevelop::IPlugin(QStringLiteral("kdevcvs"), parent) , d(new CvsPluginPrivate(this)) { core()->uiController()->addToolView(i18n("CVS"), d->m_factory); setXMLFile(QStringLiteral("kdevcvs.rc")); setupActions(); } CvsPlugin::~CvsPlugin() { } void CvsPlugin::unload() { core()->uiController()->removeToolView( d->m_factory ); } CvsProxy* CvsPlugin::proxy() { return d->m_proxy; } void CvsPlugin::setupActions() { QAction *action; action = actionCollection()->addAction(QStringLiteral("cvs_import")); action->setText(i18n("Import Directory...")); connect(action, &QAction::triggered, this, &CvsPlugin::slotImport); action = actionCollection()->addAction(QStringLiteral("cvs_checkout")); action->setText(i18n("Checkout...")); connect(action, &QAction::triggered, this, &CvsPlugin::slotCheckout); action = actionCollection()->addAction(QStringLiteral("cvs_status")); action->setText(i18n("Status...")); connect(action, &QAction::triggered, this, &CvsPlugin::slotStatus); } const QUrl CvsPlugin::urlFocusedDocument() const { KParts::ReadOnlyPart *plugin = dynamic_cast(core()->partController()->activePart()); if (plugin) { if (plugin->url().isLocalFile()) { return plugin->url(); } } return QUrl(); } void CvsPlugin::slotImport() { QUrl url = urlFocusedDocument(); ImportDialog dlg(this, url); dlg.exec(); } void CvsPlugin::slotCheckout() { ///@todo don't use proxy directly; use interface instead CheckoutDialog dlg(this); dlg.exec(); } void CvsPlugin::slotStatus() { QUrl url = urlFocusedDocument(); QList urls; urls << url; KDevelop::VcsJob* j = status(urls, KDevelop::IBasicVersionControl::Recursive); CvsJob* job = dynamic_cast(j); if (job) { CvsGenericOutputView* view = new CvsGenericOutputView(job); emit addNewTabToMainView(view, i18n("Status")); KDevelop::ICore::self()->runController()->registerJob(job); } } KDevelop::ContextMenuExtension CvsPlugin::contextMenuExtension(KDevelop::Context* context) { d->m_common->setupFromContext(context); QList const & ctxUrlList = d->m_common->contextUrlList(); bool hasVersionControlledEntries = false; foreach(const QUrl &url, ctxUrlList) { if (d->m_proxy->isValidDirectory(url)) { hasVersionControlledEntries = true; break; } } qCDebug(PLUGIN_CVS) << "version controlled?" << hasVersionControlledEntries; if (!hasVersionControlledEntries) return IPlugin::contextMenuExtension(context); QMenu* menu = d->m_common->commonActions(); menu->addSeparator(); QAction *action; // Just add actions which are not covered by the cvscommon plugin action = new QAction(i18n("Edit"), this); connect(action, &QAction::triggered, this, &CvsPlugin::ctxEdit); menu->addAction(action); action = new QAction(i18n("Unedit"), this); connect(action, &QAction::triggered, this, &CvsPlugin::ctxUnEdit); menu->addAction(action); action = new QAction(i18n("Show Editors"), this); connect(action, &QAction::triggered, this, &CvsPlugin::ctxEditors); menu->addAction(action); KDevelop::ContextMenuExtension menuExt; menuExt.addAction(KDevelop::ContextMenuExtension::VcsGroup, menu->menuAction()); return menuExt; } void CvsPlugin::ctxEdit() { QList const & urls = d->m_common->contextUrlList(); Q_ASSERT(!urls.empty()); KDevelop::VcsJob* j = edit(urls.front()); CvsJob* job = dynamic_cast(j); if (job) { connect(job, &CvsJob::result, this, &CvsPlugin::jobFinished); KDevelop::ICore::self()->runController()->registerJob(job); } } void CvsPlugin::ctxUnEdit() { QList const & urls = d->m_common->contextUrlList(); Q_ASSERT(!urls.empty()); KDevelop::VcsJob* j = unedit(urls.front()); CvsJob* job = dynamic_cast(j); if (job) { connect(job, &CvsJob::result, this, &CvsPlugin::jobFinished); KDevelop::ICore::self()->runController()->registerJob(job); } } void CvsPlugin::ctxEditors() { QList const & urls = d->m_common->contextUrlList(); Q_ASSERT(!urls.empty()); CvsJob* job = d->m_proxy->editors(findWorkingDir(urls.front()), urls); if (job) { KDevelop::ICore::self()->runController()->registerJob(job); EditorsView* view = new EditorsView(job); emit addNewTabToMainView(view, i18n("Editors")); } } QString CvsPlugin::findWorkingDir(const QUrl& location) { QFileInfo fileInfo(location.toLocalFile()); // find out correct working directory if (fileInfo.isFile()) { return fileInfo.absolutePath(); } else { return fileInfo.absoluteFilePath(); } } // Begin: KDevelop::IBasicVersionControl bool CvsPlugin::isVersionControlled(const QUrl & localLocation) { return d->m_proxy->isVersionControlled(localLocation); } KDevelop::VcsJob * CvsPlugin::repositoryLocation(const QUrl & localLocation) { Q_UNUSED(localLocation); return nullptr; } KDevelop::VcsJob * CvsPlugin::add(const QList & localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion) { CvsJob* job = d->m_proxy->add(findWorkingDir(localLocations[0]), localLocations, (recursion == KDevelop::IBasicVersionControl::Recursive) ? true : false); return job; } KDevelop::VcsJob * CvsPlugin::remove(const QList & localLocations) { CvsJob* job = d->m_proxy->remove(findWorkingDir(localLocations[0]), localLocations); return job; } KDevelop::VcsJob * CvsPlugin::localRevision(const QUrl & localLocation, KDevelop::VcsRevision::RevisionType) { Q_UNUSED(localLocation) return nullptr; } KDevelop::VcsJob * CvsPlugin::status(const QList & localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion) { CvsJob* job = d->m_proxy->status(findWorkingDir(localLocations[0]), localLocations, (recursion == KDevelop::IBasicVersionControl::Recursive) ? true : false); return job; } KDevelop::VcsJob * CvsPlugin::unedit(const QUrl& localLocation) { CvsJob* job = d->m_proxy->unedit(findWorkingDir(localLocation), QList() << localLocation); return job; } KDevelop::VcsJob * CvsPlugin::edit(const QUrl& localLocation) { CvsJob* job = d->m_proxy->edit(findWorkingDir(localLocation), QList() << localLocation); return job; } KDevelop::VcsJob * CvsPlugin::copy(const QUrl & localLocationSrc, const QUrl & localLocationDstn) { bool ok = QFile::copy(localLocationSrc.toLocalFile(), localLocationDstn.path()); if (!ok) { return nullptr; } QList listDstn; listDstn << localLocationDstn; CvsJob* job = d->m_proxy->add(findWorkingDir(localLocationDstn), listDstn, true); return job; } KDevelop::VcsJob * CvsPlugin::move(const QUrl &, const QUrl &) { return nullptr; } KDevelop::VcsJob * CvsPlugin::revert(const QList & localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion) { KDevelop::VcsRevision rev; CvsJob* job = d->m_proxy->update(findWorkingDir(localLocations[0]), localLocations, rev, QStringLiteral("-C"), (recursion == KDevelop::IBasicVersionControl::Recursive) ? true : false, false, false); return job; } KDevelop::VcsJob * CvsPlugin::update(const QList & localLocations, const KDevelop::VcsRevision & rev, KDevelop::IBasicVersionControl::RecursionMode recursion) { CvsJob* job = d->m_proxy->update(findWorkingDir(localLocations[0]), localLocations, rev, QString(), (recursion == KDevelop::IBasicVersionControl::Recursive) ? true : false, false, false); return job; } KDevelop::VcsJob * CvsPlugin::commit(const QString & message, const QList & localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion) { Q_UNUSED(recursion); QString msg = message; if (msg.isEmpty()) { CommitDialog dlg; if (dlg.exec() == QDialog::Accepted) { msg = dlg.message(); } } CvsJob* job = d->m_proxy->commit(findWorkingDir(localLocations[0]), localLocations, msg); return job; } KDevelop::VcsJob * CvsPlugin::diff(const QUrl & fileOrDirectory, const KDevelop::VcsRevision & srcRevision, const KDevelop::VcsRevision & dstRevision, KDevelop::VcsDiff::Type, KDevelop::IBasicVersionControl::RecursionMode) { CvsJob* job = d->m_proxy->diff(fileOrDirectory, srcRevision, dstRevision, QStringLiteral("-uN")/*always unified*/); return job; } KDevelop::VcsJob * CvsPlugin::log(const QUrl & localLocation, const KDevelop::VcsRevision & rev, unsigned long limit) { Q_UNUSED(limit) CvsJob* job = d->m_proxy->log(localLocation, rev); return job; } KDevelop::VcsJob * CvsPlugin::log(const QUrl & localLocation, const KDevelop::VcsRevision & rev, const KDevelop::VcsRevision & limit) { Q_UNUSED(limit) return log(localLocation, rev, 0); } KDevelop::VcsJob * CvsPlugin::annotate(const QUrl & localLocation, const KDevelop::VcsRevision & rev) { CvsJob* job = d->m_proxy->annotate(localLocation, rev); return job; } KDevelop::VcsJob * CvsPlugin::resolve(const QList & localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion) { Q_UNUSED(localLocations); Q_UNUSED(recursion); return nullptr; } KDevelop::VcsJob * CvsPlugin::import(const QString& commitMessage, const QUrl& sourceDirectory, const KDevelop::VcsLocation& destinationRepository) { if (commitMessage.isEmpty() || !sourceDirectory.isLocalFile() || !destinationRepository.isValid() || destinationRepository.type() != KDevelop::VcsLocation::RepositoryLocation) { return nullptr; } qCDebug(PLUGIN_CVS) << "CVS Import requested " << "src:" << sourceDirectory.toLocalFile() << "srv:" << destinationRepository.repositoryServer() << "module:" << destinationRepository.repositoryModule(); CvsJob* job = d->m_proxy->import(sourceDirectory, destinationRepository.repositoryServer(), destinationRepository.repositoryModule(), destinationRepository.userData().toString(), destinationRepository.repositoryTag(), commitMessage); return job; } KDevelop::VcsJob * CvsPlugin::createWorkingCopy(const KDevelop::VcsLocation & sourceRepository, const QUrl & destinationDirectory, KDevelop::IBasicVersionControl::RecursionMode recursion) { Q_UNUSED(recursion); if (!destinationDirectory.isLocalFile() || !sourceRepository.isValid() || sourceRepository.type() != KDevelop::VcsLocation::RepositoryLocation) { return nullptr; } qCDebug(PLUGIN_CVS) << "CVS Checkout requested " << "dest:" << destinationDirectory.toLocalFile() << "srv:" << sourceRepository.repositoryServer() << "module:" << sourceRepository.repositoryModule() << "branch:" << sourceRepository.repositoryBranch() << endl; CvsJob* job = d->m_proxy->checkout(destinationDirectory, sourceRepository.repositoryServer(), sourceRepository.repositoryModule(), QString(), sourceRepository.repositoryBranch(), true, true); return job; } QString CvsPlugin::name() const { return i18n("CVS"); } KDevelop::VcsImportMetadataWidget* CvsPlugin::createImportMetadataWidget(QWidget* parent) { return new ImportMetadataWidget(parent); } KDevelop::VcsLocationWidget* CvsPlugin::vcsLocation(QWidget* parent) const { return new KDevelop::StandardVcsLocationWidget(parent); } // End: KDevelop::IBasicVersionControl #include "cvsplugin.moc" diff --git a/plugins/documentview/kdevdocumentviewplugin.cpp b/plugins/documentview/kdevdocumentviewplugin.cpp index 50caa2e6d..f9d392c4f 100644 --- a/plugins/documentview/kdevdocumentviewplugin.cpp +++ b/plugins/documentview/kdevdocumentviewplugin.cpp @@ -1,108 +1,108 @@ /* * This file is part of KDevelop * * Copyright 2006 Adam Treat * * 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 "kdevdocumentviewplugin.h" #include "kdevdocumentview.h" #include "kdevdocumentmodel.h" #include "kdevdocumentselection.h" #include #include #include #include #include #include using namespace KDevelop; K_PLUGIN_FACTORY_WITH_JSON(KDevDocumentViewFactory, "kdevdocumentview.json", registerPlugin();) class KDevDocumentViewPluginFactory: public KDevelop::IToolViewFactory { public: - KDevDocumentViewPluginFactory( KDevDocumentViewPlugin *plugin ): m_plugin( plugin ) + explicit KDevDocumentViewPluginFactory( KDevDocumentViewPlugin *plugin ): m_plugin( plugin ) {} QWidget* create( QWidget *parent = nullptr ) override { KDevDocumentView* view = new KDevDocumentView( m_plugin, parent ); KDevelop::IDocumentController* docController = m_plugin->core()->documentController(); foreach(KDevelop::IDocument* doc, docController->openDocuments()) { view->opened( doc ); } QObject::connect( docController, &IDocumentController::documentActivated, view, &KDevDocumentView::activated ); QObject::connect( docController, &IDocumentController::documentSaved, view, &KDevDocumentView::saved ); QObject::connect( docController, &IDocumentController::documentOpened, view, &KDevDocumentView::opened ); QObject::connect( docController, &IDocumentController::documentClosed, view, &KDevDocumentView::closed ); QObject::connect( docController, &IDocumentController::documentContentChanged, view, &KDevDocumentView::contentChanged ); QObject::connect( docController, &IDocumentController::documentStateChanged, view, &KDevDocumentView::stateChanged ); QObject::connect( docController, &IDocumentController::documentUrlChanged, view, &KDevDocumentView::documentUrlChanged ); return view; } Qt::DockWidgetArea defaultPosition() override { return Qt::LeftDockWidgetArea; } QString id() const override { return QStringLiteral("org.kdevelop.DocumentsView"); } private: KDevDocumentViewPlugin* m_plugin; }; KDevDocumentViewPlugin::KDevDocumentViewPlugin( QObject *parent, const QVariantList& args ) : KDevelop::IPlugin( QStringLiteral( "kdevdocumentview" ), parent ) { Q_UNUSED( args ); factory = new KDevDocumentViewPluginFactory( this ); core()->uiController()->addToolView( i18n("Documents"), factory ); setXMLFile( QStringLiteral( "kdevdocumentview.rc" ) ); } KDevDocumentViewPlugin::~KDevDocumentViewPlugin() { } void KDevDocumentViewPlugin::unload() { core()->uiController()->removeToolView( factory ); } #include "kdevdocumentviewplugin.moc" diff --git a/plugins/execute/projecttargetscombobox.cpp b/plugins/execute/projecttargetscombobox.cpp index 5a67d48ce..185acbbe7 100644 --- a/plugins/execute/projecttargetscombobox.cpp +++ b/plugins/execute/projecttargetscombobox.cpp @@ -1,88 +1,88 @@ /* This file is part of KDevelop Copyright 2010 Aleix Pol Gonzalez This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "projecttargetscombobox.h" #include #include #include #include #include #include using namespace KDevelop; ProjectTargetsComboBox::ProjectTargetsComboBox(QWidget* parent) : QComboBox(parent) { } class ExecutablePathsVisitor : public ProjectVisitor { public: - ExecutablePathsVisitor(bool exec) : m_onlyExecutables(exec) {} + explicit ExecutablePathsVisitor(bool exec) : m_onlyExecutables(exec) {} using ProjectVisitor::visit; void visit(ProjectExecutableTargetItem* eit) override { if(!m_onlyExecutables || eit->type()==ProjectTargetItem::ExecutableTarget) m_paths += KDevelop::joinWithEscaping(eit->model()->pathFromIndex(eit->index()), '/', '\\'); } QStringList paths() const { return m_paths; } private: bool m_onlyExecutables; QStringList m_paths; }; void ProjectTargetsComboBox::setBaseItem(ProjectFolderItem* item, bool exec) { clear(); QList items; if(item) { items += item; } else { foreach(IProject* p, ICore::self()->projectController()->projects()) { items += p->projectItem(); } } ExecutablePathsVisitor walker(exec); foreach(ProjectFolderItem* item, items) { walker.visit(item); } foreach(const QString& item, walker.paths()) addItem(QIcon::fromTheme(QStringLiteral("system-run")), item); } QStringList ProjectTargetsComboBox::currentItemPath() const { return KDevelop::splitWithEscaping(currentText(), '/', '\\'); } void ProjectTargetsComboBox::setCurrentItemPath(const QStringList& str) { setCurrentIndex(str.isEmpty() && count() ? 0 : findText(KDevelop::joinWithEscaping(str, '/', '\\'))); } diff --git a/plugins/externalscript/externalscriptplugin.cpp b/plugins/externalscript/externalscriptplugin.cpp index b72ea3674..5ac62e014 100644 --- a/plugins/externalscript/externalscriptplugin.cpp +++ b/plugins/externalscript/externalscriptplugin.cpp @@ -1,369 +1,369 @@ /* This plugin is part of KDevelop. Copyright (C) 2010 Milian Wolff This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "externalscriptplugin.h" #include "externalscriptview.h" #include "externalscriptitem.h" #include "externalscriptjob.h" #include "externalscriptdebug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include K_PLUGIN_FACTORY_WITH_JSON(ExternalScriptFactory, "kdevexternalscript.json", registerPlugin();) class ExternalScriptViewFactory: public KDevelop::IToolViewFactory { public: - ExternalScriptViewFactory( ExternalScriptPlugin *plugin ): m_plugin( plugin ) {} + explicit ExternalScriptViewFactory( ExternalScriptPlugin *plugin ): m_plugin( plugin ) {} QWidget* create( QWidget *parent = nullptr ) override { return new ExternalScriptView( m_plugin, parent ); } Qt::DockWidgetArea defaultPosition() override { return Qt::RightDockWidgetArea; } QString id() const override { return QStringLiteral("org.kdevelop.ExternalScriptView"); } private: ExternalScriptPlugin *m_plugin; }; ExternalScriptPlugin* ExternalScriptPlugin::m_self = nullptr; ExternalScriptPlugin::ExternalScriptPlugin( QObject* parent, const QVariantList& /*args*/ ) : IPlugin( QStringLiteral("kdevexternalscript"), parent ), m_model( new QStandardItemModel( this ) ), m_factory( new ExternalScriptViewFactory( this ) ) { Q_ASSERT( !m_self ); m_self = this; QDBusConnection::sessionBus().registerObject( QStringLiteral("/org/kdevelop/ExternalScriptPlugin"), this, QDBusConnection::ExportScriptableSlots ); setXMLFile( QStringLiteral("kdevexternalscript.rc") ); //BEGIN load config KConfigGroup config = getConfig(); foreach( const QString& group, config.groupList() ) { KConfigGroup script = config.group( group ); if ( script.hasKey( "name" ) && script.hasKey( "command" ) ) { ExternalScriptItem* item = new ExternalScriptItem; item->setText( script.readEntry( "name" ) ); item->setCommand( script.readEntry( "command" )); item->setInputMode( static_cast( script.readEntry( "inputMode", 0u ) ) ); item->setOutputMode( static_cast( script.readEntry( "outputMode", 0u ) ) ); item->setErrorMode( static_cast( script.readEntry( "errorMode", 0u ) ) ); item->setSaveMode( static_cast( script.readEntry( "saveMode", 0u ) ) ); item->setFilterMode( script.readEntry( "filterMode", 0u )); item->action()->setShortcut( QKeySequence( script.readEntry( "shortcuts" ) ) ); item->setShowOutput( script.readEntry( "showOutput", true ) ); m_model->appendRow( item ); } } //END load config core()->uiController()->addToolView( i18n( "External Scripts" ), m_factory ); connect( m_model, &QStandardItemModel::rowsRemoved, this, &ExternalScriptPlugin::rowsRemoved ); connect( m_model, &QStandardItemModel::rowsInserted, this, &ExternalScriptPlugin::rowsInserted ); const bool firstUse = config.readEntry( "firstUse", true ); if ( firstUse ) { // some example scripts ExternalScriptItem* item = new ExternalScriptItem; item->setText( i18n("Quick Compile") ); item->setCommand( QStringLiteral("g++ -o %b %f && ./%b") ); m_model->appendRow( item ); item = new ExternalScriptItem; item->setText( i18n("Google Selection") ); item->setCommand( QStringLiteral("xdg-open \"http://www.google.de/search?q=%s\"") ); item->setShowOutput( false ); m_model->appendRow( item ); item = new ExternalScriptItem; item->setText( i18n("Sort Selection") ); item->setCommand( QStringLiteral("sort") ); item->setInputMode( ExternalScriptItem::InputSelectionOrDocument ); item->setOutputMode( ExternalScriptItem::OutputReplaceSelectionOrDocument ); item->setShowOutput( false ); m_model->appendRow( item ); config.writeEntry( "firstUse", false ); config.sync(); } } ExternalScriptPlugin* ExternalScriptPlugin::self() { return m_self; } ExternalScriptPlugin::~ExternalScriptPlugin() { m_self = nullptr; } KDevelop::ContextMenuExtension ExternalScriptPlugin::contextMenuExtension( KDevelop::Context* context ) { m_urls.clear(); int folderCount = 0; if ( context->type() == KDevelop::Context::FileContext ) { KDevelop::FileContext* filectx = dynamic_cast( context ); m_urls = filectx->urls(); } else if ( context->type() == KDevelop::Context::ProjectItemContext ) { KDevelop::ProjectItemContext* projctx = dynamic_cast( context ); foreach( KDevelop::ProjectBaseItem* item, projctx->items() ) { if ( item->file() ) { m_urls << item->file()->path().toUrl(); } else if ( item->folder() ) { m_urls << item->folder()->path().toUrl(); folderCount++; } } } else if ( context->type() == KDevelop::Context::EditorContext ) { KDevelop::EditorContext *econtext = dynamic_cast(context); m_urls << econtext->url(); } if ( !m_urls.isEmpty() ) { KDevelop::ContextMenuExtension ext; QMenu* menu = new QMenu(); menu->setTitle( i18n("External Scripts") ); for ( int row = 0; row < m_model->rowCount(); ++row ) { ExternalScriptItem* item = dynamic_cast( m_model->item( row ) ); Q_ASSERT( item ); if (context->type() != KDevelop::Context::EditorContext) { // filter scripts that depend on an opened document // if the context menu was not requested inside the editor if (item->performParameterReplacement() && item->command().contains(QStringLiteral("%s"))) { continue; } else if (item->inputMode() == ExternalScriptItem::InputSelectionOrNone) { continue; } } if ( folderCount == m_urls.count() ) { // when only folders filter items that don't have %d parameter (or another parameter) if (item->performParameterReplacement() && (!item->command().contains(QStringLiteral("%d")) || item->command().contains(QStringLiteral("%s")) || item->command().contains(QStringLiteral("%u")) || item->command().contains(QStringLiteral("%f")) || item->command().contains(QStringLiteral("%b")) || item->command().contains(QStringLiteral("%n")) ) ) { continue; } } QAction* scriptAction = new QAction( item->text(), this ); scriptAction->setData( QVariant::fromValue( item )); connect( scriptAction, &QAction::triggered, this, &ExternalScriptPlugin::executeScriptFromContextMenu ); menu->addAction( scriptAction ); } if (!menu->actions().isEmpty()) ext.addAction( KDevelop::ContextMenuExtension::ExtensionGroup, menu->menuAction() ); return ext; } return KDevelop::IPlugin::contextMenuExtension( context ); } void ExternalScriptPlugin::unload() { core()->uiController()->removeToolView( m_factory ); KDevelop::IPlugin::unload(); } KConfigGroup ExternalScriptPlugin::getConfig() const { return KSharedConfig::openConfig()->group("External Scripts"); } QStandardItemModel* ExternalScriptPlugin::model() const { return m_model; } void ExternalScriptPlugin::execute( ExternalScriptItem* item, const QUrl& url ) const { ExternalScriptJob* job = new ExternalScriptJob( item, url, const_cast(this) ); KDevelop::ICore::self()->runController()->registerJob( job ); } void ExternalScriptPlugin::execute(ExternalScriptItem* item) const { auto document = KDevelop::ICore::self()->documentController()->activeDocument(); execute( item, document ? document->url() : QUrl() ); } bool ExternalScriptPlugin::executeCommand ( QString command, QString workingDirectory ) const { // We extend ExternalScriptJob so that it deletes the temporarily created item on destruction class ExternalScriptJobOwningItem : public ExternalScriptJob { public: ExternalScriptJobOwningItem( ExternalScriptItem* item, const QUrl &url, ExternalScriptPlugin* parent ) : ExternalScriptJob(item, url, parent), m_item(item) { } ~ExternalScriptJobOwningItem() override { delete m_item; } private: ExternalScriptItem* m_item; }; ExternalScriptItem* item = new ExternalScriptItem; item->setCommand(command); item->setWorkingDirectory(workingDirectory); item->setPerformParameterReplacement(false); qCDebug(PLUGIN_EXTERNALSCRIPT) << "executing command " << command << " in dir " << workingDirectory << " as external script"; ExternalScriptJobOwningItem* job = new ExternalScriptJobOwningItem( item, QUrl(), const_cast(this) ); // When a command is executed, for example through the terminal, we don't want the command output to be risen job->setVerbosity(KDevelop::OutputJob::Silent); KDevelop::ICore::self()->runController()->registerJob( job ); return true; } QString ExternalScriptPlugin::executeCommandSync ( QString command, QString workingDirectory ) const { qCDebug(PLUGIN_EXTERNALSCRIPT) << "executing command " << command << " in working-dir " << workingDirectory; KProcess process; process.setWorkingDirectory( workingDirectory ); process.setShellCommand( command ); process.setOutputChannelMode( KProcess::OnlyStdoutChannel ); process.execute(); return QString::fromLocal8Bit(process.readAll()); } void ExternalScriptPlugin::executeScriptFromActionData() const { QAction* action = dynamic_cast( sender() ); Q_ASSERT( action ); ExternalScriptItem* item = action->data().value(); Q_ASSERT( item ); execute( item ); } void ExternalScriptPlugin::executeScriptFromContextMenu() const { QAction* action = dynamic_cast( sender() ); Q_ASSERT( action ); ExternalScriptItem* item = action->data().value(); Q_ASSERT( item ); foreach( const QUrl& url, m_urls) { KDevelop::ICore::self()->documentController()->openDocument( url ); execute( item, url ); } } void ExternalScriptPlugin::rowsInserted( const QModelIndex& /*parent*/, int start, int end ) { for ( int i = start; i <= end; ++i ) { saveItemForRow( i ); } } void ExternalScriptPlugin::rowsRemoved( const QModelIndex& /*parent*/, int start, int end ) { KConfigGroup config = getConfig(); for ( int i = start; i <= end; ++i ) { KConfigGroup child = config.group( QStringLiteral("script %1").arg(i) ); qCDebug(PLUGIN_EXTERNALSCRIPT) << "removing config group:" << child.name(); child.deleteGroup(); } config.sync(); } void ExternalScriptPlugin::saveItem( const ExternalScriptItem* item ) { const QModelIndex index = m_model->indexFromItem( item ); Q_ASSERT( index.isValid() ); saveItemForRow( index.row() ); } void ExternalScriptPlugin::saveItemForRow( int row ) { const QModelIndex idx = m_model->index( row, 0 ); Q_ASSERT( idx.isValid() ); ExternalScriptItem* item = dynamic_cast( m_model->item( row ) ); Q_ASSERT( item ); qCDebug(PLUGIN_EXTERNALSCRIPT) << "save extern script:" << item << idx; KConfigGroup config = getConfig().group( QStringLiteral("script %1").arg( row ) ); config.writeEntry( "name", item->text() ); config.writeEntry( "command", item->command() ); config.writeEntry( "inputMode", (uint) item->inputMode() ); config.writeEntry( "outputMode", (uint) item->outputMode() ); config.writeEntry( "errorMode", (uint) item->errorMode() ); config.writeEntry( "saveMode", (uint) item->saveMode() ); config.writeEntry( "shortcuts", item->action()->shortcut().toString() ); config.writeEntry( "showOutput", item->showOutput() ); config.writeEntry( "filterMode", item->filterMode()); config.sync(); } #include "externalscriptplugin.moc" // kate: indent-mode cstyle; space-indent on; indent-width 2; replace-tabs on; diff --git a/plugins/filemanager/kdevfilemanagerplugin.cpp b/plugins/filemanager/kdevfilemanagerplugin.cpp index 4a4944b45..8f10124b0 100644 --- a/plugins/filemanager/kdevfilemanagerplugin.cpp +++ b/plugins/filemanager/kdevfilemanagerplugin.cpp @@ -1,94 +1,94 @@ /*************************************************************************** * Copyright 2006 Alexander Dymo * * Copyright 2007 Andreas Pakulat * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * 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 "kdevfilemanagerplugin.h" #include #include #include #include #include #include "filemanager.h" K_PLUGIN_FACTORY_WITH_JSON(KDevFileManagerFactory, "kdevfilemanager.json", registerPlugin();) class KDevFileManagerViewFactory: public KDevelop::IToolViewFactory{ public: - KDevFileManagerViewFactory(KDevFileManagerPlugin *plugin): m_plugin(plugin) {} + explicit KDevFileManagerViewFactory(KDevFileManagerPlugin *plugin): m_plugin(plugin) {} QWidget* create(QWidget *parent = nullptr) override { Q_UNUSED(parent) return new FileManager(m_plugin,parent); } QList toolBarActions( QWidget* w ) const override { FileManager* m = qobject_cast(w); if( m ) return m->toolBarActions(); return KDevelop::IToolViewFactory::toolBarActions( w ); } Qt::DockWidgetArea defaultPosition() override { return Qt::LeftDockWidgetArea; } QString id() const override { return QStringLiteral("org.kdevelop.FileManagerView"); } bool allowMultiple() const override { return true; } private: KDevFileManagerPlugin *m_plugin; }; KDevFileManagerPlugin::KDevFileManagerPlugin(QObject *parent, const QVariantList &/*args*/) :KDevelop::IPlugin(QStringLiteral("kdevfilemanager"), parent) { setXMLFile(QStringLiteral("kdevfilemanager.rc")); QMetaObject::invokeMethod(this, "init", Qt::QueuedConnection); } void KDevFileManagerPlugin::init() { m_factory = new KDevFileManagerViewFactory(this); core()->uiController()->addToolView(i18n("Filesystem"), m_factory); } KDevFileManagerPlugin::~KDevFileManagerPlugin() { } void KDevFileManagerPlugin::unload() { core()->uiController()->removeToolView(m_factory); } #include "kdevfilemanagerplugin.moc" diff --git a/plugins/filetemplates/filetemplatesplugin.cpp b/plugins/filetemplates/filetemplatesplugin.cpp index 598a236e4..845a79c9f 100644 --- a/plugins/filetemplates/filetemplatesplugin.cpp +++ b/plugins/filetemplates/filetemplatesplugin.cpp @@ -1,288 +1,288 @@ #include "filetemplatesplugin.h" #include "templateclassassistant.h" #include "templatepreviewtoolview.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; K_PLUGIN_FACTORY_WITH_JSON(FileTemplatesFactory, "kdevfiletemplates.json", registerPlugin();) class TemplatePreviewFactory : public KDevelop::IToolViewFactory { public: - TemplatePreviewFactory(FileTemplatesPlugin* plugin) + explicit TemplatePreviewFactory(FileTemplatesPlugin* plugin) : KDevelop::IToolViewFactory() , m_plugin(plugin) { } QWidget* create(QWidget* parent = nullptr) override { return new TemplatePreviewToolView(m_plugin, parent); } QString id() const override { return QStringLiteral("org.kdevelop.TemplateFilePreview"); } Qt::DockWidgetArea defaultPosition() override { return Qt::RightDockWidgetArea; } private: FileTemplatesPlugin* m_plugin; }; FileTemplatesPlugin::FileTemplatesPlugin(QObject* parent, const QVariantList& args) : IPlugin(QStringLiteral("kdevfiletemplates"), parent) , m_model(nullptr) { Q_UNUSED(args); setXMLFile(QStringLiteral("kdevfiletemplates.rc")); QAction* action = actionCollection()->addAction(QStringLiteral("new_from_template")); action->setText( i18n( "New From Template" ) ); action->setIcon( QIcon::fromTheme( QStringLiteral("code-class") ) ); action->setWhatsThis( i18n( "Allows you to create new source code files, such as classes or unit tests, using templates." ) ); action->setStatusTip( i18n( "Create new files from a template" ) ); connect (action, &QAction::triggered, this, &FileTemplatesPlugin::createFromTemplate); m_toolView = new TemplatePreviewFactory(this); core()->uiController()->addToolView(i18n("Template Preview"), m_toolView); } FileTemplatesPlugin::~FileTemplatesPlugin() { } void FileTemplatesPlugin::unload() { core()->uiController()->removeToolView(m_toolView); } ContextMenuExtension FileTemplatesPlugin::contextMenuExtension (Context* context) { ContextMenuExtension ext; QUrl fileUrl; if (context->type() == Context::ProjectItemContext) { ProjectItemContext* projectContext = dynamic_cast(context); QList items = projectContext->items(); if (items.size() != 1) { return ext; } QUrl url; ProjectBaseItem* item = items.first(); if (item->folder()) { url = item->path().toUrl(); } else if (item->target()) { url = item->parent()->path().toUrl(); } if (url.isValid()) { QAction* action = new QAction(i18n("Create From Template"), this); action->setIcon(QIcon::fromTheme(QStringLiteral("code-class"))); action->setData(url); connect(action, &QAction::triggered, this, &FileTemplatesPlugin::createFromTemplate); ext.addAction(ContextMenuExtension::FileGroup, action); } if (item->file()) { fileUrl = item->path().toUrl(); } } else if (context->type() == Context::EditorContext) { KDevelop::EditorContext* editorContext = dynamic_cast(context); fileUrl = editorContext->url(); } if (fileUrl.isValid() && determineTemplateType(fileUrl) != NoTemplate) { QAction* action = new QAction(i18n("Show Template Preview"), this); action->setIcon(QIcon::fromTheme(QStringLiteral("document-preview"))); action->setData(fileUrl); connect(action, &QAction::triggered, this, &FileTemplatesPlugin::previewTemplate); ext.addAction(ContextMenuExtension::ExtensionGroup, action); } return ext; } QString FileTemplatesPlugin::name() const { return i18n("File Templates"); } QIcon FileTemplatesPlugin::icon() const { return QIcon::fromTheme(QStringLiteral("code-class")); } QAbstractItemModel* FileTemplatesPlugin::templatesModel() { if(!m_model) { m_model = new TemplatesModel(QStringLiteral("kdevfiletemplates"), this); } return m_model; } QString FileTemplatesPlugin::knsConfigurationFile() const { return QStringLiteral("kdevfiletemplates.knsrc"); } QStringList FileTemplatesPlugin::supportedMimeTypes() const { QStringList types; types << QStringLiteral("application/x-desktop"); types << QStringLiteral("application/x-bzip-compressed-tar"); types << QStringLiteral("application/zip"); return types; } void FileTemplatesPlugin::reload() { templatesModel(); m_model->refresh(); } void FileTemplatesPlugin::loadTemplate(const QString& fileName) { templatesModel(); m_model->loadTemplateFile(fileName); } void FileTemplatesPlugin::createFromTemplate() { QUrl baseUrl; if (QAction* action = qobject_cast(sender())) { baseUrl = action->data().toUrl(); } if (!baseUrl.isValid()) { // fall-back to currently active document's parent directory IDocument* doc = ICore::self()->documentController()->activeDocument(); if (doc && doc->url().isValid()) { baseUrl = doc->url().adjusted(QUrl::RemoveFilename); } } TemplateClassAssistant* assistant = new TemplateClassAssistant(QApplication::activeWindow(), baseUrl); assistant->setAttribute(Qt::WA_DeleteOnClose); assistant->show(); } FileTemplatesPlugin::TemplateType FileTemplatesPlugin::determineTemplateType(const QUrl& url) { QDir dir(url.toLocalFile()); /* * Search for a description file in the url's directory. * If it is not found there, try cascading up a maximum of 5 directories. */ int level = 0; while (dir.cdUp() && level < 5) { QStringList filters; filters << QStringLiteral("*.kdevtemplate") << QStringLiteral("*.desktop"); foreach (const QString& entry, dir.entryList(filters)) { qCDebug(PLUGIN_FILETEMPLATES) << "Trying entry" << entry; /* * This logic is not perfect, but it works for most cases. * * Project template description files usually have the suffix * ".kdevtemplate", so those are easy to find. For project templates, * all the files in the directory are template files. * * On the other hand, file templates use the generic suffix ".desktop". * Fortunately, those explicitly list input and output files, so we * only match the explicitly listed files */ if (entry.endsWith(QLatin1String(".kdevtemplate"))) { return ProjectTemplate; } KConfig* config = new KConfig(dir.absoluteFilePath(entry), KConfig::SimpleConfig); KConfigGroup group = config->group("General"); qCDebug(PLUGIN_FILETEMPLATES) << "General group keys:" << group.keyList(); if (!group.hasKey("Name") || !group.hasKey("Category")) { continue; } if (group.hasKey("Files")) { qCDebug(PLUGIN_FILETEMPLATES) << "Group has files " << group.readEntry("Files", QStringList()); foreach (const QString& outputFile, group.readEntry("Files", QStringList())) { if (dir.absoluteFilePath(config->group(outputFile).readEntry("File")) == url.toLocalFile()) { return FileTemplate; } } } if (group.hasKey("ShowFilesAfterGeneration")) { return ProjectTemplate; } } ++level; } return NoTemplate; } void FileTemplatesPlugin::previewTemplate() { QAction* action = qobject_cast(sender()); if (!action || !action->data().toUrl().isValid()) { return; } TemplatePreviewToolView* preview = qobject_cast(core()->uiController()->findToolView(i18n("Template Preview"), m_toolView)); if (!preview) { return; } core()->documentController()->activateDocument(core()->documentController()->openDocument(action->data().toUrl())); } #include "filetemplatesplugin.moc" diff --git a/plugins/filetemplates/licensepage.cpp b/plugins/filetemplates/licensepage.cpp index c2cfe68fa..3133720bf 100644 --- a/plugins/filetemplates/licensepage.cpp +++ b/plugins/filetemplates/licensepage.cpp @@ -1,277 +1,277 @@ /* This file is part of KDevelop Copyright 2008 Hamish Rodda This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "licensepage.h" #include #include "ui_licensechooser.h" #include "debug.h" #include #include #include #include #include #include namespace KDevelop { struct LicensePagePrivate { struct LicenseInfo { QString name; QString path; QString contents; bool operator< (const LicenseInfo& o) const { return name.localeAwareCompare(o.name) < 0; } }; typedef QList LicenseList; - LicensePagePrivate(LicensePage* page_) + explicit LicensePagePrivate(LicensePage* page_) : license(nullptr) , page(page_) { } // methods void initializeLicenses(); QString readLicense(int licenseIndex); bool saveLicense(); // slots void licenseComboChanged(int license); Ui::LicenseChooserDialog* license; LicenseList availableLicenses; LicensePage* page; }; //! Read all the license files in the global and local config dirs void LicensePagePrivate::initializeLicenses() { qCDebug(PLUGIN_FILETEMPLATES) << "Searching for available licenses"; QStringList licenseDirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("kdevcodegen/licenses"), QStandardPaths::LocateDirectory); //Iterate through the possible directories that contain licenses, and load their names foreach(const QString& currentDir, licenseDirs) { QDirIterator it(currentDir, QDir::Files | QDir::Readable); while(it.hasNext()) { LicenseInfo newLicense; newLicense.path = it.next(); newLicense.name = it.fileName(); qCDebug(PLUGIN_FILETEMPLATES) << "Found License: " << newLicense.name; availableLicenses.push_back(newLicense); } } std::sort(availableLicenses.begin(), availableLicenses.end()); foreach(const LicenseInfo& info, availableLicenses) { license->licenseComboBox->addItem(info.name); } //Finally add the option other for user specified licenses LicenseInfo otherLicense; availableLicenses.push_back(otherLicense); license->licenseComboBox->addItem(i18n("Other")); } // Read a license index, if it is not loaded, open it from the file QString LicensePagePrivate::readLicense(int licenseIndex) { //If the license is not loaded into memory, read it in if(availableLicenses[licenseIndex].contents.isEmpty()) { QString licenseText; //If we are dealing with the last option "other" just return a new empty string if(licenseIndex != (availableLicenses.size() - 1)) { qCDebug(PLUGIN_FILETEMPLATES) << "Reading license: " << availableLicenses[licenseIndex].name ; QFile newLicense(availableLicenses[licenseIndex].path); if(newLicense.open(QIODevice::ReadOnly | QIODevice::Text)) { QTextStream newLicenseText(&newLicense); newLicenseText.setAutoDetectUnicode(true); licenseText = newLicenseText.readAll(); newLicense.close(); } else licenseText = QStringLiteral("Error, could not open license file.\n Was it deleted?"); } availableLicenses[licenseIndex].contents = licenseText; } return availableLicenses[licenseIndex].contents; } // ---Slots--- void LicensePagePrivate::licenseComboChanged(int selectedLicense) { //If the last slot is selected enable the save license combobox if(selectedLicense == (availableLicenses.size() - 1)) { license->licenseTextEdit->clear(); license->licenseTextEdit->setReadOnly(false); license->saveLicense->setEnabled(true); } else { license->saveLicense->setEnabled(false); license->licenseTextEdit->setReadOnly(true); } if(selectedLicense < 0 || selectedLicense >= availableLicenses.size()) license->licenseTextEdit->setText(i18n("Could not load previous license")); else license->licenseTextEdit->setText(readLicense(selectedLicense)); } bool LicensePagePrivate::saveLicense() { qCDebug(PLUGIN_FILETEMPLATES) << "Attempting to save custom license: " << license->licenseName->text(); QString localDataDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)+"/kdevcodegen/licenses/"; QString fullPath = localDataDir + license->licenseName->text(); QFile newFile(fullPath); if(newFile.exists()) { KMessageBox::sorry(page, i18n("The specified license already exists. " "Please provide a different name.")); return false; } QDir().mkpath(localDataDir); newFile.open(QIODevice::WriteOnly); qint64 result = newFile.write(license->licenseTextEdit->toPlainText().toUtf8()); newFile.close(); if(result == -1) { KMessageBox::sorry(page, i18n("Failed to write custom license template to file %1.", fullPath)); return false; } // also add to our data structures, this esp. needed for proper saving // of the license index so it can be restored the next time we show up LicenseInfo info; info.name = license->licenseName->text(); info.path = localDataDir; availableLicenses << info; // find index of the new the license, omitting the very last item ('Other') int idx = availableLicenses.count() - 1; for(int i = 0; i < availableLicenses.size() - 1; ++i) { if (info < availableLicenses.at(i)) { idx = i; break; } } availableLicenses.insert(idx, info); license->licenseComboBox->insertItem(idx, info.name); license->licenseComboBox->setCurrentIndex(idx); return true; } LicensePage::LicensePage(QWidget* parent) : QWidget(parent) , d(new LicensePagePrivate(this)) { d->license = new Ui::LicenseChooserDialog; d->license->setupUi(this); connect(d->license->licenseComboBox, static_cast(&KComboBox::currentIndexChanged), this, [&] (int selectedLicense) { d->licenseComboChanged(selectedLicense); }); connect(d->license->saveLicense, &QCheckBox::clicked, d->license->licenseName, &QLineEdit::setEnabled); // Read all the available licenses from the standard dirs d->initializeLicenses(); //Set the license selection to the previous one KConfigGroup config(KSharedConfig::openConfig()->group("CodeGeneration")); d->license->licenseComboBox->setCurrentIndex(config.readEntry( "LastSelectedLicense", 0 )); // Needed to avoid a bug where licenseComboChanged doesn't get // called by QComboBox if the past selection was 0 d->licenseComboChanged(d->license->licenseComboBox->currentIndex()); } LicensePage::~LicensePage() { if (d->license->saveLicense->isChecked() && !d->license->licenseName->text().isEmpty()) { d->saveLicense(); } KConfigGroup config(KSharedConfig::openConfig()->group("CodeGeneration")); //Do not save invalid license numbers' int index = d->license->licenseComboBox->currentIndex(); if( index >= 0 || index < d->availableLicenses.size() ) { config.writeEntry("LastSelectedLicense", index); config.config()->sync(); } else { qWarning() << "Attempted to save an invalid license number: " << index << ". Number of licenses:" << d->availableLicenses.size(); } delete d->license; delete d; } QString LicensePage::license() const { QString licenseText = d->license->licenseTextEdit->document()->toPlainText(); /* Add date, name and email to license text */ licenseText.replace(QLatin1String(""), QDate::currentDate().toString(QStringLiteral("yyyy"))); licenseText.replace(QLatin1String(""), QDate::currentDate().toString(QStringLiteral("MM"))); licenseText.replace(QLatin1String(""), QDate::currentDate().toString(QStringLiteral("dd"))); QString developer(QStringLiteral("%1 <%2>")); KEMailSettings emailSettings; QString name = emailSettings.getSetting(KEMailSettings::RealName); if (name.isEmpty()) { name = QStringLiteral(""); } developer = developer.arg(name); QString email = emailSettings.getSetting(KEMailSettings::EmailAddress); if (email.isEmpty()) { email = QStringLiteral("email"); //no < > as they are already through the email field } developer = developer.arg(email); licenseText.replace(QLatin1String(""), developer); return licenseText; } } Q_DECLARE_TYPEINFO(KDevelop::LicensePagePrivate::LicenseInfo, Q_MOVABLE_TYPE); #include "moc_licensepage.cpp" diff --git a/plugins/filetemplates/outputpage.cpp b/plugins/filetemplates/outputpage.cpp index a7fb68b28..50d633f04 100644 --- a/plugins/filetemplates/outputpage.cpp +++ b/plugins/filetemplates/outputpage.cpp @@ -1,269 +1,269 @@ /* This file is part of KDevelop Copyright 2008 Hamish Rodda This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "outputpage.h" #include "ui_outputlocation.h" #include "debug.h" #include #include #include #include #include #include #include #include #include namespace KDevelop { struct OutputPagePrivate { - OutputPagePrivate(OutputPage* page_) + explicit OutputPagePrivate(OutputPage* page_) : page(page_) , output(nullptr) { } OutputPage* page; Ui::OutputLocationDialog* output; QSignalMapper urlChangedMapper; QHash outputFiles; QHash outputLines; QHash outputColumns; QList labels; QHash defaultUrls; QHash lowerCaseUrls; QStringList fileIdentifiers; void updateRanges(QSpinBox* line, QSpinBox* column, bool enable); void updateFileRange(const QString& field); void updateFileNames(); void validate(); }; void OutputPagePrivate::updateRanges(QSpinBox* line, QSpinBox* column, bool enable) { qCDebug(PLUGIN_FILETEMPLATES) << "Updating Ranges, file exists: " << enable; line->setEnabled(enable); column->setEnabled(enable); } void OutputPagePrivate::updateFileRange(const QString& field) { if (!outputFiles.contains(field)) { return; } QString url = outputFiles[field]->url().toLocalFile(); QFileInfo info(url); updateRanges(outputLines[field], outputColumns[field], info.exists() && !info.isDir()); validate(); } void OutputPagePrivate::updateFileNames() { bool lower = output->lowerFilenameCheckBox->isChecked(); const QHash urls = lower ? lowerCaseUrls : defaultUrls; for (QHash::const_iterator it = outputFiles.constBegin(); it != outputFiles.constEnd(); ++it) { const QUrl url = urls.value(it.key()); if (!url.isEmpty()) { it.value()->setUrl(url); } } //Save the setting for next time KConfigGroup codegenGroup( KSharedConfig::openConfig(), "CodeGeneration" ); codegenGroup.writeEntry( "LowerCaseFilenames", output->lowerFilenameCheckBox->isChecked() ); validate(); } void OutputPagePrivate::validate() { QStringList invalidFiles; for(QHash< QString, KUrlRequester* >::const_iterator it = outputFiles.constBegin(); it != outputFiles.constEnd(); ++it) { if (!it.value()->url().isValid()) { invalidFiles << it.key(); } else if (it.value()->url().isLocalFile() && !QFileInfo(it.value()->url().adjusted(QUrl::RemoveFilename).toLocalFile()).isWritable()) { invalidFiles << it.key(); } } bool valid = invalidFiles.isEmpty(); if (valid) { output->messageWidget->animatedHide(); } else { std::sort(invalidFiles.begin(), invalidFiles.end()); output->messageWidget->setMessageType(KMessageWidget::Error); output->messageWidget->setCloseButtonVisible(false); output->messageWidget->setText(i18np("Invalid output file: %2", "Invalid output files: %2", invalidFiles.count(), invalidFiles.join(QStringLiteral(", ")))); output->messageWidget->animatedShow(); } emit page->isValid(valid); } OutputPage::OutputPage(QWidget* parent) : QWidget(parent) , d(new OutputPagePrivate(this)) { d->output = new Ui::OutputLocationDialog; d->output->setupUi(this); d->output->messageWidget->setVisible(false); connect(&d->urlChangedMapper, static_cast(&QSignalMapper::mapped), this, [&] (const QString& field) { d->updateFileRange(field); }); connect(d->output->lowerFilenameCheckBox, &QCheckBox::stateChanged, this, [&] { d->updateFileNames(); }); } OutputPage::~OutputPage() { delete d->output; delete d; } void OutputPage::prepareForm(const SourceFileTemplate& fileTemplate) { // First clear any existing file configurations // This can happen when going back and forth between assistant pages d->fileIdentifiers.clear(); d->defaultUrls.clear(); d->lowerCaseUrls.clear(); while (d->output->urlFormLayout->count() > 0) { d->output->urlFormLayout->takeAt(0); } while (d->output->positionFormLayout->count() > 0) { d->output->positionFormLayout->takeAt(0); } foreach (KUrlRequester* req, d->outputFiles) { d->urlChangedMapper.removeMappings(req); } qDeleteAll(d->outputFiles); qDeleteAll(d->outputLines); qDeleteAll(d->outputColumns); qDeleteAll(d->labels); d->outputFiles.clear(); d->outputLines.clear(); d->outputColumns.clear(); d->labels.clear(); foreach (const SourceFileTemplate::OutputFile& file, fileTemplate.outputFiles()) { d->fileIdentifiers << file.identifier; QLabel* label = new QLabel(file.label, this); d->labels << label; KUrlRequester* requester = new KUrlRequester(this); requester->setMode( KFile::File | KFile::LocalOnly ); d->urlChangedMapper.setMapping(requester, file.identifier); connect(requester, &KUrlRequester::textChanged, &d->urlChangedMapper, static_cast(&QSignalMapper::map)); d->output->urlFormLayout->addRow(label, requester); d->outputFiles.insert(file.identifier, requester); label = new QLabel(file.label, this); d->labels << label; QHBoxLayout* layout = new QHBoxLayout(this); auto line = new QSpinBox(this); line->setPrefix(i18n("Line: ")); line->setValue(0); line->setMinimum(0); layout->addWidget(line); auto column = new QSpinBox(this); column->setPrefix(i18n("Column: ")); column->setValue(0); column->setMinimum(0); layout->addWidget(column); d->output->positionFormLayout->addRow(label, layout); d->outputLines.insert(file.identifier, line); d->outputColumns.insert(file.identifier, column); } } void OutputPage::loadFileTemplate(const SourceFileTemplate& fileTemplate, const QUrl& _baseUrl, TemplateRenderer* renderer) { QUrl baseUrl = _baseUrl; if (!baseUrl.path().endsWith('/')) { baseUrl.setPath(baseUrl.path()+'/'); } KConfigGroup codegenGroup( KSharedConfig::openConfig(), "CodeGeneration" ); bool lower = codegenGroup.readEntry( "LowerCaseFilenames", true ); d->output->lowerFilenameCheckBox->setChecked(lower); foreach (const SourceFileTemplate::OutputFile& file, fileTemplate.outputFiles()) { d->fileIdentifiers << file.identifier; QUrl url = baseUrl.resolved(QUrl(renderer->render(file.outputName))); d->defaultUrls.insert(file.identifier, url); url = baseUrl.resolved(QUrl(renderer->render(file.outputName).toLower())); d->lowerCaseUrls.insert(file.identifier, url); } d->updateFileNames(); } QHash< QString, QUrl > OutputPage::fileUrls() const { QHash urls; for (QHash::const_iterator it = d->outputFiles.constBegin(); it != d->outputFiles.constEnd(); ++it) { urls.insert(it.key(), it.value()->url()); } return urls; } QHash< QString, KTextEditor::Cursor > OutputPage::filePositions() const { QHash positions; foreach (const QString& identifier, d->fileIdentifiers) { positions.insert(identifier, KTextEditor::Cursor(d->outputLines[identifier]->value(), d->outputColumns[identifier]->value())); } return positions; } } #include "moc_outputpage.cpp" diff --git a/plugins/filetemplates/templateclassassistant.cpp b/plugins/filetemplates/templateclassassistant.cpp index 32c8166a2..e27ef7cde 100644 --- a/plugins/filetemplates/templateclassassistant.cpp +++ b/plugins/filetemplates/templateclassassistant.cpp @@ -1,587 +1,587 @@ /* 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 "debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define REMOVE_PAGE(name) \ if (d->name##Page) \ { \ removePage(d->name##Page); \ d->name##Page = nullptr; \ d->name##PageWidget = nullptr; \ } #define ZERO_PAGE(name) \ d->name##Page = nullptr; \ d->name##PageWidget = nullptr; using namespace KDevelop; class KDevelop::TemplateClassAssistantPrivate { public: - TemplateClassAssistantPrivate(const QUrl& baseUrl); + explicit TemplateClassAssistantPrivate(const QUrl& 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; QUrl baseUrl; SourceFileTemplate fileTemplate; ICreateClassHelper* helper; TemplateClassGenerator* generator; TemplateRenderer* renderer; QString type; QVariantHash templateOptions; }; TemplateClassAssistantPrivate::TemplateClassAssistantPrivate(const QUrl& baseUrl) : baseUrl(baseUrl) , helper(nullptr) , generator(nullptr) , renderer(nullptr) { } 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, QUrl >& fileUrls) { // Add the generated files to a target, if one is found QUrl 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().adjusted(QUrl::RemoveFilename); } } qCDebug(PLUGIN_FILETEMPLATES) << "Searching for targets with URL" << url; IProject* project = ICore::self()->projectController()->findProjectForUrl(url); if (!project || !project->buildSystemManager()) { qCDebug(PLUGIN_FILETEMPLATES) << "No suitable project found"; return; } QList items = project->itemsForPath(IndexedString(url)); if (items.isEmpty()) { qCDebug(PLUGIN_FILETEMPLATES) << "No suitable project items found"; return; } QList targets; ProjectTargetItem* target = nullptr; 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) { qCDebug(PLUGIN_FILETEMPLATES) << "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 QDialog; auto mainLayout = new QVBoxLayout(d); mainLayout->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(d); targetsWidget->setSelectionMode(QAbstractItemView::SingleSelection); foreach(ProjectTargetItem* target, targets) { targetsWidget->addItem(target->text()); } targetsWidget->setCurrentRow(0); mainLayout->addWidget(targetsWidget); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel); QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); d->connect(buttonBox, &QDialogButtonBox::accepted, d.data(), &QDialog::accept); d->connect(buttonBox, &QDialogButtonBox::rejected, d.data(), &QDialog::reject); mainLayout->addWidget(buttonBox); if(d->exec() == QDialog::Accepted) { if (!targetsWidget->selectedItems().isEmpty()) { target = targets[targetsWidget->currentRow()]; } else { qCDebug(PLUGIN_FILETEMPLATES) << "Did not select anything, not adding to a target"; return; } } else { qCDebug(PLUGIN_FILETEMPLATES) << "Canceled select target dialog, not adding to a target"; return; } } else { // No target, not doing anything qCDebug(PLUGIN_FILETEMPLATES) << "No possible targets for URL" << url; return; } Q_ASSERT(target); QList fileItems; foreach (const QUrl &fileUrl, fileUrls) { foreach (ProjectBaseItem* item, project->itemsForPath(IndexedString(KIO::upUrl(fileUrl)))) { if (ProjectFolderItem* folder = item->folder()) { ///FIXME: use Path instead of QUrl 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 QUrl& 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(xi18n("Create Files from Template in %1", d->baseUrl.toDisplayString())); } else { setWindowTitle(i18n("Create Files from Template")); } d->templateSelectionPageWidget = new TemplateSelectionPage(this); connect(this, &TemplateClassAssistant::accepted, d->templateSelectionPageWidget, &TemplateSelectionPage::saveConfig); d->templateSelectionPage = addPage(d->templateSelectionPageWidget, i18n("Language and Template")); d->templateSelectionPage->setIcon(QIcon::fromTheme(QStringLiteral("project-development-new-template"))); d->dummyPage = addPage(new QWidget(this), QStringLiteral("Dummy Page")); // showButton(QDialog::Help, false); } void TemplateClassAssistant::templateChosen(const QString& templateDescription) { d->fileTemplate.setTemplateDescription(templateDescription); d->type = d->fileTemplate.type(); d->generator = nullptr; if (!d->fileTemplate.isValid()) { return; } qCDebug(PLUGIN_FILETEMPLATES) << "Selected template" << templateDescription << "of type" << d->type; removePage(d->dummyPage); if (d->baseUrl.isValid()) { setWindowTitle(xi18n("Create Files from Template %1 in %2", d->fileTemplate.name(), d->baseUrl.toDisplayString())); } else { setWindowTitle(xi18n("Create Files from Template %1", d->fileTemplate.name())); } if (d->type == QLatin1String("Class")) { d->classIdentifierPageWidget = new ClassIdentifierPage(this); d->classIdentifierPage = addPage(d->classIdentifierPageWidget, i18n("Class Basics")); d->classIdentifierPage->setIcon(QIcon::fromTheme(QStringLiteral("classnew"))); connect(d->classIdentifierPageWidget, &ClassIdentifierPage::isValid, this, &TemplateClassAssistant::setCurrentPageValid); setValid(d->classIdentifierPage, false); d->overridesPageWidget = new OverridesPage(this); d->overridesPage = addPage(d->overridesPageWidget, i18n("Override Methods")); d->overridesPage->setIcon(QIcon::fromTheme(QStringLiteral("code-class"))); setValid(d->overridesPage, true); d->membersPageWidget = new ClassMembersPage(this); d->membersPage = addPage(d->membersPageWidget, i18n("Class Members")); d->membersPage->setIcon(QIcon::fromTheme(QStringLiteral("field"))); setValid(d->membersPage, true); d->helper = nullptr; QString languageName = d->fileTemplate.languageName(); auto language = ICore::self()->languageController()->language(languageName); if (language) { d->helper = language->createClassHelper(); } if (!d->helper) { qCDebug(PLUGIN_FILETEMPLATES) << "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 == QLatin1String("Test")) { d->testCasesPageWidget = new TestCasesPage(this); d->testCasesPage = addPage(d->testCasesPageWidget, i18n("Test Cases")); connect(d->testCasesPageWidget, &TestCasesPage::isValid, this, &TemplateClassAssistant::setCurrentPageValid); 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(QStringLiteral("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(QStringLiteral("document-save"))); connect(d->outputPageWidget, &OutputPage::isValid, this, &TemplateClassAssistant::setCurrentPageValid); setValid(d->outputPage, false); if (d->fileTemplate.hasCustomOptions()) { qCDebug(PLUGIN_FILETEMPLATES) << "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() { qCDebug(PLUGIN_FILETEMPLATES) << 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()); 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(QStringLiteral("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(QStringLiteral("name"), d->testCasesPageWidget->name()); d->renderer->addVariable(QStringLiteral("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 = nullptr; if (d->generator) { delete d->generator; } else { delete d->renderer; } d->generator = nullptr; d->renderer = nullptr; if (d->baseUrl.isValid()) { setWindowTitle(xi18n("Create Files from Template in %1", d->baseUrl.toDisplayString())); } else { setWindowTitle(i18n("Create Files from Template")); } d->dummyPage = addPage(new QWidget(this), QStringLiteral("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 QUrl& url, fileUrls) { ICore::self()->documentController()->openDocument(url); } KAssistantDialog::accept(); } void TemplateClassAssistant::setCurrentPageValid(bool valid) { setValid(currentPage(), valid); } QUrl TemplateClassAssistant::baseUrl() const { return d->baseUrl; } diff --git a/plugins/filetemplates/templateselectionpage.cpp b/plugins/filetemplates/templateselectionpage.cpp index d1f9c751c..92ca249ca 100644 --- a/plugins/filetemplates/templateselectionpage.cpp +++ b/plugins/filetemplates/templateselectionpage.cpp @@ -1,267 +1,267 @@ /* 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 "templateselectionpage.h" #include "templateclassassistant.h" #include "templatepreview.h" #include #include #include #include #include #include #include #include #include #include #include #include "ui_templateselection.h" #include #include #include #include #include #include #include using namespace KDevelop; static const char LastUsedTemplateEntry[] = "LastUsedTemplate"; static const char FileTemplatesGroup[] = "SourceFileTemplates"; class KDevelop::TemplateSelectionPagePrivate { public: - TemplateSelectionPagePrivate(TemplateSelectionPage* page_) + explicit TemplateSelectionPagePrivate(TemplateSelectionPage* page_) : page(page_) {} TemplateSelectionPage* page; Ui::TemplateSelection* ui; QString selectedTemplate; TemplateClassAssistant* assistant; TemplatesModel* model; void currentTemplateChanged(const QModelIndex& index); void getMoreClicked(); void loadFileClicked(); void previewTemplate(const QString& templateFile); }; void TemplateSelectionPagePrivate::currentTemplateChanged(const QModelIndex& index) { // delete preview tabs if (!index.isValid() || index.child(0, 0).isValid()) { // invalid or has child assistant->setValid(assistant->currentPage(), false); ui->previewLabel->setVisible(false); ui->tabWidget->setVisible(false); } else { selectedTemplate = model->data(index, TemplatesModel::DescriptionFileRole).toString(); assistant->setValid(assistant->currentPage(), true); previewTemplate(selectedTemplate); ui->previewLabel->setVisible(true); ui->tabWidget->setVisible(true); ui->previewLabel->setText(i18nc("%1: template comment", "Preview: %1", index.data(TemplatesModel::CommentRole).toString())); } } void TemplateSelectionPagePrivate::previewTemplate(const QString& file) { SourceFileTemplate fileTemplate(file); if (!fileTemplate.isValid() || fileTemplate.outputFiles().isEmpty()) { return; } TemplatePreviewRenderer renderer; renderer.setEmptyLinesPolicy(TemplateRenderer::TrimEmptyLines); QTemporaryDir dir; QUrl base = QUrl::fromLocalFile(dir.path() + QLatin1Char('/')); QHash fileUrls; foreach(const SourceFileTemplate::OutputFile& out, fileTemplate.outputFiles()) { QUrl url = base.resolved(QUrl(renderer.render(out.outputName))); fileUrls.insert(out.identifier, url); } DocumentChangeSet changes = renderer.renderFileTemplate(fileTemplate, base, fileUrls); changes.setActivationPolicy(DocumentChangeSet::DoNotActivate); changes.setUpdateHandling(DocumentChangeSet::NoUpdate); DocumentChangeSet::ChangeResult result = changes.applyAllChanges(); if (!result) { return; } int idx = 0; foreach(const SourceFileTemplate::OutputFile& out, fileTemplate.outputFiles()) { TemplatePreview* preview = nullptr; if (ui->tabWidget->count() > idx) { // reuse existing tab preview = qobject_cast(ui->tabWidget->widget(idx)); ui->tabWidget->setTabText(idx, out.label); Q_ASSERT(preview); } else { // create new tabs on demand preview = new TemplatePreview(page); ui->tabWidget->addTab(preview, out.label); } preview->document()->openUrl(fileUrls.value(out.identifier)); ++idx; } // remove superfluous tabs from last time while (ui->tabWidget->count() > fileUrls.size()) { delete ui->tabWidget->widget(fileUrls.size()); } return; } void TemplateSelectionPagePrivate::getMoreClicked() { KNS3::DownloadDialog(QStringLiteral("kdevfiletemplates.knsrc"), ui->view).exec(); model->refresh(); } void TemplateSelectionPagePrivate::loadFileClicked() { const QStringList filters{ QStringLiteral("application/x-desktop"), QStringLiteral("application/x-bzip-compressed-tar"), QStringLiteral("application/zip") }; QFileDialog dlg(page); dlg.setMimeTypeFilters(filters); dlg.setFileMode(QFileDialog::ExistingFiles); if (!dlg.exec()) { return; } foreach(const QString& fileName, dlg.selectedFiles()) { QString destination = model->loadTemplateFile(fileName); QModelIndexList indexes = model->templateIndexes(destination); int n = indexes.size(); if (n > 1) { ui->view->setCurrentIndex(indexes[1]); } } } void TemplateSelectionPage::saveConfig() { KSharedConfigPtr config; if (IProject* project = ICore::self()->projectController()->findProjectForUrl(d->assistant->baseUrl())) { config = project->projectConfiguration(); } else { config = ICore::self()->activeSession()->config(); } KConfigGroup group(config, FileTemplatesGroup); group.writeEntry(LastUsedTemplateEntry, d->selectedTemplate); group.sync(); } TemplateSelectionPage::TemplateSelectionPage(TemplateClassAssistant* parent, Qt::WindowFlags f) : QWidget(parent, f) , d(new TemplateSelectionPagePrivate(this)) { d->assistant = parent; d->ui = new Ui::TemplateSelection; d->ui->setupUi(this); d->model = new TemplatesModel(QStringLiteral("kdevfiletemplates"), this); d->model->refresh(); d->ui->view->setLevels(3); d->ui->view->setHeaderLabels(QStringList() << i18n("Language") << i18n("Framework") << i18n("Template")); d->ui->view->setModel(d->model); connect(d->ui->view, &MultiLevelListView::currentIndexChanged, this, [&] (const QModelIndex& index) { d->currentTemplateChanged(index); }); QModelIndex templateIndex = d->model->index(0, 0); while (templateIndex.child(0, 0).isValid()) { templateIndex = templateIndex.child(0, 0); } KSharedConfigPtr config; if (IProject* project = ICore::self()->projectController()->findProjectForUrl(d->assistant->baseUrl())) { config = project->projectConfiguration(); } else { config = ICore::self()->activeSession()->config(); } KConfigGroup group(config, FileTemplatesGroup); QString lastTemplate = group.readEntry(LastUsedTemplateEntry); QModelIndexList indexes = d->model->match(d->model->index(0, 0), TemplatesModel::DescriptionFileRole, lastTemplate, 1, Qt::MatchRecursive); if (!indexes.isEmpty()) { templateIndex = indexes.first(); } d->ui->view->setCurrentIndex(templateIndex); QPushButton* getMoreButton = new QPushButton(i18n("Get More Templates..."), d->ui->view); getMoreButton->setIcon(QIcon::fromTheme(QStringLiteral("get-hot-new-stuff"))); connect (getMoreButton, &QPushButton::clicked, this, [&] { d->getMoreClicked(); }); d->ui->view->addWidget(0, getMoreButton); QPushButton* loadButton = new QPushButton(QIcon::fromTheme(QStringLiteral("application-x-archive")), i18n("Load Template From File"), d->ui->view); connect (loadButton, &QPushButton::clicked, this, [&] { d->loadFileClicked(); }); d->ui->view->addWidget(0, loadButton); d->ui->view->setContentsMargins(0, 0, 0, 0); } TemplateSelectionPage::~TemplateSelectionPage() { delete d->ui; delete d; } QSize TemplateSelectionPage::minimumSizeHint() const { return QSize(400, 600); } QString TemplateSelectionPage::selectedTemplate() const { return d->selectedTemplate; } #include "moc_templateselectionpage.cpp" diff --git a/plugins/git/gitplugin.cpp b/plugins/git/gitplugin.cpp index 3820c0ed5..c5c7f8f5a 100644 --- a/plugins/git/gitplugin.cpp +++ b/plugins/git/gitplugin.cpp @@ -1,1510 +1,1510 @@ /*************************************************************************** * Copyright 2008 Evgeniy Ivanov * * Copyright 2009 Hugo Parente Lima * * Copyright 2010 Aleix Pol Gonzalez * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "gitplugin.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gitclonejob.h" #include #include #include "stashmanagerdialog.h" #include #include #include #include #include #include #include "gitjob.h" #include "gitmessagehighlighter.h" #include "gitplugincheckinrepositoryjob.h" #include "gitnameemaildialog.h" #include "debug.h" Q_LOGGING_CATEGORY(PLUGIN_GIT, "kdevplatform.plugins.git") using namespace KDevelop; QVariant runSynchronously(KDevelop::VcsJob* job) { QVariant ret; if(job->exec() && job->status()==KDevelop::VcsJob::JobSucceeded) { ret = job->fetchResults(); } delete job; return ret; } namespace { QDir dotGitDirectory(const QUrl& dirPath) { const QFileInfo finfo(dirPath.toLocalFile()); QDir dir = finfo.isDir() ? QDir(finfo.filePath()): finfo.absoluteDir(); static const QString gitDir = QStringLiteral(".git"); while (!dir.exists(gitDir) && dir.cdUp()) {} // cdUp, until there is a sub-directory called .git if (dir.isRoot()) { qCWarning(PLUGIN_GIT) << "couldn't find the git root for" << dirPath; } return dir; } /** * Whenever a directory is provided, change it for all the files in it but not inner directories, * that way we make sure we won't get into recursion, */ static QList preventRecursion(const QList& urls) { QList ret; foreach(const QUrl& url, urls) { QDir d(url.toLocalFile()); if(d.exists()) { QStringList entries = d.entryList(QDir::Files | QDir::NoDotAndDotDot); foreach(const QString& entry, entries) { QUrl entryUrl = QUrl::fromLocalFile(d.absoluteFilePath(entry)); ret += entryUrl; } } else ret += url; } return ret; } QString toRevisionName(const KDevelop::VcsRevision& rev, QString currentRevision=QString()) { switch(rev.revisionType()) { case VcsRevision::Special: switch(rev.revisionValue().value()) { case VcsRevision::Head: return QStringLiteral("^HEAD"); case VcsRevision::Base: return QString(); case VcsRevision::Working: return QString(); case VcsRevision::Previous: Q_ASSERT(!currentRevision.isEmpty()); return currentRevision + "^1"; case VcsRevision::Start: return QString(); case VcsRevision::UserSpecialType: //Not used Q_ASSERT(false && "i don't know how to do that"); } break; case VcsRevision::GlobalNumber: return rev.revisionValue().toString(); case VcsRevision::Date: case VcsRevision::FileNumber: case VcsRevision::Invalid: case VcsRevision::UserSpecialType: Q_ASSERT(false); } return QString(); } QString revisionInterval(const KDevelop::VcsRevision& rev, const KDevelop::VcsRevision& limit) { QString ret; if(rev.revisionType()==VcsRevision::Special && rev.revisionValue().value()==VcsRevision::Start) //if we want it to the beginning just put the revisionInterval ret = toRevisionName(limit, QString()); else { QString dst = toRevisionName(limit); if(dst.isEmpty()) ret = dst; else { QString src = toRevisionName(rev, dst); if(src.isEmpty()) ret = src; else ret = src+".."+dst; } } return ret; } QDir urlDir(const QUrl& url) { QFileInfo f(url.toLocalFile()); if(f.isDir()) return QDir(url.toLocalFile()); else return f.absoluteDir(); } QDir urlDir(const QList& urls) { return urlDir(urls.first()); } //TODO: could be improved } GitPlugin::GitPlugin( QObject *parent, const QVariantList & ) : DistributedVersionControlPlugin(parent, QStringLiteral("kdevgit")), m_oldVersion(false), m_usePrefix(true) { if (QStandardPaths::findExecutable(QStringLiteral("git")).isEmpty()) { setErrorDescription(i18n("Unable to find git executable. Is it installed on the system?")); return; } setObjectName(QStringLiteral("Git")); DVcsJob* versionJob = new DVcsJob(QDir::tempPath(), this, KDevelop::OutputJob::Silent); *versionJob << "git" << "--version"; connect(versionJob, &DVcsJob::readyForParsing, this, &GitPlugin::parseGitVersionOutput); ICore::self()->runController()->registerJob(versionJob); m_watcher = new KDirWatch(this); connect(m_watcher, &KDirWatch::dirty, this, &GitPlugin::fileChanged); connect(m_watcher, &KDirWatch::created, this, &GitPlugin::fileChanged); } GitPlugin::~GitPlugin() {} bool emptyOutput(DVcsJob* job) { QScopedPointer _job(job); if(job->exec() && job->status()==VcsJob::JobSucceeded) return job->rawOutput().trimmed().isEmpty(); return false; } bool GitPlugin::hasStashes(const QDir& repository) { return !emptyOutput(gitStash(repository, QStringList(QStringLiteral("list")), KDevelop::OutputJob::Silent)); } bool GitPlugin::hasModifications(const QDir& d) { return !emptyOutput(lsFiles(d, QStringList(QStringLiteral("-m")), OutputJob::Silent)); } bool GitPlugin::hasModifications(const QDir& repo, const QUrl& file) { return !emptyOutput(lsFiles(repo, QStringList() << QStringLiteral("-m") << file.path(), OutputJob::Silent)); } void GitPlugin::additionalMenuEntries(QMenu* menu, const QList& urls) { m_urls = urls; QDir dir=urlDir(urls); bool hasSt = hasStashes(dir); menu->addSeparator()->setText(i18n("Git Stashes")); menu->addAction(i18n("Stash Manager"), this, SLOT(ctxStashManager()))->setEnabled(hasSt); menu->addAction(i18n("Push Stash"), this, SLOT(ctxPushStash())); menu->addAction(i18n("Pop Stash"), this, SLOT(ctxPopStash()))->setEnabled(hasSt); } void GitPlugin::ctxPushStash() { VcsJob* job = gitStash(urlDir(m_urls), QStringList(), KDevelop::OutputJob::Verbose); ICore::self()->runController()->registerJob(job); } void GitPlugin::ctxPopStash() { VcsJob* job = gitStash(urlDir(m_urls), QStringList(QStringLiteral("pop")), KDevelop::OutputJob::Verbose); ICore::self()->runController()->registerJob(job); } void GitPlugin::ctxStashManager() { QPointer d = new StashManagerDialog(urlDir(m_urls), this, nullptr); d->exec(); delete d; } DVcsJob* GitPlugin::errorsFound(const QString& error, KDevelop::OutputJob::OutputJobVerbosity verbosity=OutputJob::Verbose) { DVcsJob* j = new DVcsJob(QDir::temp(), this, verbosity); *j << "echo" << i18n("error: %1", error) << "-n"; return j; } QString GitPlugin::name() const { return QStringLiteral("Git"); } QUrl GitPlugin::repositoryRoot(const QUrl& path) { return QUrl::fromLocalFile(dotGitDirectory(path).absolutePath()); } bool GitPlugin::isValidDirectory(const QUrl & dirPath) { QDir dir=dotGitDirectory(dirPath); return dir.exists(QStringLiteral(".git/HEAD")); } bool GitPlugin::isVersionControlled(const QUrl &path) { QFileInfo fsObject(path.toLocalFile()); if (!fsObject.exists()) { return false; } if (fsObject.isDir()) { return isValidDirectory(path); } QString filename = fsObject.fileName(); QStringList otherFiles = getLsFiles(fsObject.dir(), QStringList(QStringLiteral("--")) << filename, KDevelop::OutputJob::Silent); return !otherFiles.empty(); } VcsJob* GitPlugin::init(const QUrl &directory) { DVcsJob* job = new DVcsJob(urlDir(directory), this); job->setType(VcsJob::Import); *job << "git" << "init"; return job; } VcsJob* GitPlugin::createWorkingCopy(const KDevelop::VcsLocation & source, const QUrl& dest, KDevelop::IBasicVersionControl::RecursionMode) { DVcsJob* job = new GitCloneJob(urlDir(dest), this); job->setType(VcsJob::Import); *job << "git" << "clone" << "--progress" << "--" << source.localUrl().url() << dest; return job; } VcsJob* GitPlugin::add(const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion) { if (localLocations.empty()) return errorsFound(i18n("Did not specify the list of files"), OutputJob::Verbose); DVcsJob* job = new GitJob(dotGitDirectory(localLocations.front()), this); job->setType(VcsJob::Add); *job << "git" << "add" << "--" << (recursion == IBasicVersionControl::Recursive ? localLocations : preventRecursion(localLocations)); return job; } KDevelop::VcsJob* GitPlugin::status(const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion) { if (localLocations.empty()) return errorsFound(i18n("Did not specify the list of files"), OutputJob::Verbose); DVcsJob* job = new GitJob(urlDir(localLocations), this, OutputJob::Silent); job->setType(VcsJob::Status); if(m_oldVersion) { *job << "git" << "ls-files" << "-t" << "-m" << "-c" << "-o" << "-d" << "-k" << "--directory"; connect(job, &DVcsJob::readyForParsing, this, &GitPlugin::parseGitStatusOutput_old); } else { *job << "git" << "status" << "--porcelain"; job->setIgnoreError(true); connect(job, &DVcsJob::readyForParsing, this, &GitPlugin::parseGitStatusOutput); } *job << "--" << (recursion == IBasicVersionControl::Recursive ? localLocations : preventRecursion(localLocations)); return job; } VcsJob* GitPlugin::diff(const QUrl& fileOrDirectory, const KDevelop::VcsRevision& srcRevision, const KDevelop::VcsRevision& dstRevision, VcsDiff::Type /*type*/, IBasicVersionControl::RecursionMode recursion) { //TODO: control different types DVcsJob* job = new GitJob(dotGitDirectory(fileOrDirectory), this, KDevelop::OutputJob::Silent); job->setType(VcsJob::Diff); *job << "git" << "diff" << "--no-color" << "--no-ext-diff"; if (!usePrefix()) { // KDE's ReviewBoard now requires p1 patchfiles, so `git diff --no-prefix` to generate p0 patches // has become optional. *job << "--no-prefix"; } if (dstRevision.revisionType() == VcsRevision::Special && dstRevision.specialType() == VcsRevision::Working) { if (srcRevision.revisionType() == VcsRevision::Special && srcRevision.specialType() == VcsRevision::Base) { *job << "HEAD"; } else { *job << "--cached" << srcRevision.revisionValue().toString(); } } else { QString revstr = revisionInterval(srcRevision, dstRevision); if(!revstr.isEmpty()) *job << revstr; } *job << "--"; if (recursion == IBasicVersionControl::Recursive) { *job << fileOrDirectory; } else { *job << preventRecursion(QList() << fileOrDirectory); } connect(job, &DVcsJob::readyForParsing, this, &GitPlugin::parseGitDiffOutput); return job; } VcsJob* GitPlugin::revert(const QList& localLocations, IBasicVersionControl::RecursionMode recursion) { if(localLocations.isEmpty() ) return errorsFound(i18n("Could not revert changes"), OutputJob::Verbose); QDir repo = urlDir(repositoryRoot(localLocations.first())); QString modified; for (const auto& file: localLocations) { if (hasModifications(repo, file)) { modified.append(file.toDisplayString(QUrl::PreferLocalFile) + "
"); } } if (!modified.isEmpty()) { auto res = KMessageBox::questionYesNo(nullptr, i18n("The following files have uncommited changes, " "which will be lost. Continue?") + "

" + modified); if (res != KMessageBox::Yes) { return errorsFound(QString(), OutputJob::Silent); } } DVcsJob* job = new GitJob(dotGitDirectory(localLocations.front()), this); job->setType(VcsJob::Revert); *job << "git" << "checkout" << "--"; *job << (recursion == IBasicVersionControl::Recursive ? localLocations : preventRecursion(localLocations)); return job; } //TODO: git doesn't like empty messages, but "KDevelop didn't provide any message, it may be a bug" looks ugly... //If no files specified then commit already added files VcsJob* GitPlugin::commit(const QString& message, const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion) { if (localLocations.empty() || message.isEmpty()) return errorsFound(i18n("No files or message specified")); const QDir dir = dotGitDirectory(localLocations.front()); if (!ensureValidGitIdentity(dir)) { return errorsFound(i18n("Email or name for Git not specified")); } DVcsJob* job = new DVcsJob(dir, this); job->setType(VcsJob::Commit); QList files = (recursion == IBasicVersionControl::Recursive ? localLocations : preventRecursion(localLocations)); addNotVersionedFiles(dir, files); *job << "git" << "commit" << "-m" << message; *job << "--" << files; return job; } bool GitPlugin::ensureValidGitIdentity(const QDir& dir) { const QUrl url = QUrl::fromLocalFile(dir.absolutePath()); const QString name = readConfigOption(url, QStringLiteral("user.name")); const QString email = readConfigOption(url, QStringLiteral("user.email")); if (!email.isEmpty() && !name.isEmpty()) { return true; // already okay } GitNameEmailDialog dialog; dialog.setName(name); dialog.setEmail(email); if (!dialog.exec()) { return false; } runSynchronously(setConfigOption(url, QStringLiteral("user.name"), dialog.name(), dialog.isGlobal())); runSynchronously(setConfigOption(url, QStringLiteral("user.email"), dialog.email(), dialog.isGlobal())); return true; } void GitPlugin::addNotVersionedFiles(const QDir& dir, const QList& files) { QStringList otherStr = getLsFiles(dir, QStringList() << QStringLiteral("--others"), KDevelop::OutputJob::Silent); QList toadd, otherFiles; foreach(const QString& file, otherStr) { QUrl v = QUrl::fromLocalFile(dir.absoluteFilePath(file)); otherFiles += v; } //We add the files that are not versioned foreach(const QUrl& file, files) { if(otherFiles.contains(file) && QFileInfo(file.toLocalFile()).isFile()) toadd += file; } if(!toadd.isEmpty()) { VcsJob* job = add(toadd); job->exec(); } } bool isEmptyDirStructure(const QDir &dir) { foreach (const QFileInfo &i, dir.entryInfoList(QDir::AllEntries | QDir::NoDotAndDotDot)) { if (i.isDir()) { if (!isEmptyDirStructure(QDir(i.filePath()))) return false; } else if (i.isFile()) { return false; } } return true; } VcsJob* GitPlugin::remove(const QList& files) { if (files.isEmpty()) return errorsFound(i18n("No files to remove")); QDir dotGitDir = dotGitDirectory(files.front()); QList files_(files); QMutableListIterator i(files_); while (i.hasNext()) { QUrl file = i.next(); QFileInfo fileInfo(file.toLocalFile()); QStringList otherStr = getLsFiles(dotGitDir, QStringList() << QStringLiteral("--others") << QStringLiteral("--") << file.toLocalFile(), KDevelop::OutputJob::Silent); if(!otherStr.isEmpty()) { //remove files not under version control QList otherFiles; foreach(const QString &f, otherStr) { otherFiles << QUrl::fromLocalFile(dotGitDir.path()+'/'+f); } if (fileInfo.isFile()) { //if it's an unversioned file we are done, don't use git rm on it i.remove(); } auto trashJob = KIO::trash(otherFiles); trashJob->exec(); qCDebug(PLUGIN_GIT) << "other files" << otherFiles; } if (fileInfo.isDir()) { if (isEmptyDirStructure(QDir(file.toLocalFile()))) { //remove empty folders, git doesn't do that auto trashJob = KIO::trash(file); trashJob->exec(); qCDebug(PLUGIN_GIT) << "empty folder, removing" << file; //we already deleted it, don't use git rm on it i.remove(); } } } if (files_.isEmpty()) return nullptr; DVcsJob* job = new GitJob(dotGitDir, this); job->setType(VcsJob::Remove); // git refuses to delete files with local modifications // use --force to overcome this *job << "git" << "rm" << "-r" << "--force"; *job << "--" << files_; return job; } VcsJob* GitPlugin::log(const QUrl& localLocation, const KDevelop::VcsRevision& src, const KDevelop::VcsRevision& dst) { DVcsJob* job = new GitJob(dotGitDirectory(localLocation), this, KDevelop::OutputJob::Silent); job->setType(VcsJob::Log); *job << "git" << "log" << "--date=raw" << "--name-status" << "-M80%" << "--follow"; QString rev = revisionInterval(dst, src); if(!rev.isEmpty()) *job << rev; *job << "--" << localLocation; connect(job, &DVcsJob::readyForParsing, this, &GitPlugin::parseGitLogOutput); return job; } VcsJob* GitPlugin::log(const QUrl& localLocation, const KDevelop::VcsRevision& rev, unsigned long int limit) { DVcsJob* job = new GitJob(dotGitDirectory(localLocation), this, KDevelop::OutputJob::Silent); job->setType(VcsJob::Log); *job << "git" << "log" << "--date=raw" << "--name-status" << "-M80%" << "--follow"; QString revStr = toRevisionName(rev, QString()); if(!revStr.isEmpty()) *job << revStr; if(limit>0) *job << QStringLiteral("-%1").arg(limit); *job << "--" << localLocation; connect(job, &DVcsJob::readyForParsing, this, &GitPlugin::parseGitLogOutput); return job; } KDevelop::VcsJob* GitPlugin::annotate(const QUrl &localLocation, const KDevelop::VcsRevision&) { DVcsJob* job = new GitJob(dotGitDirectory(localLocation), this, KDevelop::OutputJob::Silent); job->setType(VcsJob::Annotate); *job << "git" << "blame" << "--porcelain" << "-w"; *job << "--" << localLocation; connect(job, &DVcsJob::readyForParsing, this, &GitPlugin::parseGitBlameOutput); return job; } void GitPlugin::parseGitBlameOutput(DVcsJob *job) { QVariantList results; VcsAnnotationLine* annotation = nullptr; const auto output = job->output(); const auto lines = output.splitRef('\n'); bool skipNext=false; QMap definedRevisions; for(QVector::const_iterator it=lines.constBegin(), itEnd=lines.constEnd(); it!=itEnd; ++it) { if(skipNext) { skipNext=false; results += qVariantFromValue(*annotation); continue; } if(it->isEmpty()) continue; QStringRef name = it->left(it->indexOf(' ')); QStringRef value = it->right(it->size()-name.size()-1); if(name==QLatin1String("author")) annotation->setAuthor(value.toString()); else if(name==QLatin1String("author-mail")) {} //TODO: do smth with the e-mail? else if(name==QLatin1String("author-tz")) {} //TODO: does it really matter? else if(name==QLatin1String("author-time")) annotation->setDate(QDateTime::fromTime_t(value.toUInt())); else if(name==QLatin1String("summary")) annotation->setCommitMessage(value.toString()); else if(name.startsWith(QStringLiteral("committer"))) {} //We will just store the authors else if(name==QLatin1String("previous")) {} //We don't need that either else if(name==QLatin1String("filename")) { skipNext=true; } else if(name==QLatin1String("boundary")) { definedRevisions.insert(QStringLiteral("boundary"), VcsAnnotationLine()); } else { const auto values = value.split(' '); VcsRevision rev; rev.setRevisionValue(name.left(8).toString(), KDevelop::VcsRevision::GlobalNumber); skipNext = definedRevisions.contains(name.toString()); if(!skipNext) definedRevisions.insert(name.toString(), VcsAnnotationLine()); annotation = &definedRevisions[name.toString()]; annotation->setLineNumber(values[1].toInt() - 1); annotation->setRevision(rev); } } job->setResults(results); } DVcsJob* GitPlugin::lsFiles(const QDir &repository, const QStringList &args, OutputJob::OutputJobVerbosity verbosity) { DVcsJob* job = new DVcsJob(repository, this, verbosity); *job << "git" << "ls-files" << args; return job; } DVcsJob* GitPlugin::gitStash(const QDir& repository, const QStringList& args, OutputJob::OutputJobVerbosity verbosity) { DVcsJob* job = new DVcsJob(repository, this, verbosity); *job << "git" << "stash" << args; return job; } VcsJob* GitPlugin::tag(const QUrl& repository, const QString& commitMessage, const VcsRevision& rev, const QString& tagName) { DVcsJob* job = new DVcsJob(urlDir(repository), this); *job << "git" << "tag" << "-m" << commitMessage << tagName; if(rev.revisionValue().isValid()) *job << rev.revisionValue().toString(); return job; } VcsJob* GitPlugin::switchBranch(const QUrl &repository, const QString &branch) { QDir d=urlDir(repository); if(hasModifications(d) && KMessageBox::questionYesNo(nullptr, i18n("There are pending changes, do you want to stash them first?"))==KMessageBox::Yes) { QScopedPointer stash(gitStash(d, QStringList(), KDevelop::OutputJob::Verbose)); stash->exec(); } DVcsJob* job = new DVcsJob(d, this); *job << "git" << "checkout" << branch; return job; } VcsJob* GitPlugin::branch(const QUrl& repository, const KDevelop::VcsRevision& rev, const QString& branchName) { Q_ASSERT(!branchName.isEmpty()); DVcsJob* job = new DVcsJob(urlDir(repository), this); *job << "git" << "branch" << "--" << branchName; if(!rev.prettyValue().isEmpty()) *job << rev.revisionValue().toString(); return job; } VcsJob* GitPlugin::deleteBranch(const QUrl& repository, const QString& branchName) { DVcsJob* job = new DVcsJob(urlDir(repository), this, OutputJob::Silent); *job << "git" << "branch" << "-D" << branchName; connect(job, &DVcsJob::readyForParsing, this, &GitPlugin::parseGitCurrentBranch); return job; } VcsJob* GitPlugin::renameBranch(const QUrl& repository, const QString& oldBranchName, const QString& newBranchName) { DVcsJob* job = new DVcsJob(urlDir(repository), this, OutputJob::Silent); *job << "git" << "branch" << "-m" << newBranchName << oldBranchName; connect(job, &DVcsJob::readyForParsing, this, &GitPlugin::parseGitCurrentBranch); return job; } VcsJob* GitPlugin::mergeBranch(const QUrl& repository, const QString& branchName) { Q_ASSERT(!branchName.isEmpty()); DVcsJob* job = new DVcsJob(urlDir(repository), this); *job << "git" << "merge" << branchName; return job; } VcsJob* GitPlugin::currentBranch(const QUrl& repository) { DVcsJob* job = new DVcsJob(urlDir(repository), this, OutputJob::Silent); job->setIgnoreError(true); *job << "git" << "symbolic-ref" << "-q" << "--short" << "HEAD"; connect(job, &DVcsJob::readyForParsing, this, &GitPlugin::parseGitCurrentBranch); return job; } void GitPlugin::parseGitCurrentBranch(DVcsJob* job) { QString out = job->output().trimmed(); job->setResults(out); } VcsJob* GitPlugin::branches(const QUrl &repository) { DVcsJob* job=new DVcsJob(urlDir(repository)); *job << "git" << "branch" << "-a"; connect(job, &DVcsJob::readyForParsing, this, &GitPlugin::parseGitBranchOutput); return job; } void GitPlugin::parseGitBranchOutput(DVcsJob* job) { const auto output = job->output(); const auto branchListDirty = output.splitRef('\n', QString::SkipEmptyParts); QStringList branchList; foreach(const auto & branch, branchListDirty) { // Skip pointers to another branches (one example of this is "origin/HEAD -> origin/master"); // "git rev-list" chokes on these entries and we do not need duplicate branches altogether. if (branch.contains(QStringLiteral("->"))) continue; // Skip entries such as '(no branch)' if (branch.contains(QStringLiteral("(no branch)"))) continue; QStringRef name = branch; if (name.startsWith('*')) name = branch.right(branch.size()-2); branchList << name.trimmed().toString(); } job->setResults(branchList); } /* Few words about how this hardcore works: 1. get all commits (with --paretns) 2. select master (root) branch and get all unicial commits for branches (git-rev-list br2 ^master ^br3) 3. parse allCommits. While parsing set mask (columns state for every row) for BRANCH, INITIAL, CROSS, MERGE and INITIAL are also set in DVCScommit::setParents (depending on parents count) another setType(INITIAL) is used for "bottom/root/first" commits of branches 4. find and set merges, HEADS. It's an ittaration through all commits. - first we check if parent is from the same branch, if no then we go through all commits searching parent's index and set CROSS/HCROSS for rows (in 3 rows are set EMPTY after commit with parent from another tree met) - then we check branchesShas[i][0] to mark heads 4 can be a seporate function. TODO: All this porn require refactoring (rewriting is better)! It's a very dirty implementation. FIXME: 1. HEAD which is head has extra line to connect it with further commit 2. If you menrge branch2 to master, only new commits of branch2 will be visible (it's fine, but there will be extra merge rectangle in master. If there are no extra commits in branch2, but there are another branches, then the place for branch2 will be empty (instead of be used for branch3). 3. Commits that have additional commit-data (not only history merging, but changes to fix conflicts) are shown incorrectly */ QList GitPlugin::getAllCommits(const QString &repo) { initBranchHash(repo); QStringList args; args << QStringLiteral("--all") << QStringLiteral("--pretty") << QStringLiteral("--parents"); QScopedPointer job(gitRevList(repo, args)); bool ret = job->exec(); Q_ASSERT(ret && job->status()==VcsJob::JobSucceeded && "TODO: provide a fall back in case of failing"); Q_UNUSED(ret); QStringList commits = job->output().split('\n', QString::SkipEmptyParts); static QRegExp rx_com("commit \\w{40,40}"); QListcommitList; DVcsEvent item; //used to keep where we have empty/cross/branch entry //true if it's an active branch (then cross or branch) and false if not QVector additionalFlags(branchesShas.count()); additionalFlags.fill(false); //parse output for(int i = 0; i < commits.count(); ++i) { if (commits[i].contains(rx_com)) { qCDebug(PLUGIN_GIT) << "commit found in " << commits[i]; item.setCommit(commits[i].section(' ', 1, 1).trimmed()); // qCDebug(PLUGIN_GIT) << "commit is: " << commits[i].section(' ', 1); QStringList parents; QString parent = commits[i].section(' ', 2); int section = 2; while (!parent.isEmpty()) { /* qCDebug(PLUGIN_GIT) << "Parent is: " << parent;*/ parents.append(parent.trimmed()); section++; parent = commits[i].section(' ', section); } item.setParents(parents); //Avoid Merge string while (!commits[i].contains(QStringLiteral("Author: "))) ++i; item.setAuthor(commits[i].section(QStringLiteral("Author: "), 1).trimmed()); // qCDebug(PLUGIN_GIT) << "author is: " << commits[i].section("Author: ", 1); item.setDate(commits[++i].section(QStringLiteral("Date: "), 1).trimmed()); // qCDebug(PLUGIN_GIT) << "date is: " << commits[i].section("Date: ", 1); QString log; i++; //next line! while (i < commits.count() && !commits[i].contains(rx_com)) log += commits[i++]; --i; //while took commit line item.setLog(log.trimmed()); // qCDebug(PLUGIN_GIT) << "log is: " << log; //mask is used in CommitViewDelegate to understand what we should draw for each branch QList mask; //set mask (properties for each graph column in row) for(int i = 0; i < branchesShas.count(); ++i) { qCDebug(PLUGIN_GIT)<<"commit: " << item.getCommit(); if (branchesShas[i].contains(item.getCommit())) { mask.append(item.getType()); //we set type in setParents //check if parent from the same branch, if not then we have found a root of the branch //and will use empty column for all futher (from top to bottom) revisions //FIXME: we should set CROSS between parent and child (and do it when find merge point) additionalFlags[i] = false; foreach(const QString &sha, item.getParents()) { if (branchesShas[i].contains(sha)) additionalFlags[i] = true; } if (additionalFlags[i] == false) item.setType(DVcsEvent::INITIAL); //hasn't parents from the same branch, used in drawing } else { if (additionalFlags[i] == false) mask.append(DVcsEvent::EMPTY); else mask.append(DVcsEvent::CROSS); } qCDebug(PLUGIN_GIT) << "mask " << i << "is " << mask[i]; } item.setProperties(mask); commitList.append(item); } } //find and set merges, HEADS, require refactoring! for(QList::iterator iter = commitList.begin(); iter != commitList.end(); ++iter) { QStringList parents = iter->getParents(); //we need only only child branches if (parents.count() != 1) break; QString parent = parents[0]; QString commit = iter->getCommit(); bool parent_checked = false; int heads_checked = 0; for(int i = 0; i < branchesShas.count(); ++i) { //check parent if (branchesShas[i].contains(commit)) { if (!branchesShas[i].contains(parent)) { //parent and child are not in same branch //since it is list, than parent has i+1 index //set CROSS and HCROSS for(QList::iterator f_iter = iter; f_iter != commitList.end(); ++f_iter) { if (parent == f_iter->getCommit()) { for(int j = 0; j < i; ++j) { if(branchesShas[j].contains(parent)) f_iter->setPropetry(j, DVcsEvent::MERGE); else f_iter->setPropetry(j, DVcsEvent::HCROSS); } f_iter->setType(DVcsEvent::MERGE); f_iter->setPropetry(i, DVcsEvent::MERGE_RIGHT); qCDebug(PLUGIN_GIT) << parent << " is parent of " << commit; qCDebug(PLUGIN_GIT) << f_iter->getCommit() << " is merge"; parent_checked = true; break; } else f_iter->setPropetry(i, DVcsEvent::CROSS); } } } //mark HEADs if (!branchesShas[i].empty() && commit == branchesShas[i][0]) { iter->setType(DVcsEvent::HEAD); iter->setPropetry(i, DVcsEvent::HEAD); heads_checked++; qCDebug(PLUGIN_GIT) << "HEAD found"; } //some optimization if (heads_checked == branchesShas.count() && parent_checked) break; } } return commitList; } void GitPlugin::initBranchHash(const QString &repo) { const QUrl repoUrl = QUrl::fromLocalFile(repo); QStringList gitBranches = runSynchronously(branches(repoUrl)).toStringList(); qCDebug(PLUGIN_GIT) << "BRANCHES: " << gitBranches; //Now root branch is the current branch. In future it should be the longest branch //other commitLists are got with git-rev-lits branch ^br1 ^ br2 QString root = runSynchronously(currentBranch(repoUrl)).toString(); QScopedPointer job(gitRevList(repo, QStringList(root))); bool ret = job->exec(); Q_ASSERT(ret && job->status()==VcsJob::JobSucceeded && "TODO: provide a fall back in case of failing"); Q_UNUSED(ret); QStringList commits = job->output().split('\n', QString::SkipEmptyParts); // qCDebug(PLUGIN_GIT) << "\n\n\n commits" << commits << "\n\n\n"; branchesShas.append(commits); foreach(const QString &branch, gitBranches) { if (branch == root) continue; QStringList args(branch); foreach(const QString &branch_arg, gitBranches) { if (branch_arg != branch) //man gitRevList for '^' args<<'^' + branch_arg; } QScopedPointer job(gitRevList(repo, args)); bool ret = job->exec(); Q_ASSERT(ret && job->status()==VcsJob::JobSucceeded && "TODO: provide a fall back in case of failing"); Q_UNUSED(ret); QStringList commits = job->output().split('\n', QString::SkipEmptyParts); // qCDebug(PLUGIN_GIT) << "\n\n\n commits" << commits << "\n\n\n"; branchesShas.append(commits); } } //Actually we can just copy the output without parsing. So it's a kind of draft for future void GitPlugin::parseLogOutput(const DVcsJob * job, QList& commits) const { // static QRegExp rx_sep( "[-=]+" ); // static QRegExp rx_date( "date:\\s+([^;]*);\\s+author:\\s+([^;]*).*" ); static QRegularExpression rx_com( "commit \\w{1,40}" ); const auto output = job->output(); const auto lines = output.splitRef('\n', QString::SkipEmptyParts); DVcsEvent item; QString commitLog; for (int i=0; i= 0x050500 if (rx_com.match(lines[i]).hasMatch()) { #else if (rx_com.match(lines[i].toString()).hasMatch()) { #endif // qCDebug(PLUGIN_GIT) << "MATCH COMMIT"; item.setCommit(lines[++i].toString()); item.setAuthor(lines[++i].toString()); item.setDate(lines[++i].toString()); item.setLog(commitLog); commits.append(item); } else { //FIXME: add this in a loop to the if, like in getAllCommits() commitLog += lines[i].toString() +'\n'; } } } VcsItemEvent::Actions actionsFromString(char c) { switch(c) { case 'A': return VcsItemEvent::Added; case 'D': return VcsItemEvent::Deleted; case 'R': return VcsItemEvent::Replaced; case 'M': return VcsItemEvent::Modified; } return VcsItemEvent::Modified; } void GitPlugin::parseGitLogOutput(DVcsJob * job) { static QRegExp commitRegex( "^commit (\\w{8})\\w{32}" ); static QRegExp infoRegex( "^(\\w+):(.*)" ); static QRegExp modificationsRegex("^([A-Z])[0-9]*\t([^\t]+)\t?(.*)", Qt::CaseSensitive, QRegExp::RegExp2); //R099 plugins/git/kdevgit.desktop plugins/git/kdevgit.desktop.cmake //M plugins/grepview/CMakeLists.txt QList commits; QString contents = job->output(); // check if git-log returned anything if (contents.isEmpty()) { job->setResults(commits); // empty list return; } // start parsing the output QTextStream s(&contents); VcsEvent item; QString message; bool pushCommit = false; while (!s.atEnd()) { QString line = s.readLine(); if (commitRegex.exactMatch(line)) { if (pushCommit) { item.setMessage(message.trimmed()); commits.append(QVariant::fromValue(item)); item.setItems(QList()); } else { pushCommit = true; } VcsRevision rev; rev.setRevisionValue(commitRegex.cap(1), KDevelop::VcsRevision::GlobalNumber); item.setRevision(rev); message.clear(); } else if (infoRegex.exactMatch(line)) { QString cap1 = infoRegex.cap(1); if (cap1 == QLatin1String("Author")) { item.setAuthor(infoRegex.cap(2).trimmed()); } else if (cap1 == QLatin1String("Date")) { item.setDate(QDateTime::fromTime_t(infoRegex.cap(2).trimmed().split(' ')[0].toUInt())); } } else if (modificationsRegex.exactMatch(line)) { VcsItemEvent::Actions a = actionsFromString(modificationsRegex.cap(1).at(0).toLatin1()); QString filenameA = modificationsRegex.cap(2); VcsItemEvent itemEvent; itemEvent.setActions(a); itemEvent.setRepositoryLocation(filenameA); if(a==VcsItemEvent::Replaced) { QString filenameB = modificationsRegex.cap(3); itemEvent.setRepositoryCopySourceLocation(filenameB); } item.addItem(itemEvent); } else if (line.startsWith(QLatin1String(" "))) { message += line.remove(0, 4); message += '\n'; } } item.setMessage(message.trimmed()); commits.append(QVariant::fromValue(item)); job->setResults(commits); } void GitPlugin::parseGitDiffOutput(DVcsJob* job) { VcsDiff diff; diff.setDiff(job->output()); diff.setBaseDiff(repositoryRoot(QUrl::fromLocalFile(job->directory().absolutePath()))); diff.setDepth(usePrefix()? 1 : 0); job->setResults(qVariantFromValue(diff)); } static VcsStatusInfo::State lsfilesToState(char id) { switch(id) { case 'H': return VcsStatusInfo::ItemUpToDate; //Cached case 'S': return VcsStatusInfo::ItemUpToDate; //Skip work tree case 'M': return VcsStatusInfo::ItemHasConflicts; //unmerged case 'R': return VcsStatusInfo::ItemDeleted; //removed/deleted case 'C': return VcsStatusInfo::ItemModified; //modified/changed case 'K': return VcsStatusInfo::ItemDeleted; //to be killed case '?': return VcsStatusInfo::ItemUnknown; //other } Q_ASSERT(false); return VcsStatusInfo::ItemUnknown; } void GitPlugin::parseGitStatusOutput_old(DVcsJob* job) { QStringList outputLines = job->output().split('\n', QString::SkipEmptyParts); QDir dir = job->directory(); QMap allStatus; foreach(const QString& line, outputLines) { VcsStatusInfo::State status = lsfilesToState(line[0].toLatin1()); QUrl url = QUrl::fromLocalFile(dir.absoluteFilePath(line.right(line.size()-2))); allStatus[url] = status; } QVariantList statuses; QMap< QUrl, VcsStatusInfo::State >::const_iterator it = allStatus.constBegin(), itEnd=allStatus.constEnd(); for(; it!=itEnd; ++it) { VcsStatusInfo status; status.setUrl(it.key()); status.setState(it.value()); statuses.append(qVariantFromValue(status)); } job->setResults(statuses); } void GitPlugin::parseGitStatusOutput(DVcsJob* job) { const auto output = job->output(); const auto outputLines = output.splitRef('\n', QString::SkipEmptyParts); QDir workingDir = job->directory(); QDir dotGit = dotGitDirectory(QUrl::fromLocalFile(workingDir.absolutePath())); QVariantList statuses; QList processedFiles; foreach(const QStringRef& line, outputLines) { //every line is 2 chars for the status, 1 space then the file desc QStringRef curr=line.right(line.size()-3); QStringRef state = line.left(2); int arrow = curr.indexOf(QStringLiteral(" -> ")); if(arrow>=0) { VcsStatusInfo status; status.setUrl(QUrl::fromLocalFile(dotGit.absoluteFilePath(curr.toString().left(arrow)))); status.setState(VcsStatusInfo::ItemDeleted); statuses.append(qVariantFromValue(status)); processedFiles += status.url(); curr = curr.mid(arrow+4); } if(curr.startsWith('\"') && curr.endsWith('\"')) { //if the path is quoted, unquote curr = curr.mid(1, curr.size()-2); } VcsStatusInfo status; status.setUrl(QUrl::fromLocalFile(dotGit.absoluteFilePath(curr.toString()))); status.setState(messageToState(state)); processedFiles.append(status.url()); qCDebug(PLUGIN_GIT) << "Checking git status for " << line << curr << status.state(); statuses.append(qVariantFromValue(status)); } QStringList paths; QStringList oldcmd=job->dvcsCommand(); QStringList::const_iterator it=oldcmd.constBegin()+oldcmd.indexOf(QStringLiteral("--"))+1, itEnd=oldcmd.constEnd(); for(; it!=itEnd; ++it) paths += *it; //here we add the already up to date files QStringList files = getLsFiles(job->directory(), QStringList() << QStringLiteral("-c") << QStringLiteral("--") << paths, OutputJob::Silent); foreach(const QString& file, files) { QUrl fileUrl = QUrl::fromLocalFile(workingDir.absoluteFilePath(file)); if(!processedFiles.contains(fileUrl)) { VcsStatusInfo status; status.setUrl(fileUrl); status.setState(VcsStatusInfo::ItemUpToDate); statuses.append(qVariantFromValue(status)); } } job->setResults(statuses); } void GitPlugin::parseGitVersionOutput(DVcsJob* job) { const auto output = job->output().trimmed(); auto versionString = output.midRef(output.lastIndexOf(' ')).split('.'); static const QList minimumVersion = QList() << 1 << 7; qCDebug(PLUGIN_GIT) << "checking git version" << versionString << "against" << minimumVersion; m_oldVersion = false; if (versionString.size() < minimumVersion.size()) { m_oldVersion = true; qCWarning(PLUGIN_GIT) << "invalid git version string:" << job->output().trimmed(); return; } foreach(int num, minimumVersion) { QStringRef curr = versionString.takeFirst(); int valcurr = curr.toInt(); if (valcurr < num) { m_oldVersion = true; break; } if (valcurr > num) { m_oldVersion = false; break; } } qCDebug(PLUGIN_GIT) << "the current git version is old: " << m_oldVersion; } QStringList GitPlugin::getLsFiles(const QDir &directory, const QStringList &args, KDevelop::OutputJob::OutputJobVerbosity verbosity) { QScopedPointer job(lsFiles(directory, args, verbosity)); if (job->exec() && job->status() == KDevelop::VcsJob::JobSucceeded) return job->output().split('\n', QString::SkipEmptyParts); return QStringList(); } DVcsJob* GitPlugin::gitRevParse(const QString &repository, const QStringList &args, KDevelop::OutputJob::OutputJobVerbosity verbosity) { DVcsJob* job = new DVcsJob(QDir(repository), this, verbosity); *job << "git" << "rev-parse" << args; return job; } DVcsJob* GitPlugin::gitRevList(const QString& directory, const QStringList& args) { DVcsJob* job = new DVcsJob(urlDir(QUrl::fromLocalFile(directory)), this, KDevelop::OutputJob::Silent); { *job << "git" << "rev-list" << args; return job; } } VcsStatusInfo::State GitPlugin::messageToState(const QStringRef& msg) { Q_ASSERT(msg.size()==1 || msg.size()==2); VcsStatusInfo::State ret = VcsStatusInfo::ItemUnknown; if(msg.contains('U') || msg == QLatin1String("AA") || msg == QLatin1String("DD")) ret = VcsStatusInfo::ItemHasConflicts; else switch(msg.at(0).toLatin1()) { case 'M': ret = VcsStatusInfo::ItemModified; break; case 'A': ret = VcsStatusInfo::ItemAdded; break; case 'R': ret = VcsStatusInfo::ItemModified; break; case 'C': ret = VcsStatusInfo::ItemHasConflicts; break; case ' ': ret = msg.at(1) == 'M' ? VcsStatusInfo::ItemModified : VcsStatusInfo::ItemDeleted; break; case 'D': ret = VcsStatusInfo::ItemDeleted; break; case '?': ret = VcsStatusInfo::ItemUnknown; break; default: qCDebug(PLUGIN_GIT) << "Git status not identified:" << msg; break; } return ret; } StandardJob::StandardJob(IPlugin* parent, KJob* job, OutputJob::OutputJobVerbosity verbosity) : VcsJob(parent, verbosity) , m_job(job) , m_plugin(parent) , m_status(JobNotStarted) {} void StandardJob::start() { connect(m_job, &KJob::result, this, &StandardJob::result); m_job->start(); m_status=JobRunning; } void StandardJob::result(KJob* job) { m_status=job->error() == 0? JobSucceeded : JobFailed; emitResult(); } VcsJob* GitPlugin::copy(const QUrl& localLocationSrc, const QUrl& localLocationDstn) { //TODO: Probably we should "git add" after return new StandardJob(this, KIO::copy(localLocationSrc, localLocationDstn), KDevelop::OutputJob::Silent); } VcsJob* GitPlugin::move(const QUrl& source, const QUrl& destination) { QDir dir = urlDir(source); QFileInfo fileInfo(source.toLocalFile()); if (fileInfo.isDir()) { if (isEmptyDirStructure(QDir(source.toLocalFile()))) { //move empty folder, git doesn't do that qCDebug(PLUGIN_GIT) << "empty folder" << source; return new StandardJob(this, KIO::move(source, destination), KDevelop::OutputJob::Silent); } } QStringList otherStr = getLsFiles(dir, QStringList() << QStringLiteral("--others") << QStringLiteral("--") << source.toLocalFile(), KDevelop::OutputJob::Silent); if(otherStr.isEmpty()) { DVcsJob* job = new DVcsJob(dir, this, KDevelop::OutputJob::Verbose); *job << "git" << "mv" << source.toLocalFile() << destination.toLocalFile(); return job; } else { return new StandardJob(this, KIO::move(source, destination), KDevelop::OutputJob::Silent); } } void GitPlugin::parseGitRepoLocationOutput(DVcsJob* job) { job->setResults(QVariant::fromValue(QUrl::fromLocalFile(job->output()))); } VcsJob* GitPlugin::repositoryLocation(const QUrl& localLocation) { DVcsJob* job = new DVcsJob(urlDir(localLocation), this); //Probably we should check first if origin is the proper remote we have to use but as a first attempt it works *job << "git" << "config" << "remote.origin.url"; connect(job, &DVcsJob::readyForParsing, this, &GitPlugin::parseGitRepoLocationOutput); return job; } VcsJob* GitPlugin::pull(const KDevelop::VcsLocation& localOrRepoLocationSrc, const QUrl& localRepositoryLocation) { DVcsJob* job = new DVcsJob(urlDir(localRepositoryLocation), this); job->setCommunicationMode(KProcess::MergedChannels); *job << "git" << "pull"; if(!localOrRepoLocationSrc.localUrl().isEmpty()) *job << localOrRepoLocationSrc.localUrl().url(); return job; } VcsJob* GitPlugin::push(const QUrl& localRepositoryLocation, const KDevelop::VcsLocation& localOrRepoLocationDst) { DVcsJob* job = new DVcsJob(urlDir(localRepositoryLocation), this); job->setCommunicationMode(KProcess::MergedChannels); *job << "git" << "push"; if(!localOrRepoLocationDst.localUrl().isEmpty()) *job << localOrRepoLocationDst.localUrl().url(); return job; } VcsJob* GitPlugin::resolve(const QList& localLocations, IBasicVersionControl::RecursionMode recursion) { return add(localLocations, recursion); } VcsJob* GitPlugin::update(const QList& localLocations, const KDevelop::VcsRevision& rev, IBasicVersionControl::RecursionMode recursion) { if(rev.revisionType()==VcsRevision::Special && rev.revisionValue().value()==VcsRevision::Head) { return pull(VcsLocation(), localLocations.first()); } else { DVcsJob* job = new DVcsJob(urlDir(localLocations.first()), this); { //Probably we should check first if origin is the proper remote we have to use but as a first attempt it works *job << "git" << "checkout" << rev.revisionValue().toString() << "--"; *job << (recursion == IBasicVersionControl::Recursive ? localLocations : preventRecursion(localLocations)); return job; } } } void GitPlugin::setupCommitMessageEditor(const QUrl& localLocation, KTextEdit* editor) const { new GitMessageHighlighter(editor); QFile mergeMsgFile(dotGitDirectory(localLocation).filePath(QStringLiteral(".git/MERGE_MSG"))); // Some limit on the file size should be set since whole content is going to be read into // the memory. 1Mb seems to be good value since it's rather strange to have so huge commit // message. static const qint64 maxMergeMsgFileSize = 1024*1024; if (mergeMsgFile.size() > maxMergeMsgFileSize || !mergeMsgFile.open(QIODevice::ReadOnly)) return; QString mergeMsg = QString::fromLocal8Bit(mergeMsgFile.read(maxMergeMsgFileSize)); editor->setPlainText(mergeMsg); } class GitVcsLocationWidget : public KDevelop::StandardVcsLocationWidget { Q_OBJECT public: - GitVcsLocationWidget(QWidget* parent = nullptr, Qt::WindowFlags f = nullptr) + explicit GitVcsLocationWidget(QWidget* parent = nullptr, Qt::WindowFlags f = nullptr) : StandardVcsLocationWidget(parent, f) {} bool isCorrect() const override { return !url().isEmpty(); } }; KDevelop::VcsLocationWidget* GitPlugin::vcsLocation(QWidget* parent) const { return new GitVcsLocationWidget(parent); } void GitPlugin::registerRepositoryForCurrentBranchChanges(const QUrl& repository) { QDir dir = dotGitDirectory(repository); QString headFile = dir.absoluteFilePath(QStringLiteral(".git/HEAD")); m_watcher->addFile(headFile); } void GitPlugin::fileChanged(const QString& file) { Q_ASSERT(file.endsWith(QStringLiteral("HEAD"))); //SMTH/.git/HEAD -> SMTH/ const QUrl fileUrl = Path(file).parent().parent().toUrl(); //We need to delay the emitted signal, otherwise the branch hasn't change yet //and the repository is not functional m_branchesChange.append(fileUrl); QTimer::singleShot(1000, this, SLOT(delayedBranchChanged())); } void GitPlugin::delayedBranchChanged() { emit repositoryBranchChanged(m_branchesChange.takeFirst()); } CheckInRepositoryJob* GitPlugin::isInRepository(KTextEditor::Document* document) { CheckInRepositoryJob* job = new GitPluginCheckInRepositoryJob(document, repositoryRoot(document->url()).path()); job->start(); return job; } DVcsJob* GitPlugin::setConfigOption(const QUrl& repository, const QString& key, const QString& value, bool global) { auto job = new DVcsJob(urlDir(repository), this); QStringList args; args << "git" << "config"; if(global) args << "--global"; args << key << value; *job << args; return job; } QString GitPlugin::readConfigOption(const QUrl& repository, const QString& key) { QProcess exec; exec.setWorkingDirectory(urlDir(repository).absolutePath()); exec.start("git", QStringList() << "config" << "--get" << key); exec.waitForFinished(); return exec.readAllStandardOutput().trimmed(); } #include "gitplugin.moc" diff --git a/plugins/konsole/kdevkonsoleviewplugin.cpp b/plugins/konsole/kdevkonsoleviewplugin.cpp index cb781cd82..ffe68b818 100644 --- a/plugins/konsole/kdevkonsoleviewplugin.cpp +++ b/plugins/konsole/kdevkonsoleviewplugin.cpp @@ -1,89 +1,89 @@ /*************************************************************************** * Copyright 2003, 2006 Adam Treat * * Copyright 2007 Andreas Pakulat * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "kdevkonsoleviewplugin.h" #include #include #include #include #include "kdevkonsoleview.h" #include "debug.h" Q_LOGGING_CATEGORY(PLUGIN_KONSOLE, "kdevplatform.plugins.konsole") QObject* createKonsoleView( QWidget*, QObject* op, const QVariantList& args) { KService::Ptr service = KService::serviceByDesktopName(QStringLiteral("konsolepart")); KPluginFactory* factory = nullptr; if (service) { factory = KPluginLoader(*service.data()).factory(); } if (!factory) { qWarning() << "Failed to load 'konsolepart' plugin"; } return new KDevKonsoleViewPlugin(factory, op, args); } K_PLUGIN_FACTORY_WITH_JSON(KonsoleViewFactory, "kdevkonsoleview.json", registerPlugin( QString(), &createKonsoleView );) class KDevKonsoleViewFactory: public KDevelop::IToolViewFactory{ public: - KDevKonsoleViewFactory(KDevKonsoleViewPlugin *plugin): + explicit KDevKonsoleViewFactory(KDevKonsoleViewPlugin *plugin): mplugin(plugin) {} QWidget* create(QWidget *parent = nullptr) override { return new KDevKonsoleView(mplugin, parent); } Qt::DockWidgetArea defaultPosition() override { return Qt::BottomDockWidgetArea; } QString id() const override { return QStringLiteral("org.kdevelop.KonsoleView"); } private: KDevKonsoleViewPlugin *mplugin; }; KDevKonsoleViewPlugin::KDevKonsoleViewPlugin( KPluginFactory* konsoleFactory, QObject *parent, const QVariantList & ) : KDevelop::IPlugin( QStringLiteral("kdevkonsoleview"), parent ) , m_konsoleFactory(konsoleFactory) , m_viewFactory(konsoleFactory ? new KDevKonsoleViewFactory(this) : nullptr) { if(!m_viewFactory) { setErrorDescription(i18n("Failed to load 'konsolepart' plugin")); } else { core()->uiController()->addToolView(QStringLiteral("Konsole"), m_viewFactory); } } void KDevKonsoleViewPlugin::unload() { if (m_viewFactory) { core()->uiController()->removeToolView(m_viewFactory); } } KPluginFactory* KDevKonsoleViewPlugin::konsoleFactory() const { return m_konsoleFactory; } KDevKonsoleViewPlugin::~KDevKonsoleViewPlugin() { } #include "kdevkonsoleviewplugin.moc" diff --git a/plugins/outlineview/outlineviewplugin.cpp b/plugins/outlineview/outlineviewplugin.cpp index e1cacfb46..b0c60e849 100644 --- a/plugins/outlineview/outlineviewplugin.cpp +++ b/plugins/outlineview/outlineviewplugin.cpp @@ -1,76 +1,76 @@ /* * KDevelop outline view * Copyright 2010, 2015 Alex Richardson * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "outlineviewplugin.h" #include #include #include #include #include #include "debug_outline.h" #include "outlinewidget.h" K_PLUGIN_FACTORY_WITH_JSON(KDevOutlineViewFactory, "kdevoutlineview.json", registerPlugin();) Q_LOGGING_CATEGORY(PLUGIN_OUTLINE, "kdevplatform.plugins.outline") using namespace KDevelop; class OutlineViewFactory: public KDevelop::IToolViewFactory { public: - OutlineViewFactory(OutlineViewPlugin *plugin) : m_plugin(plugin) {} + explicit OutlineViewFactory(OutlineViewPlugin *plugin) : m_plugin(plugin) {} QWidget* create(QWidget *parent = nullptr) override { return new OutlineWidget(parent, m_plugin); } Qt::DockWidgetArea defaultPosition() override { return Qt::RightDockWidgetArea; } QString id() const override { return QStringLiteral("org.kdevelop.OutlineView"); } private: OutlineViewPlugin *m_plugin; }; OutlineViewPlugin::OutlineViewPlugin(QObject *parent, const QVariantList&) : KDevelop::IPlugin(QStringLiteral("kdevoutlineview"), parent) , m_factory(new OutlineViewFactory(this)) { core()->uiController()->addToolView(i18n("Outline"), m_factory); } OutlineViewPlugin::~OutlineViewPlugin() { } void OutlineViewPlugin::unload() { core()->uiController()->removeToolView(m_factory); } #include "outlineviewplugin.moc" diff --git a/plugins/patchreview/patchreview.cpp b/plugins/patchreview/patchreview.cpp index 699720c69..42b44c276 100644 --- a/plugins/patchreview/patchreview.cpp +++ b/plugins/patchreview/patchreview.cpp @@ -1,627 +1,627 @@ /*************************************************************************** Copyright 2006-2009 David Nolden ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "patchreview.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include ///Whether arbitrary exceptions that occurred while diff-parsing within the library should be caught #define CATCHLIBDIFF /* Exclude this file from doublequote_chars check as krazy doesn't understand std::string*/ //krazy:excludeall=doublequote_chars #include #include #include #include #include #include "patchhighlighter.h" #include "patchreviewtoolview.h" #include "localpatchsource.h" #include "debug.h" Q_LOGGING_CATEGORY(PLUGIN_PATCHREVIEW, "kdevplatform.plugins.patchreview") using namespace KDevelop; namespace { // Maximum number of files to open directly within a tab when the review is started const int maximumFilesToOpenDirectly = 15; } Q_DECLARE_METATYPE( const Diff2::DiffModel* ) void PatchReviewPlugin::seekHunk( bool forwards, const QUrl& fileName ) { try { qCDebug(PLUGIN_PATCHREVIEW) << forwards << fileName << fileName.isEmpty(); if ( !m_modelList ) throw "no model"; for ( int a = 0; a < m_modelList->modelCount(); ++a ) { const Diff2::DiffModel* model = m_modelList->modelAt( a ); if ( !model || !model->differences() ) continue; QUrl file = urlForFileModel( model ); if ( !fileName.isEmpty() && fileName != file ) continue; IDocument* doc = ICore::self()->documentController()->documentForUrl( file ); if ( doc && m_highlighters.contains( doc->url() ) && m_highlighters[doc->url()] ) { if ( doc->textDocument() ) { const QList ranges = m_highlighters[doc->url()]->ranges(); KTextEditor::View * v = doc->activeTextView(); int bestLine = -1; if ( v ) { KTextEditor::Cursor c = v->cursorPosition(); for ( QList::const_iterator it = ranges.begin(); it != ranges.end(); ++it ) { int line = ( *it )->start().line(); if ( forwards ) { if ( line > c.line() && ( bestLine == -1 || line < bestLine ) ) bestLine = line; } else { if ( line < c.line() && ( bestLine == -1 || line > bestLine ) ) bestLine = line; } } if ( bestLine != -1 ) { v->setCursorPosition( KTextEditor::Cursor( bestLine, 0 ) ); return; } else if(fileName.isEmpty()) { int next = qBound(0, forwards ? a+1 : a-1, m_modelList->modelCount()-1); if (next < maximumFilesToOpenDirectly) { ICore::self()->documentController()->openDocument(urlForFileModel(m_modelList->modelAt(next))); } } } } } } } catch ( const QString & str ) { qCDebug(PLUGIN_PATCHREVIEW) << "seekHunk():" << str; } catch ( const char * str ) { qCDebug(PLUGIN_PATCHREVIEW) << "seekHunk():" << str; } qCDebug(PLUGIN_PATCHREVIEW) << "no matching hunk found"; } void PatchReviewPlugin::addHighlighting( const QUrl& highlightFile, IDocument* document ) { try { if ( !modelList() ) throw "no model"; for ( int a = 0; a < modelList()->modelCount(); ++a ) { Diff2::DiffModel* model = modelList()->modelAt( a ); if ( !model ) continue; QUrl file = urlForFileModel( model ); if ( file != highlightFile ) continue; qCDebug(PLUGIN_PATCHREVIEW) << "highlighting" << file.toDisplayString(); IDocument* doc = document; if( !doc ) doc = ICore::self()->documentController()->documentForUrl( file ); qCDebug(PLUGIN_PATCHREVIEW) << "highlighting file" << file << "with doc" << doc; if ( !doc || !doc->textDocument() ) continue; removeHighlighting( file ); m_highlighters[file] = new PatchHighlighter( model, doc, this, dynamic_cast(m_patch.data()) == nullptr ); } } catch ( const QString & str ) { qCDebug(PLUGIN_PATCHREVIEW) << "highlightFile():" << str; } catch ( const char * str ) { qCDebug(PLUGIN_PATCHREVIEW) << "highlightFile():" << str; } } void PatchReviewPlugin::highlightPatch() { try { if ( !modelList() ) throw "no model"; for ( int a = 0; a < modelList()->modelCount(); ++a ) { const Diff2::DiffModel* model = modelList()->modelAt( a ); if ( !model ) continue; QUrl file = urlForFileModel( model ); addHighlighting( file ); } } catch ( const QString & str ) { qCDebug(PLUGIN_PATCHREVIEW) << "highlightFile():" << str; } catch ( const char * str ) { qCDebug(PLUGIN_PATCHREVIEW) << "highlightFile():" << str; } } void PatchReviewPlugin::removeHighlighting( const QUrl& file ) { if ( file.isEmpty() ) { ///Remove all highlighting qDeleteAll( m_highlighters ); m_highlighters.clear(); } else { HighlightMap::iterator it = m_highlighters.find( file ); if ( it != m_highlighters.end() ) { delete *it; m_highlighters.erase( it ); } } } void PatchReviewPlugin::notifyPatchChanged() { if (m_patch) { qCDebug(PLUGIN_PATCHREVIEW) << "notifying patch change: " << m_patch->file(); m_updateKompareTimer->start( 500 ); } else { m_updateKompareTimer->stop(); } } void PatchReviewPlugin::forceUpdate() { if( m_patch ) { m_patch->update(); notifyPatchChanged(); } } void PatchReviewPlugin::updateKompareModel() { if ( !m_patch ) { ///TODO: this method should be cleaned up, it can be called by the timer and /// e.g. https://bugs.kde.org/show_bug.cgi?id=267187 shows how it could /// lead to asserts before... return; } qCDebug(PLUGIN_PATCHREVIEW) << "updating model"; removeHighlighting(); m_modelList.reset( nullptr ); m_depth = 0; delete m_diffSettings; { IDocument* patchDoc = ICore::self()->documentController()->documentForUrl( m_patch->file() ); if( patchDoc ) patchDoc->reload(); } QString patchFile; if( m_patch->file().isLocalFile() ) patchFile = m_patch->file().toLocalFile(); else if( m_patch->file().isValid() && !m_patch->file().isEmpty() ) { patchFile = QStandardPaths::writableLocation(QStandardPaths::TempLocation); bool ret = KIO::copy( m_patch->file(), QUrl::fromLocalFile(patchFile) )->exec(); if( !ret ) { qWarning() << "Problem while downloading: " << m_patch->file() << "to" << patchFile; patchFile.clear(); } } if (!patchFile.isEmpty()) //only try to construct the model if we have a patch to load try { m_diffSettings = new DiffSettings( nullptr ); m_kompareInfo.reset( new Kompare::Info() ); m_kompareInfo->localDestination = patchFile; m_kompareInfo->localSource = m_patch->baseDir().toLocalFile(); m_kompareInfo->depth = m_patch->depth(); m_kompareInfo->applied = m_patch->isAlreadyApplied(); m_modelList.reset( new Diff2::KompareModelList( m_diffSettings.data(), new QWidget, this ) ); m_modelList->slotKompareInfo( m_kompareInfo.data() ); try { m_modelList->openDirAndDiff(); } catch ( const QString & str ) { throw; } catch ( ... ) { throw QStringLiteral( "lib/libdiff2 crashed, memory may be corrupted. Please restart kdevelop." ); } for (m_depth = 0; m_depth < 10; ++m_depth) { bool allFound = true; for( int i = 0; i < m_modelList->modelCount(); i++ ) { if (!QFile::exists(urlForFileModel(m_modelList->modelAt(i)).toLocalFile())) { allFound = false; } } if (allFound) { break; // found depth } } emit patchChanged(); for( int i = 0; i < m_modelList->modelCount(); i++ ) { const Diff2::DiffModel* model = m_modelList->modelAt( i ); for( int j = 0; j < model->differences()->count(); j++ ) { model->differences()->at( j )->apply( m_patch->isAlreadyApplied() ); } } highlightPatch(); return; } catch ( const QString & str ) { KMessageBox::error( nullptr, str, i18n( "Kompare Model Update" ) ); } catch ( const char * str ) { KMessageBox::error( nullptr, str, i18n( "Kompare Model Update" ) ); } removeHighlighting(); m_modelList.reset( nullptr ); m_depth = 0; m_kompareInfo.reset( nullptr ); delete m_diffSettings; emit patchChanged(); } K_PLUGIN_FACTORY_WITH_JSON(KDevProblemReporterFactory, "kdevpatchreview.json", registerPlugin();) class PatchReviewToolViewFactory : public KDevelop::IToolViewFactory { public: - PatchReviewToolViewFactory( PatchReviewPlugin *plugin ) : m_plugin( plugin ) {} + explicit PatchReviewToolViewFactory( PatchReviewPlugin *plugin ) : m_plugin( plugin ) {} QWidget* create( QWidget *parent = nullptr ) override { return new PatchReviewToolView( parent, m_plugin ); } Qt::DockWidgetArea defaultPosition() override { return Qt::BottomDockWidgetArea; } QString id() const override { return QStringLiteral("org.kdevelop.PatchReview"); } private: PatchReviewPlugin *m_plugin; }; PatchReviewPlugin::~PatchReviewPlugin() { removeHighlighting(); // Tweak to work around a crash on OS X; see https://bugs.kde.org/show_bug.cgi?id=338829 // and http://qt-project.org/forums/viewthread/38406/#162801 // modified tweak: use setPatch() and deleteLater in that method. setPatch(nullptr); } void PatchReviewPlugin::clearPatch( QObject* _patch ) { qCDebug(PLUGIN_PATCHREVIEW) << "clearing patch" << _patch << "current:" << ( QObject* )m_patch; IPatchSource::Ptr patch( ( IPatchSource* )_patch ); if( patch == m_patch ) { qCDebug(PLUGIN_PATCHREVIEW) << "is current patch"; setPatch( IPatchSource::Ptr( new LocalPatchSource ) ); } } void PatchReviewPlugin::closeReview() { if( m_patch ) { IDocument* patchDocument = ICore::self()->documentController()->documentForUrl( m_patch->file() ); if (patchDocument) { // Revert modifications to the text document which we've done in updateReview patchDocument->setPrettyName( QString() ); patchDocument->textDocument()->setReadWrite( true ); KTextEditor::ModificationInterface* modif = dynamic_cast( patchDocument->textDocument() ); modif->setModifiedOnDiskWarning( true ); } removeHighlighting(); m_modelList.reset( nullptr ); m_depth = 0; if( !dynamic_cast( m_patch.data() ) ) { // make sure "show" button still openes the file dialog to open a custom patch file setPatch( new LocalPatchSource ); } else emit patchChanged(); Sublime::Area* area = ICore::self()->uiController()->activeArea(); if( area->objectName() == QLatin1String("review") ) { if( ICore::self()->documentController()->saveAllDocuments() ) ICore::self()->uiController()->switchToArea( QStringLiteral("code"), KDevelop::IUiController::ThisWindow ); } } } void PatchReviewPlugin::cancelReview() { if( m_patch ) { m_patch->cancelReview(); closeReview(); } } void PatchReviewPlugin::finishReview( QList selection ) { if( m_patch && m_patch->finishReview( selection ) ) { closeReview(); } } void PatchReviewPlugin::startReview( IPatchSource* patch, IPatchReview::ReviewMode mode ) { Q_UNUSED( mode ); emit startingNewReview(); setPatch( patch ); QMetaObject::invokeMethod( this, "updateReview", Qt::QueuedConnection ); } void PatchReviewPlugin::switchToEmptyReviewArea() { foreach(Sublime::Area* area, ICore::self()->uiController()->allAreas()) { if (area->objectName() == QLatin1String("review")) { area->clearDocuments(); } } if ( ICore::self()->uiController()->activeArea()->objectName() != QLatin1String("review") ) ICore::self()->uiController()->switchToArea( QStringLiteral("review"), KDevelop::IUiController::ThisWindow ); } QUrl PatchReviewPlugin::urlForFileModel( const Diff2::DiffModel* model ) { KDevelop::Path path(QDir::cleanPath(m_patch->baseDir().toLocalFile())); QVector destPath = KDevelop::Path("/"+model->destinationPath()).segments(); if (destPath.size() >= (int)m_depth) { destPath = destPath.mid(m_depth); } foreach(QString segment, destPath) { path.addPath(segment); } path.addPath(model->destinationFile()); return path.toUrl(); } void PatchReviewPlugin::updateReview() { if( !m_patch ) return; m_updateKompareTimer->stop(); switchToEmptyReviewArea(); KDevelop::IDocumentController *docController = ICore::self()->documentController(); // don't add documents opened automatically to the Files/Open Recent list IDocument* futureActiveDoc = docController->openDocument( m_patch->file(), KTextEditor::Range::invalid(), IDocumentController::DoNotAddToRecentOpen ); updateKompareModel(); if ( !m_modelList || !futureActiveDoc || !futureActiveDoc->textDocument() ) { // might happen if e.g. openDocument dialog was cancelled by user // or under the theoretic possibility of a non-text document getting opened return; } futureActiveDoc->textDocument()->setReadWrite( false ); futureActiveDoc->setPrettyName( i18n( "Overview" ) ); KTextEditor::ModificationInterface* modif = dynamic_cast( futureActiveDoc->textDocument() ); modif->setModifiedOnDiskWarning( false ); docController->activateDocument( futureActiveDoc ); PatchReviewToolView* toolView = qobject_cast(ICore::self()->uiController()->findToolView( i18n( "Patch Review" ), m_factory )); Q_ASSERT( toolView ); //Open all relates files for( int a = 0; a < m_modelList->modelCount() && a < maximumFilesToOpenDirectly; ++a ) { QUrl absoluteUrl = urlForFileModel( m_modelList->modelAt( a ) ); if (absoluteUrl.isRelative()) { KMessageBox::error( nullptr, i18n("The base directory of the patch must be an absolute directory"), i18n( "Patch Review" ) ); break; } if( QFileInfo::exists( absoluteUrl.toLocalFile() ) && absoluteUrl.toLocalFile() != QLatin1String("/dev/null") ) { toolView->open( absoluteUrl, false ); }else{ // Maybe the file was deleted qCDebug(PLUGIN_PATCHREVIEW) << "could not open" << absoluteUrl << "because it doesn't exist"; } } } void PatchReviewPlugin::setPatch( IPatchSource* patch ) { if ( patch == m_patch ) { return; } if( m_patch ) { disconnect( m_patch.data(), &IPatchSource::patchChanged, this, &PatchReviewPlugin::notifyPatchChanged ); if ( qobject_cast( m_patch ) ) { // make sure we don't leak this // TODO: what about other patch sources? m_patch->deleteLater(); } } m_patch = patch; if( m_patch ) { qCDebug(PLUGIN_PATCHREVIEW) << "setting new patch" << patch->name() << "with file" << patch->file() << "basedir" << patch->baseDir(); connect( m_patch.data(), &IPatchSource::patchChanged, this, &PatchReviewPlugin::notifyPatchChanged ); } QString finishText = i18n( "Finish Review" ); if( m_patch && !m_patch->finishReviewCustomText().isEmpty() ) finishText = m_patch->finishReviewCustomText(); m_finishReview->setText( finishText ); m_finishReview->setEnabled( patch ); notifyPatchChanged(); } PatchReviewPlugin::PatchReviewPlugin( QObject *parent, const QVariantList & ) : KDevelop::IPlugin( QStringLiteral("kdevpatchreview"), parent ), m_patch( nullptr ), m_factory( new PatchReviewToolViewFactory( this ) ) { qRegisterMetaType( "const Diff2::DiffModel*" ); setXMLFile( QStringLiteral("kdevpatchreview.rc") ); connect( ICore::self()->documentController(), &IDocumentController::documentClosed, this, &PatchReviewPlugin::documentClosed ); connect( ICore::self()->documentController(), &IDocumentController::textDocumentCreated, this, &PatchReviewPlugin::textDocumentCreated ); connect( ICore::self()->documentController(), &IDocumentController::documentSaved, this, &PatchReviewPlugin::documentSaved ); m_updateKompareTimer = new QTimer( this ); m_updateKompareTimer->setSingleShot( true ); connect( m_updateKompareTimer, &QTimer::timeout, this, &PatchReviewPlugin::updateKompareModel ); m_finishReview = new QAction(i18n("Finish Review"), this); m_finishReview->setIcon( QIcon::fromTheme( QStringLiteral("dialog-ok") ) ); actionCollection()->setDefaultShortcut( m_finishReview, Qt::CTRL|Qt::Key_Return ); actionCollection()->addAction(QStringLiteral("commit_or_finish_review"), m_finishReview); foreach(Sublime::Area* area, ICore::self()->uiController()->allAreas()) { if (area->objectName() == QLatin1String("review")) area->addAction(m_finishReview); } core()->uiController()->addToolView( i18n( "Patch Review" ), m_factory, IUiController::None ); areaChanged(ICore::self()->uiController()->activeArea()); } void PatchReviewPlugin::documentClosed( IDocument* doc ) { removeHighlighting( doc->url() ); } void PatchReviewPlugin::documentSaved( IDocument* doc ) { // Only update if the url is not the patch-file, because our call to // the reload() KTextEditor function also causes this signal, // which would lead to an endless update loop. // Also, don't automatically update local patch sources, because // they may correspond to static files which don't match any more // after an edit was done. if( m_patch && doc->url() != m_patch->file() && !dynamic_cast(m_patch.data()) ) forceUpdate(); } void PatchReviewPlugin::textDocumentCreated( IDocument* doc ) { if (m_patch) { addHighlighting( doc->url(), doc ); } } void PatchReviewPlugin::unload() { core()->uiController()->removeToolView( m_factory ); KDevelop::IPlugin::unload(); } void PatchReviewPlugin::areaChanged(Sublime::Area* area) { bool reviewing = area->objectName() == QLatin1String("review"); m_finishReview->setEnabled(reviewing); if(!reviewing) { closeReview(); } } KDevelop::ContextMenuExtension PatchReviewPlugin::contextMenuExtension( KDevelop::Context* context ) { QList urls; if ( context->type() == KDevelop::Context::FileContext ) { KDevelop::FileContext* filectx = dynamic_cast( context ); urls = filectx->urls(); } else if ( context->type() == KDevelop::Context::ProjectItemContext ) { KDevelop::ProjectItemContext* projctx = dynamic_cast( context ); foreach( KDevelop::ProjectBaseItem* item, projctx->items() ) { if ( item->file() ) { urls << item->file()->path().toUrl(); } } } else if ( context->type() == KDevelop::Context::EditorContext ) { KDevelop::EditorContext *econtext = dynamic_cast( context ); urls << econtext->url(); } if (urls.size() == 1) { QString mimetype = QMimeDatabase().mimeTypeForUrl(urls.first()).name(); QAction* reviewAction = new QAction( i18n( "Review Patch" ), this ); reviewAction->setData(QVariant(urls[0])); connect( reviewAction, &QAction::triggered, this, &PatchReviewPlugin::executeFileReviewAction ); ContextMenuExtension cm; cm.addAction( KDevelop::ContextMenuExtension::ExtensionGroup, reviewAction ); return cm; } return KDevelop::IPlugin::contextMenuExtension( context ); } void PatchReviewPlugin::executeFileReviewAction() { QAction* reviewAction = qobject_cast(sender()); KDevelop::Path path(reviewAction->data().toUrl()); LocalPatchSource* ps = new LocalPatchSource(); ps->setFilename(path.toUrl()); ps->setBaseDir(path.parent().toUrl()); ps->setAlreadyApplied(true); ps->createWidget(); startReview(ps, OpenAndRaise); } #include "patchreview.moc" // kate: space-indent on; indent-width 4; tab-width 4; replace-tabs on diff --git a/plugins/perforce/perforceplugin.h b/plugins/perforce/perforceplugin.h index 8f67aa8f7..ef9bb7b83 100644 --- a/plugins/perforce/perforceplugin.h +++ b/plugins/perforce/perforceplugin.h @@ -1,176 +1,176 @@ /*************************************************************************** * Copyright 2010 Morten Danielsen Volden * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #ifndef KDEVPERFORCEPLUGIN_H #define KDEVPERFORCEPLUGIN_H #include #include #include #include #include #include #include class QMenu; class QFileInfo; class QDir; namespace KDevelop { class ContextMenuExtension; class VcsPluginHelper; class DVcsJob; } class PerforcePlugin : public KDevelop::IPlugin, public KDevelop::ICentralizedVersionControl { Q_OBJECT Q_INTERFACES(KDevelop::IBasicVersionControl KDevelop::ICentralizedVersionControl) friend class PerforcePluginTest; public: - PerforcePlugin(QObject* parent, const QVariantList & = QVariantList()); + explicit PerforcePlugin(QObject* parent, const QVariantList & = QVariantList()); ~PerforcePlugin() override; //@{ /** Methods inherited from KDevelop::IBasicVersionControl */ QString name() const override; KDevelop::VcsImportMetadataWidget* createImportMetadataWidget(QWidget* parent) override; bool isVersionControlled(const QUrl& localLocation) override; KDevelop::VcsJob* repositoryLocation(const QUrl& localLocation) override; KDevelop::VcsJob* add(const QList& localLocations, RecursionMode recursion = IBasicVersionControl::Recursive) override; KDevelop::VcsJob* remove(const QList& localLocations) override; KDevelop::VcsJob* copy(const QUrl& localLocationSrc, const QUrl& localLocationDstn) override; KDevelop::VcsJob* move(const QUrl& localLocationSrc, const QUrl& localLocationDst) override; KDevelop::VcsJob* status(const QList& localLocations, RecursionMode recursion = IBasicVersionControl::Recursive) override; KDevelop::VcsJob* revert(const QList& localLocations, RecursionMode recursion = IBasicVersionControl::Recursive) override; KDevelop::VcsJob* update(const QList& localLocations, const KDevelop::VcsRevision& rev = KDevelop::VcsRevision::createSpecialRevision(KDevelop::VcsRevision::Head), KDevelop::IBasicVersionControl::RecursionMode recursion = KDevelop::IBasicVersionControl::Recursive) override; KDevelop::VcsJob* commit(const QString& message, const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion = KDevelop::IBasicVersionControl::Recursive) override; KDevelop::VcsJob* diff(const QUrl& fileOrDirectory, const KDevelop::VcsRevision& srcRevision, const KDevelop::VcsRevision& dstRevision, KDevelop::VcsDiff::Type = KDevelop::VcsDiff::DiffUnified, KDevelop::IBasicVersionControl::RecursionMode recursion = KDevelop::IBasicVersionControl::Recursive) override; KDevelop::VcsJob* log(const QUrl& localLocation, const KDevelop::VcsRevision& rev, unsigned long limit = 0) override; KDevelop::VcsJob* log(const QUrl& localLocation, const KDevelop::VcsRevision& rev = KDevelop::VcsRevision::createSpecialRevision(KDevelop::VcsRevision::Base), const KDevelop::VcsRevision& limit = KDevelop::VcsRevision::createSpecialRevision(KDevelop::VcsRevision::Start)) override; KDevelop::VcsJob* annotate(const QUrl& localLocation, const KDevelop::VcsRevision& rev = KDevelop::VcsRevision::createSpecialRevision(KDevelop::VcsRevision::Head)) override; KDevelop::VcsJob* resolve(const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion) override; KDevelop::VcsJob* createWorkingCopy(const KDevelop::VcsLocation & sourceRepository, const QUrl & destinationDirectory, KDevelop::IBasicVersionControl::RecursionMode recursion = IBasicVersionControl::Recursive) override; KDevelop::VcsLocationWidget* vcsLocation(QWidget* parent) const override; //@} //@{ /** Methods inherited from KDevelop::ICentralizedVersionControl */ KDevelop::VcsJob* edit(const QUrl& localLocation) override; KDevelop::VcsJob* unedit(const QUrl& localLocation) override; KDevelop::VcsJob* localRevision(const QUrl& localLocation, KDevelop::VcsRevision::RevisionType) override; KDevelop::VcsJob* import(const QString & commitMessage, const QUrl & sourceDirectory, const KDevelop::VcsLocation & destinationRepository) override; //@} /// This plugin implements its own edit method KDevelop::VcsJob* edit(const QList& localLocations); KDevelop::ContextMenuExtension contextMenuExtension(KDevelop::Context* context) override; public Q_SLOTS: /// invoked by context-menu void ctxEdit(); // void ctxUnedit(); // void ctxLocalRevision(); // void ctxImport(); private slots: void parseP4StatusOutput(KDevelop::DVcsJob* job); void parseP4DiffOutput(KDevelop::DVcsJob* job); void parseP4LogOutput(KDevelop::DVcsJob* job); void parseP4AnnotateOutput(KDevelop::DVcsJob* job); private: bool isValidDirectory(const QUrl & dirPath); KDevelop::DVcsJob* p4fstatJob(const QFileInfo& curFile, KDevelop::OutputJob::OutputJobVerbosity verbosity = KDevelop::OutputJob::Verbose); bool parseP4fstat(const QFileInfo& curFile, KDevelop::OutputJob::OutputJobVerbosity verbosity = KDevelop::OutputJob::Verbose); KDevelop::VcsJob* errorsFound(const QString& error, KDevelop::OutputJob::OutputJobVerbosity verbosity = KDevelop::OutputJob::Verbose); QString getRepositoryName(const QFileInfo& curFile); void setEnvironmentForJob(KDevelop::DVcsJob* job, QFileInfo const& fsObject); QList getQvariantFromLogOutput(QStringList const& outputLines); std::unique_ptr m_common; QMenu* m_perforcemenu; QString m_perforceConfigName; QString m_perforceExecutable; QAction* m_edit_action; }; #endif // PERFORCEPLUGIN_H diff --git a/plugins/problemreporter/problemsview.cpp b/plugins/problemreporter/problemsview.cpp index 44fb71d0e..9c6f073b7 100644 --- a/plugins/problemreporter/problemsview.cpp +++ b/plugins/problemreporter/problemsview.cpp @@ -1,523 +1,523 @@ /* * Copyright 2015 Laszlo Kis-Adam * * 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 "problemsview.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "problemtreeview.h" #include "problemmodel.h" namespace KDevelop { void ProblemsView::setupActions() { { m_fullUpdateAction = new QAction(this); m_fullUpdateAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); m_fullUpdateAction->setText(i18n("Force Full Update")); m_fullUpdateAction->setToolTip(i18nc("@info:tooltip", "Re-parse all watched documents")); m_fullUpdateAction->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); connect(m_fullUpdateAction, &QAction::triggered, this, [this]() { currentView()->model()->forceFullUpdate(); }); addAction(m_fullUpdateAction); } { m_scopeMenu = new KActionMenu(this); m_scopeMenu->setDelayed(false); m_scopeMenu->setToolTip(i18nc("@info:tooltip", "Which files to display the problems for")); m_scopeMenu->setObjectName(QStringLiteral("scopeMenu")); QActionGroup* scopeActions = new QActionGroup(this); m_currentDocumentAction = new QAction(this); m_currentDocumentAction->setText(i18n("Current Document")); m_currentDocumentAction->setToolTip(i18nc("@info:tooltip", "Display problems in current document")); QAction* openDocumentsAction = new QAction(this); openDocumentsAction->setText(i18n("Open Documents")); openDocumentsAction->setToolTip(i18nc("@info:tooltip", "Display problems in all open documents")); QAction* currentProjectAction = new QAction(this); currentProjectAction->setText(i18n("Current Project")); currentProjectAction->setToolTip(i18nc("@info:tooltip", "Display problems in current project")); QAction* allProjectAction = new QAction(this); allProjectAction->setText(i18n("All Projects")); allProjectAction->setToolTip(i18nc("@info:tooltip", "Display problems in all projects")); QVector actions; actions.push_back(m_currentDocumentAction); actions.push_back(openDocumentsAction); actions.push_back(currentProjectAction); actions.push_back(allProjectAction); m_showAllAction = new QAction(this); m_showAllAction->setText(i18n("Show All")); m_showAllAction->setToolTip(i18nc("@info:tooltip", "Display ALL problems")); actions.push_back(m_showAllAction); foreach (QAction* action, actions) { action->setCheckable(true); scopeActions->addAction(action); m_scopeMenu->addAction(action); } addAction(m_scopeMenu); QSignalMapper* scopeMapper = new QSignalMapper(this); scopeMapper->setMapping(m_currentDocumentAction, CurrentDocument); scopeMapper->setMapping(openDocumentsAction, OpenDocuments); scopeMapper->setMapping(currentProjectAction, CurrentProject); scopeMapper->setMapping(allProjectAction, AllProjects); connect(m_currentDocumentAction, &QAction::triggered, scopeMapper, static_cast(&QSignalMapper::map)); connect(openDocumentsAction, &QAction::triggered, scopeMapper, static_cast(&QSignalMapper::map)); connect(currentProjectAction, &QAction::triggered, scopeMapper, static_cast(&QSignalMapper::map)); connect(allProjectAction, &QAction::triggered, scopeMapper, static_cast(&QSignalMapper::map)); { scopeMapper->setMapping(actions.last(), BypassScopeFilter); connect(actions.last(), &QAction::triggered, scopeMapper, static_cast(&QSignalMapper::map)); } connect(scopeMapper, static_cast(&QSignalMapper::mapped), this, [this](int index) { setScope(index); }); } { m_showImportsAction = new QAction(this); addAction(m_showImportsAction); m_showImportsAction->setCheckable(true); m_showImportsAction->setChecked(false); m_showImportsAction->setText(i18n("Show Imports")); m_showImportsAction->setToolTip(i18nc("@info:tooltip", "Display problems in imported files")); connect(m_showImportsAction, &QAction::triggered, this, [this](bool checked) { currentView()->model()->setShowImports(checked); }); } { m_severityActions = new QActionGroup(this); m_errorSeverityAction = new QAction(this); m_errorSeverityAction->setToolTip(i18nc("@info:tooltip", "Display errors")); m_errorSeverityAction->setIcon(QIcon::fromTheme(QStringLiteral("dialog-error"))); m_errorSeverityAction->setIconText(i18n("Show Errors")); m_warningSeverityAction = new QAction(this); m_warningSeverityAction->setToolTip(i18nc("@info:tooltip", "Display warnings")); m_warningSeverityAction->setIcon(QIcon::fromTheme(QStringLiteral("dialog-warning"))); m_warningSeverityAction->setIconText(i18n("Show Warnings")); m_hintSeverityAction = new QAction(this); m_hintSeverityAction->setToolTip(i18nc("@info:tooltip", "Display hints")); m_hintSeverityAction->setIcon(QIcon::fromTheme(QStringLiteral("dialog-information"))); m_hintSeverityAction->setIconText(i18n("Show Hints")); QAction* severityActionArray[] = { m_errorSeverityAction, m_warningSeverityAction, m_hintSeverityAction }; for (int i = 0; i < 3; ++i) { severityActionArray[i]->setCheckable(true); m_severityActions->addAction(severityActionArray[i]); addAction(severityActionArray[i]); } m_severityActions->setExclusive(false); m_hintSeverityAction->setChecked(true); m_warningSeverityAction->setChecked(true); m_errorSeverityAction->setChecked(true); connect(m_errorSeverityAction, &QAction::toggled, this, &ProblemsView::handleSeverityActionToggled); connect(m_warningSeverityAction, &QAction::toggled, this, &ProblemsView::handleSeverityActionToggled); connect(m_hintSeverityAction, &QAction::toggled, this, &ProblemsView::handleSeverityActionToggled); } { m_groupingMenu = new KActionMenu(i18n("Grouping"), this); m_groupingMenu->setDelayed(false); QActionGroup* groupingActions = new QActionGroup(this); QAction* noGroupingAction = new QAction(i18n("None"), this); QAction* pathGroupingAction = new QAction(i18n("Path"), this); QAction* severityGroupingAction = new QAction(i18n("Severity"), this); QAction* groupingActionArray[] = { noGroupingAction, pathGroupingAction, severityGroupingAction }; for (unsigned i = 0; i < sizeof(groupingActionArray) / sizeof(QAction*); ++i) { QAction* action = groupingActionArray[i]; action->setCheckable(true); groupingActions->addAction(action); m_groupingMenu->addAction(action); } addAction(m_groupingMenu); noGroupingAction->setChecked(true); QSignalMapper* groupingMapper = new QSignalMapper(this); groupingMapper->setMapping(noGroupingAction, NoGrouping); groupingMapper->setMapping(pathGroupingAction, PathGrouping); groupingMapper->setMapping(severityGroupingAction, SeverityGrouping); connect(noGroupingAction, &QAction::triggered, groupingMapper, static_cast(&QSignalMapper::map)); connect(pathGroupingAction, &QAction::triggered, groupingMapper, static_cast(&QSignalMapper::map)); connect(severityGroupingAction, &QAction::triggered, groupingMapper, static_cast(&QSignalMapper::map)); connect(groupingMapper, static_cast(&QSignalMapper::mapped), this, [this](int index) { currentView()->model()->setGrouping(index); }); } { QTimer* filterTimer = new QTimer(this); filterTimer->setSingleShot(true); connect(filterTimer, &QTimer::timeout, this, [this]() { setFilter(m_filterEdit->text()); }); m_filterEdit = new QLineEdit(this); m_filterEdit->setClearButtonEnabled(true); m_filterEdit->setPlaceholderText(i18n("Search...")); QSizePolicy p(m_filterEdit->sizePolicy()); p.setHorizontalPolicy(QSizePolicy::Fixed); m_filterEdit->setSizePolicy(p); connect(m_filterEdit, &QLineEdit::textChanged, this, [filterTimer](const QString&) { filterTimer->start(500); }); QWidgetAction* filterAction = new QWidgetAction(this); filterAction->setDefaultWidget(m_filterEdit); addAction(filterAction); m_prevTabIdx = -1; setFocusProxy(m_filterEdit); } } void ProblemsView::updateActions() { auto problemModel = currentView()->model(); Q_ASSERT(problemModel); m_fullUpdateAction->setVisible(problemModel->features().testFlag(ProblemModel::CanDoFullUpdate)); m_showImportsAction->setVisible(problemModel->features().testFlag(ProblemModel::CanShowImports)); m_scopeMenu->setVisible(problemModel->features().testFlag(ProblemModel::ScopeFilter)); m_severityActions->setVisible(problemModel->features().testFlag(ProblemModel::SeverityFilter)); m_groupingMenu->setVisible(problemModel->features().testFlag(ProblemModel::Grouping)); m_showAllAction->setVisible(problemModel->features().testFlag(ProblemModel::CanByPassScopeFilter)); m_showImportsAction->setChecked(false); problemModel->setShowImports(false); // Show All should be default if it's supported. It helps with error messages that are otherwise invisible if (problemModel->features().testFlag(ProblemModel::CanByPassScopeFilter)) { //actions.last()->setChecked(true); setScope(BypassScopeFilter); } else { m_currentDocumentAction->setChecked(true); setScope(CurrentDocument); } problemModel->setSeverities(IProblem::Error | IProblem::Warning | IProblem::Hint); setFocus(); // set focus to default widget (filterEdit) } /// TODO: Move to util? /// Note: Support for recursing into child indices would be nice class ItemViewWalker { public: - ItemViewWalker(QItemSelectionModel* itemView); + explicit ItemViewWalker(QItemSelectionModel* itemView); void selectNextIndex(); void selectPreviousIndex(); enum Direction { NextIndex, PreviousIndex }; void selectIndex(Direction direction); private: QItemSelectionModel* m_selectionModel; }; ItemViewWalker::ItemViewWalker(QItemSelectionModel* itemView) : m_selectionModel(itemView) { } void ItemViewWalker::selectNextIndex() { selectIndex(NextIndex); } void ItemViewWalker::selectPreviousIndex() { selectIndex(PreviousIndex); } void ItemViewWalker::selectIndex(Direction direction) { if (!m_selectionModel) { return; } const QModelIndexList list = m_selectionModel->selectedRows(); const QModelIndex currentIndex = list.value(0); if (!currentIndex.isValid()) { /// no selection yet, just select the first const QModelIndex firstIndex = m_selectionModel->model()->index(0, 0); m_selectionModel->setCurrentIndex(firstIndex, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); return; } const int nextRow = currentIndex.row() + (direction == NextIndex ? 1 : -1); const QModelIndex nextIndex = currentIndex.sibling(nextRow, 0); if (!nextIndex.isValid()) { return; /// never invalidate the selection } m_selectionModel->setCurrentIndex(nextIndex, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); } ProblemsView::ProblemsView(QWidget* parent) : QWidget(parent) { setWindowTitle(i18n("Problems")); setWindowIcon(QIcon::fromTheme(QStringLiteral("script-error"), windowIcon())); auto layout = new QVBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); m_tabWidget = new QTabWidget(this); m_tabWidget->setTabPosition(QTabWidget::South); layout->addWidget(m_tabWidget); setupActions(); } ProblemsView::~ProblemsView() { } void ProblemsView::load() { m_tabWidget->clear(); KDevelop::ProblemModelSet* pms = KDevelop::ICore::self()->languageController()->problemModelSet(); QVector v = pms->models(); QVectorIterator itr(v); while (itr.hasNext()) { const KDevelop::ModelData& data = itr.next(); addModel(data); } connect(pms, &ProblemModelSet::added, this, &ProblemsView::onModelAdded); connect(pms, &ProblemModelSet::removed, this, &ProblemsView::onModelRemoved); connect(m_tabWidget, &QTabWidget::currentChanged, this, &ProblemsView::onCurrentChanged); if (m_tabWidget->currentIndex() == 0) { updateActions(); return; } m_tabWidget->setCurrentIndex(0); } void ProblemsView::onModelAdded(const ModelData& data) { addModel(data); } void ProblemsView::showModel(const QString& id) { for (int i = 0; i < m_models.size(); ++i) { if (m_models[i].id == id) { m_tabWidget->setCurrentIndex(i); return; } } } void ProblemsView::onModelRemoved(const QString& id) { for (int i = 0; i < m_models.size(); ++i) { if (m_models[i].id == id) { m_models.remove(i); QWidget* w = m_tabWidget->widget(i); m_tabWidget->removeTab(i); delete w; return; } } } void ProblemsView::onCurrentChanged(int idx) { if (idx == -1) return; setFilter(QStringLiteral(""), m_prevTabIdx); setFilter(QStringLiteral("")); m_prevTabIdx = idx; updateActions(); } void ProblemsView::onViewChanged() { ProblemTreeView* view = static_cast(sender()); int idx = m_tabWidget->indexOf(view); int rows = view->model()->rowCount(); updateTab(idx, rows); } void ProblemsView::addModel(const ModelData& newData) { // We implement follows tabs order: // // 1) First tab always used by "Parser" model due to it's the most important // problem listing, it should be at the front (K.Funk idea at #kdevelop IRC channel). // // 2) Other tabs are alphabetically ordered. static const QString parserId = QStringLiteral("Parser"); ProblemTreeView* view = new ProblemTreeView(nullptr, newData.model); connect(view, &ProblemTreeView::changed, this, &ProblemsView::onViewChanged); int insertIdx = 0; if (newData.id != parserId) { for (insertIdx = 0; insertIdx < m_models.size(); ++insertIdx) { const ModelData& currentData = m_models[insertIdx]; // Skip first element if it's already occupied by "Parser" model if (insertIdx == 0 && currentData.id == parserId) continue; if (currentData.name.localeAwareCompare(newData.name) > 0) break; } } m_tabWidget->insertTab(insertIdx, view, newData.name); m_models.insert(insertIdx, newData); updateTab(insertIdx, view->model()->rowCount()); } void ProblemsView::updateTab(int idx, int rows) { if (idx < 0 || idx >= m_models.size()) return; const QString name = m_models[idx].name; const QString tabText = i18nc("%1: tab name, %2: number of problems", "%1 (%2)", name, rows); m_tabWidget->setTabText(idx, tabText); } ProblemTreeView* ProblemsView::currentView() const { return qobject_cast(m_tabWidget->currentWidget()); } void ProblemsView::selectNextItem() { auto view = currentView(); if (view) { ItemViewWalker walker(view->selectionModel()); walker.selectNextIndex(); view->openDocumentForCurrentProblem(); } } void ProblemsView::selectPreviousItem() { auto view = currentView(); if (view) { ItemViewWalker walker(view->selectionModel()); walker.selectPreviousIndex(); view->openDocumentForCurrentProblem(); } } void ProblemsView::handleSeverityActionToggled() { currentView()->model()->setSeverities( (m_errorSeverityAction->isChecked() ? IProblem::Error : IProblem::Severities()) | (m_warningSeverityAction->isChecked() ? IProblem::Warning : IProblem::Severities()) | (m_hintSeverityAction->isChecked() ? IProblem::Hint : IProblem::Severities()) ); } void ProblemsView::setScope(int scope) { m_scopeMenu->setText(i18n("Scope: %1", m_scopeMenu->menu()->actions().at(scope)->text())); currentView()->model()->setScope(scope); } void ProblemsView::setFilter(const QString& filterText) { setFilter(filterText, m_tabWidget->currentIndex()); } void ProblemsView::setFilter(const QString& filterText, int tabIdx) { if (tabIdx < 0 || tabIdx >= m_tabWidget->count()) return; ProblemTreeView* view = static_cast(m_tabWidget->widget(tabIdx)); int rows = view->setFilter(filterText); updateTab(tabIdx, rows); if (tabIdx == m_tabWidget->currentIndex()) { QSignalBlocker blocker(m_filterEdit); m_filterEdit->setText(filterText); } } } diff --git a/plugins/projectmanagerview/projectmanagerviewplugin.cpp b/plugins/projectmanagerview/projectmanagerviewplugin.cpp index 32c5aace8..f458b936f 100644 --- a/plugins/projectmanagerview/projectmanagerviewplugin.cpp +++ b/plugins/projectmanagerview/projectmanagerviewplugin.cpp @@ -1,715 +1,715 @@ /* 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" #include "debug.h" using namespace KDevelop; Q_LOGGING_CATEGORY(PLUGIN_PROJECTMANAGERVIEW, "kdevplatform.plugins.projectmanagerview") K_PLUGIN_FACTORY_WITH_JSON(ProjectManagerFactory, "kdevprojectmanagerview.json", registerPlugin();) class KDevProjectManagerViewFactory: public KDevelop::IToolViewFactory { public: - KDevProjectManagerViewFactory( ProjectManagerViewPlugin *plugin ): mplugin( plugin ) + explicit KDevProjectManagerViewFactory( ProjectManagerViewPlugin *plugin ): mplugin( plugin ) {} QWidget* create( QWidget *parent = nullptr ) override { return new ProjectManagerView( mplugin, parent ); } Qt::DockWidgetArea defaultPosition() override { return Qt::LeftDockWidgetArea; } QString id() const override { return QStringLiteral("org.kdevelop.ProjectsView"); } private: ProjectManagerViewPlugin *mplugin; }; class ProjectManagerViewPluginPrivate { public: ProjectManagerViewPluginPrivate() {} KDevProjectManagerViewFactory *factory; QList ctxProjectItemList; QAction* m_buildAll; QAction* m_build; QAction* m_install; QAction* m_clean; QAction* m_configure; QAction* m_prune; }; static QList itemsFromIndexes(const QList& indexes) { QList items; ProjectModel* model = ICore::self()->projectController()->projectModel(); foreach(const QModelIndex& index, indexes) { items += model->itemFromIndex(index); } return items; } ProjectManagerViewPlugin::ProjectManagerViewPlugin( QObject *parent, const QVariantList& ) : IPlugin( QStringLiteral("kdevprojectmanagerview"), parent ), d(new ProjectManagerViewPluginPrivate) { d->m_buildAll = new QAction( i18n("Build all Projects"), this ); d->m_buildAll->setIcon(QIcon::fromTheme(QStringLiteral("run-build"))); connect( d->m_buildAll, &QAction::triggered, this, &ProjectManagerViewPlugin::buildAllProjects ); actionCollection()->addAction( QStringLiteral("project_buildall"), d->m_buildAll ); d->m_build = new QAction( i18n("Build Selection"), this ); d->m_build->setIconText( i18n("Build") ); actionCollection()->setDefaultShortcut( d->m_build, Qt::Key_F8 ); d->m_build->setIcon(QIcon::fromTheme(QStringLiteral("run-build"))); d->m_build->setEnabled( false ); connect( d->m_build, &QAction::triggered, this, &ProjectManagerViewPlugin::buildProjectItems ); actionCollection()->addAction( QStringLiteral("project_build"), d->m_build ); d->m_install = new QAction( i18n("Install Selection"), this ); d->m_install->setIconText( i18n("Install") ); d->m_install->setIcon(QIcon::fromTheme(QStringLiteral("run-build-install"))); actionCollection()->setDefaultShortcut( d->m_install, Qt::SHIFT + Qt::Key_F8 ); d->m_install->setEnabled( false ); connect( d->m_install, &QAction::triggered, this, &ProjectManagerViewPlugin::installProjectItems ); actionCollection()->addAction( QStringLiteral("project_install"), d->m_install ); d->m_clean = new QAction( i18n("Clean Selection"), this ); d->m_clean->setIconText( i18n("Clean") ); d->m_clean->setIcon(QIcon::fromTheme(QStringLiteral("run-build-clean"))); d->m_clean->setEnabled( false ); connect( d->m_clean, &QAction::triggered, this, &ProjectManagerViewPlugin::cleanProjectItems ); actionCollection()->addAction( QStringLiteral("project_clean"), d->m_clean ); d->m_configure = new QAction( i18n("Configure Selection"), this ); d->m_configure->setMenuRole( QAction::NoRole ); // OSX: Be explicit about role, prevent hiding due to conflict with "Preferences..." menu item d->m_configure->setIconText( i18n("Configure") ); d->m_configure->setIcon(QIcon::fromTheme(QStringLiteral("run-build-configure"))); d->m_configure->setEnabled( false ); connect( d->m_configure, &QAction::triggered, this, &ProjectManagerViewPlugin::configureProjectItems ); actionCollection()->addAction( QStringLiteral("project_configure"), d->m_configure ); d->m_prune = new QAction( i18n("Prune Selection"), this ); d->m_prune->setIconText( i18n("Prune") ); d->m_prune->setIcon(QIcon::fromTheme(QStringLiteral("run-build-prune"))); d->m_prune->setEnabled( false ); connect( d->m_prune, &QAction::triggered, this, &ProjectManagerViewPlugin::pruneProjectItems ); actionCollection()->addAction( QStringLiteral("project_prune"), d->m_prune ); // only add the action so that its known in the actionCollection // and so that it's shortcut etc. pp. is restored // apparently that is not possible to be done in the view itself *sigh* actionCollection()->addAction( QStringLiteral("locate_document") ); setXMLFile( QStringLiteral("kdevprojectmanagerview.rc") ); d->factory = new KDevProjectManagerViewFactory( this ); core()->uiController()->addToolView( i18n("Projects"), d->factory ); connect(core()->selectionController(), &ISelectionController::selectionChanged, this, &ProjectManagerViewPlugin::updateActionState); connect(ICore::self()->projectController()->buildSetModel(), &KDevelop::ProjectBuildSetModel::rowsInserted, this, &ProjectManagerViewPlugin::updateFromBuildSetChange); connect(ICore::self()->projectController()->buildSetModel(), &KDevelop::ProjectBuildSetModel::rowsRemoved, this, &ProjectManagerViewPlugin::updateFromBuildSetChange); connect(ICore::self()->projectController()->buildSetModel(), &KDevelop::ProjectBuildSetModel::modelReset, this, &ProjectManagerViewPlugin::updateFromBuildSetChange); } void ProjectManagerViewPlugin::updateFromBuildSetChange() { updateActionState( core()->selectionController()->currentSelection() ); } void ProjectManagerViewPlugin::updateActionState( KDevelop::Context* ctx ) { bool isEmpty = ICore::self()->projectController()->buildSetModel()->items().isEmpty(); if( isEmpty ) { isEmpty = !ctx || ctx->type() != Context::ProjectItemContext || dynamic_cast( ctx )->items().isEmpty(); } d->m_build->setEnabled( !isEmpty ); d->m_install->setEnabled( !isEmpty ); d->m_clean->setEnabled( !isEmpty ); d->m_configure->setEnabled( !isEmpty ); d->m_prune->setEnabled( !isEmpty ); } ProjectManagerViewPlugin::~ProjectManagerViewPlugin() { delete d; } void ProjectManagerViewPlugin::unload() { qCDebug(PLUGIN_PROJECTMANAGERVIEW) << "unloading manager view"; core()->uiController()->removeToolView(d->factory); } ContextMenuExtension ProjectManagerViewPlugin::contextMenuExtension( KDevelop::Context* context ) { if( context->type() != KDevelop::Context::ProjectItemContext ) return IPlugin::contextMenuExtension( context ); KDevelop::ProjectItemContext* ctx = dynamic_cast( context ); QList items = ctx->items(); d->ctxProjectItemList.clear(); if( items.isEmpty() ) return IPlugin::contextMenuExtension( context ); //TODO: also needs: removeTarget, removeFileFromTarget, runTargetsFromContextMenu ContextMenuExtension menuExt; bool needsCreateFile = true; bool needsCreateFolder = true; bool needsCloseProjects = true; bool needsBuildItems = true; bool needsFolderItems = true; bool needsRemoveAndRename = true; bool needsRemoveTargetFiles = true; bool needsPaste = true; //needsCreateFile if there is one item and it's a folder or target needsCreateFile &= (items.count() == 1) && (items.first()->folder() || items.first()->target()); //needsCreateFolder if there is one item and it's a folder needsCreateFolder &= (items.count() == 1) && (items.first()->folder()); needsPaste = needsCreateFolder; foreach( ProjectBaseItem* item, items ) { d->ctxProjectItemList << item->index(); //needsBuildItems if items are limited to targets and buildfolders needsBuildItems &= item->target() || item->type() == ProjectBaseItem::BuildFolder; //needsCloseProjects if items are limited to top level folders (Project Folders) needsCloseProjects &= item->folder() && !item->folder()->parent(); //needsFolderItems if items are limited to folders needsFolderItems &= (bool)item->folder(); //needsRemove if items are limited to non-top-level folders or files that don't belong to targets needsRemoveAndRename &= (item->folder() && item->parent()) || (item->file() && !item->parent()->target()); //needsRemoveTargets if items are limited to file items with target parents needsRemoveTargetFiles &= (item->file() && item->parent()->target()); } if ( needsCreateFile ) { QAction* action = new QAction( i18n( "Create File..." ), this ); action->setIcon(QIcon::fromTheme(QStringLiteral("document-new"))); connect( action, &QAction::triggered, this, &ProjectManagerViewPlugin::createFileFromContextMenu ); menuExt.addAction( ContextMenuExtension::FileGroup, action ); } if ( needsCreateFolder ) { QAction* action = new QAction( i18n( "Create Folder..." ), this ); action->setIcon(QIcon::fromTheme(QStringLiteral("folder-new"))); connect( action, &QAction::triggered, this, &ProjectManagerViewPlugin::createFolderFromContextMenu ); menuExt.addAction( ContextMenuExtension::FileGroup, action ); } if ( needsBuildItems ) { QAction* action = new QAction( i18nc( "@action", "Build" ), this ); action->setIcon(QIcon::fromTheme(QStringLiteral("run-build"))); connect( action, &QAction::triggered, this, &ProjectManagerViewPlugin::buildItemsFromContextMenu ); menuExt.addAction( ContextMenuExtension::BuildGroup, action ); action = new QAction( i18nc( "@action", "Install" ), this ); action->setIcon(QIcon::fromTheme(QStringLiteral("run-install"))); connect( action, &QAction::triggered, this, &ProjectManagerViewPlugin::installItemsFromContextMenu ); menuExt.addAction( ContextMenuExtension::BuildGroup, action ); action = new QAction( i18nc( "@action", "Clean" ), this ); action->setIcon(QIcon::fromTheme(QStringLiteral("run-clean"))); connect( action, &QAction::triggered, this, &ProjectManagerViewPlugin::cleanItemsFromContextMenu ); menuExt.addAction( ContextMenuExtension::BuildGroup, action ); action = new QAction( i18n( "Add to Build Set" ), this ); action->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); connect( action, &QAction::triggered, this, &ProjectManagerViewPlugin::addItemsFromContextMenuToBuildset ); menuExt.addAction( ContextMenuExtension::BuildGroup, action ); } if ( needsCloseProjects ) { QAction* close = new QAction( i18np( "Close Project", "Close Projects", items.count() ), this ); close->setIcon(QIcon::fromTheme(QStringLiteral("project-development-close"))); connect( close, &QAction::triggered, this, &ProjectManagerViewPlugin::closeProjects ); menuExt.addAction( ContextMenuExtension::ProjectGroup, close ); } if ( needsFolderItems ) { QAction* action = new QAction( i18n( "Reload" ), this ); action->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); connect( action, &QAction::triggered, this, &ProjectManagerViewPlugin::reloadFromContextMenu ); menuExt.addAction( ContextMenuExtension::FileGroup, action ); } if ( needsRemoveAndRename ) { QAction* remove = new QAction( i18n( "Remove" ), this ); remove->setIcon(QIcon::fromTheme(QStringLiteral("user-trash"))); connect( remove, &QAction::triggered, this, &ProjectManagerViewPlugin::removeFromContextMenu ); menuExt.addAction( ContextMenuExtension::FileGroup, remove ); QAction* rename = new QAction( i18n( "Rename..." ), this ); rename->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename"))); connect( rename, &QAction::triggered, this, &ProjectManagerViewPlugin::renameItemFromContextMenu ); menuExt.addAction( ContextMenuExtension::FileGroup, rename ); } if ( needsRemoveTargetFiles ) { QAction* remove = new QAction( i18n( "Remove From Target" ), this ); remove->setIcon(QIcon::fromTheme(QStringLiteral("user-trash"))); connect( remove, &QAction::triggered, this, &ProjectManagerViewPlugin::removeTargetFilesFromContextMenu ); menuExt.addAction( ContextMenuExtension::FileGroup, remove ); } { QAction* copy = KStandardAction::copy(this, SLOT(copyFromContextMenu()), this); copy->setShortcutContext(Qt::WidgetShortcut); menuExt.addAction( ContextMenuExtension::FileGroup, copy ); } if (needsPaste) { QAction* paste = KStandardAction::paste(this, SLOT(pasteFromContextMenu()), this); paste->setShortcutContext(Qt::WidgetShortcut); menuExt.addAction( ContextMenuExtension::FileGroup, paste ); } return menuExt; } void ProjectManagerViewPlugin::closeProjects() { QList projectsToClose; ProjectModel* model = ICore::self()->projectController()->projectModel(); foreach( const QModelIndex& index, d->ctxProjectItemList ) { KDevelop::ProjectBaseItem* item = model->itemFromIndex(index); if( !projectsToClose.contains( item->project() ) ) { projectsToClose << item->project(); } } d->ctxProjectItemList.clear(); foreach( KDevelop::IProject* proj, projectsToClose ) { core()->projectController()->closeProject( proj ); } } void ProjectManagerViewPlugin::installItemsFromContextMenu() { runBuilderJob( BuilderJob::Install, itemsFromIndexes(d->ctxProjectItemList) ); d->ctxProjectItemList.clear(); } void ProjectManagerViewPlugin::cleanItemsFromContextMenu() { runBuilderJob( BuilderJob::Clean, itemsFromIndexes( d->ctxProjectItemList ) ); d->ctxProjectItemList.clear(); } void ProjectManagerViewPlugin::buildItemsFromContextMenu() { runBuilderJob( BuilderJob::Build, itemsFromIndexes( d->ctxProjectItemList ) ); d->ctxProjectItemList.clear(); } QList ProjectManagerViewPlugin::collectAllProjects() { QList items; foreach( KDevelop::IProject* project, core()->projectController()->projects() ) { items << project->projectItem(); } return items; } void ProjectManagerViewPlugin::buildAllProjects() { runBuilderJob( BuilderJob::Build, collectAllProjects() ); } QList ProjectManagerViewPlugin::collectItems() { QList items; QList buildItems = ICore::self()->projectController()->buildSetModel()->items(); if( !buildItems.isEmpty() ) { foreach( const BuildItem& buildItem, buildItems ) { if( ProjectBaseItem* item = buildItem.findItem() ) { items << item; } } } else { KDevelop::ProjectItemContext* ctx = dynamic_cast(ICore::self()->selectionController()->currentSelection()); items = ctx->items(); } return items; } void ProjectManagerViewPlugin::runBuilderJob( BuilderJob::BuildType type, QList items ) { BuilderJob* builder = new BuilderJob; builder->addItems( type, items ); builder->updateJobName(); ICore::self()->uiController()->registerStatus(new JobStatus(builder)); ICore::self()->runController()->registerJob( builder ); } void ProjectManagerViewPlugin::installProjectItems() { runBuilderJob( KDevelop::BuilderJob::Install, collectItems() ); } void ProjectManagerViewPlugin::pruneProjectItems() { runBuilderJob( KDevelop::BuilderJob::Prune, collectItems() ); } void ProjectManagerViewPlugin::configureProjectItems() { runBuilderJob( KDevelop::BuilderJob::Configure, collectItems() ); } void ProjectManagerViewPlugin::cleanProjectItems() { runBuilderJob( KDevelop::BuilderJob::Clean, collectItems() ); } void ProjectManagerViewPlugin::buildProjectItems() { runBuilderJob( KDevelop::BuilderJob::Build, collectItems() ); } void ProjectManagerViewPlugin::addItemsFromContextMenuToBuildset( ) { foreach( KDevelop::ProjectBaseItem* item, itemsFromIndexes( d->ctxProjectItemList )) { ICore::self()->projectController()->buildSetModel()->addProjectItem( item ); } } void ProjectManagerViewPlugin::runTargetsFromContextMenu( ) { foreach( KDevelop::ProjectBaseItem* item, itemsFromIndexes( d->ctxProjectItemList )) { KDevelop::ProjectExecutableTargetItem* t=item->executable(); if(t) { qCDebug(PLUGIN_PROJECTMANAGERVIEW) << "Running target: " << t->text() << t->builtUrl(); } } } void ProjectManagerViewPlugin::projectConfiguration( ) { if( !d->ctxProjectItemList.isEmpty() ) { ProjectModel* model = ICore::self()->projectController()->projectModel(); core()->projectController()->configureProject( model->itemFromIndex(d->ctxProjectItemList.at( 0 ))->project() ); } } void ProjectManagerViewPlugin::reloadFromContextMenu( ) { QList< KDevelop::ProjectFolderItem* > folders; foreach( KDevelop::ProjectBaseItem* item, itemsFromIndexes( d->ctxProjectItemList ) ) { if ( item->folder() ) { // since reloading should be recursive, only pass the upper-most items bool found = false; foreach ( KDevelop::ProjectFolderItem* existing, folders ) { if ( existing->path().isParentOf(item->folder()->path()) ) { // simply skip this child found = true; break; } else if ( item->folder()->path().isParentOf(existing->path()) ) { // remove the child in the list and add the current item instead folders.removeOne(existing); // continue since there could be more than one existing child } } if ( !found ) { folders << item->folder(); } } } foreach( KDevelop::ProjectFolderItem* folder, folders ) { folder->project()->projectFileManager()->reload(folder); } } void ProjectManagerViewPlugin::createFolderFromContextMenu( ) { foreach( KDevelop::ProjectBaseItem* item, itemsFromIndexes( d->ctxProjectItemList )) { if ( item->folder() ) { QWidget* window(ICore::self()->uiController()->activeMainWindow()->window()); QString name = QInputDialog::getText ( window, i18n ( "Create Folder in %1", item->folder()->path().pathOrUrl() ), i18n ( "Folder Name" ) ); if (!name.isEmpty()) { item->project()->projectFileManager()->addFolder( Path(item->path(), name), item->folder() ); } } } } void ProjectManagerViewPlugin::removeFromContextMenu() { removeItems(itemsFromIndexes( d->ctxProjectItemList )); } void ProjectManagerViewPlugin::removeItems(const QList< ProjectBaseItem* >& items) { if (items.isEmpty()) { return; } //copy the list of selected items and sort it to guarantee parents will come before children QList sortedItems = items; std::sort(sortedItems.begin(), sortedItems.end(), ProjectBaseItem::pathLessThan); Path lastFolder; QHash< IProjectFileManager*, QList > filteredItems; QStringList itemPaths; foreach( KDevelop::ProjectBaseItem* item, sortedItems ) { if (item->isProjectRoot()) { continue; } else if (item->folder() || item->file()) { //make sure no children of folders that will be deleted are listed if (lastFolder.isParentOf(item->path())) { continue; } else if (item->folder()) { lastFolder = item->path(); } IProjectFileManager* manager = item->project()->projectFileManager(); if (manager) { filteredItems[manager] << item; itemPaths << item->path().pathOrUrl(); } } } if (filteredItems.isEmpty()) { return; } if (KMessageBox::warningYesNoList( QApplication::activeWindow(), i18np("Do you really want to delete this item?", "Do you really want to delete these %1 items?", itemPaths.size()), itemPaths, i18n("Delete Files"), KStandardGuiItem::del(), KStandardGuiItem::cancel() ) == KMessageBox::No) { return; } //Go though projectmanagers, have them remove the files and folders that they own QHash< IProjectFileManager*, QList >::iterator it; for (it = filteredItems.begin(); it != filteredItems.end(); ++it) { Q_ASSERT(it.key()); it.key()->removeFilesAndFolders(it.value()); } } void ProjectManagerViewPlugin::removeTargetFilesFromContextMenu() { QList items = itemsFromIndexes( d->ctxProjectItemList ); QHash< IBuildSystemManager*, QList > itemsByBuildSystem; foreach(ProjectBaseItem *item, items) itemsByBuildSystem[item->project()->buildSystemManager()].append(item->file()); QHash< IBuildSystemManager*, QList >::iterator it; for (it = itemsByBuildSystem.begin(); it != itemsByBuildSystem.end(); ++it) it.key()->removeFilesFromTargets(it.value()); } void ProjectManagerViewPlugin::renameItemFromContextMenu() { renameItems(itemsFromIndexes( d->ctxProjectItemList )); } void ProjectManagerViewPlugin::renameItems(const QList< ProjectBaseItem* >& items) { if (items.isEmpty()) { return; } QWidget* window = ICore::self()->uiController()->activeMainWindow()->window(); foreach( KDevelop::ProjectBaseItem* item, items ) { if ((item->type()!=ProjectBaseItem::BuildFolder && item->type()!=ProjectBaseItem::Folder && item->type()!=ProjectBaseItem::File) || !item->parent()) { continue; } const QString src = item->text(); //Change QInputDialog->KFileSaveDialog? QString name = QInputDialog::getText( window, i18n("Rename..."), i18n("New name for '%1':", item->text()), QLineEdit::Normal, item->text() ); if (!name.isEmpty() && name != src) { ProjectBaseItem::RenameStatus status = item->rename( name ); switch(status) { case ProjectBaseItem::RenameOk: break; case ProjectBaseItem::ExistingItemSameName: KMessageBox::error(window, i18n("There is already a file named '%1'", name)); break; case ProjectBaseItem::ProjectManagerRenameFailed: KMessageBox::error(window, i18n("Could not rename '%1'", name)); break; case ProjectBaseItem::InvalidNewName: KMessageBox::error(window, i18n("'%1' is not a valid file name", name)); break; } } } } ProjectFileItem* createFile(const ProjectFolderItem* item) { QWidget* window = ICore::self()->uiController()->activeMainWindow()->window(); QString name = QInputDialog::getText(window, i18n("Create File in %1", item->path().pathOrUrl()), i18n("File name:")); if(name.isEmpty()) return nullptr; ProjectFileItem* ret = item->project()->projectFileManager()->addFile( Path(item->path(), name), item->folder() ); if (ret) { ICore::self()->documentController()->openDocument( ret->path().toUrl() ); } return ret; } void ProjectManagerViewPlugin::createFileFromContextMenu( ) { foreach( KDevelop::ProjectBaseItem* item, itemsFromIndexes( d->ctxProjectItemList ) ) { if ( item->folder() ) { createFile(item->folder()); } else if ( item->target() ) { ProjectFolderItem* folder=dynamic_cast(item->parent()); if(folder) { ProjectFileItem* f=createFile(folder); if(f) item->project()->buildSystemManager()->addFilesToTarget(QList() << f, item->target()); } } } } void ProjectManagerViewPlugin::copyFromContextMenu() { KDevelop::ProjectItemContext* ctx = dynamic_cast(ICore::self()->selectionController()->currentSelection()); QList urls; foreach (ProjectBaseItem* item, ctx->items()) { if (item->folder() || item->file()) { urls << item->path().toUrl(); } } qCDebug(PLUGIN_PROJECTMANAGERVIEW) << urls; if (!urls.isEmpty()) { QMimeData* data = new QMimeData; data->setUrls(urls); qApp->clipboard()->setMimeData(data); } } void ProjectManagerViewPlugin::pasteFromContextMenu() { KDevelop::ProjectItemContext* ctx = dynamic_cast(ICore::self()->selectionController()->currentSelection()); if (ctx->items().count() != 1) return; //do nothing if multiple or none items are selected ProjectBaseItem* destItem = ctx->items().at(0); if (!destItem->folder()) return; //do nothing if the target is not a directory const QMimeData* data = qApp->clipboard()->mimeData(); qCDebug(PLUGIN_PROJECTMANAGERVIEW) << data->urls(); const Path::List paths = toPathList(data->urls()); bool success = destItem->project()->projectFileManager()->copyFilesAndFolders(paths, destItem->folder()); if (success) { ProjectManagerViewItemContext* viewCtx = dynamic_cast(ICore::self()->selectionController()->currentSelection()); if (viewCtx) { //expand target folder viewCtx->view()->expandItem(destItem); //and select new items QList newItems; foreach (const Path &path, paths) { const Path targetPath(destItem->path(), path.lastPathSegment()); foreach (ProjectBaseItem *item, destItem->children()) { if (item->path() == targetPath) { newItems << item; } } } viewCtx->view()->selectItems(newItems); } } } #include "projectmanagerviewplugin.moc" diff --git a/plugins/quickopen/projectitemquickopen.cpp b/plugins/quickopen/projectitemquickopen.cpp index 805480d46..50f5e37d2 100644 --- a/plugins/quickopen/projectitemquickopen.cpp +++ b/plugins/quickopen/projectitemquickopen.cpp @@ -1,376 +1,376 @@ /* This file is part of the KDE libraries Copyright (C) 2007 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "projectitemquickopen.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; namespace { struct SubstringCache { - SubstringCache( const QString& string = QString() ) + explicit SubstringCache( const QString& string = QString() ) : substring(string) { } inline int containedIn( const Identifier& id ) const { int index = id.index(); QHash::const_iterator it = cache.constFind(index); if(it != cache.constEnd()) { return *it; } const QString idStr = id.identifier().str(); int result = idStr.lastIndexOf(substring, -1, Qt::CaseInsensitive); if (result < 0 && !idStr.isEmpty() && !substring.isEmpty()) { // no match; try abbreviations result = matchesAbbreviation(idStr.midRef(0), substring) ? 0 : -1; } //here we shift the values if the matched string is bigger than the substring, //so closer matches will appear first if (result >= 0) { result = result + (idStr.size() - substring.size()); } cache[index] = result; return result; } QString substring; mutable QHash cache; }; struct ClosestMatchToText { - ClosestMatchToText( const QHash& _cache ) + explicit ClosestMatchToText( const QHash& _cache ) : cache(_cache) { } /** @brief Calculates the distance to two pre-filtered match items * * @param a The CodeModelView witch represents the first item to be tested * @param b The CodeModelView witch represents the second item to be tested * * @b */ inline bool operator()( const CodeModelViewItem& a, const CodeModelViewItem& b ) const { const int height_a = cache.value(a.m_id.index(), -1); const int height_b = cache.value(b.m_id.index(), -1); Q_ASSERT(height_a != -1); Q_ASSERT(height_b != -1); if (height_a == height_b) { // stable sorting for equal items based on index // TODO: fast string-based sorting in such cases? return a.m_id.index() < b.m_id.index(); } return height_a < height_b; } private: const QHash& cache; }; Path findProjectForForPath(const IndexedString& path) { const auto model = ICore::self()->projectController()->projectModel(); const auto item = model->itemForPath(path); return item ? item->project()->path() : Path(); } } ProjectItemDataProvider::ProjectItemDataProvider( KDevelop::IQuickOpen* quickopen ) : m_quickopen(quickopen) { } void ProjectItemDataProvider::setFilterText( const QString& text ) { m_addedItems.clear(); QStringList search(text.split(QStringLiteral("::"), QString::SkipEmptyParts)); for(int a = 0; a < search.count(); ++a) { if(search[a].endsWith(':')) { //Don't get confused while the :: is being typed search[a] = search[a].left(search[a].length()-1); } } if(!search.isEmpty() && search.back().endsWith('(')) { search.back().chop(1); } if(text.isEmpty() || search.isEmpty()) { m_filteredItems = m_currentItems; return; } KDevVarLengthArray cache; foreach(const QString& searchPart, search) { cache.append(SubstringCache(searchPart)); } if(!text.startsWith(m_currentFilter)) { m_filteredItems = m_currentItems; } m_currentFilter = text; QVector oldFiltered = m_filteredItems; QHash heights; m_filteredItems.clear(); foreach(const CodeModelViewItem& item, oldFiltered) { const QualifiedIdentifier& currentId = item.m_id; int last_pos = currentId.count() - 1; int current_height = 0; int distance = 0; //iter over each search item from last to first //this makes easier to calculate the distance based on where we hit the result or nothing //Iterating from the last item to the first is more efficient, as we want to match the //class/function name, which is the last item on the search fields and on the identifier. for(int b = search.count() - 1; b >= 0; --b) { //iter over each id for the current identifier, from last to first for(; last_pos >= 0; --last_pos, distance++) { // the more distant we are from the class definition, the less priority it will have current_height += distance * 10000; int result; //if the current search item is contained on the current identifier if((result = cache[b].containedIn( currentId.at(last_pos) )) >= 0) { //when we find a hit, whe add the distance to the searched word. //so the closest item will be displayed first current_height += result; if(b == 0) { heights[currentId.index()] = current_height; m_filteredItems << item; } break; } } } } //then, for the last part, we use the already built cache to sort the items according with their distance std::sort(m_filteredItems.begin(), m_filteredItems.end(), ClosestMatchToText(heights)); } QList ProjectItemDataProvider::data( uint start, uint end ) const { QList ret; for(uint a = start; a < end; ++a) { ret << data(a); } return ret; } KDevelop::QuickOpenDataPointer ProjectItemDataProvider::data( uint pos ) const { //Check whether this position falls into an appended item-list, else apply the offset uint filteredItemOffset = 0; for(AddedItems::const_iterator it = m_addedItems.constBegin(); it != m_addedItems.constEnd(); ++it) { int offsetInAppended = pos - (it.key()+1); if(offsetInAppended >= 0 && offsetInAppended < it.value().count()) { return it.value()[offsetInAppended]; } if(it.key() >= pos) { break; } filteredItemOffset += it.value().count(); } const uint a = pos - filteredItemOffset; if(a > (uint)m_filteredItems.size()) { return KDevelop::QuickOpenDataPointer(); } const auto& filteredItem = m_filteredItems[a]; QList ret; KDevelop::DUChainReadLocker lock( DUChain::lock() ); TopDUContext* ctx = DUChainUtils::standardContextForUrl(filteredItem.m_file.toUrl()); if(ctx) { QList decls = ctx->findDeclarations(filteredItem.m_id, CursorInRevision::invalid(), AbstractType::Ptr(), nullptr, DUContext::DirectQualifiedLookup); //Filter out forward-declarations or duplicate imported declarations foreach(Declaration* decl, decls) { bool filter = false; if (decls.size() > 1 && decl->isForwardDeclaration()) { filter = true; } else if (decl->url() != filteredItem.m_file && m_files.contains(decl->url())) { filter = true; } if (filter) { decls.removeOne(decl); } } foreach(Declaration* decl, decls) { DUChainItem item; item.m_item = decl; item.m_text = decl->qualifiedIdentifier().toString(); item.m_projectPath = findProjectForForPath(filteredItem.m_file); ret << QuickOpenDataPointer(new DUChainItemData(item)); } if(decls.isEmpty()) { DUChainItem item; item.m_text = filteredItem.m_id.toString(); item.m_projectPath = findProjectForForPath(filteredItem.m_file); ret << QuickOpenDataPointer(new DUChainItemData(item)); } } else { qCDebug(PLUGIN_QUICKOPEN) << "Could not find standard-context"; } if(!ret.isEmpty()) { QList append = ret.mid(1); if(!append.isEmpty()) { AddedItems addMap; for(AddedItems::iterator it = m_addedItems.begin(); it != m_addedItems.end();) { if(it.key() == pos) { //There already is appended data stored, nothing to do return ret.first(); } else if(it.key() > pos) { addMap[it.key() + append.count()] = it.value(); it = m_addedItems.erase(it); } else { ++it; } } m_addedItems.insert(pos, append); for(AddedItems::const_iterator it = addMap.constBegin(); it != addMap.constEnd(); ++it) { m_addedItems.insert(it.key(), it.value()); } } return ret.first(); } else { return KDevelop::QuickOpenDataPointer(); } } void ProjectItemDataProvider::reset() { m_files = m_quickopen->fileSet(); m_currentItems.clear(); m_addedItems.clear(); KDevelop::DUChainReadLocker lock( DUChain::lock() ); foreach( const IndexedString& u, m_files ) { uint count; const KDevelop::CodeModelItem* items; CodeModel::self().items( u, count, items ); for(uint a = 0; a < count; ++a) { if(!items[a].id.isValid() || items[a].kind & CodeModelItem::ForwardDeclaration) { continue; } if(((m_itemTypes & Classes) && (items[a].kind & CodeModelItem::Class)) || ((m_itemTypes & Functions) && (items[a].kind & CodeModelItem::Function))) { QualifiedIdentifier id = items[a].id.identifier(); if (id.isEmpty() || id.at(0).identifier().isEmpty()) { // id.isEmpty() not always hit when .toString() is actually empty... // anyhow, this makes sure that we don't show duchain items without // any name that could be searched for. This happens e.g. in the c++ // plugin for anonymous structs or sometimes for declarations in macro // expressions continue; } m_currentItems << CodeModelViewItem(u, id); } } } m_filteredItems = m_currentItems; m_currentFilter.clear(); } uint addedItems(const AddedItems& items) { uint add = 0; for(AddedItems::const_iterator it = items.constBegin(); it != items.constEnd(); ++it) { add += it.value().count(); } return add; } uint ProjectItemDataProvider::itemCount() const { return m_filteredItems.count() + addedItems(m_addedItems); } uint ProjectItemDataProvider::unfilteredItemCount() const { return m_currentItems.count() + addedItems(m_addedItems); } QStringList ProjectItemDataProvider::supportedItemTypes() { QStringList ret; ret << i18n("Classes"); ret << i18n("Functions"); return ret; } void ProjectItemDataProvider::enableData( const QStringList& items, const QStringList& scopes ) { //FIXME: property support different scopes if(scopes.contains(i18n("Project"))) { m_itemTypes = NoItems; if(items.contains(i18n("Classes"))) { m_itemTypes = (ItemTypes)(m_itemTypes | Classes); } if( items.contains(i18n("Functions"))) { m_itemTypes = (ItemTypes)(m_itemTypes | Functions); } } else { m_itemTypes = NoItems; } } diff --git a/plugins/quickopen/quickopenplugin.cpp b/plugins/quickopen/quickopenplugin.cpp index a9cb9ab4e..284e93d09 100644 --- a/plugins/quickopen/quickopenplugin.cpp +++ b/plugins/quickopen/quickopenplugin.cpp @@ -1,1107 +1,1107 @@ /* * This file is part of KDevelop * * Copyright 2007 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "quickopenplugin.h" #include "quickopenwidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "quickopenmodel.h" #include "projectfilequickopen.h" #include "projectitemquickopen.h" #include "declarationlistquickopen.h" #include "documentationquickopenprovider.h" #include "actionsquickopenprovider.h" #include "debug.h" #include #include #include #include #include #include #include #include #include Q_LOGGING_CATEGORY(PLUGIN_QUICKOPEN, "kdevplatform.plugins.quickopen") using namespace KDevelop; const bool noHtmlDestriptionInOutline = true; class QuickOpenWidgetCreator { public: virtual ~QuickOpenWidgetCreator() { } virtual QuickOpenWidget* createWidget() = 0; virtual QString objectNameForLine() = 0; virtual void widgetShown() { } }; class StandardQuickOpenWidgetCreator : public QuickOpenWidgetCreator { public: StandardQuickOpenWidgetCreator(const QStringList& items, const QStringList& scopes) : m_items(items) , m_scopes(scopes) { } QString objectNameForLine() override { return QStringLiteral("Quickopen"); } void setItems(const QStringList& scopes, const QStringList& items) { m_scopes = scopes; m_items = items; } QuickOpenWidget* createWidget() override { QStringList useItems = m_items; if(useItems.isEmpty()) useItems = QuickOpenPlugin::self()->lastUsedItems; QStringList useScopes = m_scopes; if(useScopes.isEmpty()) useScopes = QuickOpenPlugin::self()->lastUsedScopes; return new QuickOpenWidget( i18n("Quick Open"), QuickOpenPlugin::self()->m_model, QuickOpenPlugin::self()->lastUsedItems, useScopes, false, true ); } QStringList m_items; QStringList m_scopes; }; class OutlineFilter : public DUChainUtils::DUChainItemFilter { public: enum OutlineMode { Functions, FunctionsAndClasses }; - OutlineFilter(QList& _items, OutlineMode _mode = FunctionsAndClasses) : items(_items), mode(_mode) { + explicit OutlineFilter(QList& _items, OutlineMode _mode = FunctionsAndClasses) : items(_items), mode(_mode) { } bool accept(Declaration* decl) override { if(decl->range().isEmpty()) return false; bool collectable = mode == Functions ? decl->isFunctionDeclaration() : (decl->isFunctionDeclaration() || (decl->internalContext() && decl->internalContext()->type() == DUContext::Class)); if (collectable) { DUChainItem item; item.m_item = IndexedDeclaration(decl); item.m_text = decl->toString(); items << item; return true; } else return false; } bool accept(DUContext* ctx) override { if(ctx->type() == DUContext::Class || ctx->type() == DUContext::Namespace || ctx->type() == DUContext::Global || ctx->type() == DUContext::Other || ctx->type() == DUContext::Helper ) return true; else return false; } QList& items; OutlineMode mode; }; K_PLUGIN_FACTORY_WITH_JSON(KDevQuickOpenFactory, "kdevquickopen.json", registerPlugin();) Declaration* cursorDeclaration() { KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView(); if(!view) return nullptr; KDevelop::DUChainReadLocker lock( DUChain::lock() ); return DUChainUtils::declarationForDefinition( DUChainUtils::itemUnderCursor( view->document()->url(), KTextEditor::Cursor(view->cursorPosition()) ).declaration ); } ///The first definition that belongs to a context that surrounds the current cursor Declaration* cursorContextDeclaration() { KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView(); if(!view) return nullptr; KDevelop::DUChainReadLocker lock( DUChain::lock() ); TopDUContext* ctx = DUChainUtils::standardContextForUrl(view->document()->url()); if(!ctx) return nullptr; KTextEditor::Cursor cursor(view->cursorPosition()); DUContext* subCtx = ctx->findContext(ctx->transformToLocalRevision(cursor)); while(subCtx && !subCtx->owner()) subCtx = subCtx->parentContext(); Declaration* definition = nullptr; if(!subCtx || !subCtx->owner()) definition = DUChainUtils::declarationInLine(cursor, ctx); else definition = subCtx->owner(); if(!definition) return nullptr; return definition; } //Returns only the name, no template-parameters or scope QString cursorItemText() { KDevelop::DUChainReadLocker lock( DUChain::lock() ); Declaration* decl = cursorDeclaration(); if(!decl) return QString(); IDocument* doc = ICore::self()->documentController()->activeDocument(); if(!doc) return QString(); TopDUContext* context = DUChainUtils::standardContextForUrl( doc->url() ); if( !context ) { qCDebug(PLUGIN_QUICKOPEN) << "Got no standard context"; return QString(); } AbstractType::Ptr t = decl->abstractType(); IdentifiedType* idType = dynamic_cast(t.data()); if( idType && idType->declaration(context) ) decl = idType->declaration(context); if(!decl->qualifiedIdentifier().isEmpty()) return decl->qualifiedIdentifier().last().identifier().str(); return QString(); } QuickOpenLineEdit* QuickOpenPlugin::createQuickOpenLineWidget() { return new QuickOpenLineEdit(new StandardQuickOpenWidgetCreator(QStringList(), QStringList())); } QuickOpenLineEdit* QuickOpenPlugin::quickOpenLine(QString name) { QList< QuickOpenLineEdit* > lines = ICore::self()->uiController()->activeMainWindow()->findChildren(name); foreach(QuickOpenLineEdit* line, lines) { if(line->isVisible()) { return line; } } return nullptr; } static QuickOpenPlugin* staticQuickOpenPlugin = nullptr; QuickOpenPlugin* QuickOpenPlugin::self() { return staticQuickOpenPlugin; } void QuickOpenPlugin::createActionsForMainWindow(Sublime::MainWindow* /*window*/, QString& xmlFile, KActionCollection& actions) { xmlFile = QStringLiteral("kdevquickopen.rc"); QAction* quickOpen = actions.addAction(QStringLiteral("quick_open")); quickOpen->setText( i18n("&Quick Open") ); quickOpen->setIcon( QIcon::fromTheme(QStringLiteral("quickopen")) ); actions.setDefaultShortcut( quickOpen, Qt::CTRL | Qt::ALT | Qt::Key_Q ); connect(quickOpen, &QAction::triggered, this, &QuickOpenPlugin::quickOpen); QAction* quickOpenFile = actions.addAction(QStringLiteral("quick_open_file")); quickOpenFile->setText( i18n("Quick Open &File") ); quickOpenFile->setIcon( QIcon::fromTheme(QStringLiteral("quickopen-file")) ); actions.setDefaultShortcut( quickOpenFile, Qt::CTRL | Qt::ALT | Qt::Key_O ); connect(quickOpenFile, &QAction::triggered, this, &QuickOpenPlugin::quickOpenFile); QAction* quickOpenClass = actions.addAction(QStringLiteral("quick_open_class")); quickOpenClass->setText( i18n("Quick Open &Class") ); quickOpenClass->setIcon( QIcon::fromTheme(QStringLiteral("quickopen-class")) ); actions.setDefaultShortcut( quickOpenClass, Qt::CTRL | Qt::ALT | Qt::Key_C ); connect(quickOpenClass, &QAction::triggered, this, &QuickOpenPlugin::quickOpenClass); QAction* quickOpenFunction = actions.addAction(QStringLiteral("quick_open_function")); quickOpenFunction->setText( i18n("Quick Open &Function") ); quickOpenFunction->setIcon( QIcon::fromTheme(QStringLiteral("quickopen-function")) ); actions.setDefaultShortcut( quickOpenFunction, Qt::CTRL | Qt::ALT | Qt::Key_M ); connect(quickOpenFunction, &QAction::triggered, this, &QuickOpenPlugin::quickOpenFunction); QAction* quickOpenAlreadyOpen = actions.addAction(QStringLiteral("quick_open_already_open")); quickOpenAlreadyOpen->setText( i18n("Quick Open &Already Open File") ); quickOpenAlreadyOpen->setIcon( QIcon::fromTheme(QStringLiteral("quickopen-file")) ); connect(quickOpenAlreadyOpen, &QAction::triggered, this, &QuickOpenPlugin::quickOpenOpenFile); QAction* quickOpenDocumentation = actions.addAction(QStringLiteral("quick_open_documentation")); quickOpenDocumentation->setText( i18n("Quick Open &Documentation") ); quickOpenDocumentation->setIcon( QIcon::fromTheme(QStringLiteral("quickopen-documentation")) ); actions.setDefaultShortcut( quickOpenDocumentation, Qt::CTRL | Qt::ALT | Qt::Key_D ); connect(quickOpenDocumentation, &QAction::triggered, this, &QuickOpenPlugin::quickOpenDocumentation); QAction* quickOpenActions = actions.addAction(QStringLiteral("quick_open_actions")); quickOpenActions->setText( i18n("Quick Open &Actions") ); actions.setDefaultShortcut( quickOpenActions, Qt::CTRL | Qt::ALT | Qt::Key_A); connect(quickOpenActions, &QAction::triggered, this, &QuickOpenPlugin::quickOpenActions); m_quickOpenDeclaration = actions.addAction(QStringLiteral("quick_open_jump_declaration")); m_quickOpenDeclaration->setText( i18n("Jump to Declaration") ); m_quickOpenDeclaration->setIcon( QIcon::fromTheme(QStringLiteral("go-jump-declaration") ) ); actions.setDefaultShortcut( m_quickOpenDeclaration, Qt::CTRL | Qt::Key_Period ); connect(m_quickOpenDeclaration, &QAction::triggered, this, &QuickOpenPlugin::quickOpenDeclaration, Qt::QueuedConnection); m_quickOpenDefinition = actions.addAction(QStringLiteral("quick_open_jump_definition")); m_quickOpenDefinition->setText( i18n("Jump to Definition") ); m_quickOpenDefinition->setIcon( QIcon::fromTheme(QStringLiteral("go-jump-definition") ) ); actions.setDefaultShortcut( m_quickOpenDefinition, Qt::CTRL | Qt::Key_Comma ); connect(m_quickOpenDefinition, &QAction::triggered, this, &QuickOpenPlugin::quickOpenDefinition, Qt::QueuedConnection); QWidgetAction* quickOpenLine = new QWidgetAction(this); quickOpenLine->setText( i18n("Embedded Quick Open") ); // actions.setDefaultShortcut( quickOpenLine, Qt::CTRL | Qt::ALT | Qt::Key_E ); // connect(quickOpenLine, SIGNAL(triggered(bool)), this, SLOT(quickOpenLine(bool))); quickOpenLine->setDefaultWidget(createQuickOpenLineWidget()); actions.addAction(QStringLiteral("quick_open_line"), quickOpenLine); QAction* quickOpenNextFunction = actions.addAction(QStringLiteral("quick_open_next_function")); quickOpenNextFunction->setText( i18n("Next Function") ); actions.setDefaultShortcut( quickOpenNextFunction, Qt::CTRL| Qt::ALT | Qt::Key_PageDown ); connect(quickOpenNextFunction, &QAction::triggered, this, &QuickOpenPlugin::nextFunction); QAction* quickOpenPrevFunction = actions.addAction(QStringLiteral("quick_open_prev_function")); quickOpenPrevFunction->setText( i18n("Previous Function") ); actions.setDefaultShortcut( quickOpenPrevFunction, Qt::CTRL| Qt::ALT | Qt::Key_PageUp ); connect(quickOpenPrevFunction, &QAction::triggered, this, &QuickOpenPlugin::previousFunction); QAction* quickOpenNavigateFunctions = actions.addAction(QStringLiteral("quick_open_outline")); quickOpenNavigateFunctions->setText( i18n("Outline") ); actions.setDefaultShortcut( quickOpenNavigateFunctions, Qt::CTRL| Qt::ALT | Qt::Key_N ); connect(quickOpenNavigateFunctions, &QAction::triggered, this, &QuickOpenPlugin::quickOpenNavigateFunctions); } QuickOpenPlugin::QuickOpenPlugin(QObject *parent, const QVariantList&) : KDevelop::IPlugin(QStringLiteral("kdevquickopen"), parent) { staticQuickOpenPlugin = this; m_model = new QuickOpenModel( nullptr ); KConfigGroup quickopengrp = KSharedConfig::openConfig()->group("QuickOpen"); lastUsedScopes = quickopengrp.readEntry("SelectedScopes", QStringList() << i18n("Project") << i18n("Includes") << i18n("Includers") << i18n("Currently Open") ); lastUsedItems = quickopengrp.readEntry("SelectedItems", QStringList() ); { m_openFilesData = new OpenFilesDataProvider(); QStringList scopes, items; scopes << i18n("Currently Open"); items << i18n("Files"); m_model->registerProvider( scopes, items, m_openFilesData ); } { m_projectFileData = new ProjectFileDataProvider(); QStringList scopes, items; scopes << i18n("Project"); items << i18n("Files"); m_model->registerProvider( scopes, items, m_projectFileData ); } { m_projectItemData = new ProjectItemDataProvider(this); QStringList scopes, items; scopes << i18n("Project"); items << ProjectItemDataProvider::supportedItemTypes(); m_model->registerProvider( scopes, items, m_projectItemData ); } { m_documentationItemData = new DocumentationQuickOpenProvider; QStringList scopes, items; scopes << i18n("Includes"); items << i18n("Documentation"); m_model->registerProvider( scopes, items, m_documentationItemData ); } { m_actionsItemData = new ActionsQuickOpenProvider; QStringList scopes, items; scopes << i18n("Includes"); items << i18n("Actions"); m_model->registerProvider( scopes, items, m_actionsItemData ); } } QuickOpenPlugin::~QuickOpenPlugin() { freeModel(); delete m_model; delete m_projectFileData; delete m_projectItemData; delete m_openFilesData; delete m_documentationItemData; delete m_actionsItemData; } void QuickOpenPlugin::unload() { } ContextMenuExtension QuickOpenPlugin::contextMenuExtension(Context* context) { KDevelop::ContextMenuExtension menuExt = KDevelop::IPlugin::contextMenuExtension( context ); KDevelop::DeclarationContext *codeContext = dynamic_cast(context); if (!codeContext) return menuExt; DUChainReadLocker readLock; Declaration* decl(codeContext->declaration().data()); if (decl) { const bool isDef = FunctionDefinition::definition(decl); if (codeContext->use().isValid() || !isDef) { menuExt.addAction( KDevelop::ContextMenuExtension::ExtensionGroup, m_quickOpenDeclaration); } if(isDef) { menuExt.addAction( KDevelop::ContextMenuExtension::ExtensionGroup, m_quickOpenDefinition); } } return menuExt; } void QuickOpenPlugin::showQuickOpen(const QStringList& items) { if(!freeModel()) return; QStringList initialItems = items; QStringList useScopes = lastUsedScopes; if (!useScopes.contains(i18n("Currently Open"))) useScopes << i18n("Currently Open"); showQuickOpenWidget(initialItems, useScopes, false); } void QuickOpenPlugin::showQuickOpen( ModelTypes modes ) { if(!freeModel()) return; QStringList initialItems; if( modes & Files || modes & OpenFiles ) initialItems << i18n("Files"); if( modes & Functions ) initialItems << i18n("Functions"); if( modes & Classes ) initialItems << i18n("Classes"); QStringList useScopes; if ( modes != OpenFiles ) useScopes = lastUsedScopes; if((modes & OpenFiles) && !useScopes.contains(i18n("Currently Open"))) useScopes << i18n("Currently Open"); bool preselectText = (!(modes & Files) || modes == QuickOpenPlugin::All); showQuickOpenWidget(initialItems, useScopes, preselectText); } void QuickOpenPlugin::showQuickOpenWidget(const QStringList& items, const QStringList& scopes, bool preselectText) { QuickOpenWidgetDialog* dialog = new QuickOpenWidgetDialog( i18n("Quick Open"), m_model, items, scopes ); m_currentWidgetHandler = dialog; if (preselectText) { KDevelop::IDocument *currentDoc = core()->documentController()->activeDocument(); if (currentDoc && currentDoc->isTextDocument()) { QString preselected = currentDoc->textSelection().isEmpty() ? currentDoc->textWord() : currentDoc->textDocument()->text(currentDoc->textSelection()); dialog->widget()->setPreselectedText(preselected); } } connect( dialog->widget(), &QuickOpenWidget::scopesChanged, this, &QuickOpenPlugin::storeScopes ); //Not connecting itemsChanged to storeItems, as showQuickOpen doesn't use lastUsedItems and so shouldn't store item changes //connect( dialog->widget(), SIGNAL(itemsChanged(QStringList)), this, SLOT(storeItems(QStringList)) ); dialog->widget()->ui.itemsButton->setEnabled(false); if(quickOpenLine()) { quickOpenLine()->showWithWidget(dialog->widget()); dialog->deleteLater(); }else{ dialog->run(); } } void QuickOpenPlugin::storeScopes( const QStringList& scopes ) { lastUsedScopes = scopes; KConfigGroup grp = KSharedConfig::openConfig()->group("QuickOpen"); grp.writeEntry( "SelectedScopes", scopes ); } void QuickOpenPlugin::storeItems( const QStringList& items ) { lastUsedItems = items; KConfigGroup grp = KSharedConfig::openConfig()->group("QuickOpen"); grp.writeEntry( "SelectedItems", items ); } void QuickOpenPlugin::quickOpen() { if(quickOpenLine()) //Same as clicking on Quick Open quickOpenLine()->setFocus(); else showQuickOpen( All ); } void QuickOpenPlugin::quickOpenFile() { showQuickOpen( (ModelTypes)(Files | OpenFiles) ); } void QuickOpenPlugin::quickOpenFunction() { showQuickOpen( Functions ); } void QuickOpenPlugin::quickOpenClass() { showQuickOpen( Classes ); } void QuickOpenPlugin::quickOpenOpenFile() { showQuickOpen( OpenFiles ); } void QuickOpenPlugin::quickOpenDocumentation() { showQuickOpenWidget(QStringList(i18n("Documentation")), QStringList(i18n("Includes")), true); } void QuickOpenPlugin::quickOpenActions() { showQuickOpenWidget(QStringList(i18n("Actions")), QStringList(i18n("Includes")), true); } QSet QuickOpenPlugin::fileSet() const { return m_model->fileSet(); } void QuickOpenPlugin::registerProvider( const QStringList& scope, const QStringList& type, KDevelop::QuickOpenDataProviderBase* provider ) { m_model->registerProvider( scope, type, provider ); } bool QuickOpenPlugin::removeProvider( KDevelop::QuickOpenDataProviderBase* provider ) { m_model->removeProvider( provider ); return true; } void QuickOpenPlugin::quickOpenDeclaration() { if(jumpToSpecialObject()) return; KDevelop::DUChainReadLocker lock( DUChain::lock() ); Declaration* decl = cursorDeclaration(); if(!decl) { qCDebug(PLUGIN_QUICKOPEN) << "Found no declaration for cursor, cannot jump"; return; } decl->activateSpecialization(); IndexedString u = decl->url(); KTextEditor::Cursor c = decl->rangeInCurrentRevision().start(); if(u.isEmpty()) { qCDebug(PLUGIN_QUICKOPEN) << "Got empty url for declaration" << decl->toString(); return; } lock.unlock(); core()->documentController()->openDocument(u.toUrl(), c); } QWidget* QuickOpenPlugin::specialObjectNavigationWidget() const { KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView(); if( !view ) return nullptr; QUrl url = ICore::self()->documentController()->activeDocument()->url(); const auto languages = ICore::self()->languageController()->languagesForUrl(url); foreach (const auto language, languages) { QWidget* w = language->specialLanguageObjectNavigationWidget(url, KTextEditor::Cursor(view->cursorPosition()) ); if(w) return w; } return nullptr; } QPair QuickOpenPlugin::specialObjectJumpPosition() const { KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView(); if( !view ) return qMakePair(QUrl(), KTextEditor::Cursor()); QUrl url = ICore::self()->documentController()->activeDocument()->url(); const auto languages = ICore::self()->languageController()->languagesForUrl(url); foreach (const auto language, languages) { QPair pos = language->specialLanguageObjectJumpCursor(url, KTextEditor::Cursor(view->cursorPosition()) ); if(pos.second.isValid()) { return pos; } } return qMakePair(QUrl(), KTextEditor::Cursor::invalid()); } bool QuickOpenPlugin::jumpToSpecialObject() { QPair pos = specialObjectJumpPosition(); if(pos.second.isValid()) { if(pos.first.isEmpty()) { qCDebug(PLUGIN_QUICKOPEN) << "Got empty url for special language object"; return false; } ICore::self()->documentController()->openDocument(pos.first, pos.second); return true; } return false; } void QuickOpenPlugin::quickOpenDefinition() { if(jumpToSpecialObject()) return; KDevelop::DUChainReadLocker lock( DUChain::lock() ); Declaration* decl = cursorDeclaration(); if(!decl) { qCDebug(PLUGIN_QUICKOPEN) << "Found no declaration for cursor, cannot jump"; return; } IndexedString u = decl->url(); KTextEditor::Cursor c = decl->rangeInCurrentRevision().start(); if(FunctionDefinition* def = FunctionDefinition::definition(decl)) { def->activateSpecialization(); u = def->url(); c = def->rangeInCurrentRevision().start(); }else{ qCDebug(PLUGIN_QUICKOPEN) << "Found no definition for declaration"; decl->activateSpecialization(); } if(u.isEmpty()) { qCDebug(PLUGIN_QUICKOPEN) << "Got empty url for declaration" << decl->toString(); return; } lock.unlock(); core()->documentController()->openDocument(u.toUrl(), c); } bool QuickOpenPlugin::freeModel() { if(m_currentWidgetHandler) delete m_currentWidgetHandler; m_currentWidgetHandler = nullptr; return true; } void QuickOpenPlugin::nextFunction() { jumpToNearestFunction(NextFunction); } void QuickOpenPlugin::previousFunction() { jumpToNearestFunction(PreviousFunction); } void QuickOpenPlugin::jumpToNearestFunction(QuickOpenPlugin::FunctionJumpDirection direction) { IDocument* doc = ICore::self()->documentController()->activeDocument(); if(!doc) { qCDebug(PLUGIN_QUICKOPEN) << "No active document"; return; } KDevelop::DUChainReadLocker lock( DUChain::lock() ); TopDUContext* context = DUChainUtils::standardContextForUrl( doc->url() ); if( !context ) { qCDebug(PLUGIN_QUICKOPEN) << "Got no standard context"; return; } QList items; OutlineFilter filter(items, OutlineFilter::Functions); DUChainUtils::collectItems( context, filter ); CursorInRevision cursor = context->transformToLocalRevision(KTextEditor::Cursor(doc->cursorPosition())); if (!cursor.isValid()) return; Declaration *nearestDeclBefore = nullptr; int distanceBefore = INT_MIN; Declaration *nearestDeclAfter = nullptr; int distanceAfter = INT_MAX; for (int i = 0; i < items.count(); ++i) { Declaration *decl = items[i].m_item.data(); int distance = decl->range().start.line - cursor.line; if (distance < 0 && distance >= distanceBefore) { distanceBefore = distance; nearestDeclBefore = decl; } else if (distance > 0 && distance <= distanceAfter) { distanceAfter = distance; nearestDeclAfter = decl; } } CursorInRevision c = CursorInRevision::invalid(); if (direction == QuickOpenPlugin::NextFunction && nearestDeclAfter) c = nearestDeclAfter->range().start; else if (direction == QuickOpenPlugin::PreviousFunction && nearestDeclBefore) c = nearestDeclBefore->range().start; KTextEditor::Cursor textCursor = KTextEditor::Cursor::invalid(); if (c.isValid()) textCursor = context->transformFromLocalRevision(c); lock.unlock(); if (textCursor.isValid()) core()->documentController()->openDocument(doc->url(), textCursor); else qCDebug(PLUGIN_QUICKOPEN) << "No declaration to jump to"; } struct CreateOutlineDialog { CreateOutlineDialog() : dialog(nullptr), cursorDecl(nullptr), model(nullptr) { } void start() { if(!QuickOpenPlugin::self()->freeModel()) return; IDocument* doc = ICore::self()->documentController()->activeDocument(); if(!doc) { qCDebug(PLUGIN_QUICKOPEN) << "No active document"; return; } KDevelop::DUChainReadLocker lock( DUChain::lock() ); TopDUContext* context = DUChainUtils::standardContextForUrl( doc->url() ); if( !context ) { qCDebug(PLUGIN_QUICKOPEN) << "Got no standard context"; return; } model = new QuickOpenModel(nullptr); OutlineFilter filter(items); DUChainUtils::collectItems( context, filter ); if(noHtmlDestriptionInOutline) { for(int a = 0; a < items.size(); ++a) items[a].m_noHtmlDestription = true; } cursorDecl = cursorContextDeclaration(); model->registerProvider( QStringList(), QStringList(), new DeclarationListDataProvider(QuickOpenPlugin::self(), items, true) ); dialog = new QuickOpenWidgetDialog( i18n("Outline"), model, QStringList(), QStringList(), true ); model->setParent(dialog->widget()); } void finish() { //Select the declaration that contains the cursor if(cursorDecl && dialog) { int num = 0; foreach(const DUChainItem& item, items) { if(item.m_item.data() == cursorDecl) { QModelIndex index(model->index(num,0,QModelIndex())); // Need to invoke the scrolling later. If we did it now, then it wouldn't have any effect, // apparently because the widget internals aren't initialized yet properly (although we've // already called 'widget->show()'. auto list = dialog->widget()->ui.list; QMetaObject::invokeMethod(list, "setCurrentIndex", Qt::QueuedConnection, Q_ARG(QModelIndex, index)); QMetaObject::invokeMethod(list, "scrollTo", Qt::QueuedConnection, Q_ARG(QModelIndex, index), Q_ARG(QAbstractItemView::ScrollHint, QAbstractItemView::PositionAtCenter)); } ++num; } } } QPointer dialog; Declaration* cursorDecl; QList items; QuickOpenModel* model; }; class OutlineQuickopenWidgetCreator : public QuickOpenWidgetCreator { public: OutlineQuickopenWidgetCreator(QStringList /*scopes*/, QStringList /*items*/) : m_creator(nullptr) { } ~OutlineQuickopenWidgetCreator() override { delete m_creator; } QuickOpenWidget* createWidget() override { delete m_creator; m_creator = new CreateOutlineDialog; m_creator->start(); if(!m_creator->dialog) return nullptr; m_creator->dialog->deleteLater(); return m_creator->dialog->widget(); } void widgetShown() override { if(m_creator) { m_creator->finish(); delete m_creator; m_creator = nullptr; } } QString objectNameForLine() override { return QStringLiteral("Outline"); } CreateOutlineDialog* m_creator; }; void QuickOpenPlugin::quickOpenNavigateFunctions() { CreateOutlineDialog create; create.start(); if(!create.dialog) return; m_currentWidgetHandler = create.dialog; QuickOpenLineEdit* line = quickOpenLine(QStringLiteral("Outline")); if(!line) line = quickOpenLine(); if(line) { line->showWithWidget(create.dialog->widget()); create.dialog->deleteLater(); }else create.dialog->run(); create.finish(); } QuickOpenLineEdit::QuickOpenLineEdit(QuickOpenWidgetCreator* creator) : m_widget(nullptr), m_forceUpdate(false), m_widgetCreator(creator) { setMinimumWidth(200); setMaximumWidth(400); deactivate(); setDefaultText(i18n("Quick Open...")); setToolTip(i18n("Search for files, classes, functions and more," " allowing you to quickly navigate in your source code.")); setObjectName(m_widgetCreator->objectNameForLine()); setFocusPolicy(Qt::ClickFocus); } QuickOpenLineEdit::~QuickOpenLineEdit() { delete m_widget; delete m_widgetCreator; } bool QuickOpenLineEdit::insideThis(QObject* object) { while (object) { qCDebug(PLUGIN_QUICKOPEN) << object; if (object == this || object == m_widget) { return true; } object = object->parent(); } return false; } void QuickOpenLineEdit::widgetDestroyed(QObject* obj) { Q_UNUSED(obj); // need to use a queued connection here, because this function is called in ~QWidget! // => QuickOpenWidget instance is half-destructed => connections are not yet cleared // => clear() will trigger signals which will operate on the invalid QuickOpenWidget // So, just wait until properly destructed QMetaObject::invokeMethod(this, "deactivate", Qt::QueuedConnection); } void QuickOpenLineEdit::showWithWidget(QuickOpenWidget* widget) { connect(widget, &QuickOpenWidget::destroyed, this, &QuickOpenLineEdit::widgetDestroyed); qCDebug(PLUGIN_QUICKOPEN) << "storing widget" << widget; deactivate(); if(m_widget) { qCDebug(PLUGIN_QUICKOPEN) << "deleting" << m_widget; delete m_widget; } m_widget = widget; m_forceUpdate = true; setFocus(); } void QuickOpenLineEdit::focusInEvent(QFocusEvent* ev) { QLineEdit::focusInEvent(ev); // delete m_widget; qCDebug(PLUGIN_QUICKOPEN) << "got focus"; qCDebug(PLUGIN_QUICKOPEN) << "old widget" << m_widget << "force update:" << m_forceUpdate; if (m_widget && !m_forceUpdate) return; if (!m_forceUpdate && !QuickOpenPlugin::self()->freeModel()) { deactivate(); return; } m_forceUpdate = false; if(!m_widget) { m_widget = m_widgetCreator->createWidget(); if(!m_widget) { deactivate(); return; } } activate(); m_widget->showStandardButtons(false); m_widget->showSearchField(false); m_widget->setParent(nullptr, Qt::ToolTip); m_widget->setFocusPolicy(Qt::NoFocus); m_widget->setAlternativeSearchField(this); QuickOpenPlugin::self()->m_currentWidgetHandler = m_widget; connect(m_widget.data(), &QuickOpenWidget::ready, this, &QuickOpenLineEdit::deactivate); connect( m_widget.data(), &QuickOpenWidget::scopesChanged, QuickOpenPlugin::self(), &QuickOpenPlugin::storeScopes ); connect( m_widget.data(), &QuickOpenWidget::itemsChanged, QuickOpenPlugin::self(), &QuickOpenPlugin::storeItems ); Q_ASSERT(m_widget->ui.searchLine == this); m_widget->prepareShow(); QRect widgetGeometry = QRect(mapToGlobal(QPoint(0, height())), mapToGlobal(QPoint(width(), height() + 400))); widgetGeometry.setWidth(700); ///@todo Waste less space QRect screenGeom = QApplication::desktop()->screenGeometry(this); if (widgetGeometry.right() > screenGeom.right()) { widgetGeometry.moveRight(screenGeom.right()); } if (widgetGeometry.bottom() > screenGeom.bottom()) { widgetGeometry.moveBottom(mapToGlobal(QPoint(0, 0)).y()); } m_widget->setGeometry(widgetGeometry); m_widget->show(); m_widgetCreator->widgetShown(); } void QuickOpenLineEdit::hideEvent(QHideEvent* ev) { QWidget::hideEvent(ev); if(m_widget) QMetaObject::invokeMethod(this, "checkFocus", Qt::QueuedConnection); // deactivate(); } bool QuickOpenLineEdit::eventFilter(QObject* obj, QEvent* e) { if (!m_widget) return false; switch (e->type()) { case QEvent::KeyPress: case QEvent::ShortcutOverride: if (static_cast(e)->key() == Qt::Key_Escape) { deactivate(); e->accept(); return true; } break; case QEvent::WindowActivate: case QEvent::WindowDeactivate: QMetaObject::invokeMethod(this, "checkFocus", Qt::QueuedConnection); break; // handle bug 260657 - "Outline menu doesn't follow main window on its move" case QEvent::Move: { if (QWidget* widget = qobject_cast(obj)) { // close the outline menu in case a parent widget moved if (widget->isAncestorOf(this)) { qCDebug(PLUGIN_QUICKOPEN) << "closing because of parent widget move"; deactivate(); } break; } } case QEvent::FocusIn: if (dynamic_cast(obj)) { QFocusEvent* focusEvent = dynamic_cast(e); Q_ASSERT(focusEvent); //Eat the focus event, keep the focus qCDebug(PLUGIN_QUICKOPEN) << "focus change" << "inside this: " << insideThis(obj) << "this" << this << "obj" << obj; if(obj == this) return false; qCDebug(PLUGIN_QUICKOPEN) << "reason" << focusEvent->reason(); if (focusEvent->reason() != Qt::MouseFocusReason && focusEvent->reason() != Qt::ActiveWindowFocusReason) { QMetaObject::invokeMethod(this, "checkFocus", Qt::QueuedConnection); return false; } if (!insideThis(obj)) deactivate(); } else if(obj != this) { QMetaObject::invokeMethod(this, "checkFocus", Qt::QueuedConnection); } break; default: break; } return false; } void QuickOpenLineEdit::activate() { qCDebug(PLUGIN_QUICKOPEN) << "activating"; setText(QString()); setStyleSheet(QString()); qApp->installEventFilter(this); } void QuickOpenLineEdit::deactivate() { qCDebug(PLUGIN_QUICKOPEN) << "deactivating"; clear(); if(m_widget || hasFocus()) QMetaObject::invokeMethod(this, "checkFocus", Qt::QueuedConnection); if (m_widget) m_widget->deleteLater(); m_widget = nullptr; qApp->removeEventFilter(this); } void QuickOpenLineEdit::checkFocus() { qCDebug(PLUGIN_QUICKOPEN) << "checking focus" << m_widget; if(m_widget) { QWidget* focusWidget = QApplication::focusWidget(); bool focusWidgetInsideThis = focusWidget ? insideThis(focusWidget) : false; if(QApplication::focusWindow() && isVisible() && !isHidden() && (!focusWidget || (focusWidget && focusWidgetInsideThis))) { qCDebug(PLUGIN_QUICKOPEN) << "setting focus to line edit"; activateWindow(); setFocus(); } else { qCDebug(PLUGIN_QUICKOPEN) << "deactivating because check failed, focusWidget" << focusWidget << "insideThis" << focusWidgetInsideThis; deactivate(); } }else{ if (ICore::self()->documentController()->activeDocument()) ICore::self()->documentController()->activateDocument(ICore::self()->documentController()->activeDocument()); //Make sure the focus is somewehre else, even if there is no active document setEnabled(false); setEnabled(true); } } IQuickOpenLine* QuickOpenPlugin::createQuickOpenLine(const QStringList& scopes, const QStringList& type, IQuickOpen::QuickOpenType kind) { if(kind == Outline) return new QuickOpenLineEdit(new OutlineQuickopenWidgetCreator(scopes, type)); else return new QuickOpenLineEdit(new StandardQuickOpenWidgetCreator(scopes, type)); } #include "quickopenplugin.moc" diff --git a/plugins/quickopen/quickopenwidget.cpp b/plugins/quickopen/quickopenwidget.cpp index bcc2dcb46..85d3ba6a1 100644 --- a/plugins/quickopen/quickopenwidget.cpp +++ b/plugins/quickopen/quickopenwidget.cpp @@ -1,515 +1,515 @@ /* * This file is part of KDevelop * * Copyright 2007 David Nolden * Copyright 2016 Kevin Funk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "quickopenwidget.h" #include #include "debug.h" #include "expandingtree/expandingdelegate.h" #include "quickopenmodel.h" #include #include #include #include #include #include #include using namespace KDevelop; class QuickOpenDelegate : public ExpandingDelegate { Q_OBJECT public: - QuickOpenDelegate(ExpandingWidgetModel* model, QObject* parent = nullptr) : ExpandingDelegate(model, parent) { + explicit QuickOpenDelegate(ExpandingWidgetModel* model, QObject* parent = nullptr) : ExpandingDelegate(model, parent) { } QList createHighlighting(const QModelIndex& index, QStyleOptionViewItem& option) const override { QList highlighting = index.data(KTextEditor::CodeCompletionModel::CustomHighlight).toList(); if(!highlighting.isEmpty()) return highlightingFromVariantList(highlighting); return ExpandingDelegate::createHighlighting( index, option ); } }; QuickOpenWidget::QuickOpenWidget( QString title, QuickOpenModel* model, const QStringList& initialItems, const QStringList& initialScopes, bool listOnly, bool noSearchField ) : m_model(model) , m_expandedTemporary(false) , m_hadNoCommandSinceAlt(true) { m_filterTimer.setSingleShot(true); connect(&m_filterTimer, &QTimer::timeout, this, &QuickOpenWidget::applyFilter); Q_UNUSED( title ); ui.setupUi( this ); ui.list->header()->hide(); ui.list->setRootIsDecorated( false ); ui.list->setVerticalScrollMode( QAbstractItemView::ScrollPerItem ); connect(ui.list->verticalScrollBar(), &QScrollBar::valueChanged, m_model, &QuickOpenModel::placeExpandingWidgets); ui.searchLine->setFocus(); ui.list->setItemDelegate( new QuickOpenDelegate( m_model, ui.list ) ); if(!listOnly) { QStringList allTypes = m_model->allTypes(); QStringList allScopes = m_model->allScopes(); QMenu* itemsMenu = new QMenu(this); foreach( const QString &type, allTypes ) { QAction* action = new QAction(type, itemsMenu); action->setCheckable(true); action->setChecked(initialItems.isEmpty() || initialItems.contains( type )); connect( action, &QAction::toggled, this, &QuickOpenWidget::updateProviders, Qt::QueuedConnection ); itemsMenu->addAction(action); } ui.itemsButton->setMenu(itemsMenu); QMenu* scopesMenu = new QMenu(this); foreach( const QString &scope, allScopes ) { QAction* action = new QAction(scope, scopesMenu); action->setCheckable(true); action->setChecked(initialScopes.isEmpty() || initialScopes.contains( scope ) ); connect( action, &QAction::toggled, this, &QuickOpenWidget::updateProviders, Qt::QueuedConnection ); scopesMenu->addAction(action); } ui.scopesButton->setMenu(scopesMenu); }else{ ui.list->setFocusPolicy(Qt::StrongFocus); ui.scopesButton->hide(); ui.itemsButton->hide(); ui.label->hide(); ui.label_2->hide(); } showSearchField(!noSearchField); ui.okButton->hide(); ui.cancelButton->hide(); ui.searchLine->installEventFilter( this ); ui.list->installEventFilter( this ); ui.list->setFocusPolicy(Qt::NoFocus); ui.scopesButton->setFocusPolicy(Qt::NoFocus); ui.itemsButton->setFocusPolicy(Qt::NoFocus); connect( ui.searchLine, &QLineEdit::textChanged, this, &QuickOpenWidget::textChanged ); connect( ui.list, &ExpandingTree::doubleClicked, this, &QuickOpenWidget::doubleClicked ); connect(ui.okButton, &QPushButton::clicked, this, &QuickOpenWidget::accept); connect(ui.okButton, &QPushButton::clicked, this, &QuickOpenWidget::ready); connect(ui.cancelButton, &QPushButton::clicked, this, &QuickOpenWidget::ready); updateProviders(); updateTimerInterval(true); // no need to call this, it's done by updateProviders already // m_model->restart(); } void QuickOpenWidget::showStandardButtons(bool show) { if(show) { ui.okButton->show(); ui.cancelButton->show(); }else{ ui.okButton->hide(); ui.cancelButton->hide(); } } void QuickOpenWidget::updateTimerInterval(bool cheapFilterChange) { const int MAX_ITEMS = 10000; if ( cheapFilterChange && m_model->rowCount(QModelIndex()) < MAX_ITEMS ) { // cheap change and there are currently just a few items, // so apply filter instantly m_filterTimer.setInterval(0); } else if ( m_model->unfilteredRowCount() < MAX_ITEMS ) { // not a cheap change, but there are generally // just a few items in the list: apply filter instantly m_filterTimer.setInterval(0); } else { // otherwise use a timer to prevent sluggishness while typing m_filterTimer.setInterval(300); } } void QuickOpenWidget::showEvent(QShowEvent* e) { QWidget::showEvent(e); // The column width only has an effect _after_ the widget has been shown ui.list->setColumnWidth( 0, 20 ); } void QuickOpenWidget::setAlternativeSearchField(QLineEdit* alterantiveSearchField) { ui.searchLine = alterantiveSearchField; ui.searchLine->installEventFilter( this ); connect( ui.searchLine, &QLineEdit::textChanged, this, &QuickOpenWidget::textChanged ); } void QuickOpenWidget::showSearchField(bool b) { if(b){ ui.searchLine->show(); ui.searchLabel->show(); }else{ ui.searchLine->hide(); ui.searchLabel->hide(); } } void QuickOpenWidget::prepareShow() { ui.list->setModel( nullptr ); ui.list->setVerticalScrollMode(QAbstractItemView::ScrollPerItem); m_model->setTreeView( ui.list ); ui.list->setModel( m_model ); m_filterTimer.stop(); m_filter = QString(); if (!m_preselectedText.isEmpty()) { ui.searchLine->setText(m_preselectedText); ui.searchLine->selectAll(); } m_model->restart(false); connect( ui.list->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &QuickOpenWidget::callRowSelected ); connect( ui.list->selectionModel(), &QItemSelectionModel::selectionChanged, this, &QuickOpenWidget::callRowSelected ); } void QuickOpenWidgetDialog::run() { m_widget->prepareShow(); m_dialog->show(); } QuickOpenWidget::~QuickOpenWidget() { m_model->setTreeView( nullptr ); } QuickOpenWidgetDialog::QuickOpenWidgetDialog(QString title, QuickOpenModel* model, const QStringList& initialItems, const QStringList& initialScopes, bool listOnly, bool noSearchField) { m_widget = new QuickOpenWidget(title, model, initialItems, initialScopes, listOnly, noSearchField); // the QMenu might close on esc and we want to close the whole dialog then connect( m_widget, &QuickOpenWidget::aboutToHide, this, &QuickOpenWidgetDialog::deleteLater ); m_dialog = new QDialog( ICore::self()->uiController()->activeMainWindow() ); m_dialog->resize(QSize(800, 400)); m_dialog->setWindowTitle(title); QVBoxLayout* layout = new QVBoxLayout(m_dialog); layout->addWidget(m_widget); m_widget->showStandardButtons(true); connect(m_widget, &QuickOpenWidget::ready, m_dialog, &QDialog::close); connect( m_dialog, &QDialog::accepted, m_widget, &QuickOpenWidget::accept ); } QuickOpenWidgetDialog::~QuickOpenWidgetDialog() { delete m_dialog; } void QuickOpenWidget::setPreselectedText(const QString& text) { m_preselectedText = text; } void QuickOpenWidget::updateProviders() { if(QAction* action = (sender() ? qobject_cast(sender()) : nullptr)) { QMenu* menu = qobject_cast(action->parentWidget()); if(menu) { menu->show(); menu->setActiveAction(action); } } QStringList checkedItems; if(ui.itemsButton->menu()) { foreach( QObject* obj, ui.itemsButton->menu()->children() ) { QAction* box = qobject_cast( obj ); if( box ) { if( box->isChecked() ) checkedItems << box->text().remove('&'); } } ui.itemsButton->setText(checkedItems.join(QStringLiteral(", "))); } QStringList checkedScopes; if(ui.scopesButton->menu()) { foreach( QObject* obj, ui.scopesButton->menu()->children() ) { QAction* box = qobject_cast( obj ); if( box ) { if( box->isChecked() ) checkedScopes << box->text().remove('&'); } } ui.scopesButton->setText(checkedScopes.join(QStringLiteral(", "))); } emit itemsChanged( checkedItems ); emit scopesChanged( checkedScopes ); m_model->enableProviders( checkedItems, checkedScopes ); } void QuickOpenWidget::textChanged( const QString& str ) { // "cheap" when something was just appended to the current filter updateTimerInterval(str.startsWith(m_filter)); m_filter = str; m_filterTimer.start(); } void QuickOpenWidget::applyFilter() { m_model->textChanged( m_filter ); QModelIndex currentIndex = m_model->index(0, 0, QModelIndex()); ui.list->selectionModel()->setCurrentIndex( currentIndex, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows | QItemSelectionModel::Current ); callRowSelected(); } void QuickOpenWidget::callRowSelected() { QModelIndex currentIndex = ui.list->selectionModel()->currentIndex(); if( currentIndex.isValid() ) m_model->rowSelected( currentIndex ); else qCDebug(PLUGIN_QUICKOPEN) << "current index is not valid"; } void QuickOpenWidget::accept() { QString filterText = ui.searchLine->text(); m_model->execute( ui.list->currentIndex(), filterText ); } void QuickOpenWidget::doubleClicked ( const QModelIndex & index ) { // crash guard: https://bugs.kde.org/show_bug.cgi?id=297178 ui.list->setCurrentIndex(index); QMetaObject::invokeMethod(this, "accept", Qt::QueuedConnection); QMetaObject::invokeMethod(this, "ready", Qt::QueuedConnection); } void QuickOpenWidget::avoidMenuAltFocus() { // send an invalid key event to the main menu bar. The menu bar will // stop listening when observing another key than ALT between the press // and the release. QKeyEvent event1(QEvent::KeyPress, 0, Qt::NoModifier); QApplication::sendEvent(ICore::self()->uiController()->activeMainWindow()->menuBar(), &event1); QKeyEvent event2(QEvent::KeyRelease, 0, Qt::NoModifier); QApplication::sendEvent(ICore::self()->uiController()->activeMainWindow()->menuBar(), &event2); } bool QuickOpenWidget::eventFilter ( QObject * watched, QEvent * event ) { QKeyEvent *keyEvent = dynamic_cast(event); if( event->type() == QEvent::KeyRelease ) { if(keyEvent->key() == Qt::Key_Alt) { if((m_expandedTemporary && m_altDownTime.msecsTo( QTime::currentTime() ) > 300) || (!m_expandedTemporary && m_altDownTime.msecsTo( QTime::currentTime() ) < 300 && m_hadNoCommandSinceAlt)) { //Unexpand the item QModelIndex row = ui.list->selectionModel()->currentIndex(); if( row.isValid() ) { row = row.sibling( row.row(), 0 ); if(m_model->isExpanded( row )) m_model->setExpanded( row, false ); } } m_expandedTemporary = false; } } if( event->type() == QEvent::KeyPress ) { m_hadNoCommandSinceAlt = false; if(keyEvent->key() == Qt::Key_Alt) { avoidMenuAltFocus(); m_hadNoCommandSinceAlt = true; //Expand QModelIndex row = ui.list->selectionModel()->currentIndex(); if( row.isValid() ) { row = row.sibling( row.row(), 0 ); m_altDownTime = QTime::currentTime(); if(!m_model->isExpanded( row )) { m_expandedTemporary = true; m_model->setExpanded( row, true ); } } } switch( keyEvent->key() ) { case Qt::Key_Tab: if ( keyEvent->modifiers() == Qt::NoModifier ) { // Tab should work just like Down QCoreApplication::sendEvent(ui.list, new QKeyEvent(QEvent::KeyPress, Qt::Key_Down, Qt::NoModifier)); QCoreApplication::sendEvent(ui.list, new QKeyEvent(QEvent::KeyRelease, Qt::Key_Down, Qt::NoModifier)); return true; } break; case Qt::Key_Backtab: if ( keyEvent->modifiers() == Qt::ShiftModifier ) { // Shift + Tab should work just like Up QCoreApplication::sendEvent(ui.list, new QKeyEvent(QEvent::KeyPress, Qt::Key_Up, Qt::NoModifier)); QCoreApplication::sendEvent(ui.list, new QKeyEvent(QEvent::KeyRelease, Qt::Key_Up, Qt::NoModifier)); return true; } break; case Qt::Key_Down: case Qt::Key_Up: { if( keyEvent->modifiers() == Qt::AltModifier ) { QWidget* w = m_model->expandingWidget(ui.list->selectionModel()->currentIndex()); if( KDevelop::QuickOpenEmbeddedWidgetInterface* interface = dynamic_cast( w ) ){ if( keyEvent->key() == Qt::Key_Down ) interface->down(); else interface->up(); return true; } return false; } } case Qt::Key_PageUp: case Qt::Key_PageDown: if(watched == ui.list ) return false; QApplication::sendEvent( ui.list, event ); //callRowSelected(); return true; case Qt::Key_Left: { //Expand/unexpand if( keyEvent->modifiers() == Qt::AltModifier ) { //Eventually Send action to the widget QWidget* w = m_model->expandingWidget(ui.list->selectionModel()->currentIndex()); if( KDevelop::QuickOpenEmbeddedWidgetInterface* interface = dynamic_cast( w ) ){ interface->previous(); return true; } } else { QModelIndex row = ui.list->selectionModel()->currentIndex(); if( row.isValid() ) { row = row.sibling( row.row(), 0 ); if( m_model->isExpanded( row ) ) { m_model->setExpanded( row, false ); return true; } } } return false; } case Qt::Key_Right: { //Expand/unexpand if( keyEvent->modifiers() == Qt::AltModifier ) { //Eventually Send action to the widget QWidget* w = m_model->expandingWidget(ui.list->selectionModel()->currentIndex()); if( KDevelop::QuickOpenEmbeddedWidgetInterface* interface = dynamic_cast( w ) ){ interface->next(); return true; } } else { QModelIndex row = ui.list->selectionModel()->currentIndex(); if( row.isValid() ) { row = row.sibling( row.row(), 0 ); if( !m_model->isExpanded( row ) ) { m_model->setExpanded( row, true ); return true; } } } return false; } case Qt::Key_Return: case Qt::Key_Enter: { if (m_filterTimer.isActive()) { m_filterTimer.stop(); applyFilter(); } if( keyEvent->modifiers() == Qt::AltModifier ) { //Eventually Send action to the widget QWidget* w = m_model->expandingWidget(ui.list->selectionModel()->currentIndex()); if( KDevelop::QuickOpenEmbeddedWidgetInterface* interface = dynamic_cast( w ) ){ interface->accept(); return true; } } else { QString filterText = ui.searchLine->text(); //Safety: Track whether this object is deleted. When execute() is called, a dialog may be opened, //which kills the quickopen widget. QPointer stillExists(this); if( m_model->execute( ui.list->currentIndex(), filterText ) ) { if(!stillExists) return true; if(!(keyEvent->modifiers() & Qt::ShiftModifier)) emit ready(); } else { //Maybe the filter-text was changed: if( filterText != ui.searchLine->text() ) { ui.searchLine->setText( filterText ); } } } return true; } } } return false; } #include "quickopenwidget.moc" diff --git a/plugins/standardoutputview/standardoutputview.cpp b/plugins/standardoutputview/standardoutputview.cpp index 31bae9ab1..6ad01cc2f 100644 --- a/plugins/standardoutputview/standardoutputview.cpp +++ b/plugins/standardoutputview/standardoutputview.cpp @@ -1,313 +1,313 @@ /* KDevelop Standard OutputView * * Copyright 2006-2007 Andreas Pakulat * Copyright 2007 Dukju Ahn * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #include "standardoutputview.h" #include "outputwidget.h" #include "toolviewdata.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include #include class OutputViewFactory : public KDevelop::IToolViewFactory{ public: - OutputViewFactory(const ToolViewData* data): m_data(data) {} + explicit OutputViewFactory(const ToolViewData* data): m_data(data) {} QWidget* create(QWidget *parent = nullptr) override { return new OutputWidget( parent, m_data ); } Qt::DockWidgetArea defaultPosition() override { return Qt::BottomDockWidgetArea; } void viewCreated( Sublime::View* view ) override { m_data->views << view; } QString id() const override { //NOTE: id must be unique, see e.g. https://bugs.kde.org/show_bug.cgi?id=287093 return "org.kdevelop.OutputView." + QString::number(m_data->toolViewId); } private: const ToolViewData *m_data; }; StandardOutputView::StandardOutputView(QObject *parent, const QVariantList &) : KDevelop::IPlugin(QStringLiteral("kdevstandardoutputview"), parent) { setXMLFile(QStringLiteral("kdevstandardoutputview.rc")); connect(KDevelop::ICore::self()->uiController()->controller(), &Sublime::Controller::aboutToRemoveView, this, &StandardOutputView::removeSublimeView); } void StandardOutputView::removeSublimeView( Sublime::View* v ) { foreach( ToolViewData* d, toolviews ) { if( d->views.contains(v) ) { if( d->views.count() == 1 ) { toolviews.remove( d->toolViewId ); ids.removeAll( d->toolViewId ); delete d; } else { d->views.removeAll(v); } } } } StandardOutputView::~StandardOutputView() { } int StandardOutputView::standardToolView( KDevelop::IOutputView::StandardToolView view ) { if( standardViews.contains( view ) ) { return standardViews.value( view ); } int ret = -1; switch( view ) { case KDevelop::IOutputView::BuildView: { ret = registerToolView( i18nc("@title:window", "Build"), KDevelop::IOutputView::HistoryView, QIcon::fromTheme(QStringLiteral("run-build")), KDevelop::IOutputView::AddFilterAction ); break; } case KDevelop::IOutputView::RunView: { ret = registerToolView( i18nc("@title:window", "Run"), KDevelop::IOutputView::MultipleView, QIcon::fromTheme(QStringLiteral("system-run")), KDevelop::IOutputView::AddFilterAction ); break; } case KDevelop::IOutputView::DebugView: { ret = registerToolView( i18nc("@title:window", "Debug"), KDevelop::IOutputView::MultipleView, QIcon::fromTheme(QStringLiteral("debug-step-into")), KDevelop::IOutputView::AddFilterAction ); break; } case KDevelop::IOutputView::TestView: { ret = registerToolView( i18nc("@title:window", "Test"), KDevelop::IOutputView::HistoryView, QIcon::fromTheme(QStringLiteral("system-run"))); break; } case KDevelop::IOutputView::VcsView: { ret = registerToolView( i18nc("@title:window", "Version Control"), KDevelop::IOutputView::HistoryView, QIcon::fromTheme(QStringLiteral("system-run"))); break; } } Q_ASSERT(ret != -1); standardViews[view] = ret; return ret; } int StandardOutputView::registerToolView( const QString& title, KDevelop::IOutputView::ViewType type, const QIcon& icon, Options option, const QList& actionList ) { // try to reuse existing toolview foreach( ToolViewData* d, toolviews ) { if ( d->type == type && d->title == title ) { return d->toolViewId; } } // register new tool view const int newid = ids.isEmpty() ? 0 : (ids.last() + 1); qCDebug(PLUGIN_STANDARDOUTPUTVIEW) << "Registering view" << title << "with type:" << type << "id:" << newid; ToolViewData* tvdata = new ToolViewData( this ); tvdata->toolViewId = newid; tvdata->type = type; tvdata->title = title; tvdata->icon = icon; tvdata->plugin = this; tvdata->option = option; tvdata->actionList = actionList; core()->uiController()->addToolView( title, new OutputViewFactory( tvdata ) ); ids << newid; toolviews[newid] = tvdata; return newid; } int StandardOutputView::registerOutputInToolView( int toolViewId, const QString& title, KDevelop::IOutputView::Behaviours behaviour ) { if( !toolviews.contains( toolViewId ) ) return -1; int newid; if( ids.isEmpty() ) { newid = 0; } else { newid = ids.last()+1; } ids << newid; toolviews.value( toolViewId )->addOutput( newid, title, behaviour ); return newid; } void StandardOutputView::raiseOutput(int outputId) { foreach( int _id, toolviews.keys() ) { if( toolviews.value( _id )->outputdata.contains( outputId ) ) { foreach( Sublime::View* v, toolviews.value( _id )->views ) { if( v->hasWidget() ) { OutputWidget* w = qobject_cast( v->widget() ); w->raiseOutput( outputId ); v->requestRaise(); } } } } } void StandardOutputView::setModel( int outputId, QAbstractItemModel* model ) { int tvid = -1; foreach( int _id, toolviews.keys() ) { if( toolviews.value( _id )->outputdata.contains( outputId ) ) { tvid = _id; break; } } if( tvid == -1 ) qCDebug(PLUGIN_STANDARDOUTPUTVIEW) << "Trying to set model on unknown view-id:" << outputId; else { toolviews.value( tvid )->outputdata.value( outputId )->setModel( model ); } } void StandardOutputView::setDelegate( int outputId, QAbstractItemDelegate* delegate ) { int tvid = -1; foreach( int _id, toolviews.keys() ) { if( toolviews.value( _id )->outputdata.contains( outputId ) ) { tvid = _id; break; } } if( tvid == -1 ) qCDebug(PLUGIN_STANDARDOUTPUTVIEW) << "Trying to set model on unknown view-id:" << outputId; else { toolviews.value( tvid )->outputdata.value( outputId )->setDelegate( delegate ); } } void StandardOutputView::removeToolView( int toolviewId ) { if( toolviews.contains(toolviewId) ) { ToolViewData* td = toolviews.value(toolviewId); foreach( Sublime::View* view, td->views ) { if( view->hasWidget() ) { OutputWidget* outputWidget = qobject_cast( view->widget() ); foreach( int outid, td->outputdata.keys() ) { outputWidget->removeOutput( outid ); } } foreach( Sublime::Area* area, KDevelop::ICore::self()->uiController()->controller()->allAreas() ) { area->removeToolView( view ); } } delete td; toolviews.remove(toolviewId); emit toolViewRemoved(toolviewId); } } OutputWidget* StandardOutputView::outputWidgetForId( int outputId ) const { foreach( ToolViewData* td, toolviews ) { if( td->outputdata.contains( outputId ) ) { foreach( Sublime::View* view, td->views ) { if( view->hasWidget() ) return qobject_cast( view->widget() ); } } } return nullptr; } void StandardOutputView::scrollOutputTo( int outputId, const QModelIndex& idx ) { OutputWidget* widget = outputWidgetForId( outputId ); if( widget ) widget->scrollToIndex( idx ); } void StandardOutputView::removeOutput( int outputId ) { foreach( ToolViewData* td, toolviews ) { if( td->outputdata.contains( outputId ) ) { foreach( Sublime::View* view, td->views ) { if( view->hasWidget() ) qobject_cast( view->widget() )->removeOutput( outputId ); } td->outputdata.remove( outputId ); } } } void StandardOutputView::setTitle(int outputId, const QString& title) { outputWidgetForId(outputId)->setTitle(outputId, title); } diff --git a/plugins/subversion/svnjobbase.h b/plugins/subversion/svnjobbase.h index fdfa0d5db..e50902565 100644 --- a/plugins/subversion/svnjobbase.h +++ b/plugins/subversion/svnjobbase.h @@ -1,92 +1,92 @@ /*************************************************************************** * Copyright 2007 Dukju Ahn * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef KDEVPLATFORM_PLUGIN_SVNJOBBASE_H #define KDEVPLATFORM_PLUGIN_SVNJOBBASE_H #include #include "kdevsvncpp/context_listener.hpp" #include "kdevsvnplugin.h" #include "debug.h" #include extern "C" { #include } class QEvent; class SvnInternalJobBase; namespace ThreadWeaver { class Job; } class KDevSvnPlugin; class SvnJobBase : public KDevelop::VcsJob { Q_OBJECT public: explicit SvnJobBase( KDevSvnPlugin*, KDevelop::OutputJob::OutputJobVerbosity verbosity = KDevelop::OutputJob::Verbose ); ~SvnJobBase() override; virtual SvnInternalJobBase* internalJob() const = 0; KDevelop::VcsJob::JobStatus status() const override; KDevelop::IPlugin* vcsPlugin() const override; public slots: void askForLogin( const QString& ); void showNotification( const QString&, const QString& ); void askForCommitMessage(); void askForSslServerTrust( const QStringList&, const QString&, const QString&, const QString&, const QString&, const QString&, const QString& ); void askForSslClientCert( const QString& ); void askForSslClientCertPassword( const QString& ); protected slots: void internalJobStarted(); void internalJobDone(); void internalJobFailed(); protected: void startInternalJob(); bool doKill() override; KDevSvnPlugin* m_part; private: void outputMessage(const QString &message); KDevelop::VcsJob::JobStatus m_status; }; template class SvnJobBaseImpl : public SvnJobBase { public: - SvnJobBaseImpl(KDevSvnPlugin* plugin, + explicit SvnJobBaseImpl(KDevSvnPlugin* plugin, KDevelop::OutputJob::OutputJobVerbosity verbosity = KDevelop::OutputJob::Verbose) : SvnJobBase(plugin, verbosity) { m_job = new InternalJobClass(this); } SvnInternalJobBase* internalJob() const override { return m_job; } protected: InternalJobClass* m_job = nullptr; }; #endif diff --git a/plugins/testview/testviewplugin.cpp b/plugins/testview/testviewplugin.cpp index a341dc8ec..204f53596 100644 --- a/plugins/testview/testviewplugin.cpp +++ b/plugins/testview/testviewplugin.cpp @@ -1,155 +1,155 @@ /* This file is part of KDevelop Copyright 2012 Miha ?an?ula This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "testviewplugin.h" #include "testview.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include K_PLUGIN_FACTORY_WITH_JSON(TestViewFactory, "kdevtestview.json", registerPlugin();) using namespace KDevelop; class TestToolViewFactory: public KDevelop::IToolViewFactory { public: - TestToolViewFactory( TestViewPlugin *plugin ): mplugin( plugin ) + explicit TestToolViewFactory( TestViewPlugin *plugin ): mplugin( plugin ) {} QWidget* create( QWidget *parent = nullptr ) override { return new TestView( mplugin, parent ); } Qt::DockWidgetArea defaultPosition() override { return Qt::LeftDockWidgetArea; } QString id() const override { return QStringLiteral("org.kdevelop.TestView"); } QList< QAction* > contextMenuActions(QWidget* viewWidget) const override { return qobject_cast(viewWidget)->contextMenuActions(); } private: TestViewPlugin *mplugin; }; TestViewPlugin::TestViewPlugin(QObject* parent, const QVariantList& args) : IPlugin(QStringLiteral("kdevtestview"), parent) { Q_UNUSED(args) QAction* runAll = new QAction( QIcon::fromTheme(QStringLiteral("system-run")), i18n("Run All Tests"), this ); connect(runAll, &QAction::triggered, this, &TestViewPlugin::runAllTests); actionCollection()->addAction(QStringLiteral("run_all_tests"), runAll); QAction* stopTest = new QAction( QIcon::fromTheme(QStringLiteral("process-stop")), i18n("Stop Running Tests"), this ); connect(stopTest, &QAction::triggered, this, &TestViewPlugin::stopRunningTests); actionCollection()->addAction(QStringLiteral("stop_running_tests"), stopTest); setXMLFile(QStringLiteral("kdevtestview.rc")); m_viewFactory = new TestToolViewFactory(this); core()->uiController()->addToolView(i18n("Unit Tests"), m_viewFactory); connect(core()->runController(),&IRunController::jobRegistered, this, &TestViewPlugin::jobStateChanged); connect(core()->runController(),&IRunController::jobUnregistered, this, &TestViewPlugin::jobStateChanged); jobStateChanged(); } TestViewPlugin::~TestViewPlugin() { } void TestViewPlugin::unload() { core()->uiController()->removeToolView(m_viewFactory); } void TestViewPlugin::runAllTests() { ITestController* tc = core()->testController(); foreach (IProject* project, core()->projectController()->projects()) { QList jobs; foreach (ITestSuite* suite, tc->testSuitesForProject(project)) { if (KJob* job = suite->launchAllCases(ITestSuite::Silent)) { jobs << job; } } if (!jobs.isEmpty()) { KDevelop::ExecuteCompositeJob* compositeJob = new KDevelop::ExecuteCompositeJob(this, jobs); compositeJob->setObjectName(i18np("Run 1 test in %2", "Run %1 tests in %2", jobs.size(), project->name())); compositeJob->setProperty("test_job", true); core()->runController()->registerJob(compositeJob); } } } void TestViewPlugin::stopRunningTests() { foreach (KJob* job, core()->runController()->currentJobs()) { if (job->property("test_job").toBool()) { job->kill(); } } } void TestViewPlugin::jobStateChanged() { bool found = false; foreach (KJob* job, core()->runController()->currentJobs()) { if (job->property("test_job").toBool()) { found = true; break; } } actionCollection()->action(QStringLiteral("run_all_tests"))->setEnabled(!found); actionCollection()->action(QStringLiteral("stop_running_tests"))->setEnabled(found); } #include "testviewplugin.moc" diff --git a/plugins/vcschangesview/vcschangesviewplugin.cpp b/plugins/vcschangesview/vcschangesviewplugin.cpp index 9744e630d..64c4398a2 100644 --- a/plugins/vcschangesview/vcschangesviewplugin.cpp +++ b/plugins/vcschangesview/vcschangesviewplugin.cpp @@ -1,107 +1,107 @@ /* This file is part of KDevelop Copyright 2010 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 "vcschangesviewplugin.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "vcschangesview.h" K_PLUGIN_FACTORY_WITH_JSON(VcsProjectIntegrationFactory, "kdevvcschangesview.json", registerPlugin();) using namespace KDevelop; class VCSProjectToolViewFactory : public KDevelop::IToolViewFactory { public: - VCSProjectToolViewFactory(VcsProjectIntegrationPlugin *plugin): m_plugin(plugin) {} + explicit VCSProjectToolViewFactory(VcsProjectIntegrationPlugin *plugin): m_plugin(plugin) {} QWidget* create(QWidget *parent = nullptr) override { VcsChangesView* modif = new VcsChangesView(m_plugin, parent); modif->setModel(m_plugin->model()); QObject::connect(modif, static_cast&)>(&VcsChangesView::reload), m_plugin->model(), static_cast&)>(&ProjectChangesModel::reload)); QObject::connect(modif, static_cast&)>(&VcsChangesView::reload), m_plugin->model(), static_cast&)>(&ProjectChangesModel::reload)); QObject::connect(modif, &VcsChangesView::activated, m_plugin, &VcsProjectIntegrationPlugin::activated); return modif; } Qt::DockWidgetArea defaultPosition() override { return Qt::RightDockWidgetArea; } QString id() const override { return QStringLiteral("org.kdevelop.VCSProject"); } private: VcsProjectIntegrationPlugin *m_plugin; }; VcsProjectIntegrationPlugin::VcsProjectIntegrationPlugin(QObject* parent, const QVariantList&) : KDevelop::IPlugin(QStringLiteral("kdevvcsprojectintegration"), parent) , m_model(nullptr) { ICore::self()->uiController()->addToolView(i18n("Project Changes"), new VCSProjectToolViewFactory(this)); QAction* synaction = actionCollection()->addAction(QStringLiteral("locate_document")); synaction->setText(i18n("Locate Current Document")); synaction->setIcon(QIcon::fromTheme(QStringLiteral("dirsync"))); synaction->setToolTip(i18n("Locates the current document and selects it.")); QAction* reloadaction = actionCollection()->addAction(QStringLiteral("reload_view")); reloadaction->setText(i18n("Reload View")); reloadaction->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); reloadaction->setToolTip(i18n("Refreshes the view for all projects, in case anything changed.")); } void VcsProjectIntegrationPlugin::activated(const QModelIndex& /*idx*/) { } ProjectChangesModel* VcsProjectIntegrationPlugin::model() { if(!m_model) { m_model = ICore::self()->projectController()->changesModel(); connect(actionCollection()->action(QStringLiteral("reload_view")), &QAction::triggered, m_model, &ProjectChangesModel::reloadAll); } return m_model; } #include "vcschangesviewplugin.moc" diff --git a/project/builderjob.cpp b/project/builderjob.cpp index 77eb1698c..0fd82f6fb 100644 --- a/project/builderjob.cpp +++ b/project/builderjob.cpp @@ -1,271 +1,271 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2007 Andreas Pakulat * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "builderjob.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; struct SubJobData { BuilderJob::BuildType type; KJob* job; ProjectBaseItem* item; }; Q_DECLARE_TYPEINFO(SubJobData, Q_MOVABLE_TYPE); namespace KDevelop { class BuilderJobPrivate { public: - BuilderJobPrivate( BuilderJob* job ) + explicit BuilderJobPrivate( BuilderJob* job ) : q(job) , failOnFirstError(true) { } BuilderJob* q; void addJob( BuilderJob::BuildType, ProjectBaseItem* ); bool failOnFirstError; QString buildTypeToString( BuilderJob::BuildType type ) const; bool hasJobForProject( BuilderJob::BuildType type, IProject* project ) const { foreach(const SubJobData& data, m_metadata) { if (data.type == type && data.item->project() == project) { return true; } } return false; } /** * a structure to keep metadata of all registered jobs */ QVector m_metadata; /** * get the subjob list and clear this composite job */ QVector takeJobList(); }; } QString BuilderJobPrivate::buildTypeToString(BuilderJob::BuildType type) const { switch( type ) { case BuilderJob::Build: return i18nc( "@info:status", "build" ); case BuilderJob::Clean: return i18nc( "@info:status", "clean" ); case BuilderJob::Configure: return i18nc( "@info:status", "configure" ); case BuilderJob::Install: return i18nc( "@info:status", "install" ); case BuilderJob::Prune: return i18nc( "@info:status", "prune" ); default: return QString(); } } void BuilderJobPrivate::addJob( BuilderJob::BuildType t, ProjectBaseItem* item ) { Q_ASSERT(item); qCDebug(PROJECT) << "adding build job for item:" << item->text(); Q_ASSERT(item->project()); qCDebug(PROJECT) << "project for item:" << item->project()->name(); Q_ASSERT(item->project()->projectItem()); qCDebug(PROJECT) << "project item for the project:" << item->project()->projectItem()->text(); if( !item->project()->buildSystemManager() ) { qWarning() << "no buildsystem manager for:" << item->text() << item->project()->name(); return; } qCDebug(PROJECT) << "got build system manager"; Q_ASSERT(item->project()->buildSystemManager()->builder()); KJob* j = nullptr; switch( t ) { case BuilderJob::Build: j = item->project()->buildSystemManager()->builder()->build( item ); break; case BuilderJob::Clean: j = item->project()->buildSystemManager()->builder()->clean( item ); break; case BuilderJob::Install: j = item->project()->buildSystemManager()->builder()->install( item ); break; case BuilderJob::Prune: if (!hasJobForProject(t, item->project())) { j = item->project()->buildSystemManager()->builder()->prune( item->project() ); } break; case BuilderJob::Configure: if (!hasJobForProject(t, item->project())) { j = item->project()->buildSystemManager()->builder()->configure( item->project() ); } break; default: break; } if( j ) { q->addCustomJob( t, j, item ); } } BuilderJob::BuilderJob() : d( new BuilderJobPrivate( this ) ) { } BuilderJob::~BuilderJob() { delete d; } void BuilderJob::addItems( BuildType t, const QList& items ) { foreach( ProjectBaseItem* item, items ) { d->addJob( t, item ); } } void BuilderJob::addProjects( BuildType t, const QList& projects ) { foreach( IProject* project, projects ) { d->addJob( t, project->projectItem() ); } } void BuilderJob::addItem( BuildType t, ProjectBaseItem* item ) { d->addJob( t, item ); } void BuilderJob::addCustomJob( BuilderJob::BuildType type, KJob* job, ProjectBaseItem* item ) { if( BuilderJob* builderJob = dynamic_cast( job ) ) { // If a subjob is a builder job itself, re-own its job list to avoid having recursive composite jobs. QVector subjobs = builderJob->d->takeJobList(); builderJob->deleteLater(); foreach( const SubJobData& subjob, subjobs ) { subjob.job->setParent(this); addSubjob( subjob.job ); } d->m_metadata << subjobs; } else { job->setParent(this); addSubjob( job ); SubJobData data; data.type = type; data.job = job; data.item = item; d->m_metadata << data; } } QVector< SubJobData > BuilderJobPrivate::takeJobList() { QVector< SubJobData > ret = m_metadata; m_metadata.clear(); q->clearSubjobs(); q->setObjectName( QString() ); return ret; } void BuilderJob::updateJobName() { // Which items are mentioned in the set // Make it a list to preserve ordering; search overhead (n^2) isn't too big QList< ProjectBaseItem* > registeredItems; // Which build types are mentioned in the set // (Same rationale applies) QList< BuildType > buildTypes; // Whether there are jobs without any specific item bool hasNullItems = false; foreach( const SubJobData& subjob, d->m_metadata ) { if( subjob.item ) { if( !registeredItems.contains( subjob.item ) ) { registeredItems.append( subjob.item ); } if( !buildTypes.contains( subjob.type ) ) { buildTypes.append( subjob.type ); } } else { hasNullItems = true; } } QString itemNames; if( !hasNullItems ) { QStringList itemNamesList; foreach( ProjectBaseItem* item, registeredItems ) { itemNamesList << item->text(); } itemNames = itemNamesList.join(QStringLiteral(", ")); } else { itemNames = i18nc( "Unspecified set of build items (e. g. projects, targets)", "Various items" ); } QString methodNames; QStringList methodNamesList; foreach( BuildType type, buildTypes ) { methodNamesList << d->buildTypeToString( type ); } methodNames = methodNamesList.join( QStringLiteral( ", " ) ); QString jobName = QStringLiteral( "%1: %2" ).arg( itemNames, methodNames ); setObjectName( jobName ); } void BuilderJob::start() { // Automatically save all documents before starting to build // might need an option to turn off at some point // Also should be moved into the builder and there try to find target(s) for the given item and then just save the documents of that target -> list?? if( ICore::self()->activeSession()->config()->group("Project Manager").readEntry( "Save All Documents Before Building", true ) ) { ICore::self()->documentController()->saveAllDocuments( IDocument::Silent ); } ExecuteCompositeJob::start(); } diff --git a/project/filemanagerlistjob.h b/project/filemanagerlistjob.h index 0d145396f..75c15319a 100644 --- a/project/filemanagerlistjob.h +++ b/project/filemanagerlistjob.h @@ -1,80 +1,80 @@ /* This file is part of KDevelop Copyright 2009 Radu Benea 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_FILEMANAGERLISTJOB_H #define KDEVPLATFORM_FILEMANAGERLISTJOB_H #include #include // uncomment to time imort jobs // #define TIME_IMPORT_JOB #ifdef TIME_IMPORT_JOB #include #endif namespace KDevelop { class ProjectFolderItem; class FileManagerListJob : public KIO::Job { Q_OBJECT public: - FileManagerListJob(ProjectFolderItem* item); + explicit FileManagerListJob(ProjectFolderItem* item); ProjectFolderItem* item() const; void addSubDir(ProjectFolderItem* item); void removeSubDir(ProjectFolderItem* item); void abort(); void start() override; signals: void entries(FileManagerListJob* job, ProjectFolderItem* baseItem, const KIO::UDSEntryList& entries); void nextJob(); private slots: void slotEntries(KIO::Job* job, const KIO::UDSEntryList& entriesIn ); void slotResult(KJob* job) override; void handleResults(const KIO::UDSEntryList& entries); void startNextJob(); private: QQueue m_listQueue; /// current base dir ProjectFolderItem* m_item; KIO::UDSEntryList entryList; // kill does not delete the job instantaniously QAtomicInt m_aborted; #ifdef TIME_IMPORT_JOB QElapsedTimer m_timer; QElapsedTimer m_subTimer; qint64 m_subWaited = 0; #endif }; } #endif // KDEVPLATFORM_FILEMANAGERLISTJOB_H diff --git a/project/projectitemlineedit.cpp b/project/projectitemlineedit.cpp index 4a3853c78..89f351c80 100644 --- a/project/projectitemlineedit.cpp +++ b/project/projectitemlineedit.cpp @@ -1,254 +1,254 @@ /*************************************************************************** * Copyright 2008 Aleix Pol * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "projectitemlineedit.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "projectproxymodel.h" static const QChar sep = '/'; static const QChar escape = '\\'; class ProjectItemCompleter : public QCompleter { Q_OBJECT public: - ProjectItemCompleter(QObject* parent=nullptr); + explicit ProjectItemCompleter(QObject* parent=nullptr); QString separator() const { return sep; } QStringList splitPath(const QString &path) const override; QString pathFromIndex(const QModelIndex& index) const override; void setBaseItem( KDevelop::ProjectBaseItem* item ) { mBase = item; } private: KDevelop::ProjectModel* mModel; KDevelop::ProjectBaseItem* mBase; }; class ProjectItemValidator : public QValidator { Q_OBJECT public: - ProjectItemValidator(QObject* parent = nullptr ); + explicit ProjectItemValidator(QObject* parent = nullptr ); QValidator::State validate( QString& input, int& pos ) const override; void setBaseItem( KDevelop::ProjectBaseItem* item ) { mBase = item; } private: KDevelop::ProjectBaseItem* mBase; }; ProjectItemCompleter::ProjectItemCompleter(QObject* parent) : QCompleter(parent) , mModel(KDevelop::ICore::self()->projectController()->projectModel()) , mBase( nullptr ) { setModel(mModel); setCaseSensitivity( Qt::CaseInsensitive ); } QStringList ProjectItemCompleter::splitPath(const QString& path) const { return joinProjectBasePath( KDevelop::splitWithEscaping( path, sep, escape ), mBase ); } QString ProjectItemCompleter::pathFromIndex(const QModelIndex& index) const { QString postfix; if(mModel->itemFromIndex(index)->folder()) postfix=sep; return KDevelop::joinWithEscaping(removeProjectBasePath( mModel->pathFromIndex(index), mBase ), sep, escape)+postfix; } ProjectItemValidator::ProjectItemValidator(QObject* parent): QValidator(parent), mBase(nullptr) { } QValidator::State ProjectItemValidator::validate(QString& input, int& pos) const { Q_UNUSED( pos ); KDevelop::ProjectModel* model = KDevelop::ICore::self()->projectController()->projectModel(); QStringList path = joinProjectBasePath( KDevelop::splitWithEscaping( input, sep, escape ), mBase ); QModelIndex idx = model->pathToIndex( path ); QValidator::State state = input.isEmpty() ? QValidator::Intermediate : QValidator::Invalid; if( idx.isValid() ) { state = QValidator::Acceptable; } else if( path.count() > 1 ) { // Check beginning of path and if that is ok, then try to find a child QString end = path.takeLast(); idx = model->pathToIndex( path ); if( idx.isValid() ) { for( int i = 0; i < model->rowCount( idx ); i++ ) { if( model->data( model->index( i, 0, idx ) ).toString().startsWith( end, Qt::CaseInsensitive ) ) { state = QValidator::Intermediate; break; } } } } else if( path.count() == 1 ) { // Check for a project whose name beings with the input QString first = path.first(); foreach( KDevelop::IProject* project, KDevelop::ICore::self()->projectController()->projects() ) { if( project->name().startsWith( first, Qt::CaseInsensitive ) ) { state = QValidator::Intermediate; break; } } } return state; } ProjectItemLineEdit::ProjectItemLineEdit(QWidget* parent) : QLineEdit(parent), m_base(nullptr), m_completer( new ProjectItemCompleter( this ) ), m_validator( new ProjectItemValidator( this ) ), m_suggestion( nullptr ) { setCompleter( m_completer ); setValidator( m_validator ); setPlaceholderText( i18n("Enter the path to an item from the projects tree" ) ); QAction* selectItemAction = new QAction(QIcon::fromTheme(QStringLiteral("folder-document")), i18n("Select..."), this); connect(selectItemAction, &QAction::triggered, this, &ProjectItemLineEdit::selectItemDialog); addAction(selectItemAction); setContextMenuPolicy(Qt::CustomContextMenu); connect(this, &ProjectItemLineEdit::customContextMenuRequested, this, &ProjectItemLineEdit::showCtxMenu); } void ProjectItemLineEdit::showCtxMenu(const QPoint& p) { QScopedPointer menu(createStandardContextMenu()); menu->addActions(actions()); menu->exec(mapToGlobal(p)); } bool ProjectItemLineEdit::selectItemDialog() { KDevelop::ProjectModel* model=KDevelop::ICore::self()->projectController()->projectModel(); QWidget* w=new QWidget; w->setLayout(new QVBoxLayout(w)); QTreeView* view = new QTreeView(w); ProjectProxyModel* proxymodel = new ProjectProxyModel(view); proxymodel->setSourceModel(model); view->header()->hide(); view->setModel(proxymodel); view->setSelectionMode(QAbstractItemView::SingleSelection); w->layout()->addWidget(new QLabel(i18n("Select the item you want to get the path from."))); w->layout()->addWidget(view); QDialog dialog; dialog.setWindowTitle(i18n("Select an item...")); QVBoxLayout *mainLayout = new QVBoxLayout(&dialog); mainLayout->addWidget(w); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel); QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); connect(buttonBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); mainLayout->addWidget(buttonBox); if (m_suggestion) { const QModelIndex idx = proxymodel->proxyIndexFromItem(m_suggestion->projectItem()); view->selectionModel()->select(idx, QItemSelectionModel::ClearAndSelect); } int res = dialog.exec(); if(res==QDialog::Accepted && view->selectionModel()->hasSelection()) { QModelIndex idx=proxymodel->mapToSource(view->selectionModel()->selectedIndexes().first()); setText(KDevelop::joinWithEscaping(model->pathFromIndex(idx), sep, escape)); selectAll(); return true; } return false; } void ProjectItemLineEdit::setItemPath(const QStringList& list) { setText( KDevelop::joinWithEscaping( removeProjectBasePath( list, m_base ), sep, escape ) ); } QStringList ProjectItemLineEdit::itemPath() const { return joinProjectBasePath( KDevelop::splitWithEscaping( text(), sep, escape ), m_base ); } void ProjectItemLineEdit::setBaseItem(KDevelop::ProjectBaseItem* item) { m_base = item; m_validator->setBaseItem( m_base ); m_completer->setBaseItem( m_base ); } KDevelop::ProjectBaseItem* ProjectItemLineEdit::baseItem() const { return m_base; } KDevelop::ProjectBaseItem* ProjectItemLineEdit::currentItem() const { KDevelop::ProjectModel* model = KDevelop::ICore::self()->projectController()->projectModel(); return model->itemFromIndex(model->pathToIndex(KDevelop::splitWithEscaping(text(),'/', '\\'))); } void ProjectItemLineEdit::setSuggestion(KDevelop::IProject* project) { m_suggestion = project; } #include "projectitemlineedit.moc" #include "moc_projectitemlineedit.cpp" diff --git a/project/projectmodel.cpp b/project/projectmodel.cpp index bdf907ce4..1350f372b 100644 --- a/project/projectmodel.cpp +++ b/project/projectmodel.cpp @@ -1,1170 +1,1170 @@ /* This file is part of KDevelop Copyright 2005 Roberto Raggi Copyright 2007 Andreas Pakulat Copyright 2007 Aleix Pol This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "projectmodel.h" #include #include #include #include #include #include #include #include #include #include "interfaces/iprojectfilemanager.h" #include #include "path.h" namespace KDevelop { QStringList removeProjectBasePath( const QStringList& fullpath, KDevelop::ProjectBaseItem* item ) { QStringList result = fullpath; if( item ) { KDevelop::ProjectModel* model = KDevelop::ICore::self()->projectController()->projectModel(); QStringList basePath = model->pathFromIndex( model->indexFromItem( item ) ); if( basePath.count() >= fullpath.count() ) { return QStringList(); } for( int i = 0; i < basePath.count(); i++ ) { result.takeFirst(); } } return result; } QStringList joinProjectBasePath( const QStringList& partialpath, KDevelop::ProjectBaseItem* item ) { QStringList basePath; if( item ) { KDevelop::ProjectModel* model = KDevelop::ICore::self()->projectController()->projectModel(); basePath = model->pathFromIndex( model->indexFromItem( item ) ); } return basePath + partialpath; } inline uint indexForPath( const Path& path ) { return IndexedString::indexForString(path.pathOrUrl()); } class ProjectModelPrivate { public: - ProjectModelPrivate( ProjectModel* model ): model( model ) + explicit ProjectModelPrivate( ProjectModel* model ): model( model ) { } ProjectBaseItem* rootItem; ProjectModel* model; ProjectBaseItem* itemFromIndex( const QModelIndex& idx ) { if( !idx.isValid() ) { return rootItem; } if( idx.model() != model ) { return nullptr; } return model->itemFromIndex( idx ); } // a hash of IndexedString::indexForString(path) <-> ProjectBaseItem for fast lookup QMultiHash pathLookupTable; }; class ProjectBaseItemPrivate { public: ProjectBaseItemPrivate() : project(nullptr), parent(nullptr), row(-1), model(nullptr), m_pathIndex(0) {} IProject* project; ProjectBaseItem* parent; int row; QList children; QString text; ProjectBaseItem::ProjectItemType type; Qt::ItemFlags flags; ProjectModel* model; Path m_path; uint m_pathIndex; QString iconName; ProjectBaseItem::RenameStatus renameBaseItem(ProjectBaseItem* item, const QString& newName) { if (item->parent()) { foreach(ProjectBaseItem* sibling, item->parent()->children()) { if (sibling->text() == newName) { return ProjectBaseItem::ExistingItemSameName; } } } item->setText( newName ); return ProjectBaseItem::RenameOk; } ProjectBaseItem::RenameStatus renameFileOrFolder(ProjectBaseItem* item, const QString& newName) { Q_ASSERT(item->file() || item->folder()); if (newName.contains('/')) { return ProjectBaseItem::InvalidNewName; } if (item->text() == newName) { return ProjectBaseItem::RenameOk; } Path newPath = item->path(); newPath.setLastPathSegment(newName); auto job = KIO::stat(newPath.toUrl(), KIO::StatJob::SourceSide, 0); if (job->exec()) { // file/folder exists already return ProjectBaseItem::ExistingItemSameName; } if( !item->project() || !item->project()->projectFileManager() ) { return renameBaseItem(item, newName); } else if( item->folder() && item->project()->projectFileManager()->renameFolder(item->folder(), newPath) ) { return ProjectBaseItem::RenameOk; } else if ( item->file() && item->project()->projectFileManager()->renameFile(item->file(), newPath) ) { return ProjectBaseItem::RenameOk; } else { return ProjectBaseItem::ProjectManagerRenameFailed; } } }; ProjectBaseItem::ProjectBaseItem( IProject* project, const QString &name, ProjectBaseItem *parent ) : d_ptr(new ProjectBaseItemPrivate) { Q_ASSERT(!name.isEmpty() || !parent); Q_D(ProjectBaseItem); d->project = project; d->text = name; d->flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled; if( parent ) { parent->appendRow( this ); } } ProjectBaseItem::~ProjectBaseItem() { Q_D(ProjectBaseItem); if (model() && d->m_pathIndex) { model()->d->pathLookupTable.remove(d->m_pathIndex, this); } if( parent() ) { parent()->takeRow( d->row ); } else if( model() ) { model()->takeRow( d->row ); } removeRows(0, d->children.size()); delete d; } ProjectBaseItem* ProjectBaseItem::child( int row ) const { Q_D(const ProjectBaseItem); if( row < 0 || row >= d->children.length() ) { return nullptr; } return d->children.at( row ); } QList< ProjectBaseItem* > ProjectBaseItem::children() const { Q_D(const ProjectBaseItem); return d->children; } ProjectBaseItem* ProjectBaseItem::takeRow(int row) { Q_D(ProjectBaseItem); Q_ASSERT(row >= 0 && row < d->children.size()); if( model() ) { model()->beginRemoveRows(index(), row, row); } ProjectBaseItem* olditem = d->children.takeAt( row ); olditem->d_func()->parent = nullptr; olditem->d_func()->row = -1; olditem->setModel( nullptr ); for(int i=row; id_func()->row--; Q_ASSERT(child(i)->d_func()->row==i); } if( model() ) { model()->endRemoveRows(); } return olditem; } void ProjectBaseItem::removeRow( int row ) { delete takeRow( row ); } void ProjectBaseItem::removeRows(int row, int count) { if (!count) { return; } Q_D(ProjectBaseItem); Q_ASSERT(row >= 0 && row + count <= d->children.size()); if( model() ) { model()->beginRemoveRows(index(), row, row + count - 1); } //NOTE: we unset parent, row and model manually to speed up the deletion if (row == 0 && count == d->children.size()) { // optimize if we want to delete all foreach(ProjectBaseItem* item, d->children) { item->d_func()->parent = nullptr; item->d_func()->row = -1; item->setModel( nullptr ); delete item; } d->children.clear(); } else { for (int i = row; i < count; ++i) { ProjectBaseItem* item = d->children.at(i); item->d_func()->parent = nullptr; item->d_func()->row = -1; item->setModel( nullptr ); delete d->children.takeAt( row ); } for(int i = row; i < d->children.size(); ++i) { d->children.at(i)->d_func()->row--; Q_ASSERT(child(i)->d_func()->row==i); } } if( model() ) { model()->endRemoveRows(); } } QModelIndex ProjectBaseItem::index() const { if( model() ) { return model()->indexFromItem( this ); } return QModelIndex(); } int ProjectBaseItem::rowCount() const { Q_D(const ProjectBaseItem); return d->children.count(); } int ProjectBaseItem::type() const { return ProjectBaseItem::BaseItem; } ProjectModel* ProjectBaseItem::model() const { Q_D(const ProjectBaseItem); return d->model; } ProjectBaseItem* ProjectBaseItem::parent() const { Q_D(const ProjectBaseItem); if( model() && model()->d->rootItem == d->parent ) { return nullptr; } return d->parent; } int ProjectBaseItem::row() const { Q_D(const ProjectBaseItem); return d->row; } QString ProjectBaseItem::text() const { Q_D(const ProjectBaseItem); if( project() && !parent() ) { return project()->name(); } else { return d->text; } } void ProjectBaseItem::setModel( ProjectModel* model ) { Q_D(ProjectBaseItem); if (model == d->model) { return; } if (d->model && d->m_pathIndex) { d->model->d->pathLookupTable.remove(d->m_pathIndex, this); } d->model = model; if (model && d->m_pathIndex) { model->d->pathLookupTable.insert(d->m_pathIndex, this); } foreach( ProjectBaseItem* item, d->children ) { item->setModel( model ); } } void ProjectBaseItem::setRow( int row ) { Q_D(ProjectBaseItem); d->row = row; } void ProjectBaseItem::setText( const QString& text ) { Q_ASSERT(!text.isEmpty() || !parent()); Q_D(ProjectBaseItem); d->text = text; if( d->model ) { QModelIndex idx = index(); emit d->model->dataChanged(idx, idx); } } ProjectBaseItem::RenameStatus ProjectBaseItem::rename(const QString& newName) { Q_D(ProjectBaseItem); return d->renameBaseItem(this, newName); } KDevelop::ProjectBaseItem::ProjectItemType baseType( int type ) { if( type == KDevelop::ProjectBaseItem::Folder || type == KDevelop::ProjectBaseItem::BuildFolder ) return KDevelop::ProjectBaseItem::Folder; if( type == KDevelop::ProjectBaseItem::Target || type == KDevelop::ProjectBaseItem::ExecutableTarget || type == KDevelop::ProjectBaseItem::LibraryTarget) return KDevelop::ProjectBaseItem::Target; return static_cast( type ); } bool ProjectBaseItem::lessThan( const KDevelop::ProjectBaseItem* item ) const { if(item->type() >= KDevelop::ProjectBaseItem::CustomProjectItemType ) { // For custom types we want to make sure that if they override lessThan, then we // prefer their lessThan implementation return !item->lessThan( this ); } KDevelop::ProjectBaseItem::ProjectItemType leftType=baseType(type()), rightType=baseType(item->type()); if(leftType==rightType) { if(leftType==KDevelop::ProjectBaseItem::File) { return file()->fileName().compare(item->file()->fileName(), Qt::CaseInsensitive) < 0; } return this->text()text(); } else { return leftTypepath() < item2->path(); } IProject* ProjectBaseItem::project() const { Q_D(const ProjectBaseItem); return d->project; } void ProjectBaseItem::appendRow( ProjectBaseItem* item ) { Q_D(ProjectBaseItem); if( !item ) { return; } if( item->parent() ) { // Proper way is to first removeRow() on the original parent, then appendRow on this one qWarning() << "Ignoring double insertion of item" << item; return; } // this is too slow... O(n) and thankfully not a problem anyways // Q_ASSERT(!d->children.contains(item)); int startrow,endrow; if( model() ) { startrow = endrow = d->children.count(); model()->beginInsertRows(index(), startrow, endrow); } d->children.append( item ); item->setRow( d->children.count() - 1 ); item->d_func()->parent = this; item->setModel( model() ); if( model() ) { model()->endInsertRows(); } } Path ProjectBaseItem::path() const { Q_D(const ProjectBaseItem); return d->m_path; } QString ProjectBaseItem::baseName() const { return text(); } void ProjectBaseItem::setPath( const Path& path) { Q_D(ProjectBaseItem); if (model() && d->m_pathIndex) { model()->d->pathLookupTable.remove(d->m_pathIndex, this); } d->m_path = path; d->m_pathIndex = indexForPath(path); setText( path.lastPathSegment() ); if (model() && d->m_pathIndex) { model()->d->pathLookupTable.insert(d->m_pathIndex, this); } } Qt::ItemFlags ProjectBaseItem::flags() { Q_D(ProjectBaseItem); return d->flags; } Qt::DropActions ProjectModel::supportedDropActions() const { return (Qt::DropActions)(Qt::MoveAction); } void ProjectBaseItem::setFlags(Qt::ItemFlags flags) { Q_D(ProjectBaseItem); d->flags = flags; if(d->model) d->model->dataChanged(index(), index()); } QString ProjectBaseItem::iconName() const { return QString(); } ProjectFolderItem *ProjectBaseItem::folder() const { return nullptr; } ProjectTargetItem *ProjectBaseItem::target() const { return nullptr; } ProjectExecutableTargetItem *ProjectBaseItem::executable() const { return nullptr; } ProjectFileItem *ProjectBaseItem::file() const { return nullptr; } QList ProjectBaseItem::folderList() const { QList lst; for ( int i = 0; i < rowCount(); ++i ) { ProjectBaseItem* item = child( i ); if ( item->type() == Folder || item->type() == BuildFolder ) { ProjectFolderItem *kdevitem = dynamic_cast( item ); if ( kdevitem ) lst.append( kdevitem ); } } return lst; } QList ProjectBaseItem::targetList() const { QList lst; for ( int i = 0; i < rowCount(); ++i ) { ProjectBaseItem* item = child( i ); if ( item->type() == Target || item->type() == LibraryTarget || item->type() == ExecutableTarget ) { ProjectTargetItem *kdevitem = dynamic_cast( item ); if ( kdevitem ) lst.append( kdevitem ); } } return lst; } QList ProjectBaseItem::fileList() const { QList lst; for ( int i = 0; i < rowCount(); ++i ) { ProjectBaseItem* item = child( i ); Q_ASSERT(item); if ( item && item->type() == File ) { ProjectFileItem *kdevitem = dynamic_cast( item ); if ( kdevitem ) lst.append( kdevitem ); } } return lst; } void ProjectModel::clear() { d->rootItem->removeRows(0, d->rootItem->rowCount()); } ProjectFolderItem::ProjectFolderItem(IProject* project, const Path& path, ProjectBaseItem* parent) : ProjectBaseItem( project, path.lastPathSegment(), parent ) { setPath( path ); setFlags(flags() | Qt::ItemIsDropEnabled); if (project && project->path() != path) setFlags(flags() | Qt::ItemIsDragEnabled); } ProjectFolderItem::ProjectFolderItem( const QString & name, ProjectBaseItem * parent ) : ProjectBaseItem( parent->project(), name, parent ) { setPath( Path(parent->path(), name) ); setFlags(flags() | Qt::ItemIsDropEnabled); if (project() && project()->path() != path()) setFlags(flags() | Qt::ItemIsDragEnabled); } ProjectFolderItem::~ProjectFolderItem() { } void ProjectFolderItem::setPath( const Path& path ) { ProjectBaseItem::setPath(path); propagateRename(path); } ProjectFolderItem *ProjectFolderItem::folder() const { return const_cast(this); } int ProjectFolderItem::type() const { return ProjectBaseItem::Folder; } QString ProjectFolderItem::folderName() const { return baseName(); } void ProjectFolderItem::propagateRename( const Path& newBase ) const { Path path = newBase; path.addPath(QStringLiteral("dummy")); foreach( KDevelop::ProjectBaseItem* child, children() ) { path.setLastPathSegment( child->text() ); child->setPath( path ); const ProjectFolderItem* folder = child->folder(); if ( folder ) { folder->propagateRename( path ); } } } ProjectBaseItem::RenameStatus ProjectFolderItem::rename(const QString& newName) { return d_ptr->renameFileOrFolder(this, newName); } bool ProjectFolderItem::hasFileOrFolder(const QString& name) const { foreach ( ProjectBaseItem* item, children() ) { if ( (item->type() == Folder || item->type() == File || item->type() == BuildFolder) && name == item->baseName() ) { return true; } } return false; } bool ProjectBaseItem::isProjectRoot() const { return parent()==nullptr; } ProjectBuildFolderItem::ProjectBuildFolderItem(IProject* project, const Path& path, ProjectBaseItem *parent) : ProjectFolderItem( project, path, parent ) { } ProjectBuildFolderItem::ProjectBuildFolderItem( const QString& name, ProjectBaseItem* parent ) : ProjectFolderItem( name, parent ) { } QString ProjectFolderItem::iconName() const { return QStringLiteral("folder"); } int ProjectBuildFolderItem::type() const { return ProjectBaseItem::BuildFolder; } QString ProjectBuildFolderItem::iconName() const { return QStringLiteral("folder-development"); } ProjectFileItem::ProjectFileItem( IProject* project, const Path& path, ProjectBaseItem* parent ) : ProjectBaseItem( project, path.lastPathSegment(), parent ) { setFlags(flags() | Qt::ItemIsDragEnabled); setPath( path ); } ProjectFileItem::ProjectFileItem( const QString& name, ProjectBaseItem* parent ) : ProjectBaseItem( parent->project(), name, parent ) { setFlags(flags() | Qt::ItemIsDragEnabled); setPath( Path(parent->path(), name) ); } ProjectFileItem::~ProjectFileItem() { if( project() && d_ptr->m_pathIndex ) { project()->removeFromFileSet( this ); } } IndexedString ProjectFileItem::indexedPath() const { return IndexedString::fromIndex( d_ptr->m_pathIndex ); } ProjectBaseItem::RenameStatus ProjectFileItem::rename(const QString& newName) { return d_ptr->renameFileOrFolder(this, newName); } QString ProjectFileItem::fileName() const { return baseName(); } // Maximum length of a string to still consider it as a file extension which we cache // This has to be a slow value, so that we don't fill our file extension cache with crap static const int maximumCacheExtensionLength = 3; bool isNumeric(const QStringRef& str) { int len = str.length(); if(len == 0) return false; for(int a = 0; a < len; ++a) if(!str.at(a).isNumber()) return false; return true; } class IconNameCache { public: QString iconNameForPath(const Path& path, const QString& fileName) { // find icon name based on file extension, if possible QString extension; int extensionStart = fileName.lastIndexOf(QLatin1Char('.')); if( extensionStart != -1 && fileName.length() - extensionStart - 1 <= maximumCacheExtensionLength ) { QStringRef extRef = fileName.midRef(extensionStart + 1); if( isNumeric(extRef) ) { // don't cache numeric extensions extRef.clear(); } if( !extRef.isEmpty() ) { extension = extRef.toString(); QMutexLocker lock(&mutex); QHash< QString, QString >::const_iterator it = fileExtensionToIcon.constFind( extension ); if( it != fileExtensionToIcon.constEnd() ) { return *it; } } } QMimeType mime = QMimeDatabase().mimeTypeForFile(path.lastPathSegment(), QMimeDatabase::MatchExtension); // no I/O QMutexLocker lock(&mutex); QHash< QString, QString >::const_iterator it = mimeToIcon.constFind(mime.name()); QString iconName; if ( it == mimeToIcon.constEnd() ) { iconName = mime.iconName(); if (iconName.isEmpty()) { iconName = QStringLiteral("none"); } mimeToIcon.insert(mime.name(), iconName); } else { iconName = *it; } if ( !extension.isEmpty() ) { fileExtensionToIcon.insert(extension, iconName); } return iconName; } QMutex mutex; QHash mimeToIcon; QHash fileExtensionToIcon; }; Q_GLOBAL_STATIC(IconNameCache, s_cache); QString ProjectFileItem::iconName() const { // think of d_ptr->iconName as mutable, possible since d_ptr is not const if (d_ptr->iconName.isEmpty()) { // lazy load implementation of icon lookup d_ptr->iconName = s_cache->iconNameForPath( d_ptr->m_path, d_ptr->text ); // we should always get *some* icon name back Q_ASSERT(!d_ptr->iconName.isEmpty()); } return d_ptr->iconName; } void ProjectFileItem::setPath( const Path& path ) { if (path == d_ptr->m_path) { return; } if( project() && d_ptr->m_pathIndex ) { // remove from fileset if we are in there project()->removeFromFileSet( this ); } ProjectBaseItem::setPath( path ); if( project() && d_ptr->m_pathIndex ) { // add to fileset with new path project()->addToFileSet( this ); } // invalidate icon name for future lazy-loaded updated d_ptr->iconName.clear(); } int ProjectFileItem::type() const { return ProjectBaseItem::File; } ProjectFileItem *ProjectFileItem::file() const { return const_cast( this ); } ProjectTargetItem::ProjectTargetItem( IProject* project, const QString &name, ProjectBaseItem *parent ) : ProjectBaseItem( project, name, parent ) { setFlags(flags() | Qt::ItemIsDropEnabled); } QString ProjectTargetItem::iconName() const { return QStringLiteral("system-run"); } void ProjectTargetItem::setPath( const Path& path ) { // don't call base class, it calls setText with the new path's filename // which we do not want for target items d_ptr->m_path = path; } int ProjectTargetItem::type() const { return ProjectBaseItem::Target; } ProjectTargetItem *ProjectTargetItem::target() const { return const_cast( this ); } ProjectExecutableTargetItem::ProjectExecutableTargetItem( IProject* project, const QString &name, ProjectBaseItem *parent ) : ProjectTargetItem(project, name, parent) { } ProjectExecutableTargetItem *ProjectExecutableTargetItem::executable() const { return const_cast( this ); } int ProjectExecutableTargetItem::type() const { return ProjectBaseItem::ExecutableTarget; } ProjectLibraryTargetItem::ProjectLibraryTargetItem( IProject* project, const QString &name, ProjectBaseItem *parent ) : ProjectTargetItem(project, name, parent) {} int ProjectLibraryTargetItem::type() const { return ProjectBaseItem::LibraryTarget; } QModelIndex ProjectModel::pathToIndex(const QStringList& tofetch_) const { if(tofetch_.isEmpty()) return QModelIndex(); QStringList tofetch(tofetch_); if(tofetch.last().isEmpty()) tofetch.takeLast(); QModelIndex current=index(0,0, QModelIndex()); QModelIndex ret; for(int a = 0; a < tofetch.size(); ++a) { const QString& currentName = tofetch[a]; bool matched = false; QModelIndexList l = match(current, Qt::DisplayRole, currentName, -1, Qt::MatchExactly); foreach(const QModelIndex& idx, l) { //If this is not the last item, only match folders, as there may be targets and folders with the same name if(a == tofetch.size()-1 || itemFromIndex(idx)->folder()) { ret = idx; current = index(0,0, ret); matched = true; break; } } if(!matched) { ret = QModelIndex(); break; } } Q_ASSERT(!ret.isValid() || data(ret).toString()==tofetch.last()); return ret; } QStringList ProjectModel::pathFromIndex(const QModelIndex& index) const { if (!index.isValid()) return QStringList(); QModelIndex idx = index; QStringList list; do { QString t = data(idx, Qt::DisplayRole).toString(); list.prepend(t); QModelIndex parent = idx.parent(); idx = parent.sibling(parent.row(), index.column()); } while (idx.isValid()); return list; } int ProjectModel::columnCount( const QModelIndex& ) const { return 1; } int ProjectModel::rowCount( const QModelIndex& parent ) const { ProjectBaseItem* item = d->itemFromIndex( parent ); return item ? item->rowCount() : 0; } QModelIndex ProjectModel::parent( const QModelIndex& child ) const { if( child.isValid() ) { ProjectBaseItem* item = static_cast( child.internalPointer() ); return indexFromItem( item ); } return QModelIndex(); } QModelIndex ProjectModel::indexFromItem( const ProjectBaseItem* item ) const { if( item && item->d_func()->parent ) { return createIndex( item->row(), 0, item->d_func()->parent ); } return QModelIndex(); } ProjectBaseItem* ProjectModel::itemFromIndex( const QModelIndex& index ) const { if( index.row() >= 0 && index.column() == 0 && index.model() == this ) { ProjectBaseItem* parent = static_cast( index.internalPointer() ); if( parent ) { return parent->child( index.row() ); } } return nullptr; } QVariant ProjectModel::data( const QModelIndex& index, int role ) const { static const QSet allowedRoles = { Qt::DisplayRole, Qt::ToolTipRole, Qt::DecorationRole, ProjectItemRole, ProjectRole, UrlRole }; if( allowedRoles.contains(role) && index.isValid() ) { ProjectBaseItem* item = itemFromIndex( index ); if( item ) { switch(role) { case Qt::DecorationRole: return QIcon::fromTheme(item->iconName()); case Qt::ToolTipRole: return item->path().pathOrUrl(); case Qt::DisplayRole: return item->text(); case ProjectItemRole: return QVariant::fromValue(item); case UrlRole: return item->path().toUrl(); case ProjectRole: return QVariant::fromValue(item->project()); } } } return QVariant(); } ProjectModel::ProjectModel( QObject *parent ) : QAbstractItemModel( parent ), d( new ProjectModelPrivate( this ) ) { d->rootItem = new ProjectBaseItem( nullptr, QString(), nullptr ); d->rootItem->setModel( this ); } ProjectModel::~ProjectModel() { d->rootItem->setModel(nullptr); delete d->rootItem; delete d; } ProjectVisitor::ProjectVisitor() { } QModelIndex ProjectModel::index( int row, int column, const QModelIndex& parent ) const { ProjectBaseItem* parentItem = d->itemFromIndex( parent ); if( parentItem && row >= 0 && row < parentItem->rowCount() && column == 0 ) { return createIndex( row, column, parentItem ); } return QModelIndex(); } void ProjectModel::appendRow( ProjectBaseItem* item ) { d->rootItem->appendRow( item ); } void ProjectModel::removeRow( int row ) { d->rootItem->removeRow( row ); } ProjectBaseItem* ProjectModel::takeRow( int row ) { return d->rootItem->takeRow( row ); } ProjectBaseItem* ProjectModel::itemAt(int row) const { return d->rootItem->child(row); } QList< ProjectBaseItem* > ProjectModel::topItems() const { return d->rootItem->children(); } Qt::ItemFlags ProjectModel::flags(const QModelIndex& index) const { ProjectBaseItem* item = itemFromIndex( index ); if(item) return item->flags(); else return nullptr; } bool ProjectModel::insertColumns(int, int, const QModelIndex&) { // Not supported return false; } bool ProjectModel::insertRows(int, int, const QModelIndex&) { // Not supported return false; } bool ProjectModel::setData(const QModelIndex&, const QVariant&, int) { // Not supported return false; } QList ProjectModel::itemsForPath(const IndexedString& path) const { return d->pathLookupTable.values(path.index()); } ProjectBaseItem* ProjectModel::itemForPath(const IndexedString& path) const { return d->pathLookupTable.value(path.index()); } void ProjectVisitor::visit( ProjectModel* model ) { foreach( ProjectBaseItem* item, model->topItems() ) { visit( item->project() ); } } void ProjectVisitor::visit ( IProject* prj ) { visit( prj->projectItem() ); } void ProjectVisitor::visit ( ProjectBuildFolderItem* folder ) { visit(static_cast(folder)); } void ProjectVisitor::visit ( ProjectExecutableTargetItem* exec ) { foreach( ProjectFileItem* item, exec->fileList() ) { visit( item ); } } void ProjectVisitor::visit ( ProjectFolderItem* folder ) { foreach( ProjectFileItem* item, folder->fileList() ) { visit( item ); } foreach( ProjectTargetItem* item, folder->targetList() ) { if( item->type() == ProjectBaseItem::LibraryTarget ) { visit( dynamic_cast( item ) ); } else if( item->type() == ProjectBaseItem::ExecutableTarget ) { visit( dynamic_cast( item ) ); } } foreach( ProjectFolderItem* item, folder->folderList() ) { if( item->type() == ProjectBaseItem::BuildFolder ) { visit( dynamic_cast( item ) ); } else if( item->type() == ProjectBaseItem::Folder ) { visit( dynamic_cast( item ) ); } } } void ProjectVisitor::visit ( ProjectFileItem* ) { } void ProjectVisitor::visit ( ProjectLibraryTargetItem* lib ) { foreach( ProjectFileItem* item, lib->fileList() ) { visit( item ); } } ProjectVisitor::~ProjectVisitor() { } } diff --git a/project/tests/projectmodelperformancetest.h b/project/tests/projectmodelperformancetest.h index 1238ec7f4..04f447e29 100644 --- a/project/tests/projectmodelperformancetest.h +++ b/project/tests/projectmodelperformancetest.h @@ -1,52 +1,52 @@ /*************************************************************************** * Copyright 2010 Andreas Pakulat * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef KDEVELOP_PROJECT_PROJECTMODELPERFORMANCETEST_INCLUDED #define KDEVELOP_PROJECT_PROJECTMODELPERFORMANCETEST_INCLUDED #include #include namespace KDevelop { class ProjectModel; class ProjectBaseItem; } class QTreeView; class ProjectModelPerformanceTest : public QWidget { Q_OBJECT public: - ProjectModelPerformanceTest(QWidget* parent = nullptr); + explicit ProjectModelPerformanceTest(QWidget* parent = nullptr); ~ProjectModelPerformanceTest() override; private slots: void init(); void addSmallTree(); void addBigTree(); void addBigTreeDelayed(); void addItemDelayed(); private: QStack currentParent; int originalWidth; KDevelop::ProjectModel* model; QTreeView* view; }; #endif diff --git a/project/widgets/dependencieswidget.h b/project/widgets/dependencieswidget.h index a35b2c595..13c7c1543 100644 --- a/project/widgets/dependencieswidget.h +++ b/project/widgets/dependencieswidget.h @@ -1,61 +1,61 @@ /* 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. */ #ifndef DEPENDENCIESWIDGET_H #define DEPENDENCIESWIDGET_H #include #include #include namespace Ui { class DependenciesWidget; } namespace KDevelop { class IProject; class KDEVPLATFORMPROJECT_EXPORT DependenciesWidget : public QWidget { Q_OBJECT public: - DependenciesWidget(QWidget* parent); + explicit DependenciesWidget(QWidget* parent); ~DependenciesWidget(); void setSuggestion(KDevelop::IProject* project); void setDependencies(const QVariantList &deps); QVariantList dependencies() const; Q_SIGNALS: void changed(); private: void depEdited( const QString& str ); void checkActions( const QItemSelection& selected, const QItemSelection& unselected ); void moveDependencyDown(); void moveDependencyUp(); void addDep(); void removeDep(); void selectItemDialog(); Ui::DependenciesWidget* m_ui; }; } #endif diff --git a/serialization/itemrepository.h b/serialization/itemrepository.h index 1803bbcad..1667c1082 100644 --- a/serialization/itemrepository.h +++ b/serialization/itemrepository.h @@ -1,2251 +1,2251 @@ /* 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_ITEMREPOSITORY_H #define KDEVPLATFORM_ITEMREPOSITORY_H #include #include #include #include #include #include "referencecounting.h" #include "abstractitemrepository.h" #include "repositorymanager.h" #include "itemrepositoryregistry.h" //#define DEBUG_MONSTERBUCKETS // #define DEBUG_ITEMREPOSITORY_LOADING // #define ifDebugInfiniteRecursion(x) x #define ifDebugInfiniteRecursion(x) // #define ifDebugLostSpace(x) x #define ifDebugLostSpace(x) // #define DEBUG_INCORRECT_DELETE //Makes sure that all items stay reachable through the basic hash // #define DEBUG_ITEM_REACHABILITY ///@todo Dynamic bucket hash size #ifdef DEBUG_ITEM_REACHABILITY #define ENSURE_REACHABLE(bucket) Q_ASSERT(allItemsReachable(bucket)); #define IF_ENSURE_REACHABLE(x) x #else #define ENSURE_REACHABLE(bucket) #define IF_ENSURE_REACHABLE(x) #endif #define ITEMREPOSITORY_USE_MMAP_LOADING //Assertion macro that prevents warnings if debugging is disabled //Only use it to verify values, it should not call any functions, since else the function will even be called in release mode #ifdef QT_NO_DEBUG #define VERIFY(X) if(!(X)) {qWarning() << "Failed to verify expression" << #X;} #else #define VERIFY(X) Q_ASSERT(X) #endif ///When this is uncommented, a 64-bit test-value is written behind the area an item is allowed to write into before ///createItem(..) is called, and an assertion triggers when it was changed during createItem(), which means createItem wrote too long. ///The problem: This temporarily overwrites valid data in the following item, so it will cause serious problems if that data is accessed ///during the call to createItem(). // #define DEBUG_WRITING_EXTENTS class TestItemRepository; namespace KDevelop { /** * This file implements a generic bucket-based indexing repository, that can be used for example to index strings. * * All you need to do is define your item type that you want to store into the repository, and create a request item * similar to ExampleItemRequest that compares and fills the defined item type. * * For example the string repository uses "unsigned short" as item-type, uses that actual value to store the length of the string, * and uses the space behind to store the actual string content. * * @see AbstractItemRepository * @see ItemRepository * * @see ExampleItem * @see ExampleItemRequest * * @see typerepository.h * @see stringrepository.h * @see indexedstring.h */ enum { ItemRepositoryBucketSize = 1<<16, ItemRepositoryBucketLimit = 1<<16 }; /** * Buckets are the memory-units that are used to store the data in an ItemRepository. * * About monster buckets: Normally a bucket has a size of 64kb, but when an item is * allocated that is larger than that, a "monster bucket" is allocated, which spans the * space of multiple buckets. */ template class Bucket { public: enum { AdditionalSpacePerItem = 2 }; enum { ObjectMapSize = ((ItemRepositoryBucketSize / ItemRequest::AverageSize) * 3) / 2 + 1, MaxFreeItemsForHide = 0, //When less than this count of free items in one buckets is reached, the bucket is removed from the global list of buckets with free items MaxFreeSizeForHide = fixedItemSize ? fixedItemSize : 0, //Only when the largest free size is smaller then this, the bucket is taken from the free list MinFreeItemsForReuse = 10,//When this count of free items in one bucket is reached, consider re-assigning them to new requests MinFreeSizeForReuse = ItemRepositoryBucketSize/20 //When the largest free item is bigger then this, the bucket is automatically added to the free list }; enum { NextBucketHashSize = ObjectMapSize, //Affects the average count of bucket-chains that need to be walked in ItemRepository::index. Must be a multiple of ObjectMapSize DataSize = sizeof(char) + sizeof(unsigned int) * 3 + ItemRepositoryBucketSize + sizeof(short unsigned int) * (ObjectMapSize + NextBucketHashSize + 1) }; enum { CheckStart = 0xff00ff1, CheckEnd = 0xfafcfb }; Bucket() : m_monsterBucketExtent(0) , m_available(0) , m_data(nullptr) , m_mappedData(nullptr) , m_objectMap(nullptr) , m_largestFreeItem(0) , m_freeItemCount(0) , m_nextBucketHash(nullptr) , m_dirty(false) , m_changed(false) , m_lastUsed(0) { } ~Bucket() { if(m_data != m_mappedData) { delete[] m_data; delete[] m_nextBucketHash; delete[] m_objectMap; } } void initialize(int monsterBucketExtent) { if(!m_data) { m_monsterBucketExtent = monsterBucketExtent; m_available = ItemRepositoryBucketSize; m_data = new char[ItemRepositoryBucketSize + monsterBucketExtent * DataSize]; memset(m_data, 0, (ItemRepositoryBucketSize + monsterBucketExtent * DataSize) * sizeof(char)); //The bigger we make the map, the lower the probability of a clash(and thus bad performance). However it increases memory usage. m_objectMap = new short unsigned int[ObjectMapSize]; memset(m_objectMap, 0, ObjectMapSize * sizeof(short unsigned int)); m_nextBucketHash = new short unsigned int[NextBucketHashSize]; memset(m_nextBucketHash, 0, NextBucketHashSize * sizeof(short unsigned int)); m_changed = true; m_dirty = false; m_lastUsed = 0; } } template void readValue(char*& from, T& to) { to = *reinterpret_cast(from); from += sizeof(T); } void initializeFromMap(char* current) { if(!m_data) { char* start = current; readValue(current, m_monsterBucketExtent); Q_ASSERT(current - start == 4); readValue(current, m_available); m_objectMap = reinterpret_cast(current); current += sizeof(short unsigned int) * ObjectMapSize; m_nextBucketHash = reinterpret_cast(current); current += sizeof(short unsigned int) * NextBucketHashSize; readValue(current, m_largestFreeItem); readValue(current, m_freeItemCount); readValue(current, m_dirty); m_data = current; m_mappedData = current; m_changed = false; m_lastUsed = 0; VERIFY(current - start == (DataSize - ItemRepositoryBucketSize)); } } void store(QFile* file, size_t offset) { if(!m_data) return; if(static_cast(file->size()) < offset + (1+m_monsterBucketExtent)*DataSize) file->resize(offset + (1+m_monsterBucketExtent)*DataSize); file->seek(offset); file->write((char*)&m_monsterBucketExtent, sizeof(unsigned int)); file->write((char*)&m_available, sizeof(unsigned int)); file->write((char*)m_objectMap, sizeof(short unsigned int) * ObjectMapSize); file->write((char*)m_nextBucketHash, sizeof(short unsigned int) * NextBucketHashSize); file->write((char*)&m_largestFreeItem, sizeof(short unsigned int)); file->write((char*)&m_freeItemCount, sizeof(unsigned int)); file->write((char*)&m_dirty, sizeof(bool)); file->write(m_data, ItemRepositoryBucketSize + m_monsterBucketExtent * DataSize); if(static_cast(file->pos()) != offset + (1+m_monsterBucketExtent)*DataSize) { KMessageBox::error(nullptr, i18n("Failed writing to %1, probably the disk is full", file->fileName())); abort(); } m_changed = false; #ifdef DEBUG_ITEMREPOSITORY_LOADING { file->flush(); file->seek(offset); uint available, freeItemCount, monsterBucketExtent; short unsigned int largestFree; bool dirty; short unsigned int* m = new short unsigned int[ObjectMapSize]; short unsigned int* h = new short unsigned int[NextBucketHashSize]; file->read((char*)&monsterBucketExtent, sizeof(unsigned int)); char* d = new char[ItemRepositoryBucketSize + monsterBucketExtent * DataSize]; file->read((char*)&available, sizeof(unsigned int)); file->read((char*)m, sizeof(short unsigned int) * ObjectMapSize); file->read((char*)h, sizeof(short unsigned int) * NextBucketHashSize); file->read((char*)&largestFree, sizeof(short unsigned int)); file->read((char*)&freeItemCount, sizeof(unsigned int)); file->read((char*)&dirty, sizeof(bool)); file->read(d, ItemRepositoryBucketSize); Q_ASSERT(monsterBucketExtent == m_monsterBucketExtent); Q_ASSERT(available == m_available); Q_ASSERT(memcmp(d, m_data, ItemRepositoryBucketSize + monsterBucketExtent * DataSize) == 0); Q_ASSERT(memcmp(m, m_objectMap, sizeof(short unsigned int) * ObjectMapSize) == 0); Q_ASSERT(memcmp(h, m_nextBucketHash, sizeof(short unsigned int) * NextBucketHashSize) == 0); Q_ASSERT(m_largestFreeItem == largestFree); Q_ASSERT(m_freeItemCount == freeItemCount); Q_ASSERT(m_dirty == dirty); Q_ASSERT(static_cast(file->pos()) == offset + DataSize + m_monsterBucketExtent * DataSize); delete[] d; delete[] m; delete[] h; } #endif } inline char* data() { return m_data; } inline uint dataSize() const { return ItemRepositoryBucketSize + m_monsterBucketExtent * DataSize; } //Tries to find the index this item has in this bucket, or returns zero if the item isn't there yet. unsigned short findIndex(const ItemRequest& request) const { m_lastUsed = 0; unsigned short localHash = request.hash() % ObjectMapSize; unsigned short index = m_objectMap[localHash]; unsigned short follower = 0; //Walk the chain of items with the same local hash while(index && (follower = followerIndex(index)) && !(request.equals(itemFromIndex(index)))) index = follower; if(index && request.equals(itemFromIndex(index))) { return index; //We have found the item } return 0; } //Tries to get the index within this bucket, or returns zero. Will put the item into the bucket if there is room. //Created indices will never begin with 0xffff____, so you can use that index-range for own purposes. unsigned short index(const ItemRequest& request, unsigned int itemSize) { m_lastUsed = 0; unsigned short localHash = request.hash() % ObjectMapSize; unsigned short index = m_objectMap[localHash]; unsigned short insertedAt = 0; unsigned short follower = 0; //Walk the chain of items with the same local hash while(index && (follower = followerIndex(index)) && !(request.equals(itemFromIndex(index)))) index = follower; if(index && request.equals(itemFromIndex(index))) return index; //We have found the item ifDebugLostSpace( Q_ASSERT(!lostSpace()); ) prepareChange(); unsigned int totalSize = itemSize + AdditionalSpacePerItem; if(m_monsterBucketExtent) { ///This is a monster-bucket. Other rules are applied here. Only one item can be allocated, and that must be bigger than the bucket data Q_ASSERT(totalSize > ItemRepositoryBucketSize); Q_ASSERT(m_available); m_available = 0; insertedAt = AdditionalSpacePerItem; setFollowerIndex(insertedAt, 0); Q_ASSERT(m_objectMap[localHash] == 0); m_objectMap[localHash] = insertedAt; if(markForReferenceCounting) enableDUChainReferenceCounting(m_data, dataSize()); request.createItem(reinterpret_cast(m_data + insertedAt)); if(markForReferenceCounting) disableDUChainReferenceCounting(m_data); return insertedAt; } //The second condition is needed, else we can get problems with zero-length items and an overflow in insertedAt to zero if(totalSize > m_available || (!itemSize && totalSize == m_available)) { //Try finding the smallest freed item that can hold the data unsigned short currentIndex = m_largestFreeItem; unsigned short previousIndex = 0; unsigned short freeChunkSize = 0; ///@todo Achieve this without full iteration while(currentIndex && freeSize(currentIndex) > itemSize) { unsigned short follower = followerIndex(currentIndex); if(follower && freeSize(follower) >= itemSize) { //The item also fits into the smaller follower, so use that one previousIndex = currentIndex; currentIndex = follower; }else{ //The item fits into currentIndex, but not into the follower. So use currentIndex freeChunkSize = freeSize(currentIndex) - itemSize; //We need 2 bytes to store the free size if(freeChunkSize != 0 && freeChunkSize < AdditionalSpacePerItem+2) { //we can not manage the resulting free chunk as a separate item, so we cannot use this position. //Just pick the biggest free item, because there we can be sure that //either we can manage the split, or we cannot do anything at all in this bucket. freeChunkSize = freeSize(m_largestFreeItem) - itemSize; if(freeChunkSize == 0 || freeChunkSize >= AdditionalSpacePerItem+2) { previousIndex = 0; currentIndex = m_largestFreeItem; }else{ currentIndex = 0; } } break; } } if(!currentIndex || freeSize(currentIndex) < (totalSize-AdditionalSpacePerItem)) return 0; if(previousIndex) setFollowerIndex(previousIndex, followerIndex(currentIndex)); else m_largestFreeItem = followerIndex(currentIndex); --m_freeItemCount; //Took one free item out of the chain ifDebugLostSpace( Q_ASSERT((uint)lostSpace() == (uint)(freeSize(currentIndex) + AdditionalSpacePerItem)); ) if(freeChunkSize) { Q_ASSERT(freeChunkSize >= AdditionalSpacePerItem+2); unsigned short freeItemSize = freeChunkSize - AdditionalSpacePerItem; unsigned short freeItemPosition; //Insert the resulting free chunk into the list of free items, so we don't lose it if(isBehindFreeSpace(currentIndex)) { //Create the free item at the beginning of currentIndex, so it can be merged with the free space in front freeItemPosition = currentIndex; currentIndex += freeItemSize + AdditionalSpacePerItem; }else{ //Create the free item behind currentIndex freeItemPosition = currentIndex + itemSize + AdditionalSpacePerItem; } setFreeSize(freeItemPosition, freeItemSize); insertFreeItem(freeItemPosition); } insertedAt = currentIndex; Q_ASSERT((bool)m_freeItemCount == (bool)m_largestFreeItem); }else{ //We have to insert the item insertedAt = ItemRepositoryBucketSize - m_available; insertedAt += AdditionalSpacePerItem; //Room for the prepended follower-index m_available -= totalSize; } ifDebugLostSpace( Q_ASSERT(lostSpace() == totalSize); ) Q_ASSERT(!index || !followerIndex(index)); Q_ASSERT(!m_objectMap[localHash] || index); if(index) setFollowerIndex(index, insertedAt); setFollowerIndex(insertedAt, 0); if(m_objectMap[localHash] == 0) m_objectMap[localHash] = insertedAt; #ifdef DEBUG_CREATEITEM_EXTENTS char* borderBehind = m_data + insertedAt + (totalSize-AdditionalSpacePerItem); quint64 oldValueBehind = 0; if(m_available >= 8) { oldValueBehind = *(quint64*)borderBehind; *((quint64*)borderBehind) = 0xfafafafafafafafaLLU; } #endif //Last thing we do, because createItem may recursively do even more transformation of the repository if(markForReferenceCounting) enableDUChainReferenceCounting(m_data, dataSize()); request.createItem(reinterpret_cast(m_data + insertedAt)); if(markForReferenceCounting) disableDUChainReferenceCounting(m_data); #ifdef DEBUG_CREATEITEM_EXTENTS if(m_available >= 8) { //If this assertion triggers, then the item writes a bigger range than it advertised in Q_ASSERT(*((quint64*)borderBehind) == 0xfafafafafafafafaLLU); *((quint64*)borderBehind) = oldValueBehind; } #endif Q_ASSERT(itemFromIndex(insertedAt)->hash() == request.hash()); Q_ASSERT(itemFromIndex(insertedAt)->itemSize() == itemSize); ifDebugLostSpace( if(lostSpace()) qDebug() << "lost space:" << lostSpace(); Q_ASSERT(!lostSpace()); ) return insertedAt; } ///@param modulo Returns whether this bucket contains an item with (hash % modulo) == (item.hash % modulo) /// The default-parameter is the size of the next-bucket hash that is used by setNextBucketForHash and nextBucketForHash ///@note modulo MUST be a multiple of ObjectMapSize, because (b-a) | (x * h1) => (b-a) | h2, where a|b means a is a multiple of b. /// This this allows efficiently computing the clashes using the local object map hash. bool hasClashingItem(uint hash, uint modulo) { Q_ASSERT(modulo % ObjectMapSize == 0); m_lastUsed = 0; uint hashMod = hash % modulo; unsigned short localHash = hash % ObjectMapSize; unsigned short currentIndex = m_objectMap[localHash]; if(currentIndex == 0) return false; while(currentIndex) { uint currentHash = itemFromIndex(currentIndex)->hash(); Q_ASSERT(currentHash % ObjectMapSize == localHash); if(currentHash % modulo == hashMod) return true; //Clash currentIndex = followerIndex(currentIndex); } return false; } void countFollowerIndexLengths(uint& usedSlots, uint& lengths, uint& slotCount, uint& longestInBucketFollowerChain) { for(uint a = 0; a < ObjectMapSize; ++a) { unsigned short currentIndex = m_objectMap[a]; ++slotCount; uint length = 0; if(currentIndex) { ++usedSlots; while(currentIndex) { ++length; ++lengths; currentIndex = followerIndex(currentIndex); } if(length > longestInBucketFollowerChain) { // qDebug() << "follower-chain at" << a << ":" << length; longestInBucketFollowerChain = length; } } } } //Returns whether the given item is reachabe within this bucket, through its hash. bool itemReachable(const Item* item, uint hash) const { unsigned short localHash = hash % ObjectMapSize; unsigned short currentIndex = m_objectMap[localHash]; while(currentIndex) { if(itemFromIndex(currentIndex) == item) return true; currentIndex = followerIndex(currentIndex); } return false; } template void deleteItem(unsigned short index, unsigned int hash, Repository& repository) { ifDebugLostSpace( Q_ASSERT(!lostSpace()); ) m_lastUsed = 0; prepareChange(); unsigned int size = itemFromIndex(index)->itemSize(); //Step 1: Remove the item from the data-structures that allow finding it: m_objectMap unsigned short localHash = hash % ObjectMapSize; unsigned short currentIndex = m_objectMap[localHash]; unsigned short previousIndex = 0; //Fix the follower-link by setting the follower of the previous item to the next one, or updating m_objectMap while(currentIndex != index) { previousIndex = currentIndex; currentIndex = followerIndex(currentIndex); //If this assertion triggers, the deleted item was not registered under the given hash Q_ASSERT(currentIndex); } Q_ASSERT(currentIndex == index); if(!previousIndex) //The item was directly in the object map m_objectMap[localHash] = followerIndex(index); else setFollowerIndex(previousIndex, followerIndex(index)); Item* item = const_cast(itemFromIndex(index)); if(markForReferenceCounting) enableDUChainReferenceCounting(m_data, dataSize()); ItemRequest::destroy(item, repository); if(markForReferenceCounting) disableDUChainReferenceCounting(m_data); memset(item, 0, size); //For debugging, so we notice the data is wrong if(m_monsterBucketExtent) { ///This is a monster-bucket. Make it completely empty again. Q_ASSERT(!m_available); m_available = ItemRepositoryBucketSize; //Items are always inserted into monster-buckets at a fixed position Q_ASSERT(currentIndex == AdditionalSpacePerItem); Q_ASSERT(m_objectMap[localHash] == 0); }else{ ///Put the space into the free-set setFreeSize(index, size); //Try merging the created free item to other free items around, else add it into the free list insertFreeItem(index); if(m_freeItemCount == 1 && freeSize(m_largestFreeItem) + m_available == ItemRepositoryBucketSize) { //Everything has been deleted, there is only free space left. Make the bucket empty again, //so it can later also be used as a monster-bucket. m_available = ItemRepositoryBucketSize; m_freeItemCount = 0; m_largestFreeItem = 0; } } Q_ASSERT((bool)m_freeItemCount == (bool)m_largestFreeItem); ifDebugLostSpace( Q_ASSERT(!lostSpace()); ) #ifdef DEBUG_INCORRECT_DELETE //Make sure the item cannot be found any more { unsigned short localHash = hash % ObjectMapSize; unsigned short currentIndex = m_objectMap[localHash]; while(currentIndex && currentIndex != index) { previousIndex = currentIndex; currentIndex = followerIndex(currentIndex); } Q_ASSERT(!currentIndex); //The item must not be found } #endif // Q_ASSERT(canAllocateItem(size)); } ///@warning The returned item may be in write-protected memory, so never try doing a const_cast and changing some data /// If you need to change something, use dynamicItemFromIndex ///@warning When using multi-threading, mutex() must be locked as long as you use the returned data inline const Item* itemFromIndex(unsigned short index) const { m_lastUsed = 0; return reinterpret_cast(m_data+index); } bool isEmpty() const { return m_available == ItemRepositoryBucketSize; } ///Returns true if this bucket has no nextBucketForHash links bool noNextBuckets() const { for(int a = 0; a < NextBucketHashSize; ++a) if(m_nextBucketHash[a]) return false; return true; } uint available() const { return m_available; } uint usedMemory() const { return ItemRepositoryBucketSize - m_available; } template bool visitAllItems(Visitor& visitor) const { m_lastUsed = 0; for(uint a = 0; a < ObjectMapSize; ++a) { uint currentIndex = m_objectMap[a]; while(currentIndex) { //Get the follower early, so there is no problems when the current //index is removed if(!visitor(reinterpret_cast(m_data+currentIndex))) return false; currentIndex = followerIndex(currentIndex); } } return true; } ///Returns whether something was changed template int finalCleanup(Repository& repository) { int changed = 0; while(m_dirty) { m_dirty = false; for(uint a = 0; a < ObjectMapSize; ++a) { uint currentIndex = m_objectMap[a]; while(currentIndex) { //Get the follower early, so there is no problems when the current //index is removed const Item* item = reinterpret_cast(m_data+currentIndex); if(!ItemRequest::persistent(item)) { changed += item->itemSize(); deleteItem(currentIndex, item->hash(), repository); m_dirty = true; //Set to dirty so we re-iterate break; } currentIndex = followerIndex(currentIndex); } } } return changed; } unsigned short nextBucketForHash(uint hash) const { m_lastUsed = 0; return m_nextBucketHash[hash % NextBucketHashSize]; } void setNextBucketForHash(unsigned int hash, unsigned short bucket) { m_lastUsed = 0; prepareChange(); m_nextBucketHash[hash % NextBucketHashSize] = bucket; } uint freeItemCount() const { return m_freeItemCount; } short unsigned int totalFreeItemsSize() const { short unsigned int ret = 0; short unsigned int currentIndex = m_largestFreeItem; while(currentIndex) { ret += freeSize(currentIndex); currentIndex = followerIndex(currentIndex); } return ret; } //Size of the largest item that could be inserted into this bucket short unsigned int largestFreeSize() const { short unsigned int ret = 0; if(m_largestFreeItem) ret = freeSize(m_largestFreeItem); if(m_available > (uint)(AdditionalSpacePerItem + (uint)ret)) { ret = m_available - AdditionalSpacePerItem; Q_ASSERT(ret == (m_available - AdditionalSpacePerItem)); } return ret; } bool canAllocateItem(unsigned int size) const { short unsigned int currentIndex = m_largestFreeItem; while(currentIndex) { short unsigned int currentFree = freeSize(currentIndex); if(currentFree < size) return false; //Either we need an exact match, or 2 additional bytes to manage the resulting gap if(size == currentFree || currentFree - size >= AdditionalSpacePerItem + 2) return true; currentIndex = followerIndex(currentIndex); } return false; } void tick() const { ++m_lastUsed; } //How many ticks ago the item was last used int lastUsed() const { return m_lastUsed; } //Whether this bucket was changed since it was last stored bool changed() const { return m_changed; } void prepareChange() { m_changed = true; m_dirty = true; makeDataPrivate(); } bool dirty() const { return m_dirty; } ///Returns the count of following buckets that were merged onto this buckets data array int monsterBucketExtent() const { return m_monsterBucketExtent; } //Counts together the space that is neither accessible through m_objectMap nor through the free items uint lostSpace() { if(m_monsterBucketExtent) return 0; uint need = ItemRepositoryBucketSize - m_available; uint found = 0; for(uint a = 0; a < ObjectMapSize; ++a) { uint currentIndex = m_objectMap[a]; while(currentIndex) { found += reinterpret_cast(m_data+currentIndex)->itemSize() + AdditionalSpacePerItem; currentIndex = followerIndex(currentIndex); } } uint currentIndex = m_largestFreeItem; while(currentIndex) { found += freeSize(currentIndex) + AdditionalSpacePerItem; currentIndex = followerIndex(currentIndex); } return need-found; } private: void makeDataPrivate() { if(m_mappedData == m_data) { short unsigned int* oldObjectMap = m_objectMap; short unsigned int* oldNextBucketHash = m_nextBucketHash; m_data = new char[ItemRepositoryBucketSize + m_monsterBucketExtent * DataSize]; m_objectMap = new short unsigned int[ObjectMapSize]; m_nextBucketHash = new short unsigned int[NextBucketHashSize]; memcpy(m_data, m_mappedData, ItemRepositoryBucketSize + m_monsterBucketExtent * DataSize); memcpy(m_objectMap, oldObjectMap, ObjectMapSize * sizeof(short unsigned int)); memcpy(m_nextBucketHash, oldNextBucketHash, NextBucketHashSize * sizeof(short unsigned int)); } } ///Merges the given index item, which must have a freeSize() set, to surrounding free items, and inserts the result. ///The given index itself should not be in the free items chain yet. ///Returns whether the item was inserted somewhere. void insertFreeItem(unsigned short index) { //If the item-size is fixed, we don't need to do any management. Just keep a list of free items. Items of other size will never be requested. if(!fixedItemSize) { unsigned short currentIndex = m_largestFreeItem; unsigned short previousIndex = 0; while(currentIndex) { Q_ASSERT(currentIndex != index); #ifndef QT_NO_DEBUG unsigned short currentFreeSize = freeSize(currentIndex); #endif ///@todo Achieve this without iterating through all items in the bucket(This is very slow) //Merge behind index if(currentIndex == index + freeSize(index) + AdditionalSpacePerItem) { //Remove currentIndex from the free chain, since it's merged backwards into index if(previousIndex && followerIndex(currentIndex)) Q_ASSERT(freeSize(previousIndex) >= freeSize(followerIndex(currentIndex))); if(previousIndex) setFollowerIndex(previousIndex, followerIndex(currentIndex)); else m_largestFreeItem = followerIndex(currentIndex); --m_freeItemCount; //One was removed //currentIndex is directly behind index, touching its space. Merge them. setFreeSize(index, freeSize(index) + AdditionalSpacePerItem + freeSize(currentIndex)); //Recurse to do even more merging insertFreeItem(index); return; } //Merge before index if(index == currentIndex + freeSize(currentIndex) + AdditionalSpacePerItem) { if(previousIndex && followerIndex(currentIndex)) Q_ASSERT(freeSize(previousIndex) >= freeSize(followerIndex(currentIndex))); //Remove currentIndex from the free chain, since insertFreeItem wants //it not to be in the chain, and it will be inserted in another place if(previousIndex) setFollowerIndex(previousIndex, followerIndex(currentIndex)); else m_largestFreeItem = followerIndex(currentIndex); --m_freeItemCount; //One was removed //index is directly behind currentIndex, touching its space. Merge them. setFreeSize(currentIndex, freeSize(currentIndex) + AdditionalSpacePerItem + freeSize(index)); //Recurse to do even more merging insertFreeItem(currentIndex); return; } previousIndex = currentIndex; currentIndex = followerIndex(currentIndex); #ifndef QT_NO_DEBUG if(currentIndex) Q_ASSERT(freeSize(currentIndex) <= currentFreeSize); #endif } } insertToFreeChain(index); } ///Only inserts the item in the correct position into the free chain. index must not be in the chain yet. void insertToFreeChain(unsigned short index) { if(!fixedItemSize) { ///@todo Use some kind of tree to find the correct position in the chain(This is very slow) //Insert the free item into the chain opened by m_largestFreeItem unsigned short currentIndex = m_largestFreeItem; unsigned short previousIndex = 0; unsigned short size = freeSize(index); while(currentIndex && freeSize(currentIndex) > size) { Q_ASSERT(currentIndex != index); //must not be in the chain yet previousIndex = currentIndex; currentIndex = followerIndex(currentIndex); } if(currentIndex) Q_ASSERT(freeSize(currentIndex) <= size); setFollowerIndex(index, currentIndex); if(previousIndex) { Q_ASSERT(freeSize(previousIndex) >= size); setFollowerIndex(previousIndex, index); } else //This item is larger than all already registered free items, or there are none. m_largestFreeItem = index; }else{ Q_ASSERT(freeSize(index) == fixedItemSize); //When all items have the same size, just prepent to the front. setFollowerIndex(index, m_largestFreeItem); m_largestFreeItem = index; } ++m_freeItemCount; } ///Returns true if the given index is right behind free space, and thus can be merged to the free space. bool isBehindFreeSpace(unsigned short index) const { ///@todo Without iteration! unsigned short currentIndex = m_largestFreeItem; while(currentIndex) { if(index == currentIndex + freeSize(currentIndex) + AdditionalSpacePerItem) return true; currentIndex = followerIndex(currentIndex); } return false; } ///@param index the index of an item @return The index of the next item in the chain of items with a same local hash, or zero inline unsigned short followerIndex(unsigned short index) const { Q_ASSERT(index >= 2); return *reinterpret_cast(m_data+(index-2)); } void setFollowerIndex(unsigned short index, unsigned short follower) { Q_ASSERT(index >= 2); *reinterpret_cast(m_data+(index-2)) = follower; } // Only returns the current value if the item is actually free inline unsigned short freeSize(unsigned short index) const { return *reinterpret_cast(m_data+index); } //Convenience function to set the free-size, only for freed items void setFreeSize(unsigned short index, unsigned short size) { *reinterpret_cast(m_data+index) = size; } int m_monsterBucketExtent; //If this is a monster-bucket, this contains the count of follower-buckets that belong to this one unsigned int m_available; char* m_data; //Structure of the data: (2 byte), (item.size() byte) char* m_mappedData; //Read-only memory-mapped data. If this equals m_data, m_data must not be written short unsigned int* m_objectMap; //Points to the first object in m_data with (hash % ObjectMapSize) == index. Points to the item itself, so subtract 1 to get the pointer to the next item with same local hash. short unsigned int m_largestFreeItem; //Points to the largest item that is currently marked as free, or zero. That one points to the next largest one through followerIndex unsigned int m_freeItemCount; unsigned short* m_nextBucketHash; bool m_dirty; //Whether the data was changed since the last finalCleanup bool m_changed; //Whether this bucket was changed since it was last stored to disk mutable int m_lastUsed; //How many ticks ago this bucket was last accessed }; template struct Locker { //This is a dummy that does nothing template - Locker(const T& /*t*/) { + explicit Locker(const T& /*t*/) { } }; template<> struct Locker { - Locker(QMutex* mutex) : m_mutex(mutex) { + explicit Locker(QMutex* mutex) : m_mutex(mutex) { m_mutex->lock(); } ~Locker() { m_mutex->unlock(); } QMutex* m_mutex; }; ///This object needs to be kept alive as long as you change the contents of an item ///stored in the repository. It is needed to correctly track the reference counting ///within disk-storage. ///@warning You can not freely copy this around, when you create a copy, the copy source /// becomes invalid template class DynamicItem { public: DynamicItem(Item* i, void* start, uint size) : m_item(i), m_start(start) { if(markForReferenceCounting) enableDUChainReferenceCounting(m_start, size); // qDebug() << "enabling" << i << "to" << (void*)(((char*)i)+size); } ~DynamicItem() { if(m_start) { // qDebug() << "destructor-disabling" << m_item; if(markForReferenceCounting) disableDUChainReferenceCounting(m_start); } } DynamicItem(const DynamicItem& rhs) : m_item(rhs.m_item), m_start(rhs.m_start) { // qDebug() << "stealing" << m_item; Q_ASSERT(rhs.m_start); rhs.m_start = nullptr; } Item* operator->() { return m_item; } Item* m_item; private: mutable void* m_start; DynamicItem& operator=(const DynamicItem&); }; ///@tparam Item See ExampleItem ///@tparam ItemRequest See ExampleReqestItem ///@tparam fixedItemSize When this is true, all inserted items must have the same size. /// This greatly simplifies and speeds up the task of managing free items within the buckets. ///@tparam markForReferenceCounting Whether the data within the repository should be marked for reference-counting. /// This costs a bit of performance, but must be enabled if there may be data in the repository /// that does on-disk reference counting, like IndexedString, IndexedIdentifier, etc. ///@tparam threadSafe Whether class access should be thread-safe. Disabling this is dangerous when you do multi-threading. /// You have to make sure that mutex() is locked whenever the repository is accessed. template class ItemRepository : public AbstractItemRepository { typedef Locker ThisLocker; typedef Bucket MyBucket; enum { //Must be a multiple of Bucket::ObjectMapSize, so Bucket::hasClashingItem can be computed //Must also be a multiple of Bucket::NextBucketHashSize, for the same reason.(Currently those are same) bucketHashSize = (targetBucketHashSize / MyBucket::ObjectMapSize) * MyBucket::ObjectMapSize }; enum { BucketStartOffset = sizeof(uint) * 7 + sizeof(short unsigned int) * bucketHashSize //Position in the data where the bucket array starts }; public: ///@param registry May be zero, then the repository will not be registered at all. Else, the repository will register itself to that registry. /// If this is zero, you have to care about storing the data using store() and/or close() by yourself. It does not happen automatically. /// For the global standard registry, the storing/loading is triggered from within duchain, so you don't need to care about it. - ItemRepository(const QString& repositoryName, ItemRepositoryRegistry* registry = &globalItemRepositoryRegistry(), + explicit ItemRepository(const QString& repositoryName, ItemRepositoryRegistry* registry = &globalItemRepositoryRegistry(), uint repositoryVersion = 1, AbstractRepositoryManager* manager = nullptr) : m_ownMutex(QMutex::Recursive) , m_mutex(&m_ownMutex) , m_repositoryName(repositoryName) , m_registry(registry) , m_file(nullptr) , m_dynamicFile(nullptr) , m_repositoryVersion(repositoryVersion) , m_manager(manager) { m_unloadingEnabled = true; m_metaDataChanged = true; m_buckets.resize(10); m_buckets.fill(nullptr); memset(m_firstBucketForHash, 0, bucketHashSize * sizeof(short unsigned int)); m_statBucketHashClashes = m_statItemCount = 0; m_currentBucket = 1; //Skip the first bucket, we won't use it so we have the zero indices for special purposes if(m_registry) m_registry->registerRepository(this, m_manager); } ~ItemRepository() { if(m_registry) m_registry->unRegisterRepository(this); close(); } ///Unloading of buckets is enabled by default. Use this to disable it. When unloading is enabled, the data ///gotten from must only itemFromIndex must not be used for a long time. void setUnloadingEnabled(bool enabled) { m_unloadingEnabled = enabled; } ///Returns the index for the given item. If the item is not in the repository yet, it is inserted. ///The index can never be zero. Zero is reserved for your own usage as invalid ///@param request Item to retrieve the index from unsigned int index(const ItemRequest& request) { ThisLocker lock(m_mutex); const uint hash = request.hash(); const uint size = request.itemSize(); // Bucket indexes tracked while walking the bucket chain for this request hash unsigned short bucketInChainWithSpace = 0; unsigned short lastBucketWalked = 0; const ushort foundIndexInBucket = walkBucketChain(hash, [&](ushort bucketIdx, const MyBucket* bucketPtr) { lastBucketWalked = bucketIdx; const ushort found = bucketPtr->findIndex(request); if (!found && !bucketInChainWithSpace && bucketPtr->canAllocateItem(size)) { bucketInChainWithSpace = bucketIdx; } return found; }); if (foundIndexInBucket) { // 'request' is already present, return the existing index return createIndex(lastBucketWalked, foundIndexInBucket); } /* * Disclaimer: Writer of comment != writer of code, believe with caution * * Requested item does not yet exist. Need to find a place to put it... * * First choice is to place it in an existing bucket in the chain for the request hash * Second choice is to find an existing bucket anywhere with enough space * Otherwise use m_currentBucket (the latest unused bucket) * * If the chosen bucket fails to allocate the item, merge buckets to create a monster (dragon?) * * Finally, if the first option failed or the selected bucket failed to allocate, add the * bucket which the item ended up in to the chain of buckets for the request's hash */ m_metaDataChanged = true; const bool pickedBucketInChain = bucketInChainWithSpace; int useBucket = bucketInChainWithSpace; int reOrderFreeSpaceBucketIndex = -1; if (!pickedBucketInChain) { //Try finding an existing bucket with deleted space to store the data into for(int a = 0; a < m_freeSpaceBuckets.size(); ++a) { MyBucket* bucketPtr = bucketForIndex(m_freeSpaceBuckets[a]); Q_ASSERT(bucketPtr->largestFreeSize()); if(bucketPtr->canAllocateItem(size)) { //The item fits into the bucket. useBucket = m_freeSpaceBuckets[a]; reOrderFreeSpaceBucketIndex = a; break; } } if (!useBucket) { useBucket = m_currentBucket; } } else { reOrderFreeSpaceBucketIndex = m_freeSpaceBuckets.indexOf(useBucket); } //The item isn't in the repository yet, find a new bucket for it while(1) { if(useBucket >= m_buckets.size()) { if(m_buckets.size() >= 0xfffe) { //We have reserved the last bucket index 0xffff for special purposes //the repository has overflown. qWarning() << "Found no room for an item in" << m_repositoryName << "size of the item:" << request.itemSize(); return 0; }else{ //Allocate new buckets m_buckets.resize(m_buckets.size() + 10); } } MyBucket* bucketPtr = m_buckets.at(useBucket); if(!bucketPtr) { initializeBucket(useBucket); bucketPtr = m_buckets.at(useBucket); } ENSURE_REACHABLE(useBucket); Q_ASSERT_X(!bucketPtr->findIndex(request), Q_FUNC_INFO, "found item in unexpected bucket, ensure your ItemRequest::equals method is correct. Note: For custom AbstractType's e.g. ensure you have a proper equals() override"); unsigned short indexInBucket = bucketPtr->index(request, size); //If we could not allocate the item in an empty bucket, then we need to create a monster-bucket that //can hold the data. if(bucketPtr->isEmpty() && !indexInBucket) { ///@todo Move this compound statement into an own function uint totalSize = size + MyBucket::AdditionalSpacePerItem; Q_ASSERT((totalSize > ItemRepositoryBucketSize)); useBucket = 0; //The item did not fit in, we need a monster-bucket(Merge consecutive buckets) ///Step one: Search whether we can merge multiple empty buckets in the free-list into one monster-bucket int rangeStart = -1; int rangeEnd = -1; for(int a = 0; a < m_freeSpaceBuckets.size(); ++a) { MyBucket* bucketPtr = bucketForIndex(m_freeSpaceBuckets[a]); if(bucketPtr->isEmpty()) { //This bucket is a candidate for monster-bucket merging int index = (int)m_freeSpaceBuckets[a]; if(rangeEnd != index) { rangeStart = index; rangeEnd = index+1; }else{ ++rangeEnd; } if(rangeStart != rangeEnd) { uint extent = rangeEnd - rangeStart - 1; uint totalAvailableSpace = bucketForIndex(rangeStart)->available() + MyBucket::DataSize * (rangeEnd - rangeStart - 1); if(totalAvailableSpace > totalSize) { Q_ASSERT(extent); ///We can merge these buckets into one monster-bucket that can hold the data Q_ASSERT((uint)m_freeSpaceBuckets[a-extent] == (uint)rangeStart); m_freeSpaceBuckets.remove(a-extent, extent+1); useBucket = rangeStart; convertMonsterBucket(rangeStart, extent); break; } } } } if(!useBucket) { //Create a new monster-bucket at the end of the data int needMonsterExtent = (totalSize - ItemRepositoryBucketSize) / MyBucket::DataSize + 1; Q_ASSERT(needMonsterExtent); if(m_currentBucket + needMonsterExtent + 1 > m_buckets.size()) { m_buckets.resize(m_buckets.size() + 10 + needMonsterExtent + 1); } useBucket = m_currentBucket; convertMonsterBucket(useBucket, needMonsterExtent); m_currentBucket += 1 + needMonsterExtent; Q_ASSERT(m_currentBucket < ItemRepositoryBucketLimit); Q_ASSERT(m_buckets[m_currentBucket - 1 - needMonsterExtent] && m_buckets[m_currentBucket - 1 - needMonsterExtent]->monsterBucketExtent() == needMonsterExtent); } Q_ASSERT(useBucket); bucketPtr = bucketForIndex(useBucket); indexInBucket = bucketPtr->index(request, size); Q_ASSERT(indexInBucket); } if(indexInBucket) { ++m_statItemCount; const int previousBucketNumber = lastBucketWalked; unsigned short* const bucketHashPosition = m_firstBucketForHash + (hash % bucketHashSize); if(!(*bucketHashPosition)) { Q_ASSERT(!previousBucketNumber); (*bucketHashPosition) = useBucket; ENSURE_REACHABLE(useBucket); } else if(!pickedBucketInChain && previousBucketNumber && previousBucketNumber != useBucket) { //Should happen rarely ++m_statBucketHashClashes; ///Debug: Detect infinite recursion ifDebugInfiniteRecursion(Q_ASSERT(walkBucketLinks(*bucketHashPosition, hash, previousBucketNumber));) //Find the position where the paths of useBucket and *bucketHashPosition intersect, and insert useBucket //there. That way, we don't create loops. QPair intersect = hashChainIntersection(*bucketHashPosition, useBucket, hash); Q_ASSERT(m_buckets[previousBucketNumber]->nextBucketForHash(hash) == 0); if(!intersect.second) { ifDebugInfiniteRecursion(Q_ASSERT(!walkBucketLinks(*bucketHashPosition, hash, useBucket));) ifDebugInfiniteRecursion(Q_ASSERT(!walkBucketLinks(useBucket, hash, previousBucketNumber));) Q_ASSERT(m_buckets[previousBucketNumber]->nextBucketForHash(hash) == 0); m_buckets[previousBucketNumber]->setNextBucketForHash(hash, useBucket); ENSURE_REACHABLE(useBucket); ENSURE_REACHABLE(previousBucketNumber); ifDebugInfiniteRecursion(Q_ASSERT(walkBucketLinks(*bucketHashPosition, hash, useBucket));) } else if(intersect.first) { ifDebugInfiniteRecursion(Q_ASSERT(bucketForIndex(intersect.first)->nextBucketForHash(hash) == intersect.second);) ifDebugInfiniteRecursion(Q_ASSERT(!walkBucketLinks(*bucketHashPosition, hash, useBucket));) ifDebugInfiniteRecursion(Q_ASSERT(walkBucketLinks(*bucketHashPosition, hash, intersect.second));) ifDebugInfiniteRecursion(Q_ASSERT(walkBucketLinks(*bucketHashPosition, hash, intersect.first));) ifDebugInfiniteRecursion(Q_ASSERT(bucketForIndex(intersect.first)->nextBucketForHash(hash) == intersect.second);) ifDebugInfiniteRecursion(Q_ASSERT(walkBucketLinks(useBucket, hash, intersect.second));) ifDebugInfiniteRecursion(Q_ASSERT(!walkBucketLinks(useBucket, hash, intersect.first));) bucketForIndex(intersect.first)->setNextBucketForHash(hash, useBucket); ENSURE_REACHABLE(useBucket); ENSURE_REACHABLE(intersect.second); ifDebugInfiniteRecursion(Q_ASSERT(walkBucketLinks(*bucketHashPosition, hash, useBucket));) ifDebugInfiniteRecursion(Q_ASSERT(walkBucketLinks(*bucketHashPosition, hash, intersect.second));) } else { //State: intersect.first == 0 && intersect.second != 0. This means that whole compleet //chain opened by *bucketHashPosition with the hash-value is also following useBucket, //so useBucket can just be inserted at the top ifDebugInfiniteRecursion(Q_ASSERT(!walkBucketLinks(*bucketHashPosition, hash, useBucket));) ifDebugInfiniteRecursion(Q_ASSERT(walkBucketLinks(useBucket, hash, *bucketHashPosition));) unsigned short oldStart = *bucketHashPosition; *bucketHashPosition = useBucket; ENSURE_REACHABLE(useBucket); ENSURE_REACHABLE(oldStart); Q_UNUSED(oldStart); } } if(reOrderFreeSpaceBucketIndex != -1) updateFreeSpaceOrder(reOrderFreeSpaceBucketIndex); return createIndex(useBucket, indexInBucket); }else{ //This should never happen when we picked a bucket for re-use Q_ASSERT(!pickedBucketInChain); Q_ASSERT(reOrderFreeSpaceBucketIndex == -1); Q_ASSERT(useBucket == m_currentBucket); if(!bucketForIndex(useBucket)->isEmpty()) putIntoFreeList(useBucket, bucketPtr); ++m_currentBucket; Q_ASSERT(m_currentBucket < ItemRepositoryBucketLimit); useBucket = m_currentBucket; } } //We haven't found a bucket that already contains the item, so append it to the current bucket qWarning() << "Found no bucket for an item in" << m_repositoryName; return 0; } ///Returns zero if the item is not in the repository yet unsigned int findIndex(const ItemRequest& request) { ThisLocker lock(m_mutex); return walkBucketChain(request.hash(), [this, &request](ushort bucketIdx, const MyBucket* bucketPtr) { const ushort indexInBucket = bucketPtr->findIndex(request); return indexInBucket ? createIndex(bucketIdx, indexInBucket) : 0u; }); } ///Deletes the item from the repository. void deleteItem(unsigned int index) { verifyIndex(index); ThisLocker lock(m_mutex); m_metaDataChanged = true; const uint hash = itemFromIndex(index)->hash(); const ushort bucket = (index >> 16); //Apart from removing the item itself, we may have to recreate the nextBucketForHash link, so we need the previous bucket MyBucket* previousBucketPtr = nullptr; MyBucket* const bucketPtr = walkBucketChain(hash, [bucket, &previousBucketPtr](ushort bucketIdx, MyBucket* bucketPtr) -> MyBucket* { if (bucket != bucketIdx) { previousBucketPtr = bucketPtr; return nullptr; } return bucketPtr; // found bucket, stop looking }); //Make sure the index was reachable through the hash chain Q_ASSERT(bucketPtr); --m_statItemCount; bucketPtr->deleteItem(index, hash, *this); /** * Now check whether the link root/previousBucketNumber -> bucket is still needed. */ if (!previousBucketPtr) { // This bucket is linked in the m_firstBucketForHash array, find the next clashing bucket in the chain // There may be items in the chain that clash only with MyBucket::NextBucketHashSize, skipped here m_firstBucketForHash[hash % bucketHashSize] = walkBucketChain(hash, [hash](ushort bucketIdx, MyBucket *bucketPtr){ if (bucketPtr->hasClashingItem(hash, bucketHashSize)) { return bucketIdx; } return static_cast(0); }); } else if(!bucketPtr->hasClashingItem(hash, MyBucket::NextBucketHashSize)) { // TODO: Skip clashing items reachable from m_firstBucketForHash // (see note in usePermissiveModuloWhenRemovingClashLinks() test) ENSURE_REACHABLE(bucket); previousBucketPtr->setNextBucketForHash(hash, bucketPtr->nextBucketForHash(hash)); Q_ASSERT(m_buckets[bucketPtr->nextBucketForHash(hash)] != previousBucketPtr); } ENSURE_REACHABLE(bucket); if(bucketPtr->monsterBucketExtent()) { //Convert the monster-bucket back to multiple normal buckets, and put them into the free list uint newBuckets = bucketPtr->monsterBucketExtent()+1; Q_ASSERT(bucketPtr->isEmpty()); if (!previousBucketPtr) { // see https://bugs.kde.org/show_bug.cgi?id=272408 // the monster bucket will be deleted and new smaller ones created // the next bucket for this hash is invalid anyways as done above // but calling the below unconditionally leads to other issues... bucketPtr->setNextBucketForHash(hash, 0); } convertMonsterBucket(bucket, 0); for(uint created = bucket; created < bucket + newBuckets; ++created) { putIntoFreeList(created, bucketForIndex(created)); #ifdef DEBUG_MONSTERBUCKETS Q_ASSERT(m_freeSpaceBuckets.indexOf(created) != -1); #endif } }else{ putIntoFreeList(bucket, bucketPtr); } } typedef DynamicItem MyDynamicItem; ///This returns an editable version of the item. @warning: Never change an entry that affects the hash, ///or the equals(..) function. That would completely destroy the consistency. ///@param index The index. It must be valid(match an existing item), and nonzero. ///@warning If you use this, make sure you lock mutex() before calling, /// and hold it until you're ready using/changing the data.. MyDynamicItem dynamicItemFromIndex(unsigned int index) { verifyIndex(index); ThisLocker lock(m_mutex); unsigned short bucket = (index >> 16); MyBucket* bucketPtr = m_buckets.at(bucket); if(!bucketPtr) { initializeBucket(bucket); bucketPtr = m_buckets.at(bucket); } bucketPtr->prepareChange(); unsigned short indexInBucket = index & 0xffff; return MyDynamicItem(const_cast(bucketPtr->itemFromIndex(indexInBucket)), bucketPtr->data(), bucketPtr->dataSize()); } ///This returns an editable version of the item. @warning: Never change an entry that affects the hash, ///or the equals(..) function. That would completely destroy the consistency. ///@param index The index. It must be valid(match an existing item), and nonzero. ///@warning If you use this, make sure you lock mutex() before calling, /// and hold it until you're ready using/changing the data.. ///@warning If you change contained complex items that depend on reference-counting, you /// must use dynamicItemFromIndex(..) instead of dynamicItemFromIndexSimple(..) Item* dynamicItemFromIndexSimple(unsigned int index) { verifyIndex(index); ThisLocker lock(m_mutex); unsigned short bucket = (index >> 16); MyBucket* bucketPtr = m_buckets.at(bucket); if(!bucketPtr) { initializeBucket(bucket); bucketPtr = m_buckets.at(bucket); } bucketPtr->prepareChange(); unsigned short indexInBucket = index & 0xffff; return const_cast(bucketPtr->itemFromIndex(indexInBucket)); } ///@param index The index. It must be valid(match an existing item), and nonzero. const Item* itemFromIndex(unsigned int index) const { verifyIndex(index); ThisLocker lock(m_mutex); unsigned short bucket = (index >> 16); const MyBucket* bucketPtr = m_buckets.at(bucket); if(!bucketPtr) { initializeBucket(bucket); bucketPtr = m_buckets.at(bucket); } unsigned short indexInBucket = index & 0xffff; return bucketPtr->itemFromIndex(indexInBucket); } struct Statistics { Statistics() : loadedBuckets(-1), currentBucket(-1), usedMemory(-1), loadedMonsterBuckets(-1), usedSpaceForBuckets(-1), freeSpaceInBuckets(-1), lostSpace(-1), freeUnreachableSpace(-1), hashClashedItems(-1), totalItems(-1), hashSize(-1), hashUse(-1), averageInBucketHashSize(-1), averageInBucketUsedSlotCount(-1), averageInBucketSlotChainLength(-1), longestInBucketChain(-1), longestNextBucketChain(-1), totalBucketFollowerSlots(-1), averageNextBucketForHashSequenceLength(-1) { } uint loadedBuckets; uint currentBucket; uint usedMemory; uint loadedMonsterBuckets; uint usedSpaceForBuckets; uint freeSpaceInBuckets; uint lostSpace; uint freeUnreachableSpace; uint hashClashedItems; uint totalItems; uint emptyBuckets; uint hashSize; //How big the hash is uint hashUse; //How many slots in the hash are used uint averageInBucketHashSize; uint averageInBucketUsedSlotCount; float averageInBucketSlotChainLength; uint longestInBucketChain; uint longestNextBucketChain; uint totalBucketFollowerSlots; //Total count of used slots in the nextBucketForHash structure float averageNextBucketForHashSequenceLength; //Average sequence length of a nextBucketForHash sequence(If not empty) QString print() const { QString ret; ret += QStringLiteral("loaded buckets: %1 current bucket: %2 used memory: %3 loaded monster buckets: %4").arg(loadedBuckets).arg(currentBucket).arg(usedMemory).arg(loadedMonsterBuckets); ret += QStringLiteral("\nbucket hash clashed items: %1 total items: %2").arg(hashClashedItems).arg(totalItems); ret += QStringLiteral("\nused space for buckets: %1 free space in buckets: %2 lost space: %3").arg(usedSpaceForBuckets).arg(freeSpaceInBuckets).arg(lostSpace); ret += QStringLiteral("\nfree unreachable space: %1 empty buckets: %2").arg(freeUnreachableSpace).arg(emptyBuckets); ret += QStringLiteral("\nhash size: %1 hash slots used: %2").arg(hashSize).arg(hashUse); ret += QStringLiteral("\naverage in-bucket hash size: %1 average in-bucket used hash slot count: %2 average in-bucket slot chain length: %3 longest in-bucket follower chain: %4").arg(averageInBucketHashSize).arg(averageInBucketUsedSlotCount).arg(averageInBucketSlotChainLength).arg(longestInBucketChain); ret += QStringLiteral("\ntotal count of used next-bucket-for-hash slots: %1 average next-bucket-for-hash sequence length: %2 longest next-bucket chain: %3").arg(totalBucketFollowerSlots).arg(averageNextBucketForHashSequenceLength).arg(longestNextBucketChain); return ret; } operator QString() const { return print(); } }; QString printStatistics() const override { return statistics().print(); } Statistics statistics() const { Statistics ret; uint loadedBuckets = 0; for(int a = 0; a < m_buckets.size(); ++a) if(m_buckets[a]) ++loadedBuckets; #ifdef DEBUG_MONSTERBUCKETS for(int a = 0; a < m_freeSpaceBuckets.size(); ++a) { if(a > 0) { uint prev = a-1; uint prevLargestFree = bucketForIndex(m_freeSpaceBuckets[prev])->largestFreeSize(); uint largestFree = bucketForIndex(m_freeSpaceBuckets[a])->largestFreeSize(); Q_ASSERT( (prevLargestFree < largestFree) || (prevLargestFree == largestFree && m_freeSpaceBuckets[prev] < m_freeSpaceBuckets[a]) ); } } #endif ret.hashSize = bucketHashSize; ret.hashUse = 0; for(uint a = 0; a < bucketHashSize; ++a) if(m_firstBucketForHash[a]) ++ret.hashUse; ret.emptyBuckets = 0; uint loadedMonsterBuckets = 0; for(int a = 0; a < m_buckets.size(); ++a) if(m_buckets[a] && m_buckets[a]->monsterBucketExtent()) loadedMonsterBuckets += m_buckets[a]->monsterBucketExtent()+1; uint usedBucketSpace = MyBucket::DataSize * m_currentBucket; uint freeBucketSpace = 0, freeUnreachableSpace = 0; uint lostSpace = 0; //Should be zero, else something is wrong uint totalInBucketHashSize = 0, totalInBucketUsedSlotCount = 0, totalInBucketChainLengths = 0; uint bucketCount = 0; ret.totalBucketFollowerSlots = 0; ret.averageNextBucketForHashSequenceLength = 0; ret.longestNextBucketChain = 0; ret.longestInBucketChain = 0; for(int a = 1; a < m_currentBucket+1; ++a) { MyBucket* bucket = bucketForIndex(a); if(bucket) { ++bucketCount; bucket->countFollowerIndexLengths(totalInBucketUsedSlotCount, totalInBucketChainLengths, totalInBucketHashSize, ret.longestInBucketChain); for(uint aa = 0; aa < MyBucket::NextBucketHashSize; ++aa) { uint length = 0; uint next = bucket->nextBucketForHash(aa); if(next) { ++ret.totalBucketFollowerSlots; while(next) { ++length; ++ret.averageNextBucketForHashSequenceLength; next = bucketForIndex(next)->nextBucketForHash(aa); } } if(length > ret.longestNextBucketChain) { // qDebug() << "next-bucket-chain in" << a << aa << ":" << length; ret.longestNextBucketChain = length; } } uint bucketFreeSpace = bucket->totalFreeItemsSize() + bucket->available(); freeBucketSpace += bucketFreeSpace; if(m_freeSpaceBuckets.indexOf(a) == -1) freeUnreachableSpace += bucketFreeSpace; if(bucket->isEmpty()) { ++ret.emptyBuckets; Q_ASSERT(!bucket->monsterBucketExtent()); #ifdef DEBUG_MONSTERBUCKETS Q_ASSERT(m_freeSpaceBuckets.contains(a)); #endif } lostSpace += bucket->lostSpace(); a += bucket->monsterBucketExtent(); } } if(ret.totalBucketFollowerSlots) ret.averageNextBucketForHashSequenceLength /= ret.totalBucketFollowerSlots; ret.loadedBuckets = loadedBuckets; ret.currentBucket = m_currentBucket; ret.usedMemory = usedMemory(); ret.loadedMonsterBuckets = loadedMonsterBuckets; ret.hashClashedItems = m_statBucketHashClashes; ret.totalItems = m_statItemCount; ret.usedSpaceForBuckets = usedBucketSpace; ret.freeSpaceInBuckets = freeBucketSpace; ret.lostSpace = lostSpace; ret.freeUnreachableSpace = freeUnreachableSpace; ret.averageInBucketHashSize = totalInBucketHashSize / bucketCount; ret.averageInBucketUsedSlotCount = totalInBucketUsedSlotCount / bucketCount; ret.averageInBucketSlotChainLength = float(totalInBucketChainLengths) / totalInBucketUsedSlotCount; //If m_statBucketHashClashes is high, the bucket-hash needs to be bigger return ret; } uint usedMemory() const { uint used = 0; for(int a = 0; a < m_buckets.size(); ++a) { if(m_buckets[a]) { used += m_buckets[a]->usedMemory(); } } return used; } ///This can be used to safely iterate through all items in the repository ///@param visitor Should be an instance of a class that has a bool operator()(const Item*) member function, /// that returns whether more items are wanted. ///@param onlyInMemory If this is true, only items are visited that are currently in memory. template void visitAllItems(Visitor& visitor, bool onlyInMemory = false) const { ThisLocker lock(m_mutex); for(int a = 1; a <= m_currentBucket; ++a) { if(!onlyInMemory || m_buckets.at(a)) { if(bucketForIndex(a) && !bucketForIndex(a)->visitAllItems(visitor)) return; } } } ///Synchronizes the state on disk to the one in memory, and does some memory-management. ///Should be called on a regular basis. Can be called centrally from the global item repository registry. void store() override { QMutexLocker lock(m_mutex); if(m_file) { if(!m_file->open( QFile::ReadWrite ) || !m_dynamicFile->open( QFile::ReadWrite )) { qFatal("cannot re-open repository file for storing"); return; } for(int a = 0; a < m_buckets.size(); ++a) { if(m_buckets[a]) { if(m_buckets[a]->changed()) { storeBucket(a); } if(m_unloadingEnabled) { const int unloadAfterTicks = 2; if(m_buckets[a]->lastUsed() > unloadAfterTicks) { delete m_buckets[a]; m_buckets[a] = nullptr; }else{ m_buckets[a]->tick(); } } } } if(m_metaDataChanged) { Q_ASSERT(m_dynamicFile); m_file->seek(0); m_file->write((char*)&m_repositoryVersion, sizeof(uint)); uint hashSize = bucketHashSize; m_file->write((char*)&hashSize, sizeof(uint)); uint itemRepositoryVersion = staticItemRepositoryVersion(); m_file->write((char*)&itemRepositoryVersion, sizeof(uint)); m_file->write((char*)&m_statBucketHashClashes, sizeof(uint)); m_file->write((char*)&m_statItemCount, sizeof(uint)); const uint bucketCount = static_cast(m_buckets.size()); m_file->write((char*)&bucketCount, sizeof(uint)); m_file->write((char*)&m_currentBucket, sizeof(uint)); m_file->write((char*)m_firstBucketForHash, sizeof(short unsigned int) * bucketHashSize); Q_ASSERT(m_file->pos() == BucketStartOffset); m_dynamicFile->seek(0); const uint freeSpaceBucketsSize = static_cast(m_freeSpaceBuckets.size()); m_dynamicFile->write((char*)&freeSpaceBucketsSize, sizeof(uint)); m_dynamicFile->write((char*)m_freeSpaceBuckets.data(), sizeof(uint) * freeSpaceBucketsSize); } //To protect us from inconsistency due to crashes. flush() is not enough. We need to close. m_file->close(); m_dynamicFile->close(); Q_ASSERT(!m_file->isOpen()); Q_ASSERT(!m_dynamicFile->isOpen()); } } ///This mutex is used for the thread-safe locking when threadSafe is true. Even if threadSafe is false, it is ///always locked before storing to or loading from disk. ///@warning If threadSafe is false, and you sometimes call store() from within another thread(As happens in duchain), /// you must always make sure that this mutex is locked before you access this repository. /// Else you will get crashes and inconsistencies. /// In KDevelop This means: Make sure you _always_ lock this mutex before accessing the repository. QMutex* mutex() const { return m_mutex; } ///With this, you can replace the internal mutex with another one. void setMutex(QMutex* mutex) { m_mutex = mutex; } QString repositoryName() const override { return m_repositoryName; } private: uint createIndex(ushort bucketIndex, ushort indexInBucket) { //Combine the index in the bucket, and the bucket number into one index const uint index = (bucketIndex << 16) + indexInBucket; verifyIndex(index); return index; } /** * Walks through all buckets clashing with @p hash * * Will return the value returned by the lambda, returning early if truthy */ template auto walkBucketChain(unsigned int hash, const Visitor& visitor) const -> decltype(visitor(0, nullptr)) { unsigned short bucketIndex = m_firstBucketForHash[hash % bucketHashSize]; while (bucketIndex) { auto* bucketPtr = m_buckets.at(bucketIndex); if (!bucketPtr) { initializeBucket(bucketIndex); bucketPtr = m_buckets.at(bucketIndex); } if (auto visitResult = visitor(bucketIndex, bucketPtr)) { return visitResult; } bucketIndex = bucketPtr->nextBucketForHash(hash); } return {}; } ///Makes sure the order within m_freeSpaceBuckets is correct, after largestFreeSize has been changed for m_freeSpaceBuckets[index]. ///If too few space is free within the given bucket, it is removed from m_freeSpaceBuckets. void updateFreeSpaceOrder(uint index) { m_metaDataChanged = true; unsigned int* freeSpaceBuckets = m_freeSpaceBuckets.data(); Q_ASSERT(index < static_cast(m_freeSpaceBuckets.size())); MyBucket* bucketPtr = bucketForIndex(freeSpaceBuckets[index]); unsigned short largestFreeSize = bucketPtr->largestFreeSize(); if(largestFreeSize == 0 || (bucketPtr->freeItemCount() <= MyBucket::MaxFreeItemsForHide && largestFreeSize <= MyBucket::MaxFreeSizeForHide)) { //Remove the item from freeSpaceBuckets m_freeSpaceBuckets.remove(index); }else{ while(1) { int prev = index-1; int next = index+1; if(prev >= 0 && (bucketForIndex(freeSpaceBuckets[prev])->largestFreeSize() > largestFreeSize || (bucketForIndex(freeSpaceBuckets[prev])->largestFreeSize() == largestFreeSize && freeSpaceBuckets[index] < freeSpaceBuckets[prev])) ) { //This item should be behind the successor, either because it has a lower largestFreeSize, or because the index is lower uint oldPrevValue = freeSpaceBuckets[prev]; freeSpaceBuckets[prev] = freeSpaceBuckets[index]; freeSpaceBuckets[index] = oldPrevValue; index = prev; }else if(next < m_freeSpaceBuckets.size() && (bucketForIndex(freeSpaceBuckets[next])->largestFreeSize() < largestFreeSize || (bucketForIndex(freeSpaceBuckets[next])->largestFreeSize() == largestFreeSize && freeSpaceBuckets[index] > freeSpaceBuckets[next]))) { //This item should be behind the successor, either because it has a higher largestFreeSize, or because the index is higher uint oldNextValue = freeSpaceBuckets[next]; freeSpaceBuckets[next] = freeSpaceBuckets[index]; freeSpaceBuckets[index] = oldNextValue; index = next; }else { break; } } } } ///Does conversion from monster-bucket to normal bucket and from normal bucket to monster-bucket ///The bucket @param bucketNumber must already be loaded and empty. the "extent" buckets behind must also be loaded, ///and also be empty. ///The created buckets are not registered anywhere. When converting from monster-bucket to normal bucket, ///oldExtent+1 normal buckets are created, that must be registered somewhere. ///@warning During conversion, all the touched buckets are deleted and re-created ///@param extent When this is zero, the bucket is converted from monster-bucket to normal bucket. /// When it is nonzero, it is converted to a monster-bucket. MyBucket* convertMonsterBucket(int bucketNumber, int extent) { Q_ASSERT(bucketNumber); MyBucket* bucketPtr = m_buckets.at(bucketNumber); if(!bucketPtr) { initializeBucket(bucketNumber); bucketPtr = m_buckets.at(bucketNumber); } if(extent) { //Convert to monster-bucket #ifdef DEBUG_MONSTERBUCKETS for(int index = bucketNumber; index < bucketNumber + 1 + extent; ++index) { Q_ASSERT(bucketPtr->isEmpty()); Q_ASSERT(!bucketPtr->monsterBucketExtent()); Q_ASSERT(m_freeSpaceBuckets.indexOf(index) == -1); } #endif for(int index = bucketNumber; index < bucketNumber + 1 + extent; ++index) deleteBucket(index); m_buckets[bucketNumber] = new MyBucket(); m_buckets[bucketNumber]->initialize(extent); #ifdef DEBUG_MONSTERBUCKETS for(uint index = bucketNumber+1; index < bucketNumber + 1 + extent; ++index) { Q_ASSERT(!m_buckets[index]); } #endif }else{ Q_ASSERT(bucketPtr->monsterBucketExtent()); Q_ASSERT(bucketPtr->isEmpty()); const int oldExtent = bucketPtr->monsterBucketExtent(); deleteBucket(bucketNumber); //Delete the monster-bucket for(int index = bucketNumber; index < bucketNumber + 1 + oldExtent; ++index) { Q_ASSERT(!m_buckets[index]); m_buckets[index] = new MyBucket(); m_buckets[index]->initialize(0); Q_ASSERT(!m_buckets[index]->monsterBucketExtent()); } } return m_buckets[bucketNumber]; } MyBucket* bucketForIndex(short unsigned int index) const { MyBucket* bucketPtr = m_buckets.at(index); if(!bucketPtr) { initializeBucket(index); bucketPtr = m_buckets.at(index); } return bucketPtr; } bool open(const QString& path) override { QMutexLocker lock(m_mutex); close(); //qDebug() << "opening repository" << m_repositoryName << "at" << path; QDir dir(path); m_file = new QFile(dir.absoluteFilePath( m_repositoryName )); m_dynamicFile = new QFile(dir.absoluteFilePath( m_repositoryName + QLatin1String("_dynamic") )); if(!m_file->open( QFile::ReadWrite ) || !m_dynamicFile->open( QFile::ReadWrite ) ) { delete m_file; m_file = nullptr; delete m_dynamicFile; m_dynamicFile = nullptr; return false; } m_metaDataChanged = true; if(m_file->size() == 0) { m_file->resize(0); m_file->write((char*)&m_repositoryVersion, sizeof(uint)); uint hashSize = bucketHashSize; m_file->write((char*)&hashSize, sizeof(uint)); uint itemRepositoryVersion = staticItemRepositoryVersion(); m_file->write((char*)&itemRepositoryVersion, sizeof(uint)); m_statBucketHashClashes = m_statItemCount = 0; m_file->write((char*)&m_statBucketHashClashes, sizeof(uint)); m_file->write((char*)&m_statItemCount, sizeof(uint)); m_buckets.resize(10); m_buckets.fill(nullptr); uint bucketCount = m_buckets.size(); m_file->write((char*)&bucketCount, sizeof(uint)); memset(m_firstBucketForHash, 0, bucketHashSize * sizeof(short unsigned int)); m_currentBucket = 1; //Skip the first bucket, we won't use it so we have the zero indices for special purposes m_file->write((char*)&m_currentBucket, sizeof(uint)); m_file->write((char*)m_firstBucketForHash, sizeof(short unsigned int) * bucketHashSize); //We have completely initialized the file now if(m_file->pos() != BucketStartOffset) { KMessageBox::error(nullptr, i18n("Failed writing to %1, probably the disk is full", m_file->fileName())); abort(); } const uint freeSpaceBucketsSize = 0; m_dynamicFile->write((char*)&freeSpaceBucketsSize, sizeof(uint)); m_freeSpaceBuckets.clear(); }else{ m_file->close(); bool res = m_file->open( QFile::ReadOnly ); //Re-open in read-only mode, so we create a read-only m_fileMap VERIFY(res); //Check that the version is correct uint storedVersion = 0, hashSize = 0, itemRepositoryVersion = 0; m_file->read((char*)&storedVersion, sizeof(uint)); m_file->read((char*)&hashSize, sizeof(uint)); m_file->read((char*)&itemRepositoryVersion, sizeof(uint)); m_file->read((char*)&m_statBucketHashClashes, sizeof(uint)); m_file->read((char*)&m_statItemCount, sizeof(uint)); if(storedVersion != m_repositoryVersion || hashSize != bucketHashSize || itemRepositoryVersion != staticItemRepositoryVersion()) { qDebug() << "repository" << m_repositoryName << "version mismatch in" << m_file->fileName() << ", stored: version " << storedVersion << "hashsize" << hashSize << "repository-version" << itemRepositoryVersion << " current: version" << m_repositoryVersion << "hashsize" << bucketHashSize << "repository-version" << staticItemRepositoryVersion(); delete m_file; m_file = nullptr; delete m_dynamicFile; m_dynamicFile = nullptr; return false; } m_metaDataChanged = false; uint bucketCount = 0; m_file->read((char*)&bucketCount, sizeof(uint)); m_buckets.resize(bucketCount); m_file->read((char*)&m_currentBucket, sizeof(uint)); m_file->read((char*)m_firstBucketForHash, sizeof(short unsigned int) * bucketHashSize); Q_ASSERT(m_file->pos() == BucketStartOffset); uint freeSpaceBucketsSize = 0; m_dynamicFile->read((char*)&freeSpaceBucketsSize, sizeof(uint)); m_freeSpaceBuckets.resize(freeSpaceBucketsSize); m_dynamicFile->read((char*)m_freeSpaceBuckets.data(), sizeof(uint) * freeSpaceBucketsSize); } m_fileMapSize = 0; m_fileMap = nullptr; #ifdef ITEMREPOSITORY_USE_MMAP_LOADING if(m_file->size() > BucketStartOffset){ m_fileMap = m_file->map(BucketStartOffset, m_file->size() - BucketStartOffset); Q_ASSERT(m_file->isOpen()); Q_ASSERT(m_file->size() >= BucketStartOffset); if(m_fileMap){ m_fileMapSize = m_file->size() - BucketStartOffset; }else{ qWarning() << "mapping" << m_file->fileName() << "FAILED!"; } } #endif //To protect us from inconsistency due to crashes. flush() is not enough. m_file->close(); m_dynamicFile->close(); return true; } ///@warning by default, this does not store the current state to disk. void close(bool doStore = false) override { if(doStore) store(); if(m_file) m_file->close(); delete m_file; m_file = nullptr; m_fileMap = nullptr; m_fileMapSize = 0; if(m_dynamicFile) m_dynamicFile->close(); delete m_dynamicFile; m_dynamicFile = nullptr; qDeleteAll(m_buckets); m_buckets.clear(); memset(m_firstBucketForHash, 0, bucketHashSize * sizeof(short unsigned int)); } struct AllItemsReachableVisitor { - AllItemsReachableVisitor(ItemRepository* rep) : repository(rep) { + explicit AllItemsReachableVisitor(ItemRepository* rep) : repository(rep) { } bool operator()(const Item* item) { return repository->itemReachable(item); } ItemRepository* repository; }; //Returns whether the given item is reachable through its hash bool itemReachable(const Item* item) const { const uint hash = item->hash(); return walkBucketChain(hash, [=](ushort /*bucketIndex*/, const MyBucket* bucketPtr) { return bucketPtr->itemReachable(item, hash); }); } //Returns true if all items in the given bucket are reachable through their hashes bool allItemsReachable(unsigned short bucket) { if(!bucket) return true; MyBucket* bucketPtr = bucketForIndex(bucket); AllItemsReachableVisitor visitor(this); return bucketPtr->visitAllItems(visitor); } int finalCleanup() override { ThisLocker lock(m_mutex); int changed = 0; for(int a = 1; a <= m_currentBucket; ++a) { MyBucket* bucket = bucketForIndex(a); if(bucket && bucket->dirty()) { ///@todo Faster dirty check, without loading bucket changed += bucket->finalCleanup(*this); } a += bucket->monsterBucketExtent(); //Skip buckets that are attached as tail to monster-buckets } return changed; } inline void initializeBucket(int bucketNumber) const { Q_ASSERT(bucketNumber); #ifdef DEBUG_MONSTERBUCKETS for(uint offset = 1; offset < 5; ++offset) { int test = bucketNumber - offset; if(test >= 0 && m_buckets[test]) { Q_ASSERT(m_buckets[test]->monsterBucketExtent() < offset); } } #endif if(!m_buckets[bucketNumber]) { m_buckets[bucketNumber] = new MyBucket(); bool doMMapLoading = (bool)m_fileMap; uint offset = ((bucketNumber-1) * MyBucket::DataSize); if(m_file && offset < m_fileMapSize && doMMapLoading && *reinterpret_cast(m_fileMap + offset) == 0) { // qDebug() << "loading bucket mmap:" << bucketNumber; m_buckets[bucketNumber]->initializeFromMap(reinterpret_cast(m_fileMap + offset)); } else if(m_file) { //Either memory-mapping is disabled, or the item is not in the existing memory-map, //so we have to load it the classical way. bool res = m_file->open( QFile::ReadOnly ); if(offset + BucketStartOffset < m_file->size()) { VERIFY(res); offset += BucketStartOffset; m_file->seek(offset); uint monsterBucketExtent; m_file->read((char*)(&monsterBucketExtent), sizeof(unsigned int));; m_file->seek(offset); ///FIXME: use the data here instead of copying it again in prepareChange QByteArray data = m_file->read((1+monsterBucketExtent) * MyBucket::DataSize); m_buckets[bucketNumber]->initializeFromMap(data.data()); m_buckets[bucketNumber]->prepareChange(); }else{ m_buckets[bucketNumber]->initialize(0); } m_file->close(); }else{ m_buckets[bucketNumber]->initialize(0); } }else{ m_buckets[bucketNumber]->initialize(0); } } ///Can only be called on empty buckets void deleteBucket(int bucketNumber) { Q_ASSERT(bucketForIndex(bucketNumber)->isEmpty()); Q_ASSERT(bucketForIndex(bucketNumber)->noNextBuckets()); delete m_buckets[bucketNumber]; m_buckets[bucketNumber] = nullptr; } //m_file must be opened void storeBucket(int bucketNumber) const { if(m_file && m_buckets[bucketNumber]) { m_buckets[bucketNumber]->store(m_file, BucketStartOffset + (bucketNumber-1) * MyBucket::DataSize); } } ///Returns whether @param mustFindBucket was found ///If mustFindBucket is zero, the whole chain is just walked. This is good for debugging for infinite recursion. bool walkBucketLinks(uint checkBucket, uint hash, uint mustFindBucket = 0) const { bool found = false; while(checkBucket) { if(checkBucket == mustFindBucket) found = true; checkBucket = bucketForIndex(checkBucket)->nextBucketForHash(hash); } return found || (mustFindBucket == 0); } ///Computes the bucket where the chains opened by the buckets @param mainHead and @param intersectorHead ///with hash @param hash meet each other. ///@return QPair hashChainIntersection(uint mainHead, uint intersectorHead, uint hash) const { uint previous = 0; uint current = mainHead; while(current) { ///@todo Make this more efficient if(walkBucketLinks(intersectorHead, hash, current)) return qMakePair(previous, current); previous = current; current = bucketForIndex(current)->nextBucketForHash(hash); } return qMakePair(0u, 0u); } void putIntoFreeList(unsigned short bucket, MyBucket* bucketPtr) { Q_ASSERT(!bucketPtr->monsterBucketExtent()); int indexInFree = m_freeSpaceBuckets.indexOf(bucket); if(indexInFree == -1 && (bucketPtr->freeItemCount() >= MyBucket::MinFreeItemsForReuse || bucketPtr->largestFreeSize() >= MyBucket::MinFreeSizeForReuse)) { //Add the bucket to the list of buckets from where to re-assign free space //We only do it when a specific threshold of empty items is reached, because that way items can stay "somewhat" semantically ordered. Q_ASSERT(bucketPtr->largestFreeSize()); int insertPos; for(insertPos = 0; insertPos < m_freeSpaceBuckets.size(); ++insertPos) { if(bucketForIndex(m_freeSpaceBuckets[insertPos])->largestFreeSize() > bucketPtr->largestFreeSize()) break; } m_freeSpaceBuckets.insert(insertPos, bucket); updateFreeSpaceOrder(insertPos); }else if(indexInFree != -1) { ///Re-order so the order in m_freeSpaceBuckets is correct(sorted by largest free item size) updateFreeSpaceOrder(indexInFree); } #ifdef DEBUG_MONSTERBUCKETS if(bucketPtr->isEmpty()) { Q_ASSERT(m_freeSpaceBuckets.contains(bucket)); } #endif } void verifyIndex(uint index) const { // We don't use zero indices Q_ASSERT(index); int bucket = (index >> 16); // nor zero buckets Q_ASSERT(bucket); Q_ASSERT_X(bucket < m_buckets.size(), Q_FUNC_INFO, qPrintable(QStringLiteral("index %1 gives invalid bucket number %2, current count is: %3") .arg(index) .arg(bucket) .arg(m_buckets.size()))); // don't trigger compile warnings in release mode Q_UNUSED(bucket); Q_UNUSED(index); } bool m_metaDataChanged; mutable QMutex m_ownMutex; mutable QMutex* m_mutex; QString m_repositoryName; mutable int m_currentBucket; //List of buckets that have free space available that can be assigned. Sorted by size: Smallest space first. Second order sorting: Bucket index QVector m_freeSpaceBuckets; mutable QVector m_buckets; uint m_statBucketHashClashes, m_statItemCount; //Maps hash-values modulo 1< This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "itemrepositoryregistry.h" #include #include #include #include #include #include #include #include #include "abstractitemrepository.h" #include "debug.h" using namespace KDevelop; namespace { //If KDevelop crashed this many times consicutively, clean up the repository const int crashesBeforeCleanup = 1; void setCrashCounter(QFile& crashesFile, int count) { crashesFile.close(); crashesFile.open(QIODevice::WriteOnly | QIODevice::Truncate); QDataStream writeStream(&crashesFile); writeStream << count; } QString repositoryPathForSession(const KDevelop::ISessionLock::Ptr& session) { QString cacheDir = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation); cacheDir += QStringLiteral("/kdevduchain"); QString baseDir = QProcessEnvironment::systemEnvironment().value(QStringLiteral("KDEV_DUCHAIN_DIR"), cacheDir); baseDir += QStringLiteral("/%1-%2").arg(qApp->applicationName(), session->id()); return baseDir; } bool shouldClear(const QString& path) { QDir dir(path); if (!dir.exists()) { return false; } if (getenv("CLEAR_DUCHAIN_DIR")) { qCDebug(SERIALIZATION) << "clearing duchain directory because CLEAR_DUCHAIN_DIR is set"; return true; } if (dir.exists(QStringLiteral("is_writing"))) { qCWarning(SERIALIZATION) << "repository" << path << "was write-locked, it probably is inconsistent"; return true; } if (!dir.exists(QStringLiteral("version_%1").arg(staticItemRepositoryVersion()))) { qCWarning(SERIALIZATION) << "version-hint not found, seems to be an old version"; return true; } QFile crashesFile(dir.filePath(QStringLiteral("crash_counter"))); if (crashesFile.open(QIODevice::ReadOnly)) { int count; QDataStream stream(&crashesFile); stream >> count; qCDebug(SERIALIZATION) << "current count of crashes: " << count; if (count >= crashesBeforeCleanup && !getenv("DONT_CLEAR_DUCHAIN_DIR")) { bool userAnswer = askUser(i18np("The previous session crashed", "Session crashed %1 times in a row", count), i18nc("@action", "Clear cache"), i18nc("@title", "Session crashed"), i18n("The crash may be caused by a corruption of cached data.\n\n" "Press OK if you want KDevelop to clear the cache, otherwise press Cancel if you are sure the crash has another origin.")); if (userAnswer) { qCDebug(SERIALIZATION) << "User chose to clean repository"; return true; } else { setCrashCounter(crashesFile, 1); qCDebug(SERIALIZATION) << "User chose to reset crash counter"; } } else { ///Increase the crash-count. It will be reset if kdevelop is shut down cleanly. setCrashCounter(crashesFile, ++count); } } else { setCrashCounter(crashesFile, 1); } return false; } } namespace KDevelop { struct ItemRepositoryRegistryPrivate { ItemRepositoryRegistry* m_owner; bool m_shallDelete; QString m_path; ISessionLock::Ptr m_sessionLock; QMap m_repositories; QMap m_customCounters; mutable QMutex m_mutex; - ItemRepositoryRegistryPrivate(ItemRepositoryRegistry* owner) + explicit ItemRepositoryRegistryPrivate(ItemRepositoryRegistry* owner) : m_owner(owner) , m_shallDelete(false) , m_mutex(QMutex::Recursive) { } void lockForWriting(); void unlockForWriting(); void deleteDataDirectory(const QString& path, bool recreate = true); /// @param path A shared directory-path that the item-repositories are to be loaded from. /// @returns Whether the repository registry has been opened successfully. /// If @c false, then all registered repositories should have been deleted. /// @note Currently the given path must reference a hidden directory, just to make sure we're /// not accidentally deleting something important. bool open(const QString& path); /// Close all contained repositories. /// @warning The current state is not stored to disk. void close(); }; //The global item-reposity registry ItemRepositoryRegistry* ItemRepositoryRegistry::m_self = nullptr; ItemRepositoryRegistry::ItemRepositoryRegistry(const ISessionLock::Ptr& session) : d(new ItemRepositoryRegistryPrivate(this)) { Q_ASSERT(session); d->open(repositoryPathForSession(session)); } void ItemRepositoryRegistry::initialize(const ISessionLock::Ptr& session) { if (!m_self) { ///We intentionally leak the registry, to prevent problems in the destruction order, where ///the actual repositories might get deleted later than the repository registry. m_self = new ItemRepositoryRegistry(session); } } ItemRepositoryRegistry* ItemRepositoryRegistry::self() { Q_ASSERT(m_self); return m_self; } void ItemRepositoryRegistry::deleteRepositoryFromDisk(const ISessionLock::Ptr& session) { // Now, as we have only the global item-repository registry, assume that if and only if // the given session is ours, its cache path is used by the said global item-repository registry. const QString repositoryPath = repositoryPathForSession(session); if(m_self && m_self->d->m_path == repositoryPath) { // remove later m_self->d->m_shallDelete = true; } else { // Otherwise, given session is not ours. // remove its item-repository directory directly. QDir(repositoryPath).removeRecursively(); } } QMutex& ItemRepositoryRegistry::mutex() { return d->m_mutex; } QAtomicInt& ItemRepositoryRegistry::getCustomCounter(const QString& identity, int initialValue) { if(!d->m_customCounters.contains(identity)) d->m_customCounters.insert(identity, new QAtomicInt(initialValue)); return *d->m_customCounters[identity]; } ///The global item-repository registry that is used by default ItemRepositoryRegistry& globalItemRepositoryRegistry() { return *ItemRepositoryRegistry::self(); } void ItemRepositoryRegistry::registerRepository(AbstractItemRepository* repository, AbstractRepositoryManager* manager) { QMutexLocker lock(&d->m_mutex); d->m_repositories.insert(repository, manager); if(!d->m_path.isEmpty()) { if(!repository->open(d->m_path)) { d->deleteDataDirectory(d->m_path); qCritical() << "failed to open a repository"; abort(); } } } QString ItemRepositoryRegistry::path() const { //We cannot lock the mutex here, since this may be called with one of the repositories locked, //and that may lead to a deadlock when at the same time a storing is requested return d->m_path; } void ItemRepositoryRegistryPrivate::lockForWriting() { QMutexLocker lock(&m_mutex); //Create is_writing QFile f(m_path + "/is_writing"); f.open(QIODevice::WriteOnly); f.close(); } void ItemRepositoryRegistry::lockForWriting() { d->lockForWriting(); } void ItemRepositoryRegistryPrivate::unlockForWriting() { QMutexLocker lock(&m_mutex); //Delete is_writing QFile::remove(m_path + "/is_writing"); } void ItemRepositoryRegistry::unlockForWriting() { d->unlockForWriting(); } void ItemRepositoryRegistry::unRegisterRepository(AbstractItemRepository* repository) { QMutexLocker lock(&d->m_mutex); Q_ASSERT(d->m_repositories.contains(repository)); repository->close(); d->m_repositories.remove(repository); } //After calling this, the data-directory may be a new one void ItemRepositoryRegistryPrivate::deleteDataDirectory(const QString& path, bool recreate) { QMutexLocker lock(&m_mutex); //lockForWriting creates a file, that prevents any other KDevelop instance from using the directory as it is. //Instead, the other instance will try to delete the directory as well. lockForWriting(); bool result = QDir(path).removeRecursively(); Q_ASSERT(result); Q_UNUSED(result); // Just recreate the directory then; leave old path (as it is dependent on appname and session only). if(recreate) { QDir().mkpath(path); } } bool ItemRepositoryRegistryPrivate::open(const QString& path) { QMutexLocker mlock(&m_mutex); if(m_path == path) { return true; } // Check if the repository shall be cleared if (shouldClear(path)) { qCWarning(SERIALIZATION) << QStringLiteral("The data-repository at %1 has to be cleared.").arg(path); deleteDataDirectory(path); } QDir().mkpath(path); foreach(AbstractItemRepository* repository, m_repositories.keys()) { if(!repository->open(path)) { deleteDataDirectory(path); qCritical() << "failed to open a repository"; abort(); } } QFile f(path + "/Counters"); if(f.open(QIODevice::ReadOnly)) { QDataStream stream(&f); while(!stream.atEnd()) { //Read in all custom counter values QString counterName; stream >> counterName; int counterValue; stream >> counterValue; m_owner->getCustomCounter(counterName, 0) = counterValue; } } m_path = path; return true; } void ItemRepositoryRegistry::store() { QMutexLocker lock(&d->m_mutex); foreach(AbstractItemRepository* repository, d->m_repositories.keys()) { repository->store(); } QFile versionFile(d->m_path + QStringLiteral("/version_%1").arg(staticItemRepositoryVersion())); if(versionFile.open(QIODevice::WriteOnly)) { versionFile.close(); } else { qCWarning(SERIALIZATION) << "Could not open version file for writing"; } //Store all custom counter values QFile f(d->m_path + "/Counters"); if(f.open(QIODevice::WriteOnly)) { f.resize(0); QDataStream stream(&f); for(QMap::const_iterator it = d->m_customCounters.constBegin(); it != d->m_customCounters.constEnd(); ++it) { stream << it.key(); stream << it.value()->fetchAndAddRelaxed(0); } } else { qCWarning(SERIALIZATION) << "Could not open counter file for writing"; } } void ItemRepositoryRegistry::printAllStatistics() const { QMutexLocker lock(&d->m_mutex); foreach(AbstractItemRepository* repository, d->m_repositories.keys()) { qCDebug(SERIALIZATION) << "statistics in" << repository->repositoryName() << ":"; qCDebug(SERIALIZATION) << repository->printStatistics(); } } int ItemRepositoryRegistry::finalCleanup() { QMutexLocker lock(&d->m_mutex); int changed = false; foreach(AbstractItemRepository* repository, d->m_repositories.keys()) { int added = repository->finalCleanup(); changed += added; qCDebug(SERIALIZATION) << "cleaned in" << repository->repositoryName() << ":" << added; } return changed; } void ItemRepositoryRegistryPrivate::close() { QMutexLocker lock(&m_mutex); foreach(AbstractItemRepository* repository, m_repositories.keys()) { repository->close(); } m_path.clear(); } ItemRepositoryRegistry::~ItemRepositoryRegistry() { QMutexLocker lock(&d->m_mutex); d->close(); foreach(QAtomicInt * counter, d->m_customCounters) { delete counter; } delete d; } void ItemRepositoryRegistry::shutdown() { QMutexLocker lock(&d->m_mutex); QString path = d->m_path; // FIXME: we don't close since this can trigger crashes at shutdown // since some items are still referenced, e.g. in static variables // d->close(); if(d->m_shallDelete) { d->deleteDataDirectory(path, false); } else { QFile::remove(path + QLatin1String("/crash_counter")); } } } diff --git a/serialization/repositorymanager.h b/serialization/repositorymanager.h index 422170c6f..f0e7d30fa 100644 --- a/serialization/repositorymanager.h +++ b/serialization/repositorymanager.h @@ -1,90 +1,90 @@ /* 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 REPOSITORYMANAGER_H #define REPOSITORYMANAGER_H #include "abstractitemrepository.h" #include "itemrepositoryregistry.h" namespace KDevelop { /// This class helps managing the lifetime of a global item repository, and protecting the consistency. /// Especially it helps doing thread-safe lazy repository-creation. template struct RepositoryManager : public AbstractRepositoryManager { public: ///@param shareMutex Option repository from where this repository should take the thread-safety mutex - RepositoryManager(QString name, + explicit RepositoryManager(QString name, int version = 1, AbstractRepositoryManager*(*shareMutex)() = nullptr, ItemRepositoryRegistry& registry = globalItemRepositoryRegistry()) : m_name(name), m_version(version), m_registry(registry), m_shareMutex(shareMutex) { if(!lazy) { createRepository(); } } ~RepositoryManager() { } Q_DISABLE_COPY(RepositoryManager) ItemRepositoryType* repository() const { if(!m_repository) { createRepository(); } return static_cast(m_repository); } inline ItemRepositoryType* operator->() const { return repository(); } QMutex* repositoryMutex() const override { return (*this)->mutex(); } private: void createRepository() const { if(!m_repository) { QMutexLocker lock(&m_registry.mutex()); if(!m_repository) { m_repository = new ItemRepositoryType(m_name, &m_registry, m_version, const_cast(this)); if(m_shareMutex) { (*this)->setMutex(m_shareMutex()->repositoryMutex()); } (*this)->setUnloadingEnabled(unloadingEnabled); } } } QString m_name; int m_version; ItemRepositoryRegistry& m_registry; AbstractRepositoryManager* (*m_shareMutex)(); }; } #endif // REPOSITORYMANAGER_H diff --git a/shell/colorschemechooser.h b/shell/colorschemechooser.h index 639a1a2c5..72c3bb3b7 100644 --- a/shell/colorschemechooser.h +++ b/shell/colorschemechooser.h @@ -1,60 +1,60 @@ /************************************************************************************* * This file is part of KDevPlatform * * Copyright 2016 Zhigalin Alexander * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Lesser General Public * * License as published by the Free Software Foundation; either * * version 2.1 of the License, or (at your option) version 3, or any * * later version accepted by the membership of KDE e.V. (or its * * successor approved by the membership of KDE e.V.), which shall * * act as a proxy defined in Section 6 of version 3 of the license. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * Lesser General Public License for more details. * * * * You should have received a copy of the GNU Lesser General Public * * License along with this library. If not, see . * *************************************************************************************/ #ifndef COLORSCHEMECHOOSER_H #define COLORSCHEMECHOOSER_H #include #include #include #include #include #include "mainwindow.h" class KActionCollection; namespace KDevelop { /** * Provides a menu that will offer to change the color scheme * * Furthermore, it will save the selection in the user configuration. */ class ColorSchemeChooser : public QAction { public: - ColorSchemeChooser(QObject* parent); + explicit ColorSchemeChooser(QObject* parent); QString currentSchemeName() const; private Q_SLOTS: void slotSchemeChanged(QAction* triggeredAction); private: QString loadCurrentScheme() const; void saveCurrentScheme(const QString &name); QString currentDesktopDefaultScheme() const; }; } // namespace KDevelop #endif // COLORSCHEMECHOOSER_H diff --git a/shell/core.h b/shell/core.h index 10a04d4b7..de1f653e8 100644 --- a/shell/core.h +++ b/shell/core.h @@ -1,136 +1,136 @@ /*************************************************************************** * Copyright 2007 Alexander Dymo * * Copyright 2007 Kris Wong * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef KDEVPLATFORM_CORE_H #define KDEVPLATFORM_CORE_H #include "shellexport.h" #include class KAboutData; namespace KDevelop { class UiController; class PluginController; class ProjectController; class LanguageController; class PartController; class DocumentController; class RunController; class SessionController; class CorePrivate; class SourceFormatterController; class ProgressManager; class SelectionController; class DocumentationController; class DebugController; class WorkingSetController; class TestController; class KDEVPLATFORMSHELL_EXPORT Core: public ICore { Q_OBJECT public: enum Setup { Default=0, NoUi=1 }; static QString version(); /** Initialize the core of the kdevplatform application * returns false if the initialization fails, which may happen * if the same session is already active in another instance * * @param splash the splashscreen instance that shows the startup progress (may be 0) * @param mode the mode in which to run * @param session the name or uuid of the session to be loaded * */ static bool initialize(QObject* splash = nullptr, Setup mode=Default, const QString& session = {} ); /** * \brief Provide access an instance of Core */ static Core *self(); virtual ~Core(); IUiController *uiController() override; IPluginController *pluginController() override; IProjectController *projectController() override; ILanguageController *languageController() override; IPartController *partController() override; IDocumentController *documentController() override; IRunController *runController() override; ISourceFormatterController* sourceFormatterController() override; ISelectionController* selectionController() override; IDocumentationController* documentationController() override; IDebugController* debugController() override; ITestController* testController() override; ISession *activeSession() override; ISessionLock::Ptr activeSessionLock() override; KAboutData aboutData() const override; /// The following methods may only be used within the shell. UiController *uiControllerInternal(); PluginController *pluginControllerInternal(); ProjectController *projectControllerInternal(); LanguageController *languageControllerInternal(); PartController *partControllerInternal(); DocumentController *documentControllerInternal(); RunController *runControllerInternal(); DocumentationController *documentationControllerInternal(); DebugController *debugControllerInternal(); WorkingSetController* workingSetControllerInternal(); SourceFormatterController* sourceFormatterControllerInternal(); TestController* testControllerInternal(); /// @internal SessionController *sessionController(); /// @internal ProgressManager *progressController(); void cleanup(); bool shuttingDown() const override; Core::Setup setupFlags() const; public slots: void shutdown(); signals: void startupProgress(int percent); protected: friend class CorePrivate; - Core( KDevelop::CorePrivate* dd, QObject* parent = nullptr ); + explicit Core( KDevelop::CorePrivate* dd, QObject* parent = nullptr ); KDevelop::CorePrivate *d; static Core *m_self; private: - Core(QObject *parent = nullptr); + explicit Core(QObject *parent = nullptr); }; } #endif diff --git a/shell/documentcontroller.cpp b/shell/documentcontroller.cpp index 203ae2a45..9c08fb017 100644 --- a/shell/documentcontroller.cpp +++ b/shell/documentcontroller.cpp @@ -1,1243 +1,1243 @@ /* This file is part of the KDE project Copyright 2002 Matthias Hoelzer-Kluepfel Copyright 2002 Bernd Gehrmann Copyright 2003 Roberto Raggi Copyright 2003-2008 Hamish Rodda Copyright 2003 Harald Fernengel Copyright 2003 Jens Dagerbo Copyright 2005 Adam Treat Copyright 2004-2007 Alexander Dymo 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 "documentcontroller.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 "core.h" #include "mainwindow.h" #include "textdocument.h" #include "uicontroller.h" #include "partcontroller.h" #include "savedialog.h" #include "debug.h" #include #include #include #include #define EMPTY_DOCUMENT_URL i18n("Untitled") namespace KDevelop { struct DocumentControllerPrivate { struct OpenFileResult { QList urls; QString encoding; }; - DocumentControllerPrivate(DocumentController* c) + explicit DocumentControllerPrivate(DocumentController* c) : controller(c) , fileOpenRecent(nullptr) , globalTextEditorInstance(nullptr) { } ~DocumentControllerPrivate() { //delete temporary files so they are removed from disk foreach (QTemporaryFile *temp, tempFiles) delete temp; } // used to map urls to open docs QHash< QUrl, IDocument* > documents; QHash< QString, IDocumentFactory* > factories; QList tempFiles; struct HistoryEntry { HistoryEntry() {} HistoryEntry( const QUrl & u, const KTextEditor::Cursor& cursor ); QUrl url; KTextEditor::Cursor cursor; int id; }; void removeDocument(Sublime::Document *doc) { QList urlsForDoc = documents.keys(dynamic_cast(doc)); foreach (const QUrl &url, urlsForDoc) { qCDebug(SHELL) << "destroying document" << doc; documents.remove(url); } } OpenFileResult showOpenFile() const { QUrl dir; if ( controller->activeDocument() ) { dir = controller->activeDocument()->url().adjusted(QUrl::RemoveFilename); } else { const auto cfg = KSharedConfig::openConfig()->group("Open File"); dir = cfg.readEntry( "Last Open File Directory", Core::self()->projectController()->projectsBaseDirectory() ); } const auto caption = i18n("Open File"); const auto filter = i18n("*|Text File\n"); auto parent = Core::self()->uiControllerInternal()->defaultMainWindow(); // use special dialogs in a KDE session, native dialogs elsewhere if (qEnvironmentVariableIsSet("KDE_FULL_SESSION")) { const auto result = KEncodingFileDialog::getOpenUrlsAndEncoding(QString(), dir, filter, parent, caption); return {result.URLs, result.encoding}; } // note: can't just filter on text files using the native dialog, just display all files // see https://phabricator.kde.org/D622#11679 const auto urls = QFileDialog::getOpenFileUrls(parent, caption, dir); return {urls, QString()}; } void chooseDocument() { const auto res = showOpenFile(); if( !res.urls.isEmpty() ) { QString encoding = res.encoding; foreach( const QUrl& u, res.urls ) { openDocumentInternal(u, QString(), KTextEditor::Range::invalid(), encoding ); } } } void changeDocumentUrl(KDevelop::IDocument* document) { QMutableHashIterator it = documents; while (it.hasNext()) { if (it.next().value() == document) { if (documents.contains(document->url())) { // Weird situation (saving as a file that is aready open) IDocument* origDoc = documents[document->url()]; if (origDoc->state() & IDocument::Modified) { // given that the file has been saved, close the saved file as the other instance will become conflicted on disk document->close(); controller->activateDocument( origDoc ); break; } // Otherwise close the original document origDoc->close(); } else { // Remove the original document it.remove(); } documents.insert(document->url(), document); if (!controller->isEmptyDocumentUrl(document->url())) { fileOpenRecent->addUrl(document->url()); } break; } } } KDevelop::IDocument* findBuddyDocument(const QUrl &url, IBuddyDocumentFinder* finder) { QList allDocs = controller->openDocuments(); foreach( KDevelop::IDocument* doc, allDocs ) { if(finder->areBuddies(url, doc->url())) { return doc; } } return nullptr; } static bool fileExists(const QUrl& url) { if (url.isLocalFile()) { return QFile::exists(url.toLocalFile()); } else { auto job = KIO::stat(url, KIO::StatJob::SourceSide, 0); KJobWidgets::setWindow(job, ICore::self()->uiController()->activeMainWindow()); return job->exec(); } }; IDocument* openDocumentInternal( const QUrl & inputUrl, const QString& prefName = QString(), const KTextEditor::Range& range = KTextEditor::Range::invalid(), const QString& encoding = QString(), DocumentController::DocumentActivationParams activationParams = nullptr, IDocument* buddy = nullptr) { Q_ASSERT(!inputUrl.isRelative()); Q_ASSERT(!inputUrl.fileName().isEmpty()); QString _encoding = encoding; QUrl url = inputUrl; if ( url.isEmpty() && (!activationParams.testFlag(IDocumentController::DoNotCreateView)) ) { const auto res = showOpenFile(); if( !res.urls.isEmpty() ) url = res.urls.first(); _encoding = res.encoding; if ( url.isEmpty() ) //still no url return nullptr; } KSharedConfig::openConfig()->group("Open File").writeEntry( "Last Open File Directory", url.adjusted(QUrl::RemoveFilename) ); // clean it and resolve possible symlink url = url.adjusted( QUrl::NormalizePathSegments ); if ( url.isLocalFile() ) { QString path = QFileInfo( url.toLocalFile() ).canonicalFilePath(); if ( !path.isEmpty() ) url = QUrl::fromLocalFile( path ); } //get a part document IDocument* doc = documents.value(url); if (!doc) { QMimeType mimeType; if (DocumentController::isEmptyDocumentUrl(url)) { mimeType = QMimeDatabase().mimeTypeForName(QStringLiteral("text/plain")); } else if (!url.isValid()) { // Exit if the url is invalid (should not happen) // If the url is valid and the file does not already exist, // kate creates the file and gives a message saying so qCDebug(SHELL) << "invalid URL:" << url.url(); return nullptr; } else if (KProtocolInfo::isKnownProtocol(url.scheme()) && !fileExists(url)) { //Don't create a new file if we are not in the code mode. if (ICore::self()->uiController()->activeArea()->objectName() != QLatin1String("code")) { return nullptr; } // enfore text mime type in order to create a kate part editor which then can be used to create the file // otherwise we could end up opening e.g. okteta which then crashes, see: https://bugs.kde.org/id=326434 mimeType = QMimeDatabase().mimeTypeForName(QStringLiteral("text/plain")); } else { mimeType = QMimeDatabase().mimeTypeForUrl(url); if(!url.isLocalFile() && mimeType.isDefault()) { // fall back to text/plain, for remote files without extension, i.e. COPYING, LICENSE, ... // using a synchronous KIO::MimetypeJob is hazardous and may lead to repeated calls to // this function without it having returned in the first place // and this function is *not* reentrant, see assert below: // Q_ASSERT(!documents.contains(url) || documents[url]==doc); mimeType = QMimeDatabase().mimeTypeForName(QStringLiteral("text/plain")); } } // is the URL pointing to a directory? if (mimeType.inherits(QStringLiteral("inode/directory"))) { qCDebug(SHELL) << "cannot open directory:" << url.url(); return nullptr; } if( prefName.isEmpty() ) { // Try to find a plugin that handles this mimetype QVariantMap constraints; constraints.insert(QStringLiteral("X-KDevelop-SupportedMimeTypes"), mimeType.name()); Core::self()->pluginController()->pluginForExtension(QString(), QString(), constraints); } if( IDocumentFactory* factory = factories.value(mimeType.name())) { doc = factory->create(url, Core::self()); } if(!doc) { if( !prefName.isEmpty() ) { doc = new PartDocument(url, Core::self(), prefName); } else if ( Core::self()->partControllerInternal()->isTextType(mimeType)) { doc = new TextDocument(url, Core::self(), _encoding); } else if( Core::self()->partControllerInternal()->canCreatePart(url) ) { doc = new PartDocument(url, Core::self()); } else { int openAsText = KMessageBox::questionYesNo(nullptr, i18n("KDevelop could not find the editor for file '%1' of type %2.\nDo you want to open it as plain text?", url.fileName(), mimeType.name()), i18nc("@title:window", "Could Not Find Editor"), KStandardGuiItem::yes(), KStandardGuiItem::no(), QStringLiteral("AskOpenWithTextEditor")); if (openAsText == KMessageBox::Yes) doc = new TextDocument(url, Core::self(), _encoding); else return nullptr; } } } // The url in the document must equal the current url, else the housekeeping will get broken Q_ASSERT(!doc || doc->url() == url); if(doc && openDocumentInternal(doc, range, activationParams, buddy)) return doc; else return nullptr; } bool openDocumentInternal(IDocument* doc, const KTextEditor::Range& range, DocumentController::DocumentActivationParams activationParams, IDocument* buddy = nullptr) { IDocument* previousActiveDocument = controller->activeDocument(); KTextEditor::View* previousActiveTextView = ICore::self()->documentController()->activeTextDocumentView(); KTextEditor::Cursor previousActivePosition; if(previousActiveTextView) previousActivePosition = previousActiveTextView->cursorPosition(); QUrl url=doc->url(); UiController *uiController = Core::self()->uiControllerInternal(); Sublime::Area *area = uiController->activeArea(); //We can't have the same url in many documents //so we check it's already the same if it exists //contains=>it's the same Q_ASSERT(!documents.contains(url) || documents[url]==doc); Sublime::Document *sdoc = dynamic_cast(doc); if( !sdoc ) { documents.remove(url); delete doc; return false; } //react on document deletion - we need to cleanup controller structures QObject::connect(sdoc, &Sublime::Document::aboutToDelete, controller, &DocumentController::notifyDocumentClosed); //We check if it was already opened before bool emitOpened = !documents.contains(url); if(emitOpened) documents[url]=doc; if (!activationParams.testFlag(IDocumentController::DoNotCreateView)) { //find a view if there's one already opened in this area Sublime::View *partView = nullptr; Sublime::AreaIndex* activeViewIdx = area->indexOf(uiController->activeSublimeWindow()->activeView()); foreach (Sublime::View *view, sdoc->views()) { Sublime::AreaIndex* areaIdx = area->indexOf(view); if (areaIdx && areaIdx == activeViewIdx) { partView = view; break; } } bool addView = false; if (!partView) { //no view currently shown for this url partView = sdoc->createView(); addView = true; } if(addView) { // This code is never executed when restoring session on startup, // only when opening a file manually Sublime::View* buddyView = nullptr; bool placeAfterBuddy = true; if(Core::self()->uiControllerInternal()->arrangeBuddies() && !buddy && doc->mimeType().isValid()) { // If buddy is not set, look for a (usually) plugin which handles this URL's mimetype // and use its IBuddyDocumentFinder, if available, to find a buddy document QString mime = doc->mimeType().name(); IBuddyDocumentFinder* buddyFinder = IBuddyDocumentFinder::finderForMimeType(mime); if(buddyFinder) { buddy = findBuddyDocument(url, buddyFinder); if(buddy) { placeAfterBuddy = buddyFinder->buddyOrder(buddy->url(), doc->url()); } } } if(buddy) { Sublime::Document* sublimeDocBuddy = dynamic_cast(buddy); if(sublimeDocBuddy) { Sublime::AreaIndex *pActiveViewIndex = area->indexOf(uiController->activeSublimeWindow()->activeView()); if(pActiveViewIndex) { // try to find existing View of buddy document in current active view's tab foreach (Sublime::View *pView, pActiveViewIndex->views()) { if(sublimeDocBuddy->views().contains(pView)) { buddyView = pView; break; } } } } } // add view to the area if(buddyView && area->indexOf(buddyView)) { if(placeAfterBuddy) { // Adding new view after buddy view, simple case area->addView(partView, area->indexOf(buddyView), buddyView); } else { // First new view, then buddy view area->addView(partView, area->indexOf(buddyView), buddyView); // move buddyView tab after the new document area->removeView(buddyView); area->addView(buddyView, area->indexOf(partView), partView); } } else { // no buddy found for new document / plugin does not support buddies / buddy feature disabled Sublime::View *activeView = uiController->activeSublimeWindow()->activeView(); Sublime::UrlDocument *activeDoc = nullptr; IBuddyDocumentFinder *buddyFinder = nullptr; if(activeView) activeDoc = dynamic_cast(activeView->document()); if(activeDoc && Core::self()->uiControllerInternal()->arrangeBuddies()) { QString mime = QMimeDatabase().mimeTypeForUrl(activeDoc->url()).name(); buddyFinder = IBuddyDocumentFinder::finderForMimeType(mime); } if(Core::self()->uiControllerInternal()->openAfterCurrent() && Core::self()->uiControllerInternal()->arrangeBuddies() && buddyFinder) { // Check if active document's buddy is directly next to it. // For example, we have the already-open tabs | *foo.h* | foo.cpp | , foo.h is active. // When we open a new document here (and the buddy feature is enabled), // we do not want to separate foo.h and foo.cpp, so we take care and avoid this. Sublime::AreaIndex *activeAreaIndex = area->indexOf(activeView); int pos = activeAreaIndex->views().indexOf(activeView); Sublime::View *afterActiveView = activeAreaIndex->views().value(pos+1, nullptr); Sublime::UrlDocument *activeDoc = nullptr, *afterActiveDoc = nullptr; if(activeView && afterActiveView) { activeDoc = dynamic_cast(activeView->document()); afterActiveDoc = dynamic_cast(afterActiveView->document()); } if(activeDoc && afterActiveDoc && buddyFinder->areBuddies(activeDoc->url(), afterActiveDoc->url())) { // don't insert in between of two buddies, but after them area->addView(partView, activeAreaIndex, afterActiveView); } else { // The active document's buddy is not directly after it // => no ploblem, insert after active document area->addView(partView, activeView); } } else { // Opening as last tab won't disturb our buddies // Same, if buddies are disabled, we needn't care about them. // this method places the tab according to openAfterCurrent() area->addView(partView, activeView); } } } if (!activationParams.testFlag(IDocumentController::DoNotActivate)) { uiController->activeSublimeWindow()->activateView( partView, !activationParams.testFlag(IDocumentController::DoNotFocus)); } if (!activationParams.testFlag(IDocumentController::DoNotAddToRecentOpen) && !controller->isEmptyDocumentUrl(url)) { fileOpenRecent->addUrl( url ); } if( range.isValid() ) { if (range.isEmpty()) doc->setCursorPosition( range.start() ); else doc->setTextSelection( range ); } } // Deferred signals, wait until it's all ready first if( emitOpened ) { emit controller->documentOpened( doc ); } if (!activationParams.testFlag(IDocumentController::DoNotActivate) && doc != controller->activeDocument()) emit controller->documentActivated( doc ); saveAll->setEnabled(true); revertAll->setEnabled(true); close->setEnabled(true); closeAll->setEnabled(true); closeAllOthers->setEnabled(true); KTextEditor::Cursor activePosition; if(range.isValid()) activePosition = range.start(); else if(KTextEditor::View* v = doc->activeTextView()) activePosition = v->cursorPosition(); if (doc != previousActiveDocument || activePosition != previousActivePosition) emit controller->documentJumpPerformed(doc, activePosition, previousActiveDocument, previousActivePosition); return true; } DocumentController* controller; QList backHistory; QList forwardHistory; bool isJumping; QPointer saveAll; QPointer revertAll; QPointer close; QPointer closeAll; QPointer closeAllOthers; KRecentFilesAction* fileOpenRecent; KTextEditor::Document* globalTextEditorInstance; }; DocumentController::DocumentController( QObject *parent ) : IDocumentController( parent ) { setObjectName(QStringLiteral("DocumentController")); d = new DocumentControllerPrivate(this); QDBusConnection::sessionBus().registerObject( QStringLiteral("/org/kdevelop/DocumentController"), this, QDBusConnection::ExportScriptableSlots ); connect(this, &DocumentController::documentUrlChanged, this, [&] (IDocument* document) { d->changeDocumentUrl(document); }); if(!(Core::self()->setupFlags() & Core::NoUi)) setupActions(); } void DocumentController::initialize() { } void DocumentController::cleanup() { if (d->fileOpenRecent) d->fileOpenRecent->saveEntries( KConfigGroup(KSharedConfig::openConfig(), "Recent Files" ) ); // Close all documents without checking if they should be saved. // This is because the user gets a chance to save them during MainWindow::queryClose. foreach (IDocument* doc, openDocuments()) doc->close(IDocument::Discard); } DocumentController::~DocumentController() { delete d; } void DocumentController::setupActions() { KActionCollection* ac = Core::self()->uiControllerInternal()->defaultMainWindow()->actionCollection(); QAction* action; action = ac->addAction( QStringLiteral("file_open") ); action->setIcon(QIcon::fromTheme(QStringLiteral("document-open"))); ac->setDefaultShortcut(action, Qt::CTRL + Qt::Key_O ); action->setText(i18n( "&Open..." ) ); connect( action, &QAction::triggered, this, [&] { d->chooseDocument(); } ); action->setToolTip( i18n( "Open file" ) ); action->setWhatsThis( i18n( "Opens a file for editing." ) ); d->fileOpenRecent = KStandardAction::openRecent(this, SLOT(slotOpenDocument(QUrl)), ac); d->fileOpenRecent->setWhatsThis(i18n("This lists files which you have opened recently, and allows you to easily open them again.")); d->fileOpenRecent->loadEntries( KConfigGroup(KSharedConfig::openConfig(), "Recent Files" ) ); action = d->saveAll = ac->addAction( QStringLiteral("file_save_all") ); action->setIcon(QIcon::fromTheme(QStringLiteral("document-save"))); action->setText(i18n( "Save Al&l" ) ); connect( action, &QAction::triggered, this, &DocumentController::slotSaveAllDocuments ); action->setToolTip( i18n( "Save all open documents" ) ); action->setWhatsThis( i18n( "Save all open documents, prompting for additional information when necessary." ) ); ac->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_L) ); action->setEnabled(false); action = d->revertAll = ac->addAction( QStringLiteral("file_revert_all") ); action->setIcon(QIcon::fromTheme(QStringLiteral("document-revert"))); action->setText(i18n( "Reload All" ) ); connect( action, &QAction::triggered, this, &DocumentController::reloadAllDocuments ); action->setToolTip( i18n( "Revert all open documents" ) ); action->setWhatsThis( i18n( "Revert all open documents, returning to the previously saved state." ) ); action->setEnabled(false); action = d->close = ac->addAction( QStringLiteral("file_close") ); action->setIcon(QIcon::fromTheme(QStringLiteral("window-close"))); ac->setDefaultShortcut(action, Qt::CTRL + Qt::Key_W ); action->setText( i18n( "&Close" ) ); connect( action, &QAction::triggered, this, &DocumentController::fileClose ); action->setToolTip( i18n( "Close file" ) ); action->setWhatsThis( i18n( "Closes current file." ) ); action->setEnabled(false); action = d->closeAll = ac->addAction( QStringLiteral("file_close_all") ); action->setIcon(QIcon::fromTheme(QStringLiteral("window-close"))); action->setText(i18n( "Clos&e All" ) ); connect( action, &QAction::triggered, this, &DocumentController::closeAllDocuments ); action->setToolTip( i18n( "Close all open documents" ) ); action->setWhatsThis( i18n( "Close all open documents, prompting for additional information when necessary." ) ); action->setEnabled(false); action = d->closeAllOthers = ac->addAction( QStringLiteral("file_closeother") ); action->setIcon(QIcon::fromTheme(QStringLiteral("window-close"))); ac->setDefaultShortcut(action, Qt::CTRL + Qt::SHIFT + Qt::Key_W ); action->setText(i18n( "Close All Ot&hers" ) ); connect( action, &QAction::triggered, this, &DocumentController::closeAllOtherDocuments ); action->setToolTip( i18n( "Close all other documents" ) ); action->setWhatsThis( i18n( "Close all open documents, with the exception of the currently active document." ) ); action->setEnabled(false); action = ac->addAction( QStringLiteral("vcsannotate_current_document") ); connect( action, &QAction::triggered, this, &DocumentController::vcsAnnotateCurrentDocument ); action->setText( i18n( "Show Annotate on current document") ); action->setIconText( i18n( "Annotate" ) ); action->setIcon( QIcon::fromTheme(QStringLiteral("user-properties")) ); } void DocumentController::slotOpenDocument(const QUrl &url) { openDocument(url); } IDocument* DocumentController::openDocumentFromText( const QString& data ) { IDocument* d = openDocument(nextEmptyDocumentUrl()); Q_ASSERT(d->textDocument()); d->textDocument()->setText( data ); return d; } bool DocumentController::openDocumentFromTextSimple( QString text ) { return (bool)openDocumentFromText( text ); } bool DocumentController::openDocumentSimple( QString url, int line, int column ) { return (bool)openDocument( QUrl::fromUserInput(url), KTextEditor::Cursor( line, column ) ); } IDocument* DocumentController::openDocument( const QUrl& inputUrl, const QString& prefName ) { return d->openDocumentInternal( inputUrl, prefName ); } IDocument* DocumentController::openDocument( const QUrl & inputUrl, const KTextEditor::Range& range, DocumentActivationParams activationParams, const QString& encoding, IDocument* buddy) { return d->openDocumentInternal( inputUrl, QLatin1String(""), range, encoding, activationParams, buddy); } bool DocumentController::openDocument(IDocument* doc, const KTextEditor::Range& range, DocumentActivationParams activationParams, IDocument* buddy) { return d->openDocumentInternal( doc, range, activationParams, buddy); } void DocumentController::fileClose() { IDocument *activeDoc = activeDocument(); if (activeDoc) { UiController *uiController = Core::self()->uiControllerInternal(); Sublime::View *activeView = uiController->activeSublimeWindow()->activeView(); uiController->activeArea()->closeView(activeView); } } bool DocumentController::closeDocument( const QUrl &url ) { if( !d->documents.contains(url) ) return false; //this will remove all views and after the last view is removed, the //document will be self-destructed and removeDocument() slot will catch that //and clean up internal data structures d->documents[url]->close(); return true; } void DocumentController::notifyDocumentClosed(Sublime::Document* doc_) { IDocument* doc = dynamic_cast(doc_); Q_ASSERT(doc); d->removeDocument(doc_); if (d->documents.isEmpty()) { if (d->saveAll) d->saveAll->setEnabled(false); if (d->revertAll) d->revertAll->setEnabled(false); if (d->close) d->close->setEnabled(false); if (d->closeAll) d->closeAll->setEnabled(false); if (d->closeAllOthers) d->closeAllOthers->setEnabled(false); } emit documentClosed(doc); } IDocument * DocumentController::documentForUrl( const QUrl & dirtyUrl ) const { if (dirtyUrl.isEmpty()) { return nullptr; } Q_ASSERT(!dirtyUrl.isRelative()); Q_ASSERT(!dirtyUrl.fileName().isEmpty()); //Fix urls that might not be normalized return d->documents.value( dirtyUrl.adjusted( QUrl::NormalizePathSegments ), nullptr ); } QList DocumentController::openDocuments() const { QList opened; foreach (IDocument *doc, d->documents) { Sublime::Document *sdoc = dynamic_cast(doc); if( !sdoc ) { continue; } if (!sdoc->views().isEmpty()) opened << doc; } return opened; } void DocumentController::activateDocument( IDocument * document, const KTextEditor::Range& range ) { // TODO avoid some code in openDocument? Q_ASSERT(document); openDocument(document->url(), range, IDocumentController::DoNotAddToRecentOpen); } void DocumentController::slotSaveAllDocuments() { saveAllDocuments(IDocument::Silent); } bool DocumentController::saveAllDocuments(IDocument::DocumentSaveMode mode) { return saveSomeDocuments(openDocuments(), mode); } bool KDevelop::DocumentController::saveSomeDocuments(const QList< IDocument * > & list, IDocument::DocumentSaveMode mode) { if (mode & IDocument::Silent) { foreach (IDocument* doc, modifiedDocuments(list)) { if( !DocumentController::isEmptyDocumentUrl(doc->url()) && !doc->save(mode) ) { if( doc ) qWarning() << "!! Could not save document:" << doc->url(); else qWarning() << "!! Could not save document as its NULL"; } // TODO if (!ret) showErrorDialog() ? } } else { // Ask the user which documents to save QList checkSave = modifiedDocuments(list); if (!checkSave.isEmpty()) { KSaveSelectDialog dialog(checkSave, qApp->activeWindow()); if (dialog.exec() == QDialog::Rejected) return false; } } return true; } QList< IDocument * > KDevelop::DocumentController::visibleDocumentsInWindow(MainWindow * mw) const { // Gather a list of all documents which do have a view in the given main window // Does not find documents which are open in inactive areas QList list; foreach (IDocument* doc, openDocuments()) { if (Sublime::Document* sdoc = dynamic_cast(doc)) { foreach (Sublime::View* view, sdoc->views()) { if (view->hasWidget() && view->widget()->window() == mw) { list.append(doc); break; } } } } return list; } QList< IDocument * > KDevelop::DocumentController::documentsExclusivelyInWindow(MainWindow * mw, bool currentAreaOnly) const { // Gather a list of all documents which have views only in the given main window QList checkSave; foreach (IDocument* doc, openDocuments()) { if (Sublime::Document* sdoc = dynamic_cast(doc)) { bool inOtherWindow = false; foreach (Sublime::View* view, sdoc->views()) { foreach(Sublime::MainWindow* window, Core::self()->uiControllerInternal()->mainWindows()) if(window->containsView(view) && (window != mw || (currentAreaOnly && window == mw && !mw->area()->views().contains(view)))) inOtherWindow = true; } if (!inOtherWindow) checkSave.append(doc); } } return checkSave; } QList< IDocument * > KDevelop::DocumentController::modifiedDocuments(const QList< IDocument * > & list) const { QList< IDocument * > ret; foreach (IDocument* doc, list) if (doc->state() == IDocument::Modified || doc->state() == IDocument::DirtyAndModified) ret.append(doc); return ret; } bool DocumentController::saveAllDocumentsForWindow(KParts::MainWindow* mw, KDevelop::IDocument::DocumentSaveMode mode, bool currentAreaOnly) { QList checkSave = documentsExclusivelyInWindow(dynamic_cast(mw), currentAreaOnly); return saveSomeDocuments(checkSave, mode); } void DocumentController::reloadAllDocuments() { if (Sublime::MainWindow* mw = Core::self()->uiControllerInternal()->activeSublimeWindow()) { QList views = visibleDocumentsInWindow(dynamic_cast(mw)); if (!saveSomeDocuments(views, IDocument::Default)) // User cancelled or other error return; foreach (IDocument* doc, views) if(!isEmptyDocumentUrl(doc->url())) doc->reload(); } } bool DocumentController::closeAllDocuments() { if (Sublime::MainWindow* mw = Core::self()->uiControllerInternal()->activeSublimeWindow()) { QList views = visibleDocumentsInWindow(dynamic_cast(mw)); if (!saveSomeDocuments(views, IDocument::Default)) // User cancelled or other error return false; foreach (IDocument* doc, views) doc->close(IDocument::Discard); } return true; } void DocumentController::closeAllOtherDocuments() { if (Sublime::MainWindow* mw = Core::self()->uiControllerInternal()->activeSublimeWindow()) { Sublime::View* activeView = mw->activeView(); if (!activeView) { qWarning() << "Shouldn't there always be an active view when this function is called?"; return; } // Deal with saving unsaved solo views QList soloViews = documentsExclusivelyInWindow(dynamic_cast(mw)); soloViews.removeAll(dynamic_cast(activeView->document())); if (!saveSomeDocuments(soloViews, IDocument::Default)) // User cancelled or other error return; foreach (Sublime::View* view, mw->area()->views()) { if (view != activeView) mw->area()->closeView(view); } activeView->widget()->setFocus(); } } IDocument* DocumentController::activeDocument() const { UiController *uiController = Core::self()->uiControllerInternal(); Sublime::MainWindow* mw = uiController->activeSublimeWindow(); if( !mw || !mw->activeView() ) return nullptr; return dynamic_cast(mw->activeView()->document()); } KTextEditor::View* DocumentController::activeTextDocumentView() const { UiController *uiController = Core::self()->uiControllerInternal(); Sublime::MainWindow* mw = uiController->activeSublimeWindow(); if( !mw || !mw->activeView() ) return nullptr; TextView* view = qobject_cast(mw->activeView()); if(!view) return nullptr; return view->textView(); } QString DocumentController::activeDocumentPath( QString target ) const { if(!target.isEmpty()) { foreach(IProject* project, Core::self()->projectController()->projects()) { if(project->name().startsWith(target, Qt::CaseInsensitive)) { return project->path().pathOrUrl() + "/."; } } } IDocument* doc = activeDocument(); if(!doc || target == QStringLiteral("[selection]")) { Context* selection = ICore::self()->selectionController()->currentSelection(); if(selection && selection->type() == Context::ProjectItemContext && !static_cast(selection)->items().isEmpty()) { QString ret = static_cast(selection)->items().at(0)->path().pathOrUrl(); if(static_cast(selection)->items().at(0)->folder()) ret += QStringLiteral("/."); return ret; } return QString(); } return doc->url().toString(); } QStringList DocumentController::activeDocumentPaths() const { UiController *uiController = Core::self()->uiControllerInternal(); if( !uiController->activeSublimeWindow() ) return QStringList(); QSet documents; foreach(Sublime::View* view, uiController->activeSublimeWindow()->area()->views()) documents.insert(view->document()->documentSpecifier()); return documents.toList(); } void DocumentController::registerDocumentForMimetype( const QString& mimetype, KDevelop::IDocumentFactory* factory ) { if( !d->factories.contains( mimetype ) ) d->factories[mimetype] = factory; } QStringList DocumentController::documentTypes() const { return QStringList() << QStringLiteral("Text"); } static const QRegularExpression& emptyDocumentPattern() { static const QRegularExpression pattern(QStringLiteral("^/%1(?:\\s\\((\\d+)\\))?$").arg(EMPTY_DOCUMENT_URL)); return pattern; } bool DocumentController::isEmptyDocumentUrl(const QUrl &url) { return emptyDocumentPattern().match(url.toDisplayString(QUrl::PreferLocalFile)).hasMatch(); } QUrl DocumentController::nextEmptyDocumentUrl() { int nextEmptyDocNumber = 0; const auto& pattern = emptyDocumentPattern(); foreach (IDocument *doc, Core::self()->documentControllerInternal()->openDocuments()) { if (DocumentController::isEmptyDocumentUrl(doc->url())) { const auto match = pattern.match(doc->url().toDisplayString(QUrl::PreferLocalFile)); if (match.hasMatch()) { const int num = match.captured(1).toInt(); nextEmptyDocNumber = qMax(nextEmptyDocNumber, num + 1); } else { nextEmptyDocNumber = qMax(nextEmptyDocNumber, 1); } } } QUrl url; if (nextEmptyDocNumber > 0) url = QUrl::fromLocalFile(QStringLiteral("/%1 (%2)").arg(EMPTY_DOCUMENT_URL).arg(nextEmptyDocNumber)); else url = QUrl::fromLocalFile('/' + EMPTY_DOCUMENT_URL); return url; } IDocumentFactory* DocumentController::factory(const QString& mime) const { return d->factories.value(mime); } KTextEditor::Document* DocumentController::globalTextEditorInstance() { if(!d->globalTextEditorInstance) d->globalTextEditorInstance = Core::self()->partControllerInternal()->createTextPart(); return d->globalTextEditorInstance; } bool DocumentController::openDocumentsSimple( QStringList urls ) { Sublime::Area* area = Core::self()->uiControllerInternal()->activeArea(); Sublime::AreaIndex* areaIndex = area->rootIndex(); QList topViews = static_cast(Core::self()->uiControllerInternal()->activeMainWindow())->getTopViews(); if(Sublime::View* activeView = Core::self()->uiControllerInternal()->activeSublimeWindow()->activeView()) areaIndex = area->indexOf(activeView); qCDebug(SHELL) << "opening " << urls << " to area " << area << " index " << areaIndex << " with children " << areaIndex->first() << " " << areaIndex->second(); bool isFirstView = true; bool ret = openDocumentsWithSplitSeparators( areaIndex, urls, isFirstView ); qCDebug(SHELL) << "area arch. after opening: " << areaIndex->print(); // Required because sublime sometimes doesn't update correctly when the area-index contents has been changed // (especially when views have been moved to other indices, through unsplit, split, etc.) static_cast(Core::self()->uiControllerInternal()->activeMainWindow())->reconstructViews(topViews); return ret; } bool DocumentController::openDocumentsWithSplitSeparators( Sublime::AreaIndex* index, QStringList urlsWithSeparators, bool& isFirstView ) { qCDebug(SHELL) << "opening " << urlsWithSeparators << " index " << index << " with children " << index->first() << " " << index->second() << " view-count " << index->viewCount(); if(urlsWithSeparators.isEmpty()) return true; Sublime::Area* area = Core::self()->uiControllerInternal()->activeArea(); QList topLevelSeparators; // Indices of the top-level separators (with groups skipped) QStringList separators = QStringList() << QStringLiteral("/") << QStringLiteral("-"); QList groups; bool ret = true; { int parenDepth = 0; int groupStart = 0; for(int pos = 0; pos < urlsWithSeparators.size(); ++pos) { QString item = urlsWithSeparators[pos]; if(separators.contains(item)) { if(parenDepth == 0) topLevelSeparators << pos; }else if(item == QLatin1String("[")) { if(parenDepth == 0) groupStart = pos+1; ++parenDepth; } else if(item == QLatin1String("]")) { if(parenDepth > 0) { --parenDepth; if(parenDepth == 0) groups << urlsWithSeparators.mid(groupStart, pos-groupStart); } else{ qCDebug(SHELL) << "syntax error in " << urlsWithSeparators << ": parens do not match"; ret = false; } }else if(parenDepth == 0) { groups << (QStringList() << item); } } } if(topLevelSeparators.isEmpty()) { if(urlsWithSeparators.size() > 1) { foreach(const QStringList& group, groups) ret &= openDocumentsWithSplitSeparators( index, group, isFirstView ); }else{ while(index->isSplit()) index = index->first(); // Simply open the document into the area index IDocument* doc = Core::self()->documentControllerInternal()->openDocument(QUrl::fromUserInput(urlsWithSeparators.front()), KTextEditor::Cursor::invalid(), IDocumentController::DoNotActivate | IDocumentController::DoNotCreateView); Sublime::Document *sublimeDoc = dynamic_cast(doc); if (sublimeDoc) { Sublime::View* view = sublimeDoc->createView(); area->addView(view, index); if(isFirstView) { static_cast(Core::self()->uiControllerInternal()->activeMainWindow())->activateView(view); isFirstView = false; } }else{ ret = false; } } return ret; } // Pick a separator in the middle int pickSeparator = topLevelSeparators[topLevelSeparators.size()/2]; bool activeViewToSecondChild = false; if(pickSeparator == urlsWithSeparators.size()-1) { // There is no right child group, so the right side should be filled with the currently active views activeViewToSecondChild = true; }else{ QStringList separatorsAndParens = separators; separatorsAndParens << QStringLiteral("[") << QStringLiteral("]"); // Check if the second child-set contains an unterminated separator, which means that the active views should end up there for(int pos = pickSeparator+1; pos < urlsWithSeparators.size(); ++pos) if( separators.contains(urlsWithSeparators[pos]) && (pos == urlsWithSeparators.size()-1 || separatorsAndParens.contains(urlsWithSeparators[pos-1]) || separatorsAndParens.contains(urlsWithSeparators[pos-1])) ) activeViewToSecondChild = true; } Qt::Orientation orientation = urlsWithSeparators[pickSeparator] == QLatin1String("/") ? Qt::Horizontal : Qt::Vertical; if(!index->isSplit()) { qCDebug(SHELL) << "splitting " << index << "orientation" << orientation << "to second" << activeViewToSecondChild; index->split(orientation, activeViewToSecondChild); }else{ index->setOrientation(orientation); qCDebug(SHELL) << "WARNING: Area is already split (shouldn't be)" << urlsWithSeparators; } openDocumentsWithSplitSeparators( index->first(), urlsWithSeparators.mid(0, pickSeparator) , isFirstView ); if(pickSeparator != urlsWithSeparators.size() - 1) openDocumentsWithSplitSeparators( index->second(), urlsWithSeparators.mid(pickSeparator+1, urlsWithSeparators.size() - (pickSeparator+1) ), isFirstView ); // Clean up the child-indices, because document-loading may fail if(!index->first()->viewCount() && !index->first()->isSplit()) { qCDebug(SHELL) << "unsplitting first"; index->unsplit(index->first()); } else if(!index->second()->viewCount() && !index->second()->isSplit()) { qCDebug(SHELL) << "unsplitting second"; index->unsplit(index->second()); } return ret; } void DocumentController::vcsAnnotateCurrentDocument() { IDocument* doc = activeDocument(); QUrl url = doc->url(); IProject* project = KDevelop::ICore::self()->projectController()->findProjectForUrl(url); if(project && project->versionControlPlugin()) { IBasicVersionControl* iface = nullptr; iface = project->versionControlPlugin()->extension(); auto helper = new VcsPluginHelper(project->versionControlPlugin(), iface); connect(doc->textDocument(), &KTextEditor::Document::aboutToClose, helper, static_cast(&VcsPluginHelper::disposeEventually)); Q_ASSERT(qobject_cast(doc->activeTextView())); // can't use new signal slot syntax here, AnnotationViewInterface is not a QObject connect(doc->activeTextView(), SIGNAL(annotationBorderVisibilityChanged(View*,bool)), helper, SLOT(disposeEventually(View*, bool))); helper->addContextDocument(url); helper->annotation(); } else { KMessageBox::error(nullptr, i18n("Could not annotate the document because it is not " "part of a version-controlled project.")); } } } #include "moc_documentcontroller.cpp" diff --git a/shell/environmentconfigurebutton.cpp b/shell/environmentconfigurebutton.cpp index 1ae375cc8..e2e052437 100644 --- a/shell/environmentconfigurebutton.cpp +++ b/shell/environmentconfigurebutton.cpp @@ -1,108 +1,108 @@ /* This file is part of KDevelop Copyright 2010 Milian Wolff This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "environmentconfigurebutton.h" #include #include #include "settings/environmentpreferences.h" #include #include #include #include #include #include namespace KDevelop { class EnvironmentConfigureButtonPrivate { public: - EnvironmentConfigureButtonPrivate(EnvironmentConfigureButton* _q) + explicit EnvironmentConfigureButtonPrivate(EnvironmentConfigureButton* _q) : q(_q), selectionWidget(nullptr) { } void showDialog() { QDialog dlg(qApp->activeWindow()); QString selected; if (selectionWidget) { selected = selectionWidget->effectiveProfileName(); } EnvironmentPreferences prefs(selected, q); // TODO: This should be implicit when constructing EnvironmentPreferences prefs.initConfigManager(); prefs.reset(); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); QObject::connect(buttonBox, &QDialogButtonBox::accepted, &dlg, &QDialog::accept); QObject::connect(buttonBox, &QDialogButtonBox::rejected, &dlg, &QDialog::reject); auto layout = new QVBoxLayout; layout->addWidget(&prefs); layout->addWidget(buttonBox); dlg.setLayout(layout); dlg.setWindowTitle(prefs.fullName()); dlg.setWindowIcon(prefs.icon()); dlg.resize(480, 320); if (dlg.exec() == QDialog::Accepted) { prefs.apply(); emit q->environmentConfigured(); } } EnvironmentConfigureButton *q; EnvironmentSelectionWidget *selectionWidget; }; EnvironmentConfigureButton::EnvironmentConfigureButton(QWidget* parent) : QPushButton(parent), d(new EnvironmentConfigureButtonPrivate(this)) { setText(QString()); setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); setIcon(QIcon::fromTheme(QStringLiteral("configure"))); setToolTip(i18n("Configure environment variables")); connect(this, &EnvironmentConfigureButton::clicked, this, [&] { d->showDialog(); }); } EnvironmentConfigureButton::~EnvironmentConfigureButton() { delete d; } void EnvironmentConfigureButton::setSelectionWidget(EnvironmentSelectionWidget* widget) { connect(this, &EnvironmentConfigureButton::environmentConfigured, widget, &EnvironmentSelectionWidget::reconfigure); d->selectionWidget = widget; } } #include "moc_environmentconfigurebutton.cpp" diff --git a/shell/filteredproblemstore.cpp b/shell/filteredproblemstore.cpp index c96f62278..67f95b75e 100644 --- a/shell/filteredproblemstore.cpp +++ b/shell/filteredproblemstore.cpp @@ -1,315 +1,315 @@ /* * Copyright 2015 Laszlo Kis-Adam * * 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 "filteredproblemstore.h" #include "problem.h" #include "watcheddocumentset.h" #include "problemstorenode.h" #include using namespace KDevelop; namespace { /// Adds diagnostics as sub-nodes void addDiagnostics(ProblemStoreNode *node, const QVector &diagnostics) { foreach (const IProblem::Ptr &ptr, diagnostics) { ProblemNode *child = new ProblemNode(node, ptr); node->addChild(child); addDiagnostics(child, ptr->diagnostics()); } } /** * @brief Base class for grouping strategy classes * * These classes build the problem tree based on the respective strategies */ class GroupingStrategy { public: - GroupingStrategy( ProblemStoreNode *root ) + explicit GroupingStrategy( ProblemStoreNode *root ) : m_rootNode(root) , m_groupedRootNode(new ProblemStoreNode()) { } virtual ~GroupingStrategy(){ } /// Add a problem to the appropriate group virtual void addProblem(const IProblem::Ptr &problem) = 0; /// Find the specified noe const ProblemStoreNode* findNode(int row, ProblemStoreNode *parent = nullptr) const { if (parent == nullptr) return m_groupedRootNode->child(row); else return parent->child(row); } /// Returns the number of children nodes int count(ProblemStoreNode *parent = nullptr) { if (parent == nullptr) return m_groupedRootNode->count(); else return parent->count(); } /// Clears the problems virtual void clear() { m_groupedRootNode->clear(); } protected: ProblemStoreNode *m_rootNode; QScopedPointer m_groupedRootNode; }; ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// Implements no grouping strategy, that is just stores the problems without any grouping class NoGroupingStrategy final : public GroupingStrategy { public: - NoGroupingStrategy(ProblemStoreNode *root) + explicit NoGroupingStrategy(ProblemStoreNode *root) : GroupingStrategy(root) { } void addProblem(const IProblem::Ptr &problem) override { ProblemNode *node = new ProblemNode(m_groupedRootNode.data(), problem); addDiagnostics(node, problem->diagnostics()); m_groupedRootNode->addChild(node); } }; ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// Implements grouping based on path class PathGroupingStrategy final : public GroupingStrategy { public: - PathGroupingStrategy(ProblemStoreNode *root) + explicit PathGroupingStrategy(ProblemStoreNode *root) : GroupingStrategy(root) { } void addProblem(const IProblem::Ptr &problem) override { QString path = problem->finalLocation().document.str(); /// See if we already have this path ProblemStoreNode *parent = nullptr; foreach (ProblemStoreNode *node, m_groupedRootNode->children()) { if (node->label() == path) { parent = node; break; } } /// If not add it! if (parent == nullptr) { parent = new LabelNode(m_groupedRootNode.data(), path); m_groupedRootNode->addChild(parent); } ProblemNode *node = new ProblemNode(parent, problem); addDiagnostics(node, problem->diagnostics()); parent->addChild(node); } }; ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// Implements grouping based on severity class SeverityGroupingStrategy final : public GroupingStrategy { public: enum SeverityGroups { GroupError = 0, GroupWarning = 1, GroupHint = 2 }; - SeverityGroupingStrategy(ProblemStoreNode *root) + explicit SeverityGroupingStrategy(ProblemStoreNode *root) : GroupingStrategy(root) { /// Create the groups on construction, so there's no need to search for them on addition m_groupedRootNode->addChild(new LabelNode(m_groupedRootNode.data(), i18n("Error"))); m_groupedRootNode->addChild(new LabelNode(m_groupedRootNode.data(), i18n("Warning"))); m_groupedRootNode->addChild(new LabelNode(m_groupedRootNode.data(), i18n("Hint"))); } void addProblem(const IProblem::Ptr &problem) override { ProblemStoreNode *parent = nullptr; switch (problem->severity()) { case IProblem::Error: parent = m_groupedRootNode->child(GroupError); break; case IProblem::Warning: parent = m_groupedRootNode->child(GroupWarning); break; case IProblem::Hint: parent = m_groupedRootNode->child(GroupHint); break; default: break; } ProblemNode *node = new ProblemNode(m_groupedRootNode.data(), problem); addDiagnostics(node, problem->diagnostics()); parent->addChild(node); } void clear() override { m_groupedRootNode->child(GroupError)->clear(); m_groupedRootNode->child(GroupWarning)->clear(); m_groupedRootNode->child(GroupHint)->clear(); } }; } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// namespace KDevelop { struct FilteredProblemStorePrivate { - FilteredProblemStorePrivate(FilteredProblemStore* q) + explicit FilteredProblemStorePrivate(FilteredProblemStore* q) : q(q) , m_strategy(new NoGroupingStrategy(q->rootNode())) , m_grouping(NoGrouping) { } /// Tells if the problem matches the filters bool match(const IProblem::Ptr &problem) const; FilteredProblemStore* q; QScopedPointer m_strategy; GroupingMethod m_grouping; }; FilteredProblemStore::FilteredProblemStore(QObject *parent) : ProblemStore(parent) , d(new FilteredProblemStorePrivate(this)) { } FilteredProblemStore::~FilteredProblemStore() { } void FilteredProblemStore::addProblem(const IProblem::Ptr &problem) { ProblemStore::addProblem(problem); if (d->match(problem)) d->m_strategy->addProblem(problem); } const ProblemStoreNode* FilteredProblemStore::findNode(int row, ProblemStoreNode *parent) const { return d->m_strategy->findNode(row, parent); } int FilteredProblemStore::count(ProblemStoreNode *parent) const { return d->m_strategy->count(parent); } void FilteredProblemStore::clear() { d->m_strategy->clear(); ProblemStore::clear(); } void FilteredProblemStore::rebuild() { emit beginRebuild(); d->m_strategy->clear(); foreach (ProblemStoreNode *node, rootNode()->children()) { IProblem::Ptr problem = node->problem(); if (d->match(problem)) { d->m_strategy->addProblem(problem); } } emit endRebuild(); } void FilteredProblemStore::setGrouping(int grouping) { GroupingMethod g = GroupingMethod(grouping); if(g == d->m_grouping) return; d->m_grouping = g; switch (g) { case NoGrouping: d->m_strategy.reset(new NoGroupingStrategy(rootNode())); break; case PathGrouping: d->m_strategy.reset(new PathGroupingStrategy(rootNode())); break; case SeverityGrouping: d->m_strategy.reset(new SeverityGroupingStrategy(rootNode())); break; } rebuild(); emit changed(); } int FilteredProblemStore::grouping() const { return d->m_grouping; } bool FilteredProblemStorePrivate::match(const IProblem::Ptr &problem) const { if (q->scope() != ProblemScope::BypassScopeFilter && !q->documents()->get().contains(problem.data()->finalLocation().document) && !(q->showImports() && q->documents()->getImports().contains(problem.data()->finalLocation().document))) return false; if(problem->severity()!=IProblem::NoSeverity) { /// If the problem severity isn't in the filter severities it's discarded if(!q->severities().testFlag(problem->severity())) return false; } else { if(!q->severities().testFlag(IProblem::Hint))//workaround for problems wothout correctly set severity return false; } return true; } } diff --git a/shell/languagecontroller.cpp b/shell/languagecontroller.cpp index d58cdd85f..885850415 100644 --- a/shell/languagecontroller.cpp +++ b/shell/languagecontroller.cpp @@ -1,363 +1,363 @@ /*************************************************************************** * Copyright 2006 Adam Treat * * Copyright 2007 Alexander Dymo * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "languagecontroller.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "problemmodelset.h" #include "core.h" #include "settings/languagepreferences.h" #include "completionsettings.h" #include "debug.h" namespace { QString KEY_SupportedMimeTypes() { return QStringLiteral("X-KDevelop-SupportedMimeTypes"); } QString KEY_ILanguageSupport() { return QStringLiteral("ILanguageSupport"); } } #if QT_VERSION < 0x050600 inline uint qHash(const QMimeType& mime, uint seed = 0) { return qHash(mime.name(), seed); } #endif namespace KDevelop { typedef QHash LanguageHash; typedef QHash > LanguageCache; struct LanguageControllerPrivate { - LanguageControllerPrivate(LanguageController *controller) + explicit LanguageControllerPrivate(LanguageController *controller) : dataMutex(QMutex::Recursive) , backgroundParser(new BackgroundParser(controller)) , staticAssistantsManager(nullptr) , m_cleanedUp(false) , problemModelSet(new ProblemModelSet(controller)) , m_controller(controller) {} void documentActivated(KDevelop::IDocument *document) { QUrl url = document->url(); if (!url.isValid()) { return; } activeLanguages.clear(); QList languages = m_controller->languagesForUrl(url); foreach (const auto lang, languages) { activeLanguages << lang; } } QList activeLanguages; mutable QMutex dataMutex; LanguageHash languages; //Maps language-names to languages LanguageCache languageCache; //Maps mimetype-names to languages typedef QMultiHash MimeTypeCache; MimeTypeCache mimeTypeCache; //Maps mimetypes to languages BackgroundParser *backgroundParser; StaticAssistantsManager* staticAssistantsManager; bool m_cleanedUp; void addLanguageSupport(ILanguageSupport* support, const QStringList& mimetypes); void addLanguageSupport(ILanguageSupport* support); ProblemModelSet *problemModelSet; private: LanguageController *m_controller; }; void LanguageControllerPrivate::addLanguageSupport(ILanguageSupport* languageSupport, const QStringList& mimetypes) { Q_ASSERT(!languages.contains(languageSupport->name())); languages.insert(languageSupport->name(), languageSupport); foreach(const QString& mimeTypeName, mimetypes) { qCDebug(SHELL) << "adding supported mimetype:" << mimeTypeName << "language:" << languageSupport->name(); languageCache[mimeTypeName] << languageSupport; QMimeType mime = QMimeDatabase().mimeTypeForName(mimeTypeName); if (mime.isValid()) { mimeTypeCache.insert(mime, languageSupport); } else { qWarning() << "could not create mime-type" << mimeTypeName; } } } void LanguageControllerPrivate::addLanguageSupport(KDevelop::ILanguageSupport* languageSupport) { if (languages.contains(languageSupport->name())) return; Q_ASSERT(dynamic_cast(languageSupport)); KPluginMetaData info = Core::self()->pluginController()->pluginInfo(dynamic_cast(languageSupport)); QStringList mimetypes = KPluginMetaData::readStringList(info.rawData(), KEY_SupportedMimeTypes()); addLanguageSupport(languageSupport, mimetypes); } LanguageController::LanguageController(QObject *parent) : ILanguageController(parent) { setObjectName(QStringLiteral("LanguageController")); d = new LanguageControllerPrivate(this); } LanguageController::~LanguageController() { delete d; } void LanguageController::initialize() { d->backgroundParser->loadSettings(); d->staticAssistantsManager = new StaticAssistantsManager(this); // make sure the DUChain is setup before we try to access it from different threads at the same time DUChain::self(); connect(Core::self()->documentController(), &IDocumentController::documentActivated, this, [&] (IDocument* document) { d->documentActivated(document); }); } void LanguageController::cleanup() { QMutexLocker lock(&d->dataMutex); d->m_cleanedUp = true; } QList LanguageController::activeLanguages() { QMutexLocker lock(&d->dataMutex); return d->activeLanguages; } StaticAssistantsManager* LanguageController::staticAssistantsManager() const { return d->staticAssistantsManager; } ICompletionSettings *LanguageController::completionSettings() const { return &CompletionSettings::self(); } ProblemModelSet* LanguageController::problemModelSet() const { return d->problemModelSet; } QList LanguageController::loadedLanguages() const { QMutexLocker lock(&d->dataMutex); QList ret; if(d->m_cleanedUp) return ret; foreach(ILanguageSupport* lang, d->languages) ret << lang; return ret; } ILanguageSupport* LanguageController::language(const QString &name) const { QMutexLocker lock(&d->dataMutex); if(d->m_cleanedUp) return nullptr; if(d->languages.contains(name)) return d->languages[name]; QVariantMap constraints; constraints.insert(QStringLiteral("X-KDevelop-Language"), name); QList supports = Core::self()->pluginController()->allPluginsForExtension(KEY_ILanguageSupport(), constraints); if(!supports.isEmpty()) { ILanguageSupport *languageSupport = supports[0]->extension(); if(languageSupport) { d->addLanguageSupport(languageSupport); return languageSupport; } } return nullptr; } bool isNumeric(const QString& str) { int len = str.length(); if(len == 0) return false; for(int a = 0; a < len; ++a) if(!str[a].isNumber()) return false; return true; } QList LanguageController::languagesForUrl(const QUrl &url) { QMutexLocker lock(&d->dataMutex); QList languages; if(d->m_cleanedUp) return languages; const QString fileName = url.fileName(); ///TODO: cache regexp or simple string pattern for endsWith matching QRegExp exp("", Qt::CaseInsensitive, QRegExp::Wildcard); ///non-crashy part: Use the mime-types of known languages for(LanguageControllerPrivate::MimeTypeCache::const_iterator it = d->mimeTypeCache.constBegin(); it != d->mimeTypeCache.constEnd(); ++it) { foreach(const QString& pattern, it.key().globPatterns()) { if(pattern.startsWith('*')) { const QStringRef subPattern = pattern.midRef(1); if (!subPattern.contains('*')) { //optimize: we can skip the expensive QRegExp in this case //and do a simple string compare (much faster) if (fileName.endsWith(subPattern)) { languages << *it; } continue; } } exp.setPattern(pattern); if(int position = exp.indexIn(fileName)) { if(position != -1 && exp.matchedLength() + position == fileName.length()) languages << *it; } } } if(!languages.isEmpty()) return languages; //Never use findByUrl from within a background thread, and never load a language support //from within the backgruond thread. Both is unsafe, and can lead to crashes if(!languages.isEmpty() || QThread::currentThread() != thread()) return languages; QMimeType mimeType; if (url.isLocalFile()) { mimeType = QMimeDatabase().mimeTypeForFile(url.toLocalFile()); } else { // remote file, only look at the extension mimeType = QMimeDatabase().mimeTypeForUrl(url); } if (mimeType.isDefault()) { // ask the document controller about a more concrete mimetype IDocument* doc = ICore::self()->documentController()->documentForUrl(url); if (doc) { mimeType = doc->mimeType(); } } languages = languagesForMimetype(mimeType.name()); return languages; } QList LanguageController::languagesForMimetype(const QString& mimetype) { QMutexLocker lock(&d->dataMutex); QList languages; LanguageCache::ConstIterator it = d->languageCache.constFind(mimetype); if (it != d->languageCache.constEnd()) { languages = it.value(); } else { QVariantMap constraints; constraints.insert(KEY_SupportedMimeTypes(), mimetype); QList supports = Core::self()->pluginController()->allPluginsForExtension(KEY_ILanguageSupport(), constraints); if (supports.isEmpty()) { qCDebug(SHELL) << "no languages for mimetype:" << mimetype; d->languageCache.insert(mimetype, QList()); } else { foreach (IPlugin *support, supports) { ILanguageSupport* languageSupport = support->extension(); qCDebug(SHELL) << "language-support:" << languageSupport; if(languageSupport) { d->addLanguageSupport(languageSupport); languages << languageSupport; } } } } return languages; } QList LanguageController::mimetypesForLanguageName(const QString& languageName) { QMutexLocker lock(&d->dataMutex); QList mimetypes; for (LanguageCache::ConstIterator iter = d->languageCache.constBegin(); iter != d->languageCache.constEnd(); ++iter) { foreach (ILanguageSupport* language, iter.value()) { if (language->name() == languageName) { mimetypes << iter.key(); break; } } } return mimetypes; } BackgroundParser *LanguageController::backgroundParser() const { return d->backgroundParser; } void LanguageController::addLanguageSupport(ILanguageSupport* languageSupport, const QStringList& mimetypes) { d->addLanguageSupport(languageSupport, mimetypes); } } #include "moc_languagecontroller.cpp" diff --git a/shell/loadedpluginsdialog.cpp b/shell/loadedpluginsdialog.cpp index b34a0ba54..a87c0aae3 100644 --- a/shell/loadedpluginsdialog.cpp +++ b/shell/loadedpluginsdialog.cpp @@ -1,306 +1,306 @@ /************************************************************************** * Copyright 2009 Andreas Pakulat * * Copyright 2010 Niko Sams * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "loadedpluginsdialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "core.h" #include "plugincontroller.h" #define MARGIN 5 namespace { KPluginMetaData pluginInfo(KDevelop::IPlugin* plugin) { return KDevelop::Core::self()->pluginControllerInternal()->pluginInfo(plugin); }; QString displayName(KDevelop::IPlugin* plugin) { const auto name = pluginInfo(plugin).name(); return !name.isEmpty() ? name : plugin->componentName(); } bool sortPlugins(KDevelop::IPlugin* l, KDevelop::IPlugin* r) { return displayName(l) < displayName(r); } } class PluginsModel : public QAbstractListModel { Q_OBJECT public: enum ExtraRoles { DescriptionRole = Qt::UserRole+1 }; - PluginsModel(QObject* parent = nullptr) + explicit PluginsModel(QObject* parent = nullptr) : QAbstractListModel(parent) { m_plugins = KDevelop::Core::self()->pluginControllerInternal()->loadedPlugins(); std::sort(m_plugins.begin(), m_plugins.end(), sortPlugins); } KDevelop::IPlugin *pluginForIndex(const QModelIndex& index) const { if (!index.isValid()) return nullptr; if (index.parent().isValid()) return nullptr; if (index.column() != 0) return nullptr; if (index.row() >= m_plugins.count()) return nullptr; return m_plugins[index.row()]; } QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override { KDevelop::IPlugin* plugin = pluginForIndex(index); if (!plugin) return QVariant(); switch (role) { case Qt::DisplayRole: return displayName(plugin); case DescriptionRole: return pluginInfo(plugin).description(); case Qt::DecorationRole: return pluginInfo(plugin).iconName(); default: return QVariant(); }; } int rowCount(const QModelIndex& parent = QModelIndex()) const override { if (!parent.isValid()) { return m_plugins.count(); } return 0; } private: QList m_plugins; }; class LoadedPluginsDelegate : public KWidgetItemDelegate { Q_OBJECT public: - LoadedPluginsDelegate(QAbstractItemView *itemView, QObject *parent = nullptr) + explicit LoadedPluginsDelegate(QAbstractItemView *itemView, QObject *parent = nullptr) : KWidgetItemDelegate(itemView, parent) , pushButton(new QPushButton) { pushButton->setIcon(QIcon::fromTheme(QStringLiteral("dialog-information"))); // only for getting size matters } ~LoadedPluginsDelegate() override { delete pushButton; } QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override { int i = 5; int j = 1; QFont font = titleFont(option.font); QFontMetrics fmTitle(font); return QSize(qMax(fmTitle.width(index.model()->data(index, Qt::DisplayRole).toString()), option.fontMetrics.width(index.model()->data(index, PluginsModel::DescriptionRole).toString())) + KIconLoader::SizeMedium + MARGIN * i + pushButton->sizeHint().width() * j, qMax(KIconLoader::SizeMedium + MARGIN * 2, fmTitle.height() + option.fontMetrics.height() + MARGIN * 2)); } void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override { if (!index.isValid()) { return; } painter->save(); QApplication::style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, nullptr); int iconSize = option.rect.height() - MARGIN * 2; QIcon icon = QIcon::fromTheme(index.model()->data(index, Qt::DecorationRole).toString()); icon.paint(painter, QRect(dependantLayoutValue(MARGIN + option.rect.left(), iconSize, option.rect.width()), MARGIN + option.rect.top(), iconSize, iconSize)); QRect contentsRect(dependantLayoutValue(MARGIN * 2 + iconSize + option.rect.left(), option.rect.width() - MARGIN * 3 - iconSize, option.rect.width()), MARGIN + option.rect.top(), option.rect.width() - MARGIN * 3 - iconSize, option.rect.height() - MARGIN * 2); int lessHorizontalSpace = MARGIN * 2 + pushButton->sizeHint().width(); contentsRect.setWidth(contentsRect.width() - lessHorizontalSpace); if (option.state & QStyle::State_Selected) { painter->setPen(option.palette.highlightedText().color()); } if (itemView()->layoutDirection() == Qt::RightToLeft) { contentsRect.translate(lessHorizontalSpace, 0); } painter->save(); painter->save(); QFont font = titleFont(option.font); QFontMetrics fmTitle(font); painter->setFont(font); painter->drawText(contentsRect, Qt::AlignLeft | Qt::AlignTop, fmTitle.elidedText(index.model()->data(index, Qt::DisplayRole).toString(), Qt::ElideRight, contentsRect.width())); painter->restore(); painter->drawText(contentsRect, Qt::AlignLeft | Qt::AlignBottom, option.fontMetrics.elidedText(index.model()->data(index, PluginsModel::DescriptionRole).toString(), Qt::ElideRight, contentsRect.width())); painter->restore(); painter->restore(); } QList createItemWidgets(const QModelIndex &index) const override { Q_UNUSED(index); QPushButton *button = new QPushButton(); button->setIcon(QIcon::fromTheme(QStringLiteral("dialog-information"))); setBlockedEventTypes(button, QList() << QEvent::MouseButtonPress << QEvent::MouseButtonRelease << QEvent::MouseButtonDblClick); connect(button, &QPushButton::clicked, this, &LoadedPluginsDelegate::info); return QList() << button; } void updateItemWidgets(const QList widgets, const QStyleOptionViewItem &option, const QPersistentModelIndex &index) const override { Q_UNUSED(index); if (widgets.isEmpty()) { return; } QPushButton *aboutPushButton = static_cast(widgets[0]); QSize aboutPushButtonSizeHint = aboutPushButton->sizeHint(); aboutPushButton->resize(aboutPushButtonSizeHint); aboutPushButton->move(dependantLayoutValue(option.rect.width() - MARGIN - aboutPushButtonSizeHint.width(), aboutPushButtonSizeHint.width(), option.rect.width()), option.rect.height() / 2 - aboutPushButtonSizeHint.height() / 2); } int dependantLayoutValue(int value, int width, int totalWidth) const { if (itemView()->layoutDirection() == Qt::LeftToRight) { return value; } return totalWidth - width - value; } QFont titleFont(const QFont &baseFont) const { QFont retFont(baseFont); retFont.setBold(true); return retFont; } private Q_SLOTS: void info() { PluginsModel *m = static_cast(itemView()->model()); KDevelop::IPlugin *p = m->pluginForIndex(focusedIndex()); if (p) { KAboutData aboutData = KAboutData::fromPluginMetaData(pluginInfo(p)); if (!aboutData.componentName().isEmpty()) { // Be sure the about data is not completely empty KAboutApplicationDialog aboutPlugin(aboutData, itemView()); aboutPlugin.exec(); return; } } } private: QPushButton *pushButton; }; class PluginsView : public QListView { Q_OBJECT public: - PluginsView(QWidget* parent = nullptr) + explicit PluginsView(QWidget* parent = nullptr) :QListView(parent) { setModel(new PluginsModel(this)); setItemDelegate(new LoadedPluginsDelegate(this)); setVerticalScrollMode(QListView::ScrollPerPixel); } ~PluginsView() override { // explicitly delete the delegate here since otherwise // we get spammed by warnings that the QPushButton we return // in createItemWidgets is deleted before the delegate // *sigh* - even dfaure says KWidgetItemDelegate is a crude hack delete itemDelegate(); } QSize sizeHint() const override { QSize ret = QListView::sizeHint(); ret.setWidth(qMax(ret.width(), sizeHintForColumn(0) + 30)); return ret; } }; LoadedPluginsDialog::LoadedPluginsDialog( QWidget* parent ) : QDialog( parent ) { setWindowTitle(i18n("Loaded Plugins")); QVBoxLayout* vbox = new QVBoxLayout(this); KTitleWidget* title = new KTitleWidget(this); title->setPixmap(QIcon::fromTheme(KAboutData::applicationData().programIconName()), KTitleWidget::ImageLeft); title->setText(i18n("Plugins loaded for %1", KAboutData::applicationData().displayName())); vbox->addWidget(title); vbox->addWidget(new PluginsView()); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close); connect(buttonBox, &QDialogButtonBox::accepted, this, &LoadedPluginsDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &LoadedPluginsDialog::reject); buttonBox->button(QDialogButtonBox::Close)->setDefault(true); vbox->addWidget(buttonBox); } #include "moc_loadedpluginsdialog.cpp" #include "loadedpluginsdialog.moc" diff --git a/shell/plugincontroller.cpp b/shell/plugincontroller.cpp index e14da5e26..a916cf329 100644 --- a/shell/plugincontroller.cpp +++ b/shell/plugincontroller.cpp @@ -1,792 +1,792 @@ /* This file is part of the KDE project Copyright 2004, 2007 Alexander Dymo Copyright 2006 Matt Rogers Based on code from Kopete Copyright (c) 2002-2003 Martijn Klingens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "plugincontroller.h" #include #include #include #include #include #include // TODO: remove once we no longer support old style plugins #include #include #include #include #include #include #include #include "core.h" #include "shellextension.h" #include "runcontroller.h" #include "debugcontroller.h" #include "documentationcontroller.h" #include "sourceformattercontroller.h" #include "projectcontroller.h" #include "ktexteditorpluginintegration.h" #include "debug.h" namespace { inline QString KEY_Plugins() { return QStringLiteral("Plugins"); } inline QString KEY_Suffix_Enabled() { return QStringLiteral("Enabled"); } inline QString KEY_LoadMode() { return QStringLiteral("X-KDevelop-LoadMode"); } inline QString KEY_Category() { return QStringLiteral("X-KDevelop-Category"); } inline QString KEY_Mode() { return QStringLiteral("X-KDevelop-Mode"); } inline QString KEY_Version() { return QStringLiteral("X-KDevelop-Version"); } inline QString KEY_Interfaces() { return QStringLiteral("X-KDevelop-Interfaces"); } inline QString KEY_Required() { return QStringLiteral("X-KDevelop-IRequired"); } inline QString KEY_Optional() { return QStringLiteral("X-KDevelop-IOptional"); } inline QString KEY_Global() { return QStringLiteral("Global"); } inline QString KEY_Project() { return QStringLiteral("Project"); } inline QString KEY_Gui() { return QStringLiteral("GUI"); } inline QString KEY_AlwaysOn() { return QStringLiteral("AlwaysOn"); } inline QString KEY_UserSelectable() { return QStringLiteral("UserSelectable"); } bool isUserSelectable( const KPluginMetaData& info ) { QString loadMode = info.value(KEY_LoadMode()); return loadMode.isEmpty() || loadMode == KEY_UserSelectable(); } bool isGlobalPlugin( const KPluginMetaData& info ) { return info.value(KEY_Category()) == KEY_Global(); } bool hasMandatoryProperties( const KPluginMetaData& info ) { QString mode = info.value(KEY_Mode()); if (mode.isEmpty()) { return false; } // when the plugin is installed into the versioned plugin path, it's good to go if (info.fileName().contains(QLatin1String("/kdevplatform/" QT_STRINGIFY(KDEVELOP_PLUGIN_VERSION) "/"))) { return true; } // the version property is only required when the plugin is not installed into the right directory QVariant version = info.rawData().value(KEY_Version()).toVariant(); if (version.isValid() && version.value() == KDEVELOP_PLUGIN_VERSION) { return true; } return false; } bool constraintsMatch( const KPluginMetaData& info, const QVariantMap& constraints) { for (auto it = constraints.begin(); it != constraints.end(); ++it) { const auto property = info.rawData().value(it.key()).toVariant(); if (!property.isValid()) { return false; } else if (property.canConvert()) { QSet values = property.toStringList().toSet(); QSet expected = it.value().toStringList().toSet(); if (!values.contains(expected)) { return false; } } else if (it.value() != property) { return false; } } return true; } struct Dependency { - Dependency(const QString &dependency) + explicit Dependency(const QString &dependency) : interface(dependency) { if (dependency.contains('@')) { const auto list = dependency.split('@', QString::SkipEmptyParts); if (list.size() == 2) { interface = list.at(0); pluginName = list.at(1); } } } QString interface; QString pluginName; }; } namespace KDevelop { class PluginControllerPrivate { public: QVector plugins; //map plugin infos to currently loaded plugins typedef QHash InfoToPluginMap; InfoToPluginMap loadedPlugins; // The plugin manager's mode. The mode is StartingUp until loadAllPlugins() // has finished loading the plugins, after which it is set to Running. // ShuttingDown and DoneShutdown are used during shutdown by the // async unloading of plugins. enum CleanupMode { Running /**< the plugin manager is running */, CleaningUp /**< the plugin manager is cleaning up for shutdown */, CleanupDone /**< the plugin manager has finished cleaning up */ }; CleanupMode cleanupMode; bool canUnload(const KPluginMetaData& plugin) { qCDebug(SHELL) << "checking can unload for:" << plugin.name() << plugin.value(KEY_LoadMode()); if (plugin.value(KEY_LoadMode()) == KEY_AlwaysOn()) { return false; } const QStringList interfaces = KPluginMetaData::readStringList(plugin.rawData(), KEY_Interfaces()); qCDebug(SHELL) << "checking dependencies:" << interfaces; foreach (const KPluginMetaData& info, loadedPlugins.keys()) { if (info.pluginId() != plugin.pluginId()) { QStringList dependencies = KPluginMetaData::readStringList(plugin.rawData(), KEY_Required()); dependencies += KPluginMetaData::readStringList(plugin.rawData(), KEY_Optional()); foreach (const QString& dep, dependencies) { Dependency dependency(dep); if (!dependency.pluginName.isEmpty() && dependency.pluginName != plugin.pluginId()) { continue; } if (interfaces.contains(dependency.interface) && !canUnload(info)) { return false; } } } } return true; } KPluginMetaData infoForId( const QString& id ) const { foreach (const KPluginMetaData& info, plugins) { if (info.pluginId() == id) { return info; } } return KPluginMetaData(); } /** * Iterate over all cached plugin infos, and call the functor for every enabled plugin. * * If an extension and/or pluginName is given, the functor will only be called for * those plugins matching this information. * * The functor should return false when the iteration can be stopped, and true if it * should be continued. */ template void foreachEnabledPlugin(F func, const QString &extension = {}, const QVariantMap& constraints = QVariantMap(), const QString &pluginName = {}) { foreach (const auto& info, plugins) { if ((pluginName.isEmpty() || info.pluginId() == pluginName) && (extension.isEmpty() || KPluginMetaData::readStringList(info.rawData(), KEY_Interfaces()).contains(extension)) && constraintsMatch(info, constraints) && isEnabled(info)) { if (!func(info)) { break; } } } } /** * Decide whether a plugin is enabled */ bool isEnabled(const KPluginMetaData& info) const { static const QStringList disabledPlugins = QString::fromLatin1(qgetenv("KDEV_DISABLE_PLUGINS")).split(';'); if (disabledPlugins.contains(info.pluginId())) { return false; } if (!isUserSelectable( info )) return true; // in case there's a user preference, prefer that const KConfigGroup grp = Core::self()->activeSession()->config()->group( KEY_Plugins() ); const QString pluginEnabledKey = info.pluginId() + KEY_Suffix_Enabled(); if (grp.hasKey(pluginEnabledKey)) { return grp.readEntry(pluginEnabledKey, true); } // in all other cases: figure out if we want to load that plugin by default const auto defaultPlugins = ShellExtension::getInstance()->defaultPlugins(); const bool isDefaultPlugin = defaultPlugins.isEmpty() || defaultPlugins.contains(info.pluginId()); if (isDefaultPlugin) { return true; } if (!isGlobalPlugin( info )) { QJsonValue enabledByDefault = info.rawData()[QStringLiteral("KPlugin")].toObject()[QStringLiteral("EnabledByDefault")]; return enabledByDefault.isNull() || enabledByDefault.toBool(); //We consider plugins enabled until specified otherwise } return false; } Core *core; }; PluginController::PluginController(Core *core) : IPluginController(), d(new PluginControllerPrivate) { setObjectName(QStringLiteral("PluginController")); d->core = core; QSet foundPlugins; auto newPlugins = KPluginLoader::findPlugins(QStringLiteral("kdevplatform/" QT_STRINGIFY(KDEVELOP_PLUGIN_VERSION)), [&](const KPluginMetaData& meta) { if (meta.serviceTypes().contains(QStringLiteral("KDevelop/Plugin"))) { foundPlugins.insert(meta.pluginId()); return true; } else { qWarning() << "Plugin" << meta.fileName() << "is installed into the kdevplatform plugin directory, but does not have" " \"KDevelop/Plugin\" set as the service type. This plugin will not be loaded."; return false; } }); qCDebug(SHELL) << "Found" << newPlugins.size() << " plugins:" << foundPlugins; d->plugins = newPlugins; KTextEditorIntegration::initialize(); const QVector ktePlugins = KPluginLoader::findPlugins(QStringLiteral("ktexteditor"), [](const KPluginMetaData & md) { return md.serviceTypes().contains(QStringLiteral("KTextEditor/Plugin")) && md.serviceTypes().contains(QStringLiteral("KDevelop/Plugin")); }); foundPlugins.clear(); std::for_each(ktePlugins.cbegin(), ktePlugins.cend(), [&foundPlugins](const KPluginMetaData& data) { foundPlugins << data.pluginId(); }); qCDebug(SHELL) << "Found" << ktePlugins.size() << " KTextEditor plugins:" << foundPlugins; foreach (const auto& info, ktePlugins) { auto data = info.rawData(); // add some KDevelop specific JSON data data[KEY_Category()] = KEY_Global(); data[KEY_Mode()] = KEY_Gui(); data[KEY_Version()] = KDEVELOP_PLUGIN_VERSION; d->plugins.append({data, info.fileName(), info.metaDataFileName()}); } d->cleanupMode = PluginControllerPrivate::Running; // Register the KDevelop::IPlugin* metatype so we can properly unload it qRegisterMetaType( "KDevelop::IPlugin*" ); } PluginController::~PluginController() { if ( d->cleanupMode != PluginControllerPrivate::CleanupDone ) { qCWarning(SHELL) << "Destructing plugin controller without going through the shutdown process!"; } delete d; } KPluginMetaData PluginController::pluginInfo( const IPlugin* plugin ) const { return d->loadedPlugins.key(const_cast(plugin)); } void PluginController::cleanup() { if(d->cleanupMode != PluginControllerPrivate::Running) { //qCDebug(SHELL) << "called when not running. state =" << d->cleanupMode; return; } d->cleanupMode = PluginControllerPrivate::CleaningUp; // Ask all plugins to unload while ( !d->loadedPlugins.isEmpty() ) { //Let the plugin do some stuff before unloading unloadPlugin(d->loadedPlugins.begin().value(), Now); } d->cleanupMode = PluginControllerPrivate::CleanupDone; } IPlugin* PluginController::loadPlugin( const QString& pluginName ) { return loadPluginInternal( pluginName ); } bool PluginController::isEnabled( const KPluginMetaData& info ) const { return d->isEnabled(info); } void PluginController::initialize() { QElapsedTimer timer; timer.start(); QMap pluginMap; if( ShellExtension::getInstance()->defaultPlugins().isEmpty() ) { foreach( const KPluginMetaData& pi, d->plugins ) { pluginMap.insert( pi.pluginId(), true ); } } else { // Get the default from the ShellExtension foreach( const QString& s, ShellExtension::getInstance()->defaultPlugins() ) { pluginMap.insert( s, true ); } } KConfigGroup grp = Core::self()->activeSession()->config()->group( KEY_Plugins() ); QMap entries = grp.entryMap(); QMap::Iterator it; for ( it = entries.begin(); it != entries.end(); ++it ) { const QString key = it.key(); if (key.endsWith(KEY_Suffix_Enabled())) { bool enabled = false; const QString pluginid = key.left(key.length() - 7); const bool defValue = pluginMap.value( pluginid, false ); enabled = grp.readEntry(key, defValue); pluginMap.insert( pluginid, enabled ); } } foreach( const KPluginMetaData& pi, d->plugins ) { if( isGlobalPlugin( pi ) ) { QMap::const_iterator it = pluginMap.constFind( pi.pluginId() ); if( it != pluginMap.constEnd() && ( it.value() || !isUserSelectable( pi ) ) ) { // Plugin is mentioned in pluginmap and the value is true, so try to load it loadPluginInternal( pi.pluginId() ); if(!grp.hasKey(pi.pluginId() + KEY_Suffix_Enabled())) { if( isUserSelectable( pi ) ) { // If plugin isn't listed yet, add it with true now grp.writeEntry(pi.pluginId() + KEY_Suffix_Enabled(), true); } } else if( grp.hasKey( pi.pluginId() + "Disabled" ) && !isUserSelectable( pi ) ) { // Remove now-obsolete entries grp.deleteEntry( pi.pluginId() + "Disabled" ); } } } } // Synchronize so we're writing out to the file. grp.sync(); qCDebug(SHELL) << "Done loading plugins - took:" << timer.elapsed() << "ms"; } QList PluginController::loadedPlugins() const { return d->loadedPlugins.values(); } bool PluginController::unloadPlugin( const QString & pluginId ) { IPlugin *thePlugin = plugin( pluginId ); bool canUnload = d->canUnload( d->infoForId( pluginId ) ); qCDebug(SHELL) << "Unloading plugin:" << pluginId << "?" << thePlugin << canUnload; if( thePlugin && canUnload ) { return unloadPlugin(thePlugin, Later); } return (canUnload && thePlugin); } bool PluginController::unloadPlugin(IPlugin* plugin, PluginDeletion deletion) { qCDebug(SHELL) << "unloading plugin:" << plugin << pluginInfo( plugin ).name(); emit unloadingPlugin(plugin); plugin->unload(); emit pluginUnloaded(plugin); //Remove the plugin from our list of plugins so we create a new //instance when we're asked for it again. //This is important to do right here, not later when the plugin really //vanishes. For example project re-opening might try to reload the plugin //and then would get the "old" pointer which will be deleted in the next //event loop run and thus causing crashes. for ( PluginControllerPrivate::InfoToPluginMap::Iterator it = d->loadedPlugins.begin(); it != d->loadedPlugins.end(); ++it ) { if ( it.value() == plugin ) { d->loadedPlugins.erase( it ); break; } } if (deletion == Later) plugin->deleteLater(); else delete plugin; return true; } KPluginMetaData PluginController::infoForPluginId( const QString &pluginId ) const { foreach (const KPluginMetaData& info, d->plugins) { if (info.pluginId() == pluginId) { return info; } } return KPluginMetaData(); } IPlugin *PluginController::loadPluginInternal( const QString &pluginId ) { QElapsedTimer timer; timer.start(); KPluginMetaData info = infoForPluginId( pluginId ); if ( !info.isValid() ) { qCWarning(SHELL) << "Unable to find a plugin named '" << pluginId << "'!" ; return nullptr; } if ( IPlugin* plugin = d->loadedPlugins.value( info ) ) { return plugin; } if ( !isEnabled( info ) ) { // Do not load disabled plugins qWarning() << "Not loading plugin named" << pluginId << "because it has been disabled!"; return nullptr; } if ( !hasMandatoryProperties( info ) ) { qWarning() << "Unable to load plugin named" << pluginId << "because not all mandatory properties are set."; return nullptr; } if ( info.value(KEY_Mode()) == KEY_Gui() && Core::self()->setupFlags() == Core::NoUi ) { qCDebug(SHELL) << "Not loading plugin named" << pluginId << "- Running in No-Ui mode, but the plugin says it needs a GUI"; return nullptr; } qCDebug(SHELL) << "Attempting to load" << pluginId << "- name:" << info.name(); emit loadingPlugin( info.pluginId() ); // first, ensure all dependencies are available and not disabled // this is unrelated to whether they are loaded already or not. // when we depend on e.g. A and B, but B cannot be found, then we // do not want to load A first and then fail on B and leave A loaded. // this would happen if we'd skip this step here and directly loadDependencies. QStringList missingInterfaces; if ( !hasUnresolvedDependencies( info, missingInterfaces ) ) { qWarning() << "Can't load plugin" << pluginId << "some of its required dependencies could not be fulfilled:" << missingInterfaces.join(QStringLiteral(",")); return nullptr; } // now ensure all dependencies are loaded QString failedDependency; if( !loadDependencies( info, failedDependency ) ) { qWarning() << "Can't load plugin" << pluginId << "because a required dependency could not be loaded:" << failedDependency; return nullptr; } // same for optional dependencies, but don't error out if anything fails loadOptionalDependencies( info ); // now we can finally load the plugin itself KPluginLoader loader(info.fileName()); auto factory = loader.factory(); if (!factory) { qWarning() << "Can't load plugin" << pluginId << "because a factory to load the plugin could not be obtained:" << loader.errorString(); return nullptr; } // now create it auto plugin = factory->create(d->core); if (!plugin) { if (auto katePlugin = factory->create(d->core, QVariantList() << info.pluginId())) { plugin = new KTextEditorIntegration::Plugin(katePlugin, d->core); } else { qCWarning(SHELL) << "Creating plugin" << pluginId << "failed."; return nullptr; } } KConfigGroup group = Core::self()->activeSession()->config()->group(KEY_Plugins()); // runtime errors such as missing executables on the system or such get checked now if (plugin->hasError()) { qCWarning(SHELL) << "Could not load plugin" << pluginId << ", it reported the error:" << plugin->errorDescription() << "Disabling the plugin now."; group.writeEntry(info.pluginId() + KEY_Suffix_Enabled(), false); // do the same as KPluginInfo did group.sync(); unloadPlugin(pluginId); return nullptr; } // yay, it all worked - the plugin is loaded d->loadedPlugins.insert(info, plugin); group.writeEntry(info.pluginId() + KEY_Suffix_Enabled(), true); // do the same as KPluginInfo did group.sync(); qCDebug(SHELL) << "Successfully loaded plugin" << pluginId << "from" << loader.fileName() << "- took:" << timer.elapsed() << "ms"; emit pluginLoaded( plugin ); return plugin; } IPlugin* PluginController::plugin( const QString& pluginId ) { KPluginMetaData info = infoForPluginId( pluginId ); if ( !info.isValid() ) return nullptr; return d->loadedPlugins.value( info ); } bool PluginController::hasUnresolvedDependencies( const KPluginMetaData& info, QStringList& missing ) const { QSet required = KPluginMetaData::readStringList(info.rawData(), KEY_Required()).toSet(); if (!required.isEmpty()) { d->foreachEnabledPlugin([&required] (const KPluginMetaData& plugin) -> bool { foreach (const QString& iface, KPluginMetaData::readStringList(plugin.rawData(), KEY_Interfaces())) { required.remove(iface); required.remove(iface + '@' + plugin.pluginId()); } return !required.isEmpty(); }); } // if we found all dependencies required should be empty now if (!required.isEmpty()) { missing = required.toList(); return false; } return true; } void PluginController::loadOptionalDependencies( const KPluginMetaData& info ) { const QStringList dependencies = KPluginMetaData::readStringList(info.rawData(), KEY_Optional()); foreach (const QString& dep, dependencies) { Dependency dependency(dep); if (!pluginForExtension(dependency.interface, dependency.pluginName)) { qCDebug(SHELL) << "Couldn't load optional dependency:" << dep << info.pluginId(); } } } bool PluginController::loadDependencies( const KPluginMetaData& info, QString& failedDependency ) { const QStringList dependencies = KPluginMetaData::readStringList(info.rawData(), KEY_Required()); foreach (const QString& value, dependencies) { Dependency dependency(value); if (!pluginForExtension(dependency.interface, dependency.pluginName)) { failedDependency = value; return false; } } return true; } IPlugin *PluginController::pluginForExtension(const QString &extension, const QString &pluginName, const QVariantMap& constraints) { IPlugin* plugin = nullptr; d->foreachEnabledPlugin([this, &plugin] (const KPluginMetaData& info) -> bool { plugin = d->loadedPlugins.value( info ); if( !plugin ) { plugin = loadPluginInternal( info.pluginId() ); } return !plugin; }, extension, constraints, pluginName); return plugin; } QList PluginController::allPluginsForExtension(const QString &extension, const QVariantMap& constraints) { //qCDebug(SHELL) << "Finding all Plugins for Extension:" << extension << "|" << constraints; QList plugins; d->foreachEnabledPlugin([this, &plugins] (const KPluginMetaData& info) -> bool { IPlugin* plugin = d->loadedPlugins.value( info ); if( !plugin) { plugin = loadPluginInternal( info.pluginId() ); } if (plugin && !plugins.contains(plugin)) { plugins << plugin; } return true; }, extension, constraints); return plugins; } QVector PluginController::queryExtensionPlugins(const QString& extension, const QVariantMap& constraints) const { QVector plugins; d->foreachEnabledPlugin([&plugins] (const KPluginMetaData& info) -> bool { plugins << info; return true; }, extension, constraints); return plugins; } QStringList PluginController::allPluginNames() { QStringList names; Q_FOREACH( const KPluginMetaData& info , d->plugins ) { names << info.pluginId(); } return names; } QList PluginController::queryPluginsForContextMenuExtensions(KDevelop::Context* context) const { // This fixes random order of extension menu items between different runs of KDevelop. // Without sorting we have random reordering of "Analyze With" submenu for example: // 1) "Cppcheck" actions, "Vera++" actions - first run // 2) "Vera++" actions, "Cppcheck" actions - some other run. QMultiMap sortedPlugins; for (auto it = d->loadedPlugins.constBegin(); it != d->loadedPlugins.constEnd(); ++it) { sortedPlugins.insert(it.key().name(), it.value()); } QList exts; foreach (IPlugin* plugin, sortedPlugins) { exts << plugin->contextMenuExtension(context); } exts << Core::self()->debugControllerInternal()->contextMenuExtension(context); exts << Core::self()->documentationControllerInternal()->contextMenuExtension(context); exts << Core::self()->sourceFormatterControllerInternal()->contextMenuExtension(context); exts << Core::self()->runControllerInternal()->contextMenuExtension(context); exts << Core::self()->projectControllerInternal()->contextMenuExtension(context); return exts; } QStringList PluginController::projectPlugins() { QStringList names; foreach (const KPluginMetaData& info, d->plugins) { if (info.value(KEY_Category()) == KEY_Project()) { names << info.pluginId(); } } return names; } void PluginController::loadProjectPlugins() { Q_FOREACH( const QString& name, projectPlugins() ) { loadPluginInternal( name ); } } void PluginController::unloadProjectPlugins() { Q_FOREACH( const QString& name, projectPlugins() ) { unloadPlugin( name ); } } QVector PluginController::allPluginInfos() const { return d->plugins; } void PluginController::updateLoadedPlugins() { QStringList defaultPlugins = ShellExtension::getInstance()->defaultPlugins(); KConfigGroup grp = Core::self()->activeSession()->config()->group( KEY_Plugins() ); foreach( const KPluginMetaData& info, d->plugins ) { if( isGlobalPlugin( info ) ) { bool enabled = grp.readEntry(info.pluginId() + KEY_Suffix_Enabled(), ( defaultPlugins.isEmpty() || defaultPlugins.contains( info.pluginId() ) ) ) || !isUserSelectable( info ); bool loaded = d->loadedPlugins.contains( info ); if( loaded && !enabled ) { qCDebug(SHELL) << "unloading" << info.pluginId(); if( !unloadPlugin( info.pluginId() ) ) { grp.writeEntry( info.pluginId() + KEY_Suffix_Enabled(), false ); } } else if( !loaded && enabled ) { loadPluginInternal( info.pluginId() ); } } } } void PluginController::resetToDefaults() { KSharedConfigPtr cfg = Core::self()->activeSession()->config(); cfg->deleteGroup( KEY_Plugins() ); cfg->sync(); KConfigGroup grp = cfg->group( KEY_Plugins() ); QStringList plugins = ShellExtension::getInstance()->defaultPlugins(); if( plugins.isEmpty() ) { foreach( const KPluginMetaData& info, d->plugins ) { plugins << info.pluginId(); } } foreach( const QString& s, plugins ) { grp.writeEntry(s + KEY_Suffix_Enabled(), true); } grp.sync(); } } diff --git a/shell/problemmodel.cpp b/shell/problemmodel.cpp index 6094bb4ec..87db994cd 100644 --- a/shell/problemmodel.cpp +++ b/shell/problemmodel.cpp @@ -1,372 +1,372 @@ /* * 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 #include #include namespace { QIcon iconForSeverity(KDevelop::IProblem::Severity severity) { switch (severity) { case KDevelop::IProblem::Hint: return QIcon::fromTheme(QStringLiteral("dialog-information")); case KDevelop::IProblem::Warning: return QIcon::fromTheme(QStringLiteral("dialog-warning")); case KDevelop::IProblem::Error: return QIcon::fromTheme(QStringLiteral("dialog-error")); case KDevelop::IProblem::NoSeverity: return {}; } return {}; } } struct ProblemModelPrivate { - ProblemModelPrivate(KDevelop::ProblemStore *store) + explicit ProblemModelPrivate(KDevelop::ProblemStore *store) : m_problems(store) , m_features(KDevelop::ProblemModel::NoFeatures) { } QScopedPointer m_problems; KDevelop::ProblemModel::Features m_features; }; namespace KDevelop { ProblemModel::ProblemModel(QObject * parent, ProblemStore *store) : QAbstractItemModel(parent) , d(new ProblemModelPrivate(store)) { if (!d->m_problems) { d->m_problems.reset(new FilteredProblemStore()); d->m_features = ScopeFilter | SeverityFilter | Grouping | CanByPassScopeFilter; } setScope(CurrentDocument); connect(ICore::self()->documentController(), &IDocumentController::documentActivated, this, &ProblemModel::setCurrentDocument); connect(ICore::self()->documentController(), &IDocumentController::documentClosed, this, &ProblemModel::closedDocument); /// CompletionSettings include a list of todo markers we care for, so need to update connect(ICore::self()->languageController()->completionSettings(), &ICompletionSettings::settingsChanged, this, &ProblemModel::forceFullUpdate); if (ICore::self()->documentController()->activeDocument()) { setCurrentDocument(ICore::self()->documentController()->activeDocument()); } connect(d->m_problems.data(), &ProblemStore::beginRebuild, this, &ProblemModel::onBeginRebuild); connect(d->m_problems.data(), &ProblemStore::endRebuild, this, &ProblemModel::onEndRebuild); connect(d->m_problems.data(), &ProblemStore::problemsChanged, this, &ProblemModel::problemsChanged); } ProblemModel::~ ProblemModel() { } int ProblemModel::rowCount(const QModelIndex& parent) const { if (!parent.isValid()) { return d->m_problems->count(); } else { return d->m_problems->count(reinterpret_cast(parent.internalPointer())); } } static QString displayUrl(const QUrl &url, const QUrl &base) { if (base.isParentOf(url)) { return url.toDisplayString(QUrl::PreferLocalFile).mid(base.toDisplayString(QUrl::PreferLocalFile).length()); } else { return ICore::self()->projectController()->prettyFileName(url, IProjectController::FormatPlain); } } QVariant ProblemModel::data(const QModelIndex& index, int role) const { if (!index.isValid()) return QVariant(); QUrl baseDirectory = d->m_problems->currentDocument().toUrl().adjusted(QUrl::RemoveFilename); IProblem::Ptr p = problemForIndex(index); if (!p.constData()) { if (role == Qt::DisplayRole && index.column() == Error) { ProblemStoreNode *node = reinterpret_cast(index.internalPointer()); if (node) { return node->label(); } } return {}; } if (role == SeverityRole) { return p->severity(); } else if (role == ProblemRole) { return QVariant::fromValue(p); } switch (role) { case Qt::DisplayRole: switch (index.column()) { case Source: return p->sourceString(); case Error: return p->description(); case File: return displayUrl(p->finalLocation().document.toUrl(), 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::DecorationRole: if (index.column() == Error) { return iconForSeverity(p->severity()); } break; case Qt::ToolTipRole: return p->explanation(); default: break; } return {}; } QModelIndex ProblemModel::parent(const QModelIndex& index) const { ProblemStoreNode *node = reinterpret_cast(index.internalPointer()); if (!node) { return {}; } ProblemStoreNode *parent = node->parent(); if (!parent || parent->isRoot()) { return {}; } int idx = parent->index(); return createIndex(idx, 0, parent); } QModelIndex ProblemModel::index(int row, int column, const QModelIndex& parent) const { if (row < 0 || row >= rowCount(parent) || column < 0 || column >= LastColumn) { return QModelIndex(); } ProblemStoreNode *parentNode = reinterpret_cast(parent.internalPointer()); const ProblemStoreNode *node = d->m_problems->findNode(row, parentNode); return createIndex(row, column, (void*)node); } int ProblemModel::columnCount(const QModelIndex& parent) const { Q_UNUSED(parent) return LastColumn; } IProblem::Ptr ProblemModel::problemForIndex(const QModelIndex& index) const { ProblemStoreNode *node = reinterpret_cast(index.internalPointer()); if (!node) { return {}; } else { return node->problem(); } } ProblemModel::Features ProblemModel::features() const { return d->m_features; } void ProblemModel::setFeatures(Features features) { d->m_features = features; } void ProblemModel::addProblem(const IProblem::Ptr &problem) { int c = d->m_problems->count(); beginInsertRows(QModelIndex(), c, c + 1); d->m_problems->addProblem(problem); endInsertRows(); } void ProblemModel::setProblems(const QVector &problems) { beginResetModel(); d->m_problems->setProblems(problems); endResetModel(); } void ProblemModel::clearProblems() { beginResetModel(); d->m_problems->clear(); endResetModel(); } QVector ProblemModel::problems(const KDevelop::IndexedString& document) { return d->m_problems->problems(document); } QVariant ProblemModel::headerData(int section, Qt::Orientation orientation, int role) const { Q_UNUSED(orientation); if (role != Qt::DisplayRole) return {}; 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 {}; } void ProblemModel::setCurrentDocument(IDocument* document) { Q_ASSERT(thread() == QThread::currentThread()); QUrl currentDocument = document->url(); /// Will trigger signals beginRebuild(), endRebuild() if problems change and are rebuilt d->m_problems->setCurrentDocument(IndexedString(currentDocument)); } void ProblemModel::closedDocument(IDocument* document) { if (IndexedString(document->url()) == d->m_problems->currentDocument()) { // reset current document d->m_problems->setCurrentDocument(IndexedString()); } } void ProblemModel::onBeginRebuild() { beginResetModel(); } void ProblemModel::onEndRebuild() { endResetModel(); } void ProblemModel::setShowImports(bool showImports) { Q_ASSERT(thread() == QThread::currentThread()); d->m_problems->setShowImports(showImports); } bool ProblemModel::showImports() { return d->m_problems->showImports(); } void ProblemModel::setScope(int scope) { Q_ASSERT(thread() == QThread::currentThread()); if (!features().testFlag(ScopeFilter)) scope = ProblemScope::BypassScopeFilter; /// Will trigger signals beginRebuild(), endRebuild() if problems change and are rebuilt d->m_problems->setScope(scope); } void ProblemModel::setSeverity(int severity) { switch (severity) { case KDevelop::IProblem::Error: setSeverities(KDevelop::IProblem::Error); break; case KDevelop::IProblem::Warning: setSeverities(KDevelop::IProblem::Error | KDevelop::IProblem::Warning); break; case KDevelop::IProblem::Hint: setSeverities(KDevelop::IProblem::Error | KDevelop::IProblem::Warning | KDevelop::IProblem::Hint); break; } } void ProblemModel::setSeverities(KDevelop::IProblem::Severities severities) { Q_ASSERT(thread() == QThread::currentThread()); /// Will trigger signals beginRebuild(), endRebuild() if problems change and are rebuilt d->m_problems->setSeverities(severities); } void ProblemModel::setGrouping(int grouping) { /// Will trigger signals beginRebuild(), endRebuild() if problems change and are rebuilt d->m_problems->setGrouping(grouping); } ProblemStore* ProblemModel::store() const { return d->m_problems.data(); } } diff --git a/shell/projectcontroller.cpp b/shell/projectcontroller.cpp index e1ee6037f..3b184a748 100644 --- a/shell/projectcontroller.cpp +++ b/shell/projectcontroller.cpp @@ -1,1195 +1,1195 @@ /* This file is part of KDevelop Copyright 2006 Adam Treat 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 "projectcontroller.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "core.h" // TODO: Should get rid off this include (should depend on IProject only) #include "project.h" #include "mainwindow.h" #include "shellextension.h" #include "plugincontroller.h" #include "configdialog.h" #include "uicontroller.h" #include "documentcontroller.h" #include "openprojectdialog.h" #include "sessioncontroller.h" #include "session.h" #include "debug.h" namespace KDevelop { class ProjectControllerPrivate { public: QList m_projects; QMap< IProject*, QList > m_projectPlugins; QPointer m_recentAction; Core* m_core; // IProject* m_currentProject; ProjectModel* model; QPointer m_openProject; QPointer m_fetchProject; QPointer m_closeProject; QPointer m_openConfig; IProjectDialogProvider* dialog; QList m_currentlyOpening; // project-file urls that are being opened ProjectController* q; ProjectBuildSetModel* buildset; bool m_foundProjectFile; //Temporary flag used while searching the hierarchy for a project file bool m_cleaningUp; //Temporary flag enabled while destroying the project-controller QPointer m_changesModel; QHash< IProject*, QPointer > m_parseJobs; // parse jobs that add files from the project to the background parser. - ProjectControllerPrivate( ProjectController* p ) + explicit ProjectControllerPrivate( ProjectController* p ) : m_core(nullptr), model(nullptr), dialog(nullptr), q(p), buildset(nullptr), m_foundProjectFile(false), m_cleaningUp(false) { } void unloadAllProjectPlugins() { if( m_projects.isEmpty() ) m_core->pluginControllerInternal()->unloadProjectPlugins(); } void projectConfig( QObject * obj ) { if( !obj ) return; Project* proj = qobject_cast(obj); if( !proj ) return; QVector configPages; auto mainWindow = m_core->uiController()->activeMainWindow(); ProjectConfigOptions options; options.developerFile = proj->developerFile(); options.developerTempFile = proj->developerTempFile(); options.projectTempFile = proj->projectTempFile(); options.project = proj; foreach (IPlugin* plugin, findPluginsForProject(proj)) { for (int i = 0; i < plugin->perProjectConfigPages(); ++i) { configPages.append(plugin->perProjectConfigPage(i, options, mainWindow)); } } std::sort(configPages.begin(), configPages.end(), [](const ConfigPage* a, const ConfigPage* b) { return a->name() < b->name(); }); auto cfgDlg = new KDevelop::ConfigDialog(configPages, mainWindow); cfgDlg->setAttribute(Qt::WA_DeleteOnClose); cfgDlg->setModal(true); QObject::connect(cfgDlg, &ConfigDialog::configSaved, cfgDlg, [this, proj](ConfigPage* page) { Q_UNUSED(page) Q_ASSERT_X(proj, Q_FUNC_INFO, "ConfigDialog signalled project config change, but no project set for configuring!"); emit q->projectConfigurationChanged(proj); }); cfgDlg->setWindowTitle(i18n("Configure Project %1", proj->name())); QObject::connect(cfgDlg, &KDevelop::ConfigDialog::finished, [this, proj]() { proj->projectConfiguration()->sync(); }); cfgDlg->show(); } void saveListOfOpenedProjects() { auto activeSession = Core::self()->activeSession(); if (!activeSession) { return; } QList openProjects; openProjects.reserve( m_projects.size() ); foreach( IProject* project, m_projects ) { openProjects.append(project->projectFile().toUrl()); } activeSession->setContainedProjects( openProjects ); } // Recursively collects builder dependencies for a project. static void collectBuilders( QList< IProjectBuilder* >& destination, IProjectBuilder* topBuilder, IProject* project ) { QList< IProjectBuilder* > auxBuilders = topBuilder->additionalBuilderPlugins( project ); destination.append( auxBuilders ); foreach( IProjectBuilder* auxBuilder, auxBuilders ) { collectBuilders( destination, auxBuilder, project ); } } QVector findPluginsForProject( IProject* project ) { QList plugins = m_core->pluginController()->loadedPlugins(); QVector projectPlugins; QList buildersForKcm; // Important to also include the "top" builder for the project, so // projects with only one such builder are kept working. Otherwise the project config // dialog is empty for such cases. if( IBuildSystemManager* buildSystemManager = project->buildSystemManager() ) { buildersForKcm << buildSystemManager->builder(); collectBuilders( buildersForKcm, buildSystemManager->builder(), project ); } foreach(auto plugin, plugins) { auto info = m_core->pluginController()->pluginInfo(plugin); IProjectFileManager* manager = plugin->extension(); if( manager && manager != project->projectFileManager() ) { // current plugin is a manager but does not apply to given project, skip continue; } IProjectBuilder* builder = plugin->extension(); if ( builder && !buildersForKcm.contains( builder ) ) { continue; } qCDebug(SHELL) << "Using plugin" << info.pluginId() << "for project" << project->name(); projectPlugins << plugin; } return projectPlugins; } void updateActionStates( Context* ctx ) { ProjectItemContext* itemctx = dynamic_cast(ctx); m_openConfig->setEnabled( itemctx && itemctx->items().count() == 1 ); m_closeProject->setEnabled( itemctx && itemctx->items().count() > 0 ); } void openProjectConfig() { ProjectItemContext* ctx = dynamic_cast( Core::self()->selectionController()->currentSelection() ); if( ctx && ctx->items().count() == 1 ) { q->configureProject( ctx->items().at(0)->project() ); } } void closeSelectedProjects() { ProjectItemContext* ctx = dynamic_cast( Core::self()->selectionController()->currentSelection() ); if( ctx && ctx->items().count() > 0 ) { QSet projects; foreach( ProjectBaseItem* item, ctx->items() ) { projects.insert( item->project() ); } foreach( IProject* project, projects ) { q->closeProject( project ); } } } void importProject(const QUrl& url_) { QUrl url(url_); if (url.isLocalFile()) { const QString path = QFileInfo(url.toLocalFile()).canonicalFilePath(); if (!path.isEmpty()) { url = QUrl::fromLocalFile(path); } } if ( !url.isValid() ) { KMessageBox::error(Core::self()->uiControllerInternal()->activeMainWindow(), i18n("Invalid Location: %1", url.toDisplayString(QUrl::PreferLocalFile))); return; } if ( m_currentlyOpening.contains(url)) { qCDebug(SHELL) << "Already opening " << url << ". Aborting."; KPassivePopup::message( i18n( "Project already being opened"), i18n( "Already opening %1, not opening again", url.toDisplayString(QUrl::PreferLocalFile) ), m_core->uiController()->activeMainWindow() ); return; } foreach( IProject* project, m_projects ) { if ( url == project->projectFile().toUrl() ) { if ( dialog->userWantsReopen() ) { // close first, then open again by falling through q->closeProject(project); } else { // abort return; } } } m_currentlyOpening += url; m_core->pluginControllerInternal()->loadProjectPlugins(); Project* project = new Project(); QObject::connect(project, &Project::aboutToOpen, q, &ProjectController::projectAboutToBeOpened); if ( !project->open( Path(url) ) ) { m_currentlyOpening.removeAll(url); q->abortOpeningProject(project); project->deleteLater(); } } void areaChanged(Sublime::Area* area) { KActionCollection* ac = m_core->uiControllerInternal()->defaultMainWindow()->actionCollection(); ac->action(QStringLiteral("commit_current_project"))->setEnabled(area->objectName() == QLatin1String("code")); ac->action(QStringLiteral("commit_current_project"))->setVisible(area->objectName() == QLatin1String("code")); } }; IProjectDialogProvider::IProjectDialogProvider() {} IProjectDialogProvider::~IProjectDialogProvider() {} ProjectDialogProvider::ProjectDialogProvider(ProjectControllerPrivate* const p) : d(p) {} ProjectDialogProvider::~ProjectDialogProvider() {} bool writeNewProjectFile( const QString& localConfigFile, const QString& name, const QString& createdFrom, const QString& manager ) { KSharedConfigPtr cfg = KSharedConfig::openConfig( localConfigFile, KConfig::SimpleConfig ); if (!cfg->isConfigWritable(true)) { qCDebug(SHELL) << "can't write to configfile"; return false; } KConfigGroup grp = cfg->group( "Project" ); grp.writeEntry( "Name", name ); grp.writeEntry( "CreatedFrom", createdFrom ); grp.writeEntry( "Manager", manager ); cfg->sync(); return true; } bool writeProjectSettingsToConfigFile(const QUrl& projectFileUrl, OpenProjectDialog* dlg) { if ( !projectFileUrl.isLocalFile() ) { QTemporaryFile tmp; if ( !tmp.open() ) { return false; } if ( !writeNewProjectFile( tmp.fileName(), dlg->projectName(), dlg->selectedUrl().fileName(), dlg->projectManager() ) ) { return false; } // explicitly close file before uploading it, see also: https://bugs.kde.org/show_bug.cgi?id=254519 tmp.close(); auto uploadJob = KIO::file_copy(QUrl::fromLocalFile(tmp.fileName()), projectFileUrl); KJobWidgets::setWindow(uploadJob, Core::self()->uiControllerInternal()->defaultMainWindow()); return uploadJob->exec(); } // Here and above we take .filename() part of the selectedUrl() to make it relative to the project root, // thus keeping .kdev file relocatable return writeNewProjectFile( projectFileUrl.toLocalFile(), dlg->projectName(), dlg->selectedUrl().fileName(), dlg->projectManager() ); } bool projectFileExists( const QUrl& u ) { if( u.isLocalFile() ) { return QFileInfo::exists( u.toLocalFile() ); } else { auto statJob = KIO::stat(u, KIO::StatJob::DestinationSide, 0); KJobWidgets::setWindow(statJob, Core::self()->uiControllerInternal()->activeMainWindow()); return statJob->exec(); } } bool equalProjectFile( const QString& configPath, OpenProjectDialog* dlg ) { KSharedConfigPtr cfg = KSharedConfig::openConfig( configPath, KConfig::SimpleConfig ); KConfigGroup grp = cfg->group( "Project" ); QString defaultName = dlg->projectFileUrl().adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).fileName(); return (grp.readEntry( "Name", QString() ) == dlg->projectName() || dlg->projectName() == defaultName) && grp.readEntry( "Manager", QString() ) == dlg->projectManager(); } QUrl ProjectDialogProvider::askProjectConfigLocation(bool fetch, const QUrl& startUrl) { Q_ASSERT(d); OpenProjectDialog dlg( fetch, startUrl, Core::self()->uiController()->activeMainWindow() ); if(dlg.exec() == QDialog::Rejected) return QUrl(); QUrl projectFileUrl = dlg.projectFileUrl(); qCDebug(SHELL) << "selected project:" << projectFileUrl << dlg.projectName() << dlg.projectManager(); if ( dlg.projectManager() == "" ) { return projectFileUrl; } // controls if existing project file should be saved bool writeProjectConfigToFile = true; if( projectFileExists( projectFileUrl ) ) { // check whether config is equal bool shouldAsk = true; if( projectFileUrl == dlg.selectedUrl() ) { if( projectFileUrl.isLocalFile() ) { shouldAsk = !equalProjectFile( projectFileUrl.toLocalFile(), &dlg ); } else { shouldAsk = false; QTemporaryFile tmpFile; if (tmpFile.open()) { auto downloadJob = KIO::file_copy(projectFileUrl, QUrl::fromLocalFile(tmpFile.fileName())); KJobWidgets::setWindow(downloadJob, qApp->activeWindow()); if (downloadJob->exec()) { shouldAsk = !equalProjectFile(tmpFile.fileName(), &dlg); } } } } if ( shouldAsk ) { KGuiItem yes = KStandardGuiItem::yes(); yes.setText(i18n("Override")); yes.setToolTip(i18nc("@info:tooltip", "Continue to open the project and use the just provided project configuration.")); yes.setIcon(QIcon()); KGuiItem no = KStandardGuiItem::no(); no.setText(i18n("Open Existing File")); no.setToolTip(i18nc("@info:tooltip", "Continue to open the project but use the existing project configuration.")); no.setIcon(QIcon()); KGuiItem cancel = KStandardGuiItem::cancel(); cancel.setToolTip(i18nc("@info:tooltip", "Cancel and do not open the project.")); int ret = KMessageBox::questionYesNoCancel(qApp->activeWindow(), i18n("There already exists a project configuration file at %1.\n" "Do you want to override it or open the existing file?", projectFileUrl.toDisplayString(QUrl::PreferLocalFile)), i18n("Override existing project configuration"), yes, no, cancel ); if ( ret == KMessageBox::No ) { writeProjectConfigToFile = false; } else if ( ret == KMessageBox::Cancel ) { return QUrl(); } // else fall through and write new file } else { writeProjectConfigToFile = false; } } if (writeProjectConfigToFile) { if (!writeProjectSettingsToConfigFile(projectFileUrl, &dlg)) { KMessageBox::error(d->m_core->uiControllerInternal()->defaultMainWindow(), i18n("Unable to create configuration file %1", projectFileUrl.url())); return QUrl(); } } return projectFileUrl; } bool ProjectDialogProvider::userWantsReopen() { Q_ASSERT(d); return (KMessageBox::questionYesNo( d->m_core->uiControllerInternal()->defaultMainWindow(), i18n( "Reopen the current project?" ) ) == KMessageBox::No) ? false : true; } void ProjectController::setDialogProvider(IProjectDialogProvider* dialog) { Q_ASSERT(d->dialog); delete d->dialog; d->dialog = dialog; } ProjectController::ProjectController( Core* core ) : IProjectController( core ), d( new ProjectControllerPrivate( this ) ) { qRegisterMetaType>(); setObjectName(QStringLiteral("ProjectController")); d->m_core = core; d->model = new ProjectModel(); //NOTE: this is required to be called here, such that the // actions are available when the UI controller gets // initialized *before* the project controller if (Core::self()->setupFlags() != Core::NoUi) { setupActions(); } } void ProjectController::setupActions() { KActionCollection * ac = d->m_core->uiControllerInternal()->defaultMainWindow()->actionCollection(); QAction*action; d->m_openProject = action = ac->addAction( QStringLiteral("project_open") ); action->setText(i18nc( "@action", "Open / Import Project..." ) ); action->setToolTip( i18nc( "@info:tooltip", "Open or import project" ) ); action->setWhatsThis( i18nc( "@info:whatsthis", "Open an existing KDevelop 4 project or import " "an existing Project into KDevelop 4. This entry " "allows one to select a KDevelop4 project file " "or an existing directory to open it in KDevelop. " "When opening an existing directory that does " "not yet have a KDevelop4 project file, the file " "will be created." ) ); action->setIcon(QIcon::fromTheme(QStringLiteral("project-open"))); connect(action, &QAction::triggered, this, [&] { openProject(); }); d->m_fetchProject = action = ac->addAction( QStringLiteral("project_fetch") ); action->setText(i18nc( "@action", "Fetch Project..." ) ); action->setIcon( QIcon::fromTheme( QStringLiteral("edit-download") ) ); action->setToolTip( i18nc( "@info:tooltip", "Fetch project" ) ); action->setWhatsThis( i18nc( "@info:whatsthis", "Guides the user through the project fetch " "and then imports it into KDevelop 4." ) ); // action->setIcon(QIcon::fromTheme("project-open")); connect( action, &QAction::triggered, this, &ProjectController::fetchProject ); // action = ac->addAction( "project_close" ); // action->setText( i18n( "C&lose Project" ) ); // connect( action, SIGNAL(triggered(bool)), SLOT(closeProject()) ); // action->setToolTip( i18n( "Close project" ) ); // action->setWhatsThis( i18n( "Closes the current project." ) ); // action->setEnabled( false ); d->m_closeProject = action = ac->addAction( QStringLiteral("project_close") ); connect( action, &QAction::triggered, this, [&] { d->closeSelectedProjects(); } ); action->setText( i18nc( "@action", "Close Project(s)" ) ); action->setIcon( QIcon::fromTheme( QStringLiteral("project-development-close") ) ); action->setToolTip( i18nc( "@info:tooltip", "Closes all currently selected projects" ) ); action->setEnabled( false ); d->m_openConfig = action = ac->addAction( QStringLiteral("project_open_config") ); connect( action, &QAction::triggered, this, [&] { d->openProjectConfig(); } ); action->setText( i18n( "Open Configuration..." ) ); action->setIcon( QIcon::fromTheme(QStringLiteral("configure")) ); action->setEnabled( false ); action = ac->addAction( QStringLiteral("commit_current_project") ); connect( action, &QAction::triggered, this, &ProjectController::commitCurrentProject ); action->setText( i18n( "Commit Current Project..." ) ); action->setIconText( i18n( "Commit..." ) ); action->setIcon( QIcon::fromTheme(QStringLiteral("svn-commit")) ); connect(d->m_core->uiControllerInternal()->defaultMainWindow(), &MainWindow::areaChanged, this, [&] (Sublime::Area* area) { d->areaChanged(area); }); d->m_core->uiControllerInternal()->area(0, QStringLiteral("code"))->addAction(action); KSharedConfig * config = KSharedConfig::openConfig().data(); // KConfigGroup group = config->group( "General Options" ); d->m_recentAction = KStandardAction::openRecent(this, SLOT(openProject(QUrl)), this); ac->addAction( QStringLiteral("project_open_recent"), d->m_recentAction ); d->m_recentAction->setText( i18n( "Open Recent Project" ) ); d->m_recentAction->setWhatsThis( i18nc( "@info:whatsthis", "Opens recently opened project." ) ); d->m_recentAction->loadEntries( KConfigGroup(config, "RecentProjects") ); QAction* openProjectForFileAction = new QAction( this ); ac->addAction(QStringLiteral("project_open_for_file"), openProjectForFileAction); openProjectForFileAction->setText(i18n("Open Project for Current File")); connect( openProjectForFileAction, &QAction::triggered, this, &ProjectController::openProjectForUrlSlot); } ProjectController::~ProjectController() { delete d->model; delete d->dialog; delete d; } void ProjectController::cleanup() { if ( d->m_currentlyOpening.isEmpty() ) { d->saveListOfOpenedProjects(); } d->m_cleaningUp = true; if( buildSetModel() ) { buildSetModel()->storeToSession( Core::self()->activeSession() ); } closeAllProjects(); } void ProjectController::initialize() { d->buildset = new ProjectBuildSetModel( this ); buildSetModel()->loadFromSession( Core::self()->activeSession() ); connect( this, &ProjectController::projectOpened, d->buildset, &ProjectBuildSetModel::loadFromProject ); connect( this, &ProjectController::projectClosing, d->buildset, &ProjectBuildSetModel::saveToProject ); connect( this, &ProjectController::projectClosed, d->buildset, &ProjectBuildSetModel::projectClosed ); loadSettings(false); d->dialog = new ProjectDialogProvider(d); QDBusConnection::sessionBus().registerObject( QStringLiteral("/org/kdevelop/ProjectController"), this, QDBusConnection::ExportScriptableSlots ); KSharedConfigPtr config = Core::self()->activeSession()->config(); KConfigGroup group = config->group( "General Options" ); QList openProjects = group.readEntry( "Open Projects", QList() ); QMetaObject::invokeMethod(this, "openProjects", Qt::QueuedConnection, Q_ARG(QList, openProjects)); connect( Core::self()->selectionController(), &ISelectionController::selectionChanged, this, [&] (Context* ctx) { d->updateActionStates(ctx); } ); } void ProjectController::openProjects(const QList& projects) { foreach (const QUrl& url, projects) openProject(url); } void ProjectController::loadSettings( bool projectIsLoaded ) { Q_UNUSED(projectIsLoaded) } void ProjectController::saveSettings( bool projectIsLoaded ) { Q_UNUSED( projectIsLoaded ); } int ProjectController::projectCount() const { return d->m_projects.count(); } IProject* ProjectController::projectAt( int num ) const { if( !d->m_projects.isEmpty() && num >= 0 && num < d->m_projects.count() ) return d->m_projects.at( num ); return nullptr; } QList ProjectController::projects() const { return d->m_projects; } void ProjectController::eventuallyOpenProjectFile(KIO::Job* _job, KIO::UDSEntryList entries ) { KIO::SimpleJob* job(dynamic_cast(_job)); Q_ASSERT(job); foreach(const KIO::UDSEntry& entry, entries) { if(d->m_foundProjectFile) break; if(!entry.isDir()) { QString name = entry.stringValue( KIO::UDSEntry::UDS_NAME ); if(name.endsWith(QLatin1String(".kdev4"))) { //We have found a project-file, open it openProject(Path(Path(job->url()), name).toUrl()); d->m_foundProjectFile = true; } } } } void ProjectController::openProjectForUrlSlot(bool) { if(ICore::self()->documentController()->activeDocument()) { QUrl url = ICore::self()->documentController()->activeDocument()->url(); IProject* project = ICore::self()->projectController()->findProjectForUrl(url); if(!project) { openProjectForUrl(url); }else{ KMessageBox::error(Core::self()->uiController()->activeMainWindow(), i18n("Project already open: %1", project->name())); } }else{ KMessageBox::error(Core::self()->uiController()->activeMainWindow(), i18n("No active document")); } } void ProjectController::openProjectForUrl(const QUrl& sourceUrl) { Q_ASSERT(!sourceUrl.isRelative()); QUrl dirUrl = sourceUrl.adjusted(QUrl::RemoveFilename); QUrl testAt = dirUrl; d->m_foundProjectFile = false; while(!testAt.path().isEmpty()) { KIO::ListJob* job = KIO::listDir(testAt); connect(job, &KIO::ListJob::entries, this, &ProjectController::eventuallyOpenProjectFile); KJobWidgets::setWindow(job, ICore::self()->uiController()->activeMainWindow()); job->exec(); if(d->m_foundProjectFile) { //Fine! We have directly opened the project d->m_foundProjectFile = false; return; } QUrl oldTest = testAt.adjusted(QUrl::RemoveFilename); if(oldTest == testAt) break; } QUrl askForOpen = d->dialog->askProjectConfigLocation(false, dirUrl); if(askForOpen.isValid()) openProject(askForOpen); } void ProjectController::openProject( const QUrl &projectFile ) { QUrl url = projectFile; if ( url.isEmpty() ) { url = d->dialog->askProjectConfigLocation(false); if ( url.isEmpty() ) { return; } } Q_ASSERT(!url.isRelative()); QList existingSessions; if(!Core::self()->sessionController()->activeSession()->containedProjects().contains(url)) { foreach( const Session* session, Core::self()->sessionController()->sessions()) { if(session->containedProjects().contains(url)) { existingSessions << session; #if 0 ///@todo Think about this! Problem: The session might already contain files, the debugger might be active, etc. //If this session is empty, close it if(Core::self()->sessionController()->activeSession()->description().isEmpty()) { //Terminate this instance of kdevelop if the user agrees foreach(Sublime::MainWindow* window, Core::self()->uiController()->controller()->mainWindows()) window->close(); } #endif } } } if ( ! existingSessions.isEmpty() ) { QDialog dialog(Core::self()->uiControllerInternal()->activeMainWindow()); dialog.setWindowTitle(i18n("Project Already Open")); auto mainLayout = new QVBoxLayout(&dialog); mainLayout->addWidget(new QLabel(i18n("The project you're trying to open is already open in at least one " "other session.
What do you want to do?"))); QGroupBox sessions; sessions.setLayout(new QVBoxLayout); QRadioButton* newSession = new QRadioButton(i18n("Add project to current session")); sessions.layout()->addWidget(newSession); newSession->setChecked(true); foreach ( const Session* session, existingSessions ) { QRadioButton* button = new QRadioButton(i18n("Open session %1", session->description())); button->setProperty("sessionid", session->id().toString()); sessions.layout()->addWidget(button); } sessions.layout()->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Expanding)); mainLayout->addWidget(&sessions); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Abort); auto okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); connect(buttonBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); mainLayout->addWidget(buttonBox); bool success = dialog.exec(); if (!success) return; foreach ( const QObject* obj, sessions.children() ) { if ( const QRadioButton* button = qobject_cast(obj) ) { QString sessionid = button->property("sessionid").toString(); if ( button->isChecked() && ! sessionid.isEmpty() ) { Core::self()->sessionController()->loadSession(sessionid); return; } } } } if ( url.isEmpty() ) { url = d->dialog->askProjectConfigLocation(false); } if ( !url.isEmpty() ) { d->importProject(url); } } void ProjectController::fetchProject() { QUrl url = d->dialog->askProjectConfigLocation(true); if ( !url.isEmpty() ) { d->importProject(url); } } void ProjectController::projectImportingFinished( IProject* project ) { if( !project ) { qWarning() << "OOOPS: 0-pointer project"; return; } IPlugin *managerPlugin = project->managerPlugin(); QList pluglist; pluglist.append( managerPlugin ); d->m_projectPlugins.insert( project, pluglist ); d->m_projects.append( project ); if ( d->m_currentlyOpening.isEmpty() ) { d->saveListOfOpenedProjects(); } if (Core::self()->setupFlags() != Core::NoUi) { d->m_recentAction->addUrl( project->projectFile().toUrl() ); KSharedConfig * config = KSharedConfig::openConfig().data(); KConfigGroup recentGroup = config->group("RecentProjects"); d->m_recentAction->saveEntries( recentGroup ); config->sync(); } Q_ASSERT(d->m_currentlyOpening.contains(project->projectFile().toUrl())); d->m_currentlyOpening.removeAll(project->projectFile().toUrl()); emit projectOpened( project ); reparseProject(project); } // helper method for closeProject() void ProjectController::unloadUnusedProjectPlugins(IProject* proj) { QList pluginsForProj = d->m_projectPlugins.value( proj ); d->m_projectPlugins.remove( proj ); QList otherProjectPlugins; Q_FOREACH( const QList& _list, d->m_projectPlugins ) { otherProjectPlugins << _list; } QSet pluginsForProjSet = QSet::fromList( pluginsForProj ); QSet otherPrjPluginsSet = QSet::fromList( otherProjectPlugins ); // loaded - target = tobe unloaded. QSet tobeRemoved = pluginsForProjSet.subtract( otherPrjPluginsSet ); Q_FOREACH( IPlugin* _plugin, tobeRemoved ) { KPluginMetaData _plugInfo = Core::self()->pluginController()->pluginInfo( _plugin ); if( _plugInfo.isValid() ) { QString _plugName = _plugInfo.pluginId(); qCDebug(SHELL) << "about to unloading :" << _plugName; Core::self()->pluginController()->unloadPlugin( _plugName ); } } } // helper method for closeProject() void ProjectController::closeAllOpenedFiles(IProject* proj) { foreach(IDocument* doc, Core::self()->documentController()->openDocuments()) { if (proj->inProject(IndexedString(doc->url()))) { doc->close(); } } } // helper method for closeProject() void ProjectController::initializePluginCleanup(IProject* proj) { // Unloading (and thus deleting) these plugins is not a good idea just yet // as we're being called by the view part and it gets deleted when we unload the plugin(s) // TODO: find a better place to unload connect(proj, &IProject::destroyed, this, [&] { d->unloadAllProjectPlugins(); }); } void ProjectController::takeProject(IProject* proj) { if (!proj) { return; } // loading might have failed d->m_currentlyOpening.removeAll(proj->projectFile().toUrl()); d->m_projects.removeAll(proj); emit projectClosing(proj); //Core::self()->saveSettings(); // The project file is being closed. // Now we can save settings for all of the Core // objects including this one!! unloadUnusedProjectPlugins(proj); closeAllOpenedFiles(proj); proj->close(); if (d->m_projects.isEmpty()) { initializePluginCleanup(proj); } if(!d->m_cleaningUp) d->saveListOfOpenedProjects(); emit projectClosed(proj); } void ProjectController::closeProject(IProject* proj) { takeProject(proj); proj->deleteLater(); // be safe when deleting } void ProjectController::closeAllProjects() { foreach (auto project, d->m_projects) { closeProject(project); } } void ProjectController::abortOpeningProject(IProject* proj) { d->m_currentlyOpening.removeAll(proj->projectFile().toUrl()); emit projectOpeningAborted(proj); } ProjectModel* ProjectController::projectModel() { return d->model; } IProject* ProjectController::findProjectForUrl( const QUrl& url ) const { if (d->m_projects.isEmpty()) { return nullptr; } ProjectBaseItem* item = d->model->itemForPath(IndexedString(url)); if (item) { return item->project(); } return nullptr; } IProject* ProjectController::findProjectByName( const QString& name ) { Q_FOREACH( IProject* proj, d->m_projects ) { if( proj->name() == name ) { return proj; } } return nullptr; } void ProjectController::configureProject( IProject* project ) { d->projectConfig( project ); } void ProjectController::addProject(IProject* project) { Q_ASSERT(project); if (d->m_projects.contains(project)) { qWarning() << "Project already tracked by this project controller:" << project; return; } // fake-emit signals so listeners are aware of a new project being added emit projectAboutToBeOpened(project); project->setParent(this); d->m_projects.append(project); emit projectOpened(project); } bool ProjectController::isProjectNameUsed( const QString& name ) const { foreach( IProject* p, projects() ) { if( p->name() == name ) { return true; } } return false; } QUrl ProjectController::projectsBaseDirectory() const { KConfigGroup group = ICore::self()->activeSession()->config()->group( "Project Manager" ); return group.readEntry( "Projects Base Directory", QUrl::fromLocalFile( QDir::homePath() + "/projects" ) ); } QString ProjectController::prettyFilePath(const QUrl& url, FormattingOptions format) const { IProject* project = Core::self()->projectController()->findProjectForUrl(url); if(!project) { // Find a project with the correct base directory at least foreach(IProject* candidateProject, Core::self()->projectController()->projects()) { if(candidateProject->path().toUrl().isParentOf(url)) { project = candidateProject; break; } } } Path parent = Path(url).parent(); QString prefixText; if (project) { if (format == FormatHtml) { prefixText = "" + project->name() + "/"; } else { prefixText = project->name() + ':'; } QString relativePath = project->path().relativePath(parent); if(relativePath.startsWith(QLatin1String("./"))) { relativePath = relativePath.mid(2); } if (!relativePath.isEmpty()) { prefixText += relativePath + '/'; } } else { prefixText = parent.pathOrUrl() + '/'; } return prefixText; } QString ProjectController::prettyFileName(const QUrl& url, FormattingOptions format) const { IProject* project = Core::self()->projectController()->findProjectForUrl(url); if(project && project->path() == Path(url)) { if (format == FormatHtml) { return "" + project->name() + ""; } else { return project->name(); } } QString prefixText = prettyFilePath( url, format ); if (format == FormatHtml) { return prefixText + "" + url.fileName() + ""; } else { return prefixText + url.fileName(); } } ContextMenuExtension ProjectController::contextMenuExtension ( Context* ctx ) { ContextMenuExtension ext; if ( ctx->type() != Context::ProjectItemContext || !static_cast(ctx)->items().isEmpty() ) { return ext; } ext.addAction(ContextMenuExtension::ProjectGroup, d->m_openProject); ext.addAction(ContextMenuExtension::ProjectGroup, d->m_fetchProject); ext.addAction(ContextMenuExtension::ProjectGroup, d->m_recentAction); return ext; } ProjectBuildSetModel* ProjectController::buildSetModel() { return d->buildset; } ProjectChangesModel* ProjectController::changesModel() { if(!d->m_changesModel) d->m_changesModel=new ProjectChangesModel(this); return d->m_changesModel; } void ProjectController::commitCurrentProject() { IDocument* doc=ICore::self()->documentController()->activeDocument(); if(!doc) return; QUrl url=doc->url(); IProject* project = ICore::self()->projectController()->findProjectForUrl(url); if(project && project->versionControlPlugin()) { IPlugin* plugin = project->versionControlPlugin(); IBasicVersionControl* vcs=plugin->extension(); if(vcs) { ICore::self()->documentController()->saveAllDocuments(KDevelop::IDocument::Silent); const Path basePath = project->path(); VCSCommitDiffPatchSource* patchSource = new VCSCommitDiffPatchSource(new VCSStandardDiffUpdater(vcs, basePath.toUrl())); bool ret = showVcsDiff(patchSource); if(!ret) { VcsCommitDialog *commitDialog = new VcsCommitDialog(patchSource); commitDialog->setCommitCandidates(patchSource->infos()); commitDialog->exec(); } } } } QString ProjectController::mapSourceBuild( const QString& path_, bool reverse, bool fallbackRoot ) const { Path path(path_); IProject* sourceDirProject = nullptr, *buildDirProject = nullptr; Q_FOREACH(IProject* proj, d->m_projects) { if(proj->path().isParentOf(path) || proj->path() == path) sourceDirProject = proj; if(proj->buildSystemManager()) { Path buildDir = proj->buildSystemManager()->buildDirectory(proj->projectItem()); if(buildDir.isValid() && (buildDir.isParentOf(path) || buildDir == path)) buildDirProject = proj; } } if(!reverse) { // Map-target is the build directory if(sourceDirProject && sourceDirProject->buildSystemManager()) { // We're in the source, map into the build directory QString relativePath = sourceDirProject->path().relativePath(path); Path build = sourceDirProject->buildSystemManager()->buildDirectory(sourceDirProject->projectItem()); build.addPath(relativePath); while(!QFile::exists(build.path())) build = build.parent(); return build.pathOrUrl(); }else if(buildDirProject && fallbackRoot) { // We're in the build directory, map to the build directory root return buildDirProject->buildSystemManager()->buildDirectory(buildDirProject->projectItem()).pathOrUrl(); } }else{ // Map-target is the source directory if(buildDirProject) { Path build = buildDirProject->buildSystemManager()->buildDirectory(buildDirProject->projectItem()); // We're in the source, map into the build directory QString relativePath = build.relativePath(path); Path source = buildDirProject->path(); source.addPath(relativePath); while(!QFile::exists(source.path())) source = source.parent(); return source.pathOrUrl(); }else if(sourceDirProject && fallbackRoot) { // We're in the source directory, map to the root return sourceDirProject->path().pathOrUrl(); } } return QString(); } void ProjectController::reparseProject( IProject* project, bool forceUpdate ) { if (auto job = d->m_parseJobs.value(project)) { job->kill(); } d->m_parseJobs[project] = new KDevelop::ParseProjectJob(project, forceUpdate); ICore::self()->runController()->registerJob(d->m_parseJobs[project]); } } #include "moc_projectcontroller.cpp" diff --git a/shell/sessioncontroller.cpp b/shell/sessioncontroller.cpp index a8aab80bb..4bdb91e56 100644 --- a/shell/sessioncontroller.cpp +++ b/shell/sessioncontroller.cpp @@ -1,664 +1,664 @@ /* 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 #include #include #include #include #include #include #include #include #include #include "session.h" #include "core.h" #include "uicontroller.h" #include "shellextension.h" #include "sessionlock.h" #include "sessionchooserdialog.h" #include "debug.h" #include #include #include #include #include namespace KDevelop { namespace { int argc = 0; char** argv = nullptr; }; 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]); if(arg.startsWith(QLatin1String("-graphicssystem")) || arg.startsWith(QLatin1String("-style"))) { ret << '-' + arg; if(a+1 < argc) ret << QString::fromLocal8Bit(argv[a+1]); } } return ret; } class SessionControllerPrivate : public QObject { Q_OBJECT public: - SessionControllerPrivate( SessionController* s ) + explicit SessionControllerPrivate( SessionController* s ) : q(s) , activeSession(nullptr) , grp(nullptr) { } ~SessionControllerPrivate() override { } Session* findSessionForName( const QString& name ) const { foreach( Session* s, sessionActions.keys() ) { if( s->name() == name ) return s; } return nullptr; } Session* findSessionForId(QString idString) { QUuid id(idString); foreach( Session* s, sessionActions.keys() ) { if( s->id() == id) return s; } return nullptr; } void newSession() { qsrand(QDateTime::currentDateTimeUtc().toTime_t()); Session* session = new Session( QUuid::createUuid().toString() ); KProcess::startDetached(ShellExtension::getInstance()->binaryPath(), QStringList() << QStringLiteral("-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 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() { bool ok; auto newSessionName = QInputDialog::getText(Core::self()->uiController()->activeMainWindow(), i18n("Rename Session"), i18n("New Session Name:"), QLineEdit::Normal, q->activeSession()->name(), &ok); if (ok) { static_cast(q->activeSession())->setName(newSessionName); } q->updateXmlGuiActionList(); // resort } bool loadSessionExternally( Session* s ) { Q_ASSERT( s ); KProcess::startDetached(ShellExtension::getInstance()->binaryPath(), QStringList() << QStringLiteral("-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 = nullptr; 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* action) { auto session = action->data().value(); loadSessionExternally(session); } void addSession( Session* s ) { if (Core::self()->setupFlags() & Core::NoUi) { sessionActions[s] = nullptr; return; } QAction* a = new QAction( grp ); a->setText( s->description() ); a->setCheckable( false ); a->setData(QVariant::fromValue(s)); sessionActions[s] = a; q->actionCollection()->addAction( "session_"+s->id().toString(), a ); connect( s, &Session::sessionUpdated, this, &SessionControllerPrivate::sessionUpdated ); sessionUpdated( s ); } SessionController* q; QHash sessionActions; ISession* activeSession; QActionGroup* grp; ISessionLock::Ptr sessionLock; static QString sessionBaseDirectory() { return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) +'/'+ qApp->applicationName() + "/sessions/"; } QString ownSessionDirectory() const { Q_ASSERT(activeSession); return q->sessionDirectory( activeSession->id().toString() ); } private slots: void sessionUpdated( KDevelop::ISession* s ) { sessionActions[static_cast( s )]->setText( KStringHandler::rsqueeze(s->description()) ); } }; SessionController::SessionController( QObject *parent ) : QObject( parent ), d(new SessionControllerPrivate(this)) { setObjectName(QStringLiteral("SessionController")); setComponentName(QStringLiteral("kdevsession"), QStringLiteral("KDevSession")); setXMLFile(QStringLiteral("kdevsessionui.rc")); QDBusConnection::sessionBus().registerObject( QStringLiteral("/org/kdevelop/SessionController"), this, QDBusConnection::ExportScriptableSlots ); if (Core::self()->setupFlags() & Core::NoUi) return; QAction* action = actionCollection()->addAction( QStringLiteral("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(QStringLiteral("window-new"))); action = actionCollection()->addAction( QStringLiteral("rename_session"), this, SLOT(renameSession()) ); action->setText( i18n("Rename Current Session...") ); action->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename"))); action = actionCollection()->addAction( QStringLiteral("delete_session"), this, SLOT(deleteCurrentSession()) ); action->setText( i18n("Delete Current Session...") ); action->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete"))); action = actionCollection()->addAction( QStringLiteral("quit"), this, SIGNAL(quitSession()) ); action->setText( i18n("Quit") ); action->setMenuRole( QAction::NoRole ); // OSX: prevent QT from hiding this due to conflict with 'Quit KDevelop...' actionCollection()->setDefaultShortcut( action, Qt::CTRL | Qt::Key_Q ); action->setIcon(QIcon::fromTheme(QStringLiteral("application-exit"))); d->grp = new QActionGroup( this ); connect( d->grp, &QActionGroup::triggered, this, [&] (QAction* a) { d->loadSessionFromAction(a); } ); } SessionController::~SessionController() { delete d; } void SessionController::startNewSession() { d->newSession(); } void SessionController::cleanup() { if (d->activeSession) { Q_ASSERT(d->activeSession->id().toString() == d->sessionLock->id()); if (d->activeSession->isTemporary()) { deleteSessionFromDisk(d->sessionLock); } d->activeSession = nullptr; } 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 ); updateXmlGuiActionList(); } 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::currentDateTimeUtc().toTime_t()); s = new Session( QUuid::createUuid().toString() ); s->setName( name ); } d->addSession( s ); updateXmlGuiActionList(); 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( QStringLiteral("available_sessions") ); actionCollection()->removeAction(*it); if (d->grp) { // happens in unit tests d->grp->removeAction(*it); plugActionList( QStringLiteral("available_sessions"), d->grp->actions() ); } if (s == d->activeSession) { d->activeSession = nullptr; } deleteSessionFromDisk(lock); emit sessionDeleted( s->id().toString() ); d->sessionActions.remove(s); delete s; } void SessionController::deleteSessionFromDisk( const ISessionLock::Ptr& lock ) { qCDebug(SHELL) << "Deleting session:" << lock->id(); static_cast(lock.data())->removeFromDisk(); 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 = nullptr; 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::currentDateTimeUtc().toTime_t()); QUuid id = QUuid::createUuid(); auto copyJob = KIO::copy(QUrl::fromLocalFile(sessionDirectory(origSession->id().toString())), QUrl::fromLocalFile(sessionDirectory( id.toString()))); KJobWidgets::setWindow(copyJob, Core::self()->uiController()->activeMainWindow()); copyJob->exec(); Session* newSession = new Session( id.toString() ); newSession->setName( i18n( "Copy of %1", origSession->name() ) ); d->addSession(newSession); updateXmlGuiActionList(); return newSession->name(); } void SessionController::updateXmlGuiActionList() { unplugActionList( QStringLiteral("available_sessions") ); if (d->grp) { auto actions = d->grp->actions(); std::sort(actions.begin(), actions.end(), [](const QAction* lhs, const QAction* rhs) { auto s1 = lhs->data().value(); auto s2 = rhs->data().value(); return QString::localeAwareCompare(s1->description(), s2->description()) < 0; }); plugActionList(QStringLiteral("available_sessions"), actions); } } QString SessionController::cfgSessionGroup() { return QStringLiteral("Sessions"); } QString SessionController::cfgActiveSessionEntry() { return QStringLiteral("Active Session ID"); } QList SessionController::availableSessionInfo() { return availableSessionInfos().toList(); } SessionInfos SessionController::availableSessionInfos() { SessionInfos sessionInfos; foreach( const QString& sessionId, QDir( SessionControllerPrivate::sessionBaseDirectory() ).entryList( QDir::AllDirs ) ) { if( !QUuid( sessionId ).isNull() ) { sessionInfos << Session::parse( sessionId ); } } return sessionInfos; } QString SessionController::sessionDirectory(const QString& sessionId) { return SessionControllerPrivate::sessionBaseDirectory() + sessionId; } TryLockSessionResult SessionController::tryLockSession(const QString& id, bool doLocking) { return SessionLock::tryLockSession(id, doLocking); } 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) { ///FIXME: move this code into sessiondialog.cpp QListView* view = new QListView; QLineEdit* filter = new QLineEdit; filter->setClearButtonEnabled( true ); filter->setPlaceholderText(i18n("Search")); QStandardItemModel* model = new QStandardItemModel(view); QSortFilterProxyModel *proxy = new QSortFilterProxyModel(model); proxy->setSourceModel(model); proxy->setFilterKeyColumn( 1 ); proxy->setFilterCaseSensitivity( Qt::CaseInsensitive ); connect(filter, &QLineEdit::textChanged, proxy, &QSortFilterProxyModel::setFilterFixedString); 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; QString defaultSession = KSharedConfig::openConfig()->group( cfgSessionGroup() ).readEntry( cfgActiveSessionEntry(), "default" ); foreach(const KDevelop::SessionInfo& si, KDevelop::SessionController::availableSessionInfos()) { if ( si.name.isEmpty() && si.projects.isEmpty() ) { continue; } bool running = KDevelop::SessionController::isSessionRunning(si.uuid.toString()); if(onlyRunning && !running) continue; model->setItem(row, 0, new QStandardItem(si.uuid.toString())); model->setItem(row, 1, new QStandardItem(si.description)); model->setItem(row, 2, new QStandardItem); ++row; } model->sort(1); if(!onlyRunning) { model->setItem(row, 0, new QStandardItem); model->setItem(row, 1, new QStandardItem(QIcon::fromTheme(QStringLiteral("window-new")), i18n("Create New Session"))); } dialog.updateState(); dialog.mainWidget()->layout()->setContentsMargins(0,0,0,0); 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.resize(QSize(900, 600)); if(dialog.exec() != QDialog::Accepted) { return QString(); } QModelIndex selected = view->selectionModel()->currentIndex(); if (!selected.isValid()) 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::currentDateTimeUtc().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 7f1a5eeb2..0ec13e4a2 100644 --- a/shell/sessioncontroller.h +++ b/shell/sessioncontroller.h @@ -1,180 +1,180 @@ /* 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 qint64 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) + explicit TryLockSessionResult(const ISessionLock::Ptr& _lock) : lock(_lock) {} - TryLockSessionResult(const SessionRunInfo& _runInfo) + explicit 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 Q_CLASSINFO("D-Bus Interface", "org.kdevelop.SessionController") public: explicit SessionController( QObject *parent = nullptr ); ~SessionController() override; 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, bool doLocking=true); /** * @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 QT_DEPRECATED QList availableSessionInfo(); // use availableSessionInfos() static SessionInfos availableSessionInfos(); /** * 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 currentSessionId 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 updateXmlGuiActionList(); 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 deleteCurrentSession() ) Q_PRIVATE_SLOT( d, void renameSession() ) Q_PRIVATE_SLOT( d, void loadSessionFromAction( QAction* ) ) class SessionControllerPrivate* const d; }; } #endif diff --git a/shell/sourceformattercontroller.cpp b/shell/sourceformattercontroller.cpp index a45668e68..c5efd0b76 100644 --- a/shell/sourceformattercontroller.cpp +++ b/shell/sourceformattercontroller.cpp @@ -1,641 +1,641 @@ /* 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "core.h" #include "debug.h" #include "plugincontroller.h" namespace { namespace Strings { QString SourceFormatter() { return QStringLiteral("SourceFormatter"); } } } namespace KDevelop { QString SourceFormatterController::kateModeLineConfigKey() { return QStringLiteral("ModelinesEnabled"); } QString SourceFormatterController::kateOverrideIndentationConfigKey() { return QStringLiteral("OverrideKateIndentation"); } QString SourceFormatterController::styleCaptionKey() { return QStringLiteral("Caption"); } QString SourceFormatterController::styleContentKey() { return QStringLiteral("Content"); } QString SourceFormatterController::styleMimeTypesKey() { return QStringLiteral("MimeTypes"); } QString SourceFormatterController::styleSampleKey() { return QStringLiteral("StyleSample"); } SourceFormatterController::SourceFormatterController(QObject *parent) : ISourceFormatterController(parent), m_enabled(true) { setObjectName(QStringLiteral("SourceFormatterController")); setComponentName(QStringLiteral("kdevsourceformatter"), QStringLiteral("kdevsourceformatter")); setXMLFile(QStringLiteral("kdevsourceformatter.rc")); if (Core::self()->setupFlags() & Core::NoUi) return; m_formatTextAction = actionCollection()->addAction(QStringLiteral("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, &QAction::triggered, this, &SourceFormatterController::beautifySource); m_formatLine = actionCollection()->addAction(QStringLiteral("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, &QAction::triggered, this, &SourceFormatterController::beautifyLine); m_formatFilesAction = actionCollection()->addAction(QStringLiteral("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, &QAction::triggered, this, static_cast(&SourceFormatterController::formatFiles)); m_formatTextAction->setEnabled(false); m_formatFilesAction->setEnabled(true); connect(Core::self()->documentController(), &IDocumentController::documentActivated, this, &SourceFormatterController::activeDocumentChanged); // Use a queued connection, because otherwise the view is not yet fully set up connect(Core::self()->documentController(), &IDocumentController::documentLoaded, this, &SourceFormatterController::documentLoaded, 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; } QMimeType mime = QMimeDatabase().mimeTypeForUrl(doc->url()); adaptEditorIndentationMode( doc->textDocument(), formatterForMimeType(mime) ); } void SourceFormatterController::initialize() { } SourceFormatterController::~SourceFormatterController() { } ISourceFormatter* SourceFormatterController::formatterForUrl(const QUrl &url) { QMimeType mime = QMimeDatabase().mimeTypeForUrl(url); return formatterForMimeType(mime); } KConfigGroup SourceFormatterController::sessionConfig() const { return KDevelop::Core::self()->activeSession()->config()->group( Strings::SourceFormatter() ); } KConfigGroup SourceFormatterController::globalConfig() const { return KSharedConfig::openConfig()->group( Strings::SourceFormatter() ); } ISourceFormatter* SourceFormatterController::findFirstFormatterForMimeType(const QMimeType& mime ) const { static QHash knownFormatters; if (knownFormatters.contains(mime.name())) return knownFormatters[mime.name()]; QList plugins = Core::self()->pluginController()->allPluginsForExtension( QStringLiteral("org.kdevelop.ISourceFormatter") ); foreach( IPlugin* p, plugins) { ISourceFormatter *iformatter = p->extension(); QSharedPointer formatter(createFormatterForPlugin(iformatter)); if( formatter->supportedMimeTypes().contains(mime.name()) ) { knownFormatters[mime.name()] = iformatter; return iformatter; } } knownFormatters[mime.name()] = nullptr; return nullptr; } 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 = globalConfig(); 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 QMimeType& mime) { if( !m_enabled || !isMimeTypeSupported( mime ) ) { return nullptr; } QString formatter = sessionConfig().readEntry( mime.name(), QString() ); if( formatter.isEmpty() ) { return findFirstFormatterForMimeType( mime ); } QStringList formatterinfo = formatter.split( QStringLiteral("||"), QString::SkipEmptyParts ); if( formatterinfo.size() != 2 ) { qCDebug(SHELL) << "Broken formatting entry for mime:" << mime.name() << "current value:" << formatter; return nullptr; } return Core::self()->pluginControllerInternal()->extensionForPlugin( QStringLiteral("org.kdevelop.ISourceFormatter"), formatterinfo.at(0) ); } bool SourceFormatterController::isMimeTypeSupported(const QMimeType& mime) { if( findFirstFormatterForMimeType( mime ) ) { return true; } return false; } QString SourceFormatterController::indentationMode(const QMimeType& mime) { if (mime.inherits(QStringLiteral("text/x-c++src")) || mime.inherits(QStringLiteral("text/x-chdr")) || mime.inherits(QStringLiteral("text/x-c++hdr")) || mime.inherits(QStringLiteral("text/x-csrc")) || mime.inherits(QStringLiteral("text/x-java")) || mime.inherits(QStringLiteral("text/x-csharp"))) { return QStringLiteral("cstyle"); } return QStringLiteral("none"); } QString SourceFormatterController::addModelineForCurrentLang(QString input, const QUrl& url, const QMimeType& 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( !sessionConfig().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(QStringLiteral("// kate: ") + QStringLiteral("indent-mode ") + indentationMode(mime) + QStringLiteral("; ")); if(indentation.indentWidth) // We know something about indentation-width modeline.append(QStringLiteral("indent-width %1; ").arg(indentation.indentWidth)); if(indentation.indentationTabWidth != 0) // We know something about tab-usage { modeline.append(QStringLiteral("replace-tabs %1; ").arg(QLatin1String((indentation.indentationTabWidth == -1) ? "on" : "off"))); if(indentation.indentationTabWidth > 0) modeline.append(QStringLiteral("tab-width %1; ").arg(indentation.indentationTabWidth)); } qCDebug(SHELL) << "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 qCDebug(SHELL) << "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 << ";"; qCDebug(SHELL) << "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) { QMimeType mime = QMimeDatabase().mimeTypeForUrl(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 QMimeType mime = QMimeDatabase().mimeTypeForUrl(doc->url()); ISourceFormatter *formatter = formatterForMimeType(mime); if( !formatter ) { qCDebug(SHELL) << "no formatter available for" << mime.name(); 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( dynamic_cast( KDevelop::createCodeRepresentation( IndexedString( doc->url() ) ).data() ) ); 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 QMimeType mime = QMimeDatabase().mimeTypeForUrl(doc->url()); ISourceFormatter *formatter = formatterForMimeType(mime); if( !formatter ) { qCDebug(SHELL) << "no formatter available for" << mime.name(); 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(dynamic_cast( KDevelop::createCodeRepresentation( IndexedString( doc->url() ) ).data() ) ); 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 QMimeType& 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( sessionConfig().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 || !sessionConfig().readEntry( SourceFormatterController::kateOverrideIndentationConfigKey(), false ) || !doc ) return; qCDebug(SHELL) << "adapting mode for" << doc->url(); QRegExp kateModelineWithNewline("\\s*\\n//\\s*kate:(.*)$"); // modelines should always take precedence if( !ignoreModeline && kateModelineWithNewline.indexIn( doc->text() ) != -1 ) { qCDebug(SHELL) << "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), editor(KTextEditor::Editor::instance()) { + explicit CommandCaller(KTextEditor::Document* _doc) : doc(_doc), editor(KTextEditor::Editor::instance()) { Q_ASSERT(editor); } void operator()(QString cmd) { KTextEditor::Command* command = editor->queryCommand( cmd ); Q_ASSERT(command); QString msg; qCDebug(SHELL) << "calling" << cmd; foreach(KTextEditor::View* view, doc->views()) if( !command->exec( view, cmd, msg ) ) qWarning() << "setting indentation width failed: " << msg; } KTextEditor::Document* doc; KTextEditor::Editor* editor; } call(doc); if( indentation.indentWidth ) // We know something about indentation-width call( QStringLiteral("set-indent-width %1").arg(indentation.indentWidth ) ); if( indentation.indentationTabWidth != 0 ) // We know something about tab-usage { call( QStringLiteral("set-replace-tabs %1").arg( (indentation.indentationTabWidth == -1) ? 1 : 0 ) ); if( indentation.indentationTabWidth > 0 ) call( QStringLiteral("set-tab-width %1").arg(indentation.indentationTabWidth ) ); } }else{ qCDebug(SHELL) << "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()); } auto win = ICore::self()->uiController()->activeMainWindow()->window(); auto reply = QMessageBox::question(win, i18n("Reformat files?"), i18n("Reformat all files in the selected folder?")); if ( reply == QMessageBox::Yes ) { formatFiles(m_urls); } } void SourceFormatterController::formatFiles(QList &list) { //! \todo IStatus for (int fileCount = 0; fileCount < list.size(); fileCount++) { // check mimetype QMimeType mime = QMimeDatabase().mimeTypeForUrl(list[fileCount]); qCDebug(SHELL) << "Checking file " << list[fileCount] << " 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) { qCDebug(SHELL) << "Processing file " << list[fileCount] << "opened in editor" << endl; formatDocument(doc, formatter, mime); continue; } qCDebug(SHELL) << "Processing file " << list[fileCount] << endl; KIO::StoredTransferJob *job = KIO::storedGet(list[fileCount]); if (job->exec()) { QByteArray data = job->data(); QString output = formatter->formatSource(data, list[fileCount], mime); data += addModelineForCurrentLang(output, list[fileCount], mime).toUtf8(); job = KIO::storedPut(data, list[fileCount], -1); if (!job->exec()) KMessageBox::error(nullptr, job->errorString()); } else KMessageBox::error(nullptr, job->errorString()); } } 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 QMimeType& mime) { QStringList formatter = sessionConfig().readEntry( mime.name(), QString() ).split( QStringLiteral("||"), QString::SkipEmptyParts ); if( formatter.count() == 2 ) { SourceFormatterStyle s( formatter.at( 1 ) ); KConfigGroup fmtgrp = globalConfig().group( formatter.at(0) ); if( fmtgrp.hasGroup( formatter.at(1) ) ) { KConfigGroup stylegrp = fmtgrp.group( formatter.at(1) ); populateStyleFromConfigGroup(&s, stylegrp); } return s; } return SourceFormatterStyle(); } void SourceFormatterController::disableSourceFormatting(bool disable) { m_enabled = !disable; } bool SourceFormatterController::sourceFormattingEnabled() { return m_enabled; } } diff --git a/shell/tests/nonguiinterfaceplugin.h b/shell/tests/nonguiinterfaceplugin.h index be41240e3..d08f6f81e 100644 --- a/shell/tests/nonguiinterfaceplugin.h +++ b/shell/tests/nonguiinterfaceplugin.h @@ -1,48 +1,48 @@ /* * This file is part of KDevelop * * Copyright 2007 Hamish Rodda * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef NONGUIINTERFACEPLUGIN #define NONGUIINTERFACEPLUGIN #include #include #include class ITestNonGuiInterface { public: virtual ~ITestNonGuiInterface() {} }; Q_DECLARE_INTERFACE( ITestNonGuiInterface, "org.kdevelop.ITestNonGuiInterface" ) class NonGuiInterfacePlugin : public KDevelop::IPlugin, ITestNonGuiInterface { Q_OBJECT Q_INTERFACES(ITestNonGuiInterface) public: - NonGuiInterfacePlugin( QObject* parent, const QVariantList& = QVariantList() ); + explicit NonGuiInterfacePlugin( QObject* parent, const QVariantList& = QVariantList() ); }; #endif diff --git a/shell/tests/test_ktexteditorpluginintegration.cpp b/shell/tests/test_ktexteditorpluginintegration.cpp index f9c576ccd..f228c310f 100644 --- a/shell/tests/test_ktexteditorpluginintegration.cpp +++ b/shell/tests/test_ktexteditorpluginintegration.cpp @@ -1,198 +1,198 @@ /* Copyright 2015 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 "test_ktexteditorpluginintegration.h" #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; namespace { template QPointer makeQPointer(T *ptr) { return {ptr}; } IToolViewFactory *findToolView(const QString &id) { const auto uiController = Core::self()->uiControllerInternal(); const auto map = uiController->factoryDocuments(); for (auto it = map.begin(); it != map.end(); ++it) { if (it.key()->id() == id) { return it.key(); } } return nullptr; } class TestPlugin : public KTextEditor::Plugin { Q_OBJECT public: - TestPlugin(QObject *parent) + explicit TestPlugin(QObject *parent) : Plugin(parent) { } QObject *createView(KTextEditor::MainWindow * mainWindow) override { return new QObject(mainWindow); } }; } void TestKTextEditorPluginIntegration::initTestCase() { QLoggingCategory::setFilterRules(QStringLiteral("*.debug=false\ndefault.debug=true\n")); AutoTestShell::init({QStringLiteral("katesnippetsplugin")}); TestCore::initialize(); QVERIFY(KTextEditor::Editor::instance()); } void TestKTextEditorPluginIntegration::cleanupTestCase() { auto controller = Core::self()->pluginController(); const auto id = QStringLiteral("katesnippetsplugin"); auto plugin = makeQPointer(controller->loadPlugin(id)); const auto editor = makeQPointer(KTextEditor::Editor::instance()); const auto application = makeQPointer(editor->application()); const auto window = makeQPointer(application->activeMainWindow()); TestCore::shutdown(); QVERIFY(!plugin); QVERIFY(!window); QVERIFY(!application); } void TestKTextEditorPluginIntegration::testApplication() { auto app = KTextEditor::Editor::instance()->application(); QVERIFY(app); QVERIFY(app->parent()); QCOMPARE(app->parent()->metaObject()->className(), "KTextEditorIntegration::Application"); QVERIFY(app->activeMainWindow()); QCOMPARE(app->mainWindows().size(), 1); QVERIFY(app->mainWindows().contains(app->activeMainWindow())); } void TestKTextEditorPluginIntegration::testMainWindow() { auto window = KTextEditor::Editor::instance()->application()->activeMainWindow(); QVERIFY(window); QVERIFY(window->parent()); QCOMPARE(window->parent()->metaObject()->className(), "KTextEditorIntegration::MainWindow"); const auto id = QStringLiteral("kte_integration_toolview"); const auto icon = QIcon::fromTheme(QStringLiteral("kdevelop")); const auto text = QStringLiteral("some text"); QVERIFY(!findToolView(id)); auto plugin = new TestPlugin(this); auto toolView = makeQPointer(window->createToolView(plugin, id, KTextEditor::MainWindow::Bottom, icon, text)); QVERIFY(toolView); auto factory = findToolView(id); QVERIFY(factory); // we reuse the same view QWidget parent; auto kdevToolview = makeQPointer(factory->create(&parent)); QCOMPARE(kdevToolview->parentWidget(), &parent); QCOMPARE(toolView->parentWidget(), kdevToolview.data()); // the children are kept alive when the toolview gets destroyed delete kdevToolview; QVERIFY(toolView); kdevToolview = factory->create(&parent); // and we reuse the ktexteditor toolview for the new kdevelop toolview QCOMPARE(toolView->parentWidget(), kdevToolview.data()); delete toolView; delete kdevToolview; delete plugin; QVERIFY(!findToolView(id)); } void TestKTextEditorPluginIntegration::testPlugin() { auto controller = Core::self()->pluginController(); const auto id = QStringLiteral("katesnippetsplugin"); auto plugin = makeQPointer(controller->loadPlugin(id)); if (!plugin) { QSKIP("Cannot continue without katesnippetsplugin, install Kate"); } auto app = KTextEditor::Editor::instance()->application(); auto ktePlugin = makeQPointer(app->plugin(id)); QVERIFY(ktePlugin); auto view = makeQPointer(app->activeMainWindow()->pluginView(id)); QVERIFY(view); const auto rawView = view.data(); QSignalSpy spy(app->activeMainWindow(), &KTextEditor::MainWindow::pluginViewDeleted); QVERIFY(controller->unloadPlugin(id)); QVERIFY(!ktePlugin); QCOMPARE(spy.count(), 1); QCOMPARE(spy.first().count(), 2); QCOMPARE(spy.first().at(0), QVariant::fromValue(id)); QCOMPARE(spy.first().at(1), QVariant::fromValue(rawView)); QVERIFY(!view); } void TestKTextEditorPluginIntegration::testPluginUnload() { auto controller = Core::self()->pluginController(); const auto id = QStringLiteral("katesnippetsplugin"); auto plugin = makeQPointer(controller->loadPlugin(id)); if (!plugin) { QSKIP("Cannot continue without katesnippetsplugin, install Kate"); } auto app = KTextEditor::Editor::instance()->application(); auto ktePlugin = makeQPointer(app->plugin(id)); QVERIFY(ktePlugin); delete ktePlugin; // don't crash plugin->unload(); } QTEST_MAIN(TestKTextEditorPluginIntegration); #include \ No newline at end of file diff --git a/shell/tests/test_projectcontroller.cpp b/shell/tests/test_projectcontroller.cpp index a204040e7..330f4d7e7 100644 --- a/shell/tests/test_projectcontroller.cpp +++ b/shell/tests/test_projectcontroller.cpp @@ -1,574 +1,574 @@ /*************************************************************************** * Copyright 2008 Manuel Breugelmans * * * * 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 "test_projectcontroller.h" #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; Q_DECLARE_METATYPE(KDevelop::IProject*) namespace { class DialogProviderFake : public IProjectDialogProvider { Q_OBJECT public: DialogProviderFake() : m_reopen(true) {} ~DialogProviderFake() override {} bool m_reopen; public slots: QUrl askProjectConfigLocation(bool /*fetch*/, const QUrl& /*startUrl*/ = QUrl()) override { return QUrl(); } bool userWantsReopen() override { return m_reopen; } }; } /*! A Filemanager plugin that allows you to setup a file & directory structure */ class FakeFileManager : public IPlugin, public IProjectFileManager { Q_OBJECT Q_INTERFACES(KDevelop::IProjectFileManager) public: FakeFileManager(QObject*, const QVariantList&) : IPlugin(ICore::self()->aboutData().componentName(), Core::self()) { } FakeFileManager() : IPlugin(ICore::self()->aboutData().componentName(), Core::self()) { } ~FakeFileManager() override {} Features features() const override { return IProjectFileManager::Files | IProjectFileManager::Folders; } QMap m_filesInFolder; // initialize QMap m_subFoldersInFolder; /*! Setup this manager such that @p folder contains @p file */ void addFileToFolder(const Path& folder, const Path& file) { if (!m_filesInFolder.contains(folder)) { m_filesInFolder[folder] = Path::List(); } m_filesInFolder[folder] << file; } /*! Setup this manager such that @p folder has @p subFolder */ void addSubFolderTo(const Path& folder, Path subFolder) { if (!m_subFoldersInFolder.contains(folder)) { m_subFoldersInFolder[folder] = Path::List(); } m_subFoldersInFolder[folder] << subFolder; } QList parse(ProjectFolderItem *dom) override { Path::List files = m_filesInFolder[dom->path()]; foreach (const Path& file, files) { new ProjectFileItem(dom->project(), file, dom); } Path::List folderPaths = m_subFoldersInFolder[dom->path()]; QList folders; foreach (const Path& folderPath, folderPaths) { folders << new ProjectFolderItem(dom->project(), folderPath, dom); } return folders; } ProjectFolderItem *import(IProject *project) override { ProjectFolderItem* it = new ProjectFolderItem(project, project->path()); return it; } ProjectFolderItem* addFolder(const Path& /*folder*/, ProjectFolderItem */*parent*/) override { return nullptr; } ProjectFileItem* addFile(const Path& /*file*/, ProjectFolderItem */*parent*/) override { return nullptr; } bool removeFilesAndFolders(const QList &/*items*/) override { return false; } bool moveFilesAndFolders(const QList< KDevelop::ProjectBaseItem* > &/*items*/, KDevelop::ProjectFolderItem* /*newParent*/) override { return false; } bool copyFilesAndFolders(const Path::List &/*items*/, KDevelop::ProjectFolderItem* /*newParent*/) override { return false; } bool renameFile(ProjectFileItem* /*file*/, const Path& /*newPath*/) override { return false; } bool renameFolder(ProjectFolderItem* /*oldFolder*/, const Path& /*newPath*/ ) override { return false; } bool reload(ProjectFolderItem* /*item*/) override { return false; } }; class FakePluginController : public PluginController { Q_OBJECT public: - FakePluginController(Core* core) + explicit FakePluginController(Core* core) : PluginController(core) , m_fakeFileManager(new FakeFileManager) { } IPlugin* pluginForExtension(const QString& extension, const QString& pluginName = {}, const QVariantMap& constraints = QVariantMap()) override { if (extension == qobject_interface_iid()) { return m_fakeFileManager; } return PluginController::pluginForExtension(extension, pluginName, constraints); } private: FakeFileManager* m_fakeFileManager; }; ////////////////////// Fixture /////////////////////////////////////////////// void TestProjectController::initTestCase() { AutoTestShell::init(); TestCore* testCore = new TestCore; testCore->setPluginController( new FakePluginController(testCore) ); testCore->initialize(); qRegisterMetaType(); m_core = Core::self(); m_scratchDir = QDir(QDir::tempPath()); m_scratchDir.mkdir(QStringLiteral("prjctrltest")); m_scratchDir.cd(QStringLiteral("prjctrltest")); } void TestProjectController::cleanupTestCase() { TestCore::shutdown(); } void TestProjectController::init() { m_projName = QStringLiteral("foo"); m_projFilePath = writeProjectConfig(m_projName); m_projCtrl = m_core->projectControllerInternal(); m_tmpConfigs << m_projFilePath; m_projFolder = Path(m_scratchDir.absolutePath() + '/'); } void TestProjectController::cleanup() { // also close any opened projects as we do not get a clean fixture, // following tests should start off clean. foreach(IProject* p, m_projCtrl->projects()) { m_projCtrl->closeProject(p); } foreach(const Path &cfg, m_tmpConfigs) { QFile::remove(cfg.pathOrUrl()); } qDeleteAll(m_fileManagerGarbage); m_fileManagerGarbage.clear(); } ////////////////////// Commands ////////////////////////////////////////////// #define WAIT_FOR_OPEN_SIGNAL \ {\ QSignalSpy signal(m_projCtrl, SIGNAL(projectOpened(KDevelop::IProject*)));\ QVERIFY2(signal.wait(30000), "Timeout while waiting for opened signal");\ } void(0) void TestProjectController::openProject() { QSignalSpy* spy = createOpenedSpy(); QVERIFY(!m_projCtrl->isProjectNameUsed(m_projName)); m_projCtrl->openProject(m_projFilePath.toUrl()); WAIT_FOR_OPEN_SIGNAL; QCOMPARE(m_projCtrl->projectCount(), 1); IProject* proj; assertProjectOpened(m_projName, proj);QVERIFY(proj); assertSpyCaughtProject(spy, proj); QCOMPARE(proj->projectFile(), m_projFilePath); QCOMPARE(proj->path(), Path(m_scratchDir.absolutePath()+'/')); QVERIFY(m_projCtrl->isProjectNameUsed(m_projName)); } void TestProjectController::closeProject() { m_projCtrl->openProject(m_projFilePath.toUrl()); WAIT_FOR_OPEN_SIGNAL; IProject* proj = m_projCtrl->findProjectByName(m_projName); Q_ASSERT(proj); QSignalSpy* spy1 = createClosedSpy(); QSignalSpy* spy2 = createClosingSpy(); m_projCtrl->closeProject(proj); QVERIFY(!m_projCtrl->isProjectNameUsed(m_projName)); QCOMPARE(m_projCtrl->projectCount(), 0); assertProjectClosed(proj); assertSpyCaughtProject(spy1, proj); assertSpyCaughtProject(spy2, proj); } void TestProjectController::openCloseOpen() { m_projCtrl->openProject(m_projFilePath.toUrl()); WAIT_FOR_OPEN_SIGNAL; IProject* proj; assertProjectOpened(m_projName, proj); m_projCtrl->closeProject(proj); QSignalSpy* spy = createOpenedSpy(); m_projCtrl->openProject(m_projFilePath.toUrl()); WAIT_FOR_OPEN_SIGNAL; QVERIFY(m_projCtrl->isProjectNameUsed(m_projName)); QCOMPARE(m_projCtrl->projectCount(), 1); assertProjectOpened(m_projName, proj); assertSpyCaughtProject(spy, proj); } void TestProjectController::reopen() { m_projCtrl->setDialogProvider(new DialogProviderFake); m_projCtrl->openProject(m_projFilePath.toUrl()); WAIT_FOR_OPEN_SIGNAL; QSignalSpy* spy = createOpenedSpy(); m_projCtrl->openProject(m_projFilePath.toUrl()); WAIT_FOR_OPEN_SIGNAL; QCOMPARE(m_projCtrl->projectCount(), 1); QVERIFY(m_projCtrl->isProjectNameUsed(m_projName)); IProject* proj; assertProjectOpened(m_projName, proj); assertSpyCaughtProject(spy, proj); } void TestProjectController::reopenWhileLoading() { // Open the same project again while the first is still // loading. The second open request should be blocked. m_projCtrl->setDialogProvider(new DialogProviderFake); QSignalSpy* spy = createOpenedSpy(); m_projCtrl->openProject(m_projFilePath.toUrl()); //m_projCtrl->openProject(m_projFilePath.toUrl()); WAIT_FOR_OPEN_SIGNAL; // wait a bit for a second signal, this should timeout QSignalSpy signal(m_projCtrl, SIGNAL(projectOpened(KDevelop::IProject*))); QVERIFY2(!signal.wait(100), "Received 2 projectOpened signals."); QCOMPARE(m_projCtrl->projectCount(), 1); IProject* proj; assertProjectOpened(m_projName, proj); assertSpyCaughtProject(spy, proj); } void TestProjectController::openMultiple() { QString secondProj(QStringLiteral("bar")); Path secondCfgUrl = writeProjectConfig(secondProj); QSignalSpy* spy = createOpenedSpy(); m_projCtrl->openProject(m_projFilePath.toUrl()); WAIT_FOR_OPEN_SIGNAL; m_projCtrl->openProject(secondCfgUrl.toUrl()); WAIT_FOR_OPEN_SIGNAL; QCOMPARE(m_projCtrl->projectCount(), 2); IProject *proj1, *proj2; assertProjectOpened(m_projName, proj1); assertProjectOpened(secondProj, proj2); QVERIFY(m_projCtrl->isProjectNameUsed(m_projName)); QVERIFY(m_projCtrl->isProjectNameUsed(QStringLiteral("bar"))); QCOMPARE(spy->size(), 2); IProject* emittedProj1 = (*spy)[0][0].value(); IProject* emittedProj2 = (*spy)[1][0].value(); QCOMPARE(emittedProj1, proj1); QCOMPARE(emittedProj2, proj2); m_tmpConfigs << secondCfgUrl; } /*! Verify that the projectmodel contains a single project. Put this project's * ProjectFolderItem in the output parameter @p RootItem */ #define ASSERT_SINGLE_PROJECT_IN_MODEL(rootItem) \ {\ QCOMPARE(m_projCtrl->projectModel()->rowCount(), 1); \ QModelIndex projIndex = m_projCtrl->projectModel()->index(0,0); \ QVERIFY(projIndex.isValid()); \ ProjectBaseItem* i = m_projCtrl->projectModel()->itemFromIndex( projIndex ); \ QVERIFY(i); \ QVERIFY(i->folder()); \ rootItem = i->folder();\ } void(0) /*! Verify that the projectitem @p item has a single child item * named @p name with url @p url. @p subFolder is an output parameter * that contains the sub-folder projectitem. */ #define ASSERT_SINGLE_SUBFOLDER_IN(item, name, path__, subFolder) \ {\ QCOMPARE(item->rowCount(), 1);\ QCOMPARE(item->folderList().size(), 1);\ ProjectFolderItem* fo = item->folderList().at(0);\ QVERIFY(fo);\ QCOMPARE(fo->path(), path__);\ QCOMPARE(fo->folderName(), QStringLiteral(name));\ subFolder = fo;\ } void(0) #define ASSERT_SINGLE_FILE_IN(rootFolder, name, path__, fileItem)\ {\ QCOMPARE(rootFolder->rowCount(), 1);\ QCOMPARE(rootFolder->fileList().size(), 1);\ fileItem = rootFolder->fileList().at(0);\ QVERIFY(fileItem);\ QCOMPARE(fileItem->path(), path__);\ QCOMPARE(fileItem->fileName(), QStringLiteral(name));\ } void(0) // command void TestProjectController::emptyProject() { // verify that the project model contains a single top-level folder after loading // an empty project assertEmptyProjectModel(); m_projCtrl->openProject(m_projFilePath.toUrl()); WAIT_FOR_OPEN_SIGNAL; Project* proj; assertProjectOpened(m_projName, (KDevelop::IProject*&)proj); FakeFileManager* fileMng = createFileManager(); Q_ASSERT(fileMng); proj->setManagerPlugin(fileMng); proj->reloadModel(); QTest::qWait(100); ProjectFolderItem* rootFolder; ASSERT_SINGLE_PROJECT_IN_MODEL(rootFolder); // check that the project is empty QCOMPARE(rootFolder->rowCount(), 0); QCOMPARE(rootFolder->project()->name(), m_projName); QCOMPARE(rootFolder->path(), m_projFolder); } // command void TestProjectController::singleFile() { // verify that the project model contains a single file in the // top folder. First setup a FakeFileManager with this file m_projCtrl->openProject(m_projFilePath.toUrl()); WAIT_FOR_OPEN_SIGNAL; Project* proj; assertProjectOpened(m_projName, (KDevelop::IProject*&)proj); FakeFileManager* fileMng = createFileManager(); proj->setManagerPlugin(fileMng); Path filePath = Path(m_projFolder, QStringLiteral("foobar")); fileMng->addFileToFolder(m_projFolder, filePath); proj->reloadModel(); QTest::qWait(100); // NO signals for reload ... ProjectFolderItem* rootFolder; ASSERT_SINGLE_PROJECT_IN_MODEL(rootFolder); ProjectFileItem* fi; ASSERT_SINGLE_FILE_IN(rootFolder, "foobar", filePath, fi); QCOMPARE(fi->rowCount(), 0); ASSERT_SINGLE_PROJECT_IN_MODEL(rootFolder); ASSERT_SINGLE_FILE_IN(rootFolder, "foobar", filePath, fi); } // command void TestProjectController::singleDirectory() { // verify that the project model contains a single folder in the // top folder. First setup a FakeFileManager with this folder m_projCtrl->openProject(m_projFilePath.toUrl()); WAIT_FOR_OPEN_SIGNAL; Project* proj; assertProjectOpened(m_projName, (KDevelop::IProject*&)proj); Path folderPath = Path(m_projFolder, QStringLiteral("foobar/")); FakeFileManager* fileMng = createFileManager(); fileMng->addSubFolderTo(m_projFolder, folderPath); proj->setManagerPlugin(fileMng); proj->reloadModel(); QTest::qWait(100); ProjectFolderItem* rootFolder; ASSERT_SINGLE_PROJECT_IN_MODEL(rootFolder); // check that the project contains a single subfolder ProjectFolderItem* sub; ASSERT_SINGLE_SUBFOLDER_IN(rootFolder, "foobar", folderPath, sub); QCOMPARE(sub->rowCount(), 0); } // command void TestProjectController::fileInSubdirectory() { // verify that the project model contains a single file in a subfolder // First setup a FakeFileManager with this folder + file m_projCtrl->openProject(m_projFilePath.toUrl()); WAIT_FOR_OPEN_SIGNAL; Project* proj; assertProjectOpened(m_projName, (KDevelop::IProject*&)proj); Path folderPath = Path(m_projFolder, QStringLiteral("foobar/")); FakeFileManager* fileMng = createFileManager(); fileMng->addSubFolderTo(m_projFolder, folderPath); Path filePath = Path(folderPath, QStringLiteral("zoo")); fileMng->addFileToFolder(folderPath, filePath); proj->setManagerPlugin(fileMng); ProjectFolderItem* rootFolder = nullptr; ProjectFolderItem* sub = nullptr; ProjectFileItem* file = nullptr; proj->reloadModel(); QTest::qWait(100); ASSERT_SINGLE_PROJECT_IN_MODEL(rootFolder); ASSERT_SINGLE_SUBFOLDER_IN(rootFolder, "foobar", folderPath, sub); ASSERT_SINGLE_FILE_IN(sub,"zoo",filePath,file); ASSERT_SINGLE_PROJECT_IN_MODEL(rootFolder); ASSERT_SINGLE_SUBFOLDER_IN(rootFolder, "foobar", folderPath, sub); ASSERT_SINGLE_FILE_IN(sub,"zoo",filePath,file); } void TestProjectController::prettyFileName_data() { QTest::addColumn("relativeFilePath"); QTest::newRow("basic") << "foobar.txt"; QTest::newRow("subfolder") << "sub/foobar.txt"; } void TestProjectController::prettyFileName() { QFETCH(QString, relativeFilePath); m_projCtrl->openProject(m_projFilePath.toUrl()); WAIT_FOR_OPEN_SIGNAL; Project* proj; assertProjectOpened(m_projName, (KDevelop::IProject*&)proj); FakeFileManager* fileMng = createFileManager(); proj->setManagerPlugin(fileMng); Path filePath = Path(m_projFolder, relativeFilePath); fileMng->addFileToFolder(m_projFolder, filePath); QCOMPARE(m_projCtrl->prettyFileName(filePath.toUrl(), ProjectController::FormattingOptions::FormatPlain), QString(m_projName + ':' + relativeFilePath)); } ////////////////////// Helpers /////////////////////////////////////////////// Path TestProjectController::writeProjectConfig(const QString& name) { Path configPath = Path(m_scratchDir.absolutePath() + '/' + name + ".kdev4"); QFile f(configPath.pathOrUrl()); f.open(QIODevice::WriteOnly); QTextStream str(&f); str << "[Project]\n" << "Name=" << name << "\n"; f.close(); return configPath; } ////////////////// Custom assertions ///////////////////////////////////////// void TestProjectController::assertProjectOpened(const QString& name, IProject*& proj) { QVERIFY(proj = m_projCtrl->findProjectByName(name)); QVERIFY(m_projCtrl->projects().contains(proj)); } void TestProjectController::assertSpyCaughtProject(QSignalSpy* spy, IProject* proj) { QCOMPARE(spy->size(), 1); IProject* emittedProj = (*spy)[0][0].value(); QCOMPARE(proj, emittedProj); } void TestProjectController::assertProjectClosed(IProject* proj) { IProject* p = m_projCtrl->findProjectByName(proj->name()); QVERIFY(p == nullptr); QVERIFY(!m_projCtrl->projects().contains(proj)); } void TestProjectController::assertEmptyProjectModel() { ProjectModel* m = m_projCtrl->projectModel(); Q_ASSERT(m); QCOMPARE(m->rowCount(), 0); } ///////////////////// Creation stuff ///////////////////////////////////////// QSignalSpy* TestProjectController::createOpenedSpy() { return new QSignalSpy(m_projCtrl, SIGNAL(projectOpened(KDevelop::IProject*))); } QSignalSpy* TestProjectController::createClosedSpy() { return new QSignalSpy(m_projCtrl, SIGNAL(projectClosed(KDevelop::IProject*))); } QSignalSpy* TestProjectController::createClosingSpy() { return new QSignalSpy(m_projCtrl, SIGNAL(projectClosing(KDevelop::IProject*))); } FakeFileManager* TestProjectController::createFileManager() { FakeFileManager* fileMng = new FakeFileManager; m_fileManagerGarbage << fileMng; return fileMng; } QTEST_MAIN(TestProjectController) #include "moc_test_projectcontroller.cpp" #include "test_projectcontroller.moc" diff --git a/shell/textdocument.cpp b/shell/textdocument.cpp index 4c6f6e4a1..310e1809b 100644 --- a/shell/textdocument.cpp +++ b/shell/textdocument.cpp @@ -1,793 +1,793 @@ /*************************************************************************** * Copyright 2007 Alexander Dymo * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "textdocument.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "core.h" #include "mainwindow.h" #include "uicontroller.h" #include "partcontroller.h" #include "plugincontroller.h" #include "documentcontroller.h" #include "debug.h" #include #include namespace KDevelop { const int MAX_DOC_SETTINGS = 20; // This sets cursor position and selection on the view to the given // range. Selection is set only for non-empty ranges // Factored into a function since its needed in 3 places already static void selectAndReveal( KTextEditor::View* view, const KTextEditor::Range& range ) { Q_ASSERT(view); if (range.isValid()) { view->setCursorPosition(range.start()); if (!range.isEmpty()) { view->setSelection(range); } } } struct TextDocumentPrivate { - TextDocumentPrivate(TextDocument *textDocument) + explicit TextDocumentPrivate(TextDocument *textDocument) : document(nullptr), state(IDocument::Clean), encoding(), q(textDocument) , m_loaded(false), m_addedContextMenu(nullptr) { } ~TextDocumentPrivate() { delete m_addedContextMenu; m_addedContextMenu = nullptr; saveSessionConfig(); delete document; } QPointer document; IDocument::DocumentState state; QString encoding; void setStatus(KTextEditor::Document* document, bool dirty) { QIcon statusIcon; if (document->isModified()) if (dirty) { state = IDocument::DirtyAndModified; statusIcon = QIcon::fromTheme(QStringLiteral("edit-delete")); } else { state = IDocument::Modified; statusIcon = QIcon::fromTheme(QStringLiteral("document-save")); } else if (dirty) { state = IDocument::Dirty; statusIcon = QIcon::fromTheme(QStringLiteral("document-revert")); } else { state = IDocument::Clean; } q->notifyStateChanged(); Core::self()->uiControllerInternal()->setStatusIcon(q, statusIcon); } inline KConfigGroup katePartSettingsGroup() const { return KSharedConfig::openConfig()->group("KatePart Settings"); } inline QString docConfigGroupName() const { return document->url().toDisplayString(QUrl::PreferLocalFile); } inline KConfigGroup docConfigGroup() const { return katePartSettingsGroup().group(docConfigGroupName()); } void saveSessionConfig() { if(document && document->url().isValid()) { // make sure only MAX_DOC_SETTINGS entries are stored KConfigGroup katePartSettings = katePartSettingsGroup(); // ordered list of documents QStringList documents = katePartSettings.readEntry("documents", QStringList()); // ensure this document is "new", i.e. at the end of the list documents.removeOne(docConfigGroupName()); documents.append(docConfigGroupName()); // remove "old" documents + their group while(documents.size() >= MAX_DOC_SETTINGS) { katePartSettings.group(documents.takeFirst()).deleteGroup(); } // update order katePartSettings.writeEntry("documents", documents); // actually save session config KConfigGroup group = docConfigGroup(); document->writeSessionConfig(group); } } void loadSessionConfig() { if (!document || !katePartSettingsGroup().hasGroup(docConfigGroupName())) { return; } document->readSessionConfig(docConfigGroup(), {QStringLiteral("SkipUrl")}); } // Determines whether the current contents of this document in the editor // could be retrieved from the VCS if they were dismissed. void queryCanRecreateFromVcs(KTextEditor::Document* document) const { IProject* project = nullptr; // Find projects by checking which one contains the file's parent directory, // to avoid issues with the cmake manager temporarily removing files from a project // during reloading. KDevelop::Path path(document->url()); foreach ( KDevelop::IProject* current, Core::self()->projectController()->projects() ) { if ( current->path().isParentOf(path) ) { project = current; break; } } if (!project) { return; } IContentAwareVersionControl* iface; iface = qobject_cast< KDevelop::IContentAwareVersionControl* >(project->versionControlPlugin()); if (!iface) { return; } if ( !qobject_cast( document ) ) { return; } CheckInRepositoryJob* req = iface->isInRepository( document ); if ( !req ) { return; } QObject::connect(req, &CheckInRepositoryJob::finished, q, &TextDocument::repositoryCheckFinished); // Abort the request when the user edits the document QObject::connect(q->textDocument(), &KTextEditor::Document::textChanged, req, &CheckInRepositoryJob::abort); } void modifiedOnDisk(KTextEditor::Document *document, bool /*isModified*/, KTextEditor::ModificationInterface::ModifiedOnDiskReason reason) { bool dirty = false; switch (reason) { case KTextEditor::ModificationInterface::OnDiskUnmodified: break; case KTextEditor::ModificationInterface::OnDiskModified: case KTextEditor::ModificationInterface::OnDiskCreated: case KTextEditor::ModificationInterface::OnDiskDeleted: dirty = true; break; } // In some cases, the VCS (e.g. git) can know whether the old contents are "valuable", i.e. // not retrieveable from the VCS. If that is not the case, then the document can safely be // reloaded without displaying a dialog asking the user. if ( dirty ) { queryCanRecreateFromVcs(document); } setStatus(document, dirty); } TextDocument * const q; bool m_loaded; // we want to remove the added stuff when the menu hides QMenu* m_addedContextMenu; }; struct TextViewPrivate { - TextViewPrivate(TextView* q) : q(q) {} + explicit TextViewPrivate(TextView* q) : q(q) {} TextView* const q; QPointer view; KTextEditor::Range initialRange; }; TextDocument::TextDocument(const QUrl &url, ICore* core, const QString& encoding) :PartDocument(url, core), d(new TextDocumentPrivate(this)) { d->encoding = encoding; } TextDocument::~TextDocument() { delete d; } bool TextDocument::isTextDocument() const { if( !d->document ) { /// @todo Somehow it can happen that d->document is zero, which makes /// code relying on "isTextDocument() == (bool)textDocument()" crash qWarning() << "Broken text-document: " << url(); return false; } return true; } KTextEditor::Document *TextDocument::textDocument() const { return d->document; } QWidget *TextDocument::createViewWidget(QWidget *parent) { KTextEditor::View* view = nullptr; if (!d->document) { d->document = Core::self()->partControllerInternal()->createTextPart(); // Connect to the first text changed signal, it occurs before the completed() signal connect(d->document.data(), &KTextEditor::Document::textChanged, this, &TextDocument::slotDocumentLoaded); // Also connect to the completed signal, sometimes the first text changed signal is missed because the part loads too quickly (? TODO - confirm this is necessary) connect(d->document.data(), static_cast(&KTextEditor::Document::completed), this, &TextDocument::slotDocumentLoaded); // force a reparse when a document gets reloaded connect(d->document.data(), &KTextEditor::Document::reloaded, this, [] (KTextEditor::Document* document) { ICore::self()->languageController()->backgroundParser()->addDocument(IndexedString(document->url()), (TopDUContext::Features) ( TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdate ), BackgroundParser::BestPriority, nullptr); }); // Set encoding passed via constructor // Needs to be done before openUrl, else katepart won't use the encoding // @see KTextEditor::Document::setEncoding if (!d->encoding.isEmpty()) d->document->setEncoding(d->encoding); if (!url().isEmpty() && !DocumentController::isEmptyDocumentUrl(url())) d->document->openUrl( url() ); d->setStatus(d->document, false); /* It appears, that by default a part will be deleted the first view containing it is deleted. Since we do want to have several views, disable that behaviour. */ d->document->setAutoDeletePart(false); Core::self()->partController()->addPart(d->document, false); d->loadSessionConfig(); connect(d->document.data(), &KTextEditor::Document::modifiedChanged, this, &TextDocument::newDocumentStatus); connect(d->document.data(), &KTextEditor::Document::textChanged, this, &TextDocument::textChanged); connect(d->document.data(), &KTextEditor::Document::documentUrlChanged, this, &TextDocument::documentUrlChanged); connect(d->document.data(), &KTextEditor::Document::documentSavedOrUploaded, this, &TextDocument::documentSaved ); if (qobject_cast(d->document)) { // can't use new signal/slot syntax here, MarkInterface is not a QObject connect(d->document.data(), SIGNAL(marksChanged(KTextEditor::Document*)), this, SLOT(saveSessionConfig())); } if (auto iface = qobject_cast(d->document)) { iface->setModifiedOnDiskWarning(true); // can't use new signal/slot syntax here, ModificationInterface is not a QObject connect(d->document.data(), SIGNAL(modifiedOnDisk(KTextEditor::Document*,bool,KTextEditor::ModificationInterface::ModifiedOnDiskReason)), this, SLOT(modifiedOnDisk(KTextEditor::Document*,bool,KTextEditor::ModificationInterface::ModifiedOnDiskReason))); } notifyTextDocumentCreated(); } view = d->document->createView(parent); // get rid of some actions regarding the config dialog, we merge that one into the kdevelop menu already delete view->actionCollection()->action(QStringLiteral("set_confdlg")); delete view->actionCollection()->action(QStringLiteral("editor_options")); view->setStatusBarEnabled(Core::self()->partControllerInternal()->showTextEditorStatusBar()); connect(view, &KTextEditor::View::contextMenuAboutToShow, this, &TextDocument::populateContextMenu); if (KTextEditor::CodeCompletionInterface* cc = dynamic_cast(view)) cc->setAutomaticInvocationEnabled(core()->languageController()->completionSettings()->automaticCompletionEnabled()); if (KTextEditor::ConfigInterface *config = qobject_cast(view)) { config->setConfigValue(QStringLiteral("allow-mark-menu"), false); config->setConfigValue(QStringLiteral("default-mark-type"), KTextEditor::MarkInterface::BreakpointActive); } return view; } KParts::Part *TextDocument::partForView(QWidget *view) const { if (d->document && d->document->views().contains((KTextEditor::View*)view)) return d->document; return nullptr; } // KDevelop::IDocument implementation void TextDocument::reload() { if (!d->document) return; KTextEditor::ModificationInterface* modif=nullptr; if(d->state==Dirty) { modif = qobject_cast(d->document); modif->setModifiedOnDiskWarning(false); } d->document->documentReload(); if(modif) modif->setModifiedOnDiskWarning(true); } bool TextDocument::save(DocumentSaveMode mode) { if (!d->document) return true; if (mode & Discard) return true; switch (d->state) { case IDocument::Clean: return true; case IDocument::Modified: break; case IDocument::Dirty: case IDocument::DirtyAndModified: if (!(mode & Silent)) { int code = KMessageBox::warningYesNoCancel( Core::self()->uiController()->activeMainWindow(), i18n("The file \"%1\" is modified on disk.\n\nAre " "you sure you want to overwrite it? (External " "changes will be lost.)", d->document->url().toLocalFile()), i18nc("@title:window", "Document Externally Modified")); if (code != KMessageBox::Yes) return false; } break; } if (!KDevelop::ensureWritable(QList() << url())) { return false; } QUrl urlBeforeSave = d->document->url(); if (d->document->documentSave()) { if (d->document->url() != urlBeforeSave) notifyUrlChanged(); return true; } return false; } IDocument::DocumentState TextDocument::state() const { return d->state; } KTextEditor::Cursor KDevelop::TextDocument::cursorPosition() const { if (!d->document) { return KTextEditor::Cursor::invalid(); } KTextEditor::View *view = activeTextView(); if (view) return view->cursorPosition(); return KTextEditor::Cursor::invalid(); } void TextDocument::setCursorPosition(const KTextEditor::Cursor &cursor) { if (!cursor.isValid() || !d->document) return; KTextEditor::View *view = activeTextView(); // Rodda: Cursor must be accurate here, to the definition of accurate for KTextEditor::Cursor. // ie, starting from 0,0 if (view) view->setCursorPosition(cursor); } KTextEditor::Range TextDocument::textSelection() const { if (!d->document) { return KTextEditor::Range::invalid(); } KTextEditor::View *view = activeTextView(); if (view && view->selection()) { return view->selectionRange(); } return PartDocument::textSelection(); } QString TextDocument::text(const KTextEditor::Range &range) const { if (!d->document) { return QString(); } return d->document->text( range ); } QString TextDocument::textLine() const { if (!d->document) { return QString(); } KTextEditor::View *view = activeTextView(); if (view) { return d->document->line( view->cursorPosition().line() ); } return PartDocument::textLine(); } QString TextDocument::textWord() const { if (!d->document) { return QString(); } KTextEditor::View *view = activeTextView(); if (view) { KTextEditor::Cursor start = view->cursorPosition(); qCDebug(SHELL) << "got start position from view:" << start.line() << start.column(); QString linestr = textLine(); int startPos = qMax( qMin( start.column(), linestr.length() - 1 ), 0 ); int endPos = startPos; startPos --; while( startPos >= 0 && ( linestr[startPos].isLetterOrNumber() || linestr[startPos] == '_' || linestr[startPos] == '~' ) ) { --startPos; } while( endPos < linestr.length() && ( linestr[endPos].isLetterOrNumber() || linestr[endPos] == '_' || linestr[endPos] == '~' ) ) { ++endPos; } if( startPos != endPos ) { qCDebug(SHELL) << "found word" << startPos << endPos << linestr.mid( startPos+1, endPos - startPos - 1 ); return linestr.mid( startPos + 1, endPos - startPos - 1 ); } } return PartDocument::textWord(); } void TextDocument::setTextSelection(const KTextEditor::Range &range) { if (!range.isValid() || !d->document) return; KTextEditor::View *view = activeTextView(); if (view) { selectAndReveal(view, range); } } bool TextDocument::close(DocumentSaveMode mode) { if (!PartDocument::close(mode)) return false; if ( d->document ) { d->saveSessionConfig(); delete d->document; //We have to delete the document right now, to prevent random crashes in the event handler } return true; } Sublime::View* TextDocument::newView(Sublime::Document* doc) { Q_UNUSED(doc); return new TextView(this); } } KDevelop::TextView::TextView(TextDocument * doc) : View(doc, View::TakeOwnership), d(new TextViewPrivate(this)) { } KDevelop::TextView::~TextView() { delete d; } QWidget * KDevelop::TextView::createWidget(QWidget * parent) { auto textDocument = qobject_cast(document()); Q_ASSERT(textDocument); QWidget* widget = textDocument->createViewWidget(parent); d->view = qobject_cast(widget); Q_ASSERT(d->view); connect(d->view.data(), &KTextEditor::View::cursorPositionChanged, this, &KDevelop::TextView::sendStatusChanged); return widget; } QString KDevelop::TextView::viewState() const { if (d->view) { if (d->view->selection()) { KTextEditor::Range selection = d->view->selectionRange(); return QStringLiteral("Selection=%1,%2,%3,%4").arg(selection.start().line()) .arg(selection.start().column()) .arg(selection.end().line()) .arg(selection.end().column()); } else { KTextEditor::Cursor cursor = d->view->cursorPosition(); return QStringLiteral("Cursor=%1,%2").arg(cursor.line()).arg(cursor.column()); } } else { KTextEditor::Range selection = d->initialRange; return QStringLiteral("Selection=%1,%2,%3,%4").arg(selection.start().line()) .arg(selection.start().column()) .arg(selection.end().line()) .arg(selection.end().column()); } } void KDevelop::TextView::setInitialRange(const KTextEditor::Range& range) { if (d->view) { selectAndReveal(d->view, range); } else { d->initialRange = range; } } KTextEditor::Range KDevelop::TextView::initialRange() const { return d->initialRange; } void KDevelop::TextView::setState(const QString & state) { static QRegExp reCursor("Cursor=([\\d]+),([\\d]+)"); static QRegExp reSelection("Selection=([\\d]+),([\\d]+),([\\d]+),([\\d]+)"); if (reCursor.exactMatch(state)) { setInitialRange(KTextEditor::Range(KTextEditor::Cursor(reCursor.cap(1).toInt(), reCursor.cap(2).toInt()), 0)); } else if (reSelection.exactMatch(state)) { KTextEditor::Range range(reSelection.cap(1).toInt(), reSelection.cap(2).toInt(), reSelection.cap(3).toInt(), reSelection.cap(4).toInt()); setInitialRange(range); } } QString KDevelop::TextDocument::documentType() const { return QStringLiteral("Text"); } QIcon KDevelop::TextDocument::defaultIcon() const { if (d->document) { QMimeType mime = QMimeDatabase().mimeTypeForName(d->document->mimeType()); QIcon icon = QIcon::fromTheme(mime.iconName()); if (!icon.isNull()) { return icon; } } return PartDocument::defaultIcon(); } KTextEditor::View *KDevelop::TextView::textView() const { return d->view; } QString KDevelop::TextView::viewStatus() const { // only show status when KTextEditor's own status bar isn't already enabled const bool showStatus = !Core::self()->partControllerInternal()->showTextEditorStatusBar(); if (!showStatus) { return QString(); } const KTextEditor::Cursor pos = d->view ? d->view->cursorPosition() : KTextEditor::Cursor::invalid(); return i18n(" Line: %1 Col: %2 ", pos.line() + 1, pos.column() + 1); } void KDevelop::TextView::sendStatusChanged() { emit statusChanged(this); } KTextEditor::View* KDevelop::TextDocument::activeTextView() const { KTextEditor::View* fallback = nullptr; for (auto view : views()) { auto textView = qobject_cast(view)->textView(); if (!textView) { continue; } if (textView->hasFocus()) { return textView; } else if (textView->isVisible()) { fallback = textView; } else if (!fallback) { fallback = textView; } } return fallback; } void KDevelop::TextDocument::newDocumentStatus(KTextEditor::Document *document) { bool dirty = (d->state == IDocument::Dirty || d->state == IDocument::DirtyAndModified); d->setStatus(document, dirty); } void KDevelop::TextDocument::textChanged(KTextEditor::Document *document) { Q_UNUSED(document); notifyContentChanged(); } void KDevelop::TextDocument::populateContextMenu( KTextEditor::View* v, QMenu* menu ) { if (d->m_addedContextMenu) { foreach ( QAction* action, d->m_addedContextMenu->actions() ) { menu->removeAction(action); } delete d->m_addedContextMenu; } d->m_addedContextMenu = new QMenu(); EditorContext c(v, v->cursorPosition()); auto extensions = Core::self()->pluginController()->queryPluginsForContextMenuExtensions(&c); ContextMenuExtension::populateMenu(d->m_addedContextMenu, extensions); { QUrl url = v->document()->url(); QList< ProjectBaseItem* > items = Core::self()->projectController()->projectModel()->itemsForPath( IndexedString(url) ); if (!items.isEmpty()) { populateParentItemsMenu( items.front(), d->m_addedContextMenu ); } } foreach ( QAction* action, d->m_addedContextMenu->actions() ) { menu->addAction(action); } } void KDevelop::TextDocument::repositoryCheckFinished(bool canRecreate) { if ( d->state != IDocument::Dirty && d->state != IDocument::DirtyAndModified ) { // document is not dirty for whatever reason, nothing to do. return; } if ( ! canRecreate ) { return; } KTextEditor::ModificationInterface* modIface = qobject_cast( d->document ); Q_ASSERT(modIface); // Ok, all safe, we can clean up the document. Close it if the file is gone, // and reload if it's still there. d->setStatus(d->document, false); modIface->setModifiedOnDisk(KTextEditor::ModificationInterface::OnDiskUnmodified); if ( QFile::exists(d->document->url().path()) ) { reload(); } else { close(KDevelop::IDocument::Discard); } } void KDevelop::TextDocument::slotDocumentLoaded() { if (d->m_loaded) return; // Tell the editor integrator first d->m_loaded = true; notifyLoaded(); } void KDevelop::TextDocument::documentSaved(KTextEditor::Document* document, bool saveAs) { Q_UNUSED(document); Q_UNUSED(saveAs); notifySaved(); notifyStateChanged(); } void KDevelop::TextDocument::documentUrlChanged(KTextEditor::Document* document) { Q_UNUSED(document); if (url() != d->document->url()) setUrl(d->document->url()); } #include "moc_textdocument.cpp" diff --git a/shell/uicontroller.cpp b/shell/uicontroller.cpp index 1c0bcb253..5e3167f21 100644 --- a/shell/uicontroller.cpp +++ b/shell/uicontroller.cpp @@ -1,757 +1,757 @@ /*************************************************************************** * Copyright 2007 Alexander Dymo * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "uicontroller.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "core.h" #include "configpage.h" #include "configdialog.h" #include "debug.h" #include "editorconfigpage.h" #include "shellextension.h" #include "partcontroller.h" #include "plugincontroller.h" #include "mainwindow.h" #include "partdocument.h" #include "textdocument.h" #include "documentcontroller.h" #include #include "workingsetcontroller.h" #include "workingsets/workingset.h" #include "settings/bgpreferences.h" #include "settings/languagepreferences.h" #include "settings/environmentpreferences.h" #include "settings/pluginpreferences.h" #include "settings/projectpreferences.h" #include "settings/sourceformattersettings.h" #include "settings/uipreferences.h" #include "settings/templateconfig.h" #include "settings/analyzerspreferences.h" #include "settings/documentationpreferences.h" namespace KDevelop { class UiControllerPrivate { public: - UiControllerPrivate(UiController *controller) + explicit UiControllerPrivate(UiController *controller) : areasRestored(false), m_controller(controller) { if (Core::self()->workingSetControllerInternal()) Core::self()->workingSetControllerInternal()->initializeController(m_controller); m_controller->connect(m_controller, &Sublime::Controller::mainWindowAdded, m_controller, &UiController::mainWindowAdded); QMap desired; desired[QStringLiteral("org.kdevelop.ClassBrowserView")] = Sublime::Left; desired[QStringLiteral("org.kdevelop.DocumentsView")] = Sublime::Left; desired[QStringLiteral("org.kdevelop.ProjectsView")] = Sublime::Left; desired[QStringLiteral("org.kdevelop.FileManagerView")] = Sublime::Left; desired[QStringLiteral("org.kdevelop.ProblemReporterView")] = Sublime::Bottom; desired[QStringLiteral("org.kdevelop.OutputView")] = Sublime::Bottom; desired[QStringLiteral("org.kdevelop.ContextBrowser")] = Sublime::Bottom; desired[QStringLiteral("org.kdevelop.KonsoleView")] = Sublime::Bottom; desired[QStringLiteral("org.kdevelop.SnippetView")] = Sublime::Right; desired[QStringLiteral("org.kdevelop.ExternalScriptView")] = Sublime::Right; Sublime::Area* a = new Sublime::Area(m_controller, QStringLiteral("code"), i18n("Code")); a->setDesiredToolViews(desired); a->setIconName(QStringLiteral("document-edit")); m_controller->addDefaultArea(a); desired.clear(); desired[QStringLiteral("org.kdevelop.debugger.VariablesView")] = Sublime::Left; desired[QStringLiteral("org.kdevelop.debugger.BreakpointsView")] = Sublime::Bottom; desired[QStringLiteral("org.kdevelop.debugger.StackView")] = Sublime::Bottom; desired[QStringLiteral("org.kdevelop.debugger.ConsoleView")] = Sublime::Bottom; desired[QStringLiteral("org.kdevelop.KonsoleView")] = Sublime::Bottom; a = new Sublime::Area(m_controller, QStringLiteral("debug"), i18n("Debug")); a->setDesiredToolViews(desired); a->setIconName(QStringLiteral("tools-report-bug")); m_controller->addDefaultArea(a); desired.clear(); desired[QStringLiteral("org.kdevelop.ProjectsView")] = Sublime::Left; desired[QStringLiteral("org.kdevelop.PatchReview")] = Sublime::Bottom; a = new Sublime::Area(m_controller, QStringLiteral("review"), i18n("Review")); a->setDesiredToolViews(desired); a->setIconName(QStringLiteral("applications-engineering")); m_controller->addDefaultArea(a); if(!(Core::self()->setupFlags() & Core::NoUi)) { defaultMainWindow = new MainWindow(m_controller); m_controller->addMainWindow(defaultMainWindow); activeSublimeWindow = defaultMainWindow; } else { activeSublimeWindow = defaultMainWindow = nullptr; } m_assistantTimer.setSingleShot(true); m_assistantTimer.setInterval(100); } void widgetChanged(QWidget*, QWidget* now) { if (now) { Sublime::MainWindow* win = qobject_cast(now->window()); if( win ) { activeSublimeWindow = win; } } } Core *core; QPointer defaultMainWindow; QHash factoryDocuments; QPointer activeSublimeWindow; bool areasRestored; /// QWidget implementing IToolViewActionListener interface, or null QPointer activeActionListener; QTimer m_assistantTimer; private: UiController *m_controller; }; class UiToolViewFactory: public Sublime::ToolFactory { public: - UiToolViewFactory(IToolViewFactory *factory): m_factory(factory) {} + explicit UiToolViewFactory(IToolViewFactory *factory): m_factory(factory) {} ~UiToolViewFactory() override { delete m_factory; } QWidget* create(Sublime::ToolDocument *doc, QWidget *parent = nullptr) override { Q_UNUSED( doc ); return m_factory->create(parent); } QList< QAction* > contextMenuActions(QWidget* viewWidget) const override { return m_factory->contextMenuActions( viewWidget ); } QList toolBarActions( QWidget* viewWidget ) const override { return m_factory->toolBarActions( viewWidget ); } QString id() const override { return m_factory->id(); } private: IToolViewFactory *m_factory; }; class ViewSelectorItem: public QListWidgetItem { public: - ViewSelectorItem(const QString &text, QListWidget *parent = nullptr, int type = Type) + explicit ViewSelectorItem(const QString &text, QListWidget *parent = nullptr, int type = Type) :QListWidgetItem(text, parent, type) {} IToolViewFactory *factory; }; class NewToolViewListWidget: public QListWidget { Q_OBJECT public: - NewToolViewListWidget(MainWindow *mw, QWidget* parent = nullptr) + explicit NewToolViewListWidget(MainWindow *mw, QWidget* parent = nullptr) :QListWidget(parent), m_mw(mw) { connect(this, &NewToolViewListWidget::doubleClicked, this, &NewToolViewListWidget::addNewToolViewByDoubleClick); } Q_SIGNALS: void addNewToolView(MainWindow *mw, QListWidgetItem *item); private Q_SLOTS: void addNewToolViewByDoubleClick(QModelIndex index) { QListWidgetItem *item = itemFromIndex(index); // Disable item so that the toolview can not be added again. item->setFlags(item->flags() & ~Qt::ItemIsEnabled); emit addNewToolView(m_mw, item); } private: MainWindow *m_mw; }; UiController::UiController(Core *core) :Sublime::Controller(nullptr), IUiController(), d(new UiControllerPrivate(this)) { setObjectName(QStringLiteral("UiController")); d->core = core; if (!defaultMainWindow() || (Core::self()->setupFlags() & Core::NoUi)) return; connect(qApp, &QApplication::focusChanged, this, [&] (QWidget* old, QWidget* now) { d->widgetChanged(old, now); } ); setupActions(); } UiController::~UiController() { delete d; } void UiController::setupActions() { } void UiController::mainWindowAdded(Sublime::MainWindow* mainWindow) { connect(mainWindow, &MainWindow::activeToolViewChanged, this, &UiController::slotActiveToolViewChanged); connect(mainWindow, &MainWindow::areaChanged, this, &UiController::slotAreaChanged); // also check after area reconstruction } // FIXME: currently, this always create new window. Probably, // should just rename it. void UiController::switchToArea(const QString &areaName, SwitchMode switchMode) { if (switchMode == ThisWindow) { showArea(areaName, activeSublimeWindow()); return; } MainWindow *main = new MainWindow(this); addMainWindow(main); showArea(areaName, main); main->initialize(); // WTF? First, enabling this code causes crashes since we // try to disconnect some already-deleted action, or something. // Second, this code will disconnection the clients from guiFactory // of the previous main window. Ick! #if 0 //we need to add all existing guiclients to the new mainwindow //@todo adymo: add only ones that belong to the area (when the area code is there) foreach (KXMLGUIClient *client, oldMain->guiFactory()->clients()) main->guiFactory()->addClient(client); #endif main->show(); } QWidget* UiController::findToolView(const QString& name, IToolViewFactory *factory, FindFlags flags) { if(!d->areasRestored || !activeArea()) return nullptr; QList< Sublime::View* > views = activeArea()->toolViews(); foreach(Sublime::View* view, views) { Sublime::ToolDocument *doc = dynamic_cast(view->document()); if(doc && doc->title() == name && view->widget()) { if(flags & Raise) view->requestRaise(); return view->widget(); } } QWidget* ret = nullptr; if(flags & Create) { Sublime::ToolDocument* doc = d->factoryDocuments.value(factory); if(!doc) { doc = new Sublime::ToolDocument(name, this, new UiToolViewFactory(factory)); d->factoryDocuments.insert(factory, doc); } Sublime::View* view = addToolViewToArea(factory, doc, activeArea()); if(view) ret = view->widget(); if(flags & Raise) findToolView(name, factory, Raise); } return ret; } void UiController::raiseToolView(QWidget* toolViewWidget) { if(!d->areasRestored) return; QList< Sublime::View* > views = activeArea()->toolViews(); foreach(Sublime::View* view, views) { if(view->widget() == toolViewWidget) { view->requestRaise(); return; } } } void UiController::addToolView(const QString & name, IToolViewFactory *factory, FindFlags state) { if (!factory) return; qCDebug(SHELL) ; Sublime::ToolDocument *doc = new Sublime::ToolDocument(name, this, new UiToolViewFactory(factory)); d->factoryDocuments[factory] = doc; /* Until areas are restored, we don't know which views should be really added, and which not, so we just record view availability. */ if (d->areasRestored && state != None) { foreach (Sublime::Area* area, allAreas()) { addToolViewToArea(factory, doc, area); } } } void KDevelop::UiController::raiseToolView(Sublime::View * view) { foreach( Sublime::Area* area, allAreas() ) { if( area->toolViews().contains( view ) ) area->raiseToolView( view ); } slotActiveToolViewChanged(view); } void UiController::slotAreaChanged(Sublime::Area*) { // this slot gets call if an area in *any* MainWindow changed // so let's first get the "active area" const auto area = activeSublimeWindow()->area(); if (area) { // walk through shown tool views and maku sure the const auto shownIds = area->shownToolViews(Sublime::AllPositions); foreach (Sublime::View* toolView, area->toolViews()) { if (shownIds.contains(toolView->document()->documentSpecifier())) { slotActiveToolViewChanged(toolView); } } } } void UiController::slotActiveToolViewChanged(Sublime::View* view) { if (!view) { return; } // record the last active tool view action listener if (qobject_cast(view->widget())) { d->activeActionListener = view->widget(); } } void KDevelop::UiController::removeToolView(IToolViewFactory *factory) { if (!factory) return; qCDebug(SHELL) ; //delete the tooldocument Sublime::ToolDocument *doc = d->factoryDocuments.value(factory); ///@todo adymo: on document deletion all its views shall be also deleted foreach (Sublime::View *view, doc->views()) { foreach (Sublime::Area *area, allAreas()) if (area->removeToolView(view)) view->deleteLater(); } d->factoryDocuments.remove(factory); delete doc; } Sublime::Area *UiController::activeArea() { Sublime::MainWindow *m = activeSublimeWindow(); if (m) return activeSublimeWindow()->area(); return nullptr; } Sublime::MainWindow *UiController::activeSublimeWindow() { return d->activeSublimeWindow; } MainWindow *UiController::defaultMainWindow() { return d->defaultMainWindow; } void UiController::initialize() { defaultMainWindow()->initialize(); } void UiController::cleanup() { foreach (Sublime::MainWindow* w, mainWindows()) w->saveSettings(); saveAllAreas(KSharedConfig::openConfig()); } void UiController::selectNewToolViewToAdd(MainWindow *mw) { if (!mw || !mw->area()) return; QDialog *dia = new QDialog(mw); dia->setWindowTitle(i18n("Select Tool View to Add")); auto mainLayout = new QVBoxLayout(dia); NewToolViewListWidget *list = new NewToolViewListWidget(mw, dia); list->setSelectionMode(QAbstractItemView::ExtendedSelection); list->setSortingEnabled(true); for (QHash::const_iterator it = d->factoryDocuments.constBegin(); it != d->factoryDocuments.constEnd(); ++it) { ViewSelectorItem *item = new ViewSelectorItem(it.value()->title(), list); item->factory = it.key(); if (!item->factory->allowMultiple() && toolViewPresent(it.value(), mw->area())) { // Disable item if the toolview is already present. item->setFlags(item->flags() & ~Qt::ItemIsEnabled); } list->addItem(item); } list->setFocus(); connect(list, &NewToolViewListWidget::addNewToolView, this, &UiController::addNewToolView); mainLayout->addWidget(list); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel); auto okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); dia->connect(buttonBox, &QDialogButtonBox::accepted, dia, &QDialog::accept); dia->connect(buttonBox, &QDialogButtonBox::rejected, dia, &QDialog::reject); mainLayout->addWidget(buttonBox); if (dia->exec() == QDialog::Accepted) { foreach (QListWidgetItem* item, list->selectedItems()) { addNewToolView(mw, item); } } delete dia; } void UiController::addNewToolView(MainWindow *mw, QListWidgetItem* item) { ViewSelectorItem *current = static_cast(item); Sublime::ToolDocument *doc = d->factoryDocuments[current->factory]; Sublime::View *view = doc->createView(); mw->area()->addToolView(view, Sublime::dockAreaToPosition(current->factory->defaultPosition())); current->factory->viewCreated(view); } void UiController::showSettingsDialog() { auto parent = activeMainWindow(); auto editorConfigPage = new EditorConfigPage(parent); auto languageConfigPage = new LanguagePreferences(parent); auto analyzersPreferences = new AnalyzersPreferences(parent); auto documentationPreferences = new DocumentationPreferences(parent); auto configPages = QVector { new UiPreferences(parent), new PluginPreferences(parent), new SourceFormatterSettings(parent), new ProjectPreferences(parent), new EnvironmentPreferences(QString(), parent), new TemplateConfig(parent), editorConfigPage }; ConfigDialog cfgDlg(configPages, parent); auto addPluginPages = [&](IPlugin* plugin) { for (int i = 0, numPages = plugin->configPages(); i < numPages; ++i) { auto page = plugin->configPage(i, &cfgDlg); if (!page) continue; if (page->configPageType() == ConfigPage::LanguageConfigPage) { cfgDlg.addSubConfigPage(languageConfigPage, page); } else if (page->configPageType() == ConfigPage::AnalyzerConfigPage) { cfgDlg.addSubConfigPage(analyzersPreferences, page); } else if (page->configPageType() == ConfigPage::DocumentationConfigPage) { cfgDlg.addSubConfigPage(documentationPreferences, page); } else { // insert them before the editor config page cfgDlg.addConfigPage(page, editorConfigPage); } } }; cfgDlg.addConfigPage(documentationPreferences, configPages[5]); cfgDlg.addConfigPage(analyzersPreferences, documentationPreferences); cfgDlg.addConfigPage(languageConfigPage, analyzersPreferences); cfgDlg.addSubConfigPage(languageConfigPage, new BGPreferences(parent)); foreach (IPlugin* plugin, ICore::self()->pluginController()->loadedPlugins()) { addPluginPages(plugin); } // TODO: only load settings if a UI related page was changed? connect(&cfgDlg, &ConfigDialog::configSaved, activeSublimeWindow(), &Sublime::MainWindow::loadSettings); // make sure that pages get added whenever a new plugin is loaded (probably from the plugin selection dialog) // removal on plugin unload is already handled in ConfigDialog connect(ICore::self()->pluginController(), &IPluginController::pluginLoaded, &cfgDlg, addPluginPages); cfgDlg.exec(); } Sublime::Controller* UiController::controller() { return this; } KParts::MainWindow *UiController::activeMainWindow() { return activeSublimeWindow(); } void UiController::saveArea(Sublime::Area * area, KConfigGroup & group) { area->save(group); if (!area->workingSet().isEmpty()) { WorkingSet* set = Core::self()->workingSetControllerInternal()->getWorkingSet(area->workingSet()); set->saveFromArea(area, area->rootIndex()); } } void UiController::loadArea(Sublime::Area * area, const KConfigGroup & group) { area->load(group); if (!area->workingSet().isEmpty()) { WorkingSet* set = Core::self()->workingSetControllerInternal()->getWorkingSet(area->workingSet()); Q_ASSERT(set->isConnected(area)); Q_UNUSED(set); } } void UiController::saveAllAreas(KSharedConfigPtr config) { KConfigGroup uiConfig(config, "User Interface"); int wc = mainWindows().size(); uiConfig.writeEntry("Main Windows Count", wc); for (int w = 0; w < wc; ++w) { KConfigGroup mainWindowConfig(&uiConfig, QStringLiteral("Main Window %1").arg(w)); foreach (Sublime::Area* defaultArea, defaultAreas()) { // FIXME: using object name seems ugly. QString type = defaultArea->objectName(); Sublime::Area* area = this->area(w, type); KConfigGroup areaConfig(&mainWindowConfig, "Area " + type); areaConfig.deleteGroup(); areaConfig.writeEntry("id", type); saveArea(area, areaConfig); areaConfig.sync(); } } uiConfig.sync(); } void UiController::loadAllAreas(KSharedConfigPtr config) { KConfigGroup uiConfig(config, "User Interface"); int wc = uiConfig.readEntry("Main Windows Count", 1); /* It is expected the main windows are restored before restoring areas. */ if (wc > mainWindows().size()) wc = mainWindows().size(); QList changedAreas; /* Offer all toolviews to the default areas. */ foreach (Sublime::Area *area, defaultAreas()) { QHash::const_iterator i, e; for (i = d->factoryDocuments.constBegin(), e = d->factoryDocuments.constEnd(); i != e; ++i) { addToolViewIfWanted(i.key(), i.value(), area); } } /* Restore per-windows areas. */ for (int w = 0; w < wc; ++w) { KConfigGroup mainWindowConfig(&uiConfig, QStringLiteral("Main Window %1").arg(w)); Sublime::MainWindow *mw = mainWindows()[w]; /* We loop over default areas. This means that if the config file has an area of some type that is not in default set, we'd just ignore it. I think it's fine -- the model were a given mainwindow can has it's own area types not represented in the default set is way too complex. */ foreach (Sublime::Area* defaultArea, defaultAreas()) { QString type = defaultArea->objectName(); Sublime::Area* area = this->area(w, type); KConfigGroup areaConfig(&mainWindowConfig, "Area " + type); qCDebug(SHELL) << "Trying to restore area " << type; /* This is just an easy check that a group exists, to avoid "restoring" area from empty config group, wiping away programmatically installed defaults. */ if (areaConfig.readEntry("id", "") == type) { qCDebug(SHELL) << "Restoring area " << type; loadArea(area, areaConfig); } // At this point we know which toolviews the area wants. // Tender all tool views we have. QHash::const_iterator i, e; for (i = d->factoryDocuments.constBegin(), e = d->factoryDocuments.constEnd(); i != e; ++i) { addToolViewIfWanted(i.key(), i.value(), area); } } // Force reload of the changes. showAreaInternal(mw->area(), mw); mw->enableAreaSettingsSave(); } d->areasRestored = true; } void UiController::addToolViewToDockArea(IToolViewFactory* factory, Qt::DockWidgetArea area) { addToolViewToArea(factory, d->factoryDocuments.value(factory), activeArea(), Sublime::dockAreaToPosition(area)); } bool UiController::toolViewPresent(Sublime::ToolDocument* doc, Sublime::Area* area) { for (Sublime::View *view : doc->views()) { if( area->toolViews().contains( view ) ) return true; } return false; } void UiController::addToolViewIfWanted(IToolViewFactory* factory, Sublime::ToolDocument* doc, Sublime::Area* area) { if (area->wantToolView(factory->id())) { addToolViewToArea(factory, doc, area); } } Sublime::View* UiController::addToolViewToArea(IToolViewFactory* factory, Sublime::ToolDocument* doc, Sublime::Area* area, Sublime::Position p) { Sublime::View* view = doc->createView(); area->addToolView( view, p == Sublime::AllPositions ? Sublime::dockAreaToPosition(factory->defaultPosition()) : p); connect(view, &Sublime::View::raise, this, static_cast(&UiController::raiseToolView)); factory->viewCreated(view); return view; } void UiController::registerStatus(QObject* status) { Sublime::MainWindow* w = activeSublimeWindow(); if (!w) return; MainWindow* mw = qobject_cast(w); if (!mw) return; mw->registerStatus(status); } void UiController::showErrorMessage(const QString& message, int timeout) { Sublime::MainWindow* w = activeSublimeWindow(); if (!w) return; MainWindow* mw = qobject_cast(w); if (!mw) return; QMetaObject::invokeMethod(mw, "showErrorMessage", Q_ARG(QString, message), Q_ARG(int, timeout)); } const QHash< IToolViewFactory*, Sublime::ToolDocument* >& UiController::factoryDocuments() const { return d->factoryDocuments; } QWidget* UiController::activeToolViewActionListener() const { return d->activeActionListener; } QList UiController::allAreas() const { return Sublime::Controller::allAreas(); } } #include "uicontroller.moc" #include "moc_uicontroller.cpp" diff --git a/shell/watcheddocumentset.cpp b/shell/watcheddocumentset.cpp index 9bcc77ca1..cbe2b2207 100644 --- a/shell/watcheddocumentset.cpp +++ b/shell/watcheddocumentset.cpp @@ -1,359 +1,359 @@ /* * KDevelop Problem Reporter * * Copyright 2010 Dmitry Risenberg * * 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 "watcheddocumentset.h" #include #include #include #include #include #include #include #include #include namespace KDevelop { enum ActionFlag { DoUpdate = 1, DoEmit = 2 }; Q_DECLARE_FLAGS(ActionFlags, ActionFlag); Q_DECLARE_OPERATORS_FOR_FLAGS(ActionFlags); class WatchedDocumentSetPrivate : public QObject { Q_OBJECT public: using DocumentSet = WatchedDocumentSet::DocumentSet; - WatchedDocumentSetPrivate(WatchedDocumentSet* documentSet) + explicit WatchedDocumentSetPrivate(WatchedDocumentSet* documentSet) : m_documentSet(documentSet) , m_showImports(false) { connect(DUChain::self(), &DUChain::updateReady, this, &WatchedDocumentSetPrivate::updateReady); } inline bool showImports() const { return m_showImports; } void setShowImports(bool showImports) { if (m_showImports == showImports) return; DocumentSet oldImports = m_imports; m_showImports = showImports; updateImports(); if (m_imports != oldImports) emit m_documentSet->changed(); } inline const DocumentSet& documents() const { return m_documents; } inline const DocumentSet& imports() const { return m_imports; } inline void doUpdate(ActionFlags flags) { if (flags.testFlag(DoUpdate)) updateImports(); if (flags.testFlag(DoEmit)) emit m_documentSet->changed(); } void setDocuments(const DocumentSet& docs, ActionFlags flags = nullptr) { m_documents = docs; doUpdate(flags); } void addDocument(const IndexedString& doc, ActionFlags flags = nullptr) { if (m_documents.contains(doc)) return; m_documents.insert(doc); doUpdate(flags); } void delDocument(const IndexedString& doc, ActionFlags flags = nullptr) { if (!m_documents.contains(doc)) return; m_documents.remove(doc); doUpdate(flags); } void updateImports() { if (!m_showImports) { if (!m_imports.isEmpty()) { m_imports.clear(); return; } return; } getImportsFromDUChain(); } private: void getImportsFromDU(TopDUContext* context, QSet& visitedContexts) { if (!context || visitedContexts.contains(context)) return; visitedContexts.insert(context); foreach (const DUContext::Import& ctx, context->importedParentContexts()) { TopDUContext* topCtx = dynamic_cast(ctx.context(nullptr)); if (topCtx) getImportsFromDU(topCtx, visitedContexts); } } void getImportsFromDUChain() { QSet visitedContexts; m_imports.clear(); foreach (const IndexedString& doc, m_documents) { TopDUContext* ctx = DUChain::self()->chainForDocument(doc); getImportsFromDU(ctx, visitedContexts); visitedContexts.remove(ctx); } foreach (TopDUContext* ctx, visitedContexts) { m_imports.insert(ctx->url()); } } void updateReady(const IndexedString& doc, const ReferencedTopDUContext&) { if (!m_showImports || !m_documents.contains(doc)) return; DocumentSet oldImports = m_imports; updateImports(); if (m_imports != oldImports) emit m_documentSet->changed(); } WatchedDocumentSet* m_documentSet; DocumentSet m_documents; DocumentSet m_imports; bool m_showImports; }; WatchedDocumentSet::WatchedDocumentSet(QObject* parent) : QObject(parent) , d(new WatchedDocumentSetPrivate(this)) { } WatchedDocumentSet::~WatchedDocumentSet() { } bool WatchedDocumentSet::showImports() const { return d->showImports(); } void WatchedDocumentSet::setShowImports(bool showImports) { d->setShowImports(showImports); } void WatchedDocumentSet::setCurrentDocument(const IndexedString&) { } WatchedDocumentSet::DocumentSet WatchedDocumentSet::get() const { return d->documents(); } WatchedDocumentSet::DocumentSet WatchedDocumentSet::getImports() const { return d->imports(); } CurrentDocumentSet::CurrentDocumentSet(const IndexedString& document, QObject* parent) : WatchedDocumentSet(parent) { d->setDocuments({document}, DoUpdate); } void CurrentDocumentSet::setCurrentDocument(const IndexedString& url) { d->setDocuments({url}, DoUpdate | DoEmit); } ProblemScope CurrentDocumentSet::getScope() const { return CurrentDocument; } OpenDocumentSet::OpenDocumentSet(QObject* parent) : WatchedDocumentSet(parent) { foreach (IDocument* doc, ICore::self()->documentController()->openDocuments()) { d->addDocument(IndexedString(doc->url())); } d->updateImports(); connect(ICore::self()->documentController(), &IDocumentController::documentClosed, this, &OpenDocumentSet::documentClosed); connect(ICore::self()->documentController(), &IDocumentController::textDocumentCreated, this, &OpenDocumentSet::documentCreated); } void OpenDocumentSet::documentClosed(IDocument* doc) { d->delDocument(IndexedString(doc->url()), DoUpdate | DoEmit); } void OpenDocumentSet::documentCreated(IDocument* doc) { d->addDocument(IndexedString(doc->url()), DoUpdate | DoEmit); } ProblemScope OpenDocumentSet::getScope() const { return OpenDocuments; } ProjectSet::ProjectSet(QObject* parent) : WatchedDocumentSet(parent) { } void ProjectSet::fileAdded(ProjectFileItem* file) { d->addDocument(IndexedString(file->indexedPath()), DoUpdate | DoEmit); } void ProjectSet::fileRemoved(ProjectFileItem* file) { d->delDocument(IndexedString(file->indexedPath()), DoUpdate | DoEmit); } void ProjectSet::fileRenamed(const Path& oldFile, ProjectFileItem* newFile) { d->delDocument(IndexedString(oldFile.pathOrUrl())); d->addDocument(IndexedString(newFile->indexedPath()), DoUpdate | DoEmit); } void ProjectSet::trackProjectFiles(const IProject* project) { if (project) { // The implementation should derive from QObject somehow QObject* fileManager = dynamic_cast(project->projectFileManager()); if (fileManager) { // can't use new signal/slot syntax here, IProjectFileManager is no a QObject connect(fileManager, SIGNAL(fileAdded(ProjectFileItem*)), this, SLOT(fileAdded(ProjectFileItem*))); connect(fileManager, SIGNAL(fileRemoved(ProjectFileItem*)), this, SLOT(fileRemoved(ProjectFileItem*))); connect(fileManager, SIGNAL(fileRenamed(Path,ProjectFileItem*)), this, SLOT(fileRenamed(Path,ProjectFileItem*))); } } } CurrentProjectSet::CurrentProjectSet(const IndexedString& document, QObject* parent) : ProjectSet(parent) , m_currentProject(nullptr) { setCurrentDocumentInternal(document); } void CurrentProjectSet::setCurrentDocument(const IndexedString& url) { setCurrentDocumentInternal(url); } void CurrentProjectSet::setCurrentDocumentInternal(const IndexedString& url) { IProject* projectForUrl = ICore::self()->projectController()->findProjectForUrl(url.toUrl()); if (projectForUrl && projectForUrl != m_currentProject) { m_currentProject = projectForUrl; d->setDocuments(m_currentProject->fileSet()); d->addDocument(IndexedString(m_currentProject->path().toLocalFile()), DoUpdate | DoEmit); trackProjectFiles(m_currentProject); } } ProblemScope CurrentProjectSet::getScope() const { return CurrentProject; } AllProjectSet::AllProjectSet(QObject* parent) : ProjectSet(parent) { foreach(const IProject* project, ICore::self()->projectController()->projects()) { foreach (const IndexedString &indexedString, project->fileSet()) { d->addDocument(indexedString); } d->addDocument(IndexedString(project->path().toLocalFile())); trackProjectFiles(project); } d->updateImports(); emit changed(); } ProblemScope AllProjectSet::getScope() const { return AllProjects; } BypassSet::BypassSet(QObject* parent) : WatchedDocumentSet(parent) { } ProblemScope BypassSet::getScope() const { return BypassScopeFilter; } } #include "watcheddocumentset.moc" diff --git a/shell/workingsets/workingset.cpp b/shell/workingsets/workingset.cpp index 722fd742c..eab936a59 100644 --- a/shell/workingsets/workingset.cpp +++ b/shell/workingsets/workingset.cpp @@ -1,565 +1,565 @@ /* Copyright David Nolden This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "workingset.h" #include "../debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #define SYNC_OFTEN using namespace KDevelop; bool WorkingSet::m_loading = false; namespace { QIcon generateIcon(const WorkingSetIconParameters& params) { QImage pixmap(16, 16, QImage::Format_ARGB32); // fill the background with a transparent color pixmap.fill(QColor::fromRgba(qRgba(0, 0, 0, 0))); const uint coloredCount = params.coloredCount; // coordinates of the rectangles to draw, for 16x16 icons specifically QList rects; rects << QRect(1, 1, 5, 5) << QRect(1, 9, 5, 5) << QRect(9, 1, 5, 5) << QRect(9, 9, 5, 5); if ( params.swapDiagonal ) { rects.swap(1, 2); } QPainter painter(&pixmap); // color for non-colored squares, paint them brighter if the working set is the active one const int inact = 40; QColor darkColor = QColor::fromRgb(inact, inact, inact); // color for colored squares // this code is not fragile, you can just tune the magic formulas at random and see what looks good. // just make sure to keep it within the 0-360 / 0-255 / 0-255 space of the HSV model QColor brightColor = QColor::fromHsv(params.hue, qMin(255, 215 + (params.setId*5) % 150), 205 + (params.setId*11) % 50); // Y'UV "Y" value, the approximate "lightness" of the color // If it is above 0.6, then making the color darker a bit is okay, // if it is below 0.35, then the color should be a bit brighter. float brightY = 0.299 * brightColor.redF() + 0.587 * brightColor.greenF() + 0.114 * brightColor.blueF(); if ( brightY > 0.6 ) { if ( params.setId % 7 < 2 ) { // 2/7 chance to make the color significantly darker brightColor = brightColor.darker(120 + (params.setId*7) % 35); } else if ( params.setId % 5 == 0 ) { // 1/5 chance to make it a bit darker brightColor = brightColor.darker(110 + (params.setId*3) % 10); } } if ( brightY < 0.35 ) { // always make the color brighter to avoid very dark colors (like rgb(0, 0, 255)) brightColor = brightColor.lighter(120 + (params.setId*13) % 55); } int at = 0; foreach ( const QRect rect, rects ) { QColor currentColor; // pick the colored squares; you can get different patterns by re-ordering the "rects" list if ( (at + params.setId*7) % 4 < coloredCount ) { currentColor = brightColor; } else { currentColor = darkColor; } // draw the filling of the square painter.setPen(QColor(currentColor)); painter.setBrush(QBrush(currentColor)); painter.drawRect(rect); // draw a slight set-in shadow for the square -- it's barely recognizeable, // but it looks way better than without painter.setBrush(Qt::NoBrush); painter.setPen(QColor(0, 0, 0, 50)); painter.drawRect(rect); painter.setPen(QColor(0, 0, 0, 25)); painter.drawRect(rect.x() + 1, rect.y() + 1, rect.width() - 2, rect.height() - 2); at += 1; } return QIcon(QPixmap::fromImage(pixmap)); } } WorkingSet::WorkingSet(const QString& id) : QObject() , m_id(id) - , m_icon(generateIcon(id)) + , m_icon(generateIcon(WorkingSetIconParameters(id))) { } void WorkingSet::saveFromArea( Sublime::Area* a, Sublime::AreaIndex * area, KConfigGroup setGroup, KConfigGroup areaGroup ) { if (area->isSplit()) { setGroup.writeEntry("Orientation", area->orientation() == Qt::Horizontal ? "Horizontal" : "Vertical"); if (area->first()) { saveFromArea(a, area->first(), KConfigGroup(&setGroup, "0"), KConfigGroup(&areaGroup, "0")); } if (area->second()) { saveFromArea(a, area->second(), KConfigGroup(&setGroup, "1"), KConfigGroup(&areaGroup, "1")); } } else { setGroup.writeEntry("View Count", area->viewCount()); areaGroup.writeEntry("View Count", area->viewCount()); int index = 0; foreach (Sublime::View* view, area->views()) { //The working set config gets an updated list of files QString docSpec = view->document()->documentSpecifier(); //only save the documents from protocols KIO understands //otherwise we try to load kdev:// too early if (!KProtocolInfo::isKnownProtocol(QUrl(docSpec))) { continue; } setGroup.writeEntry(QStringLiteral("View %1").arg(index), docSpec); //The area specific config stores the working set documents in order along with their state areaGroup.writeEntry(QStringLiteral("View %1").arg(index), docSpec); areaGroup.writeEntry(QStringLiteral("View %1 State").arg(index), view->viewState()); ++index; } } } bool WorkingSet::isEmpty() const { KConfigGroup setConfig(Core::self()->activeSession()->config(), "Working File Sets"); KConfigGroup group = setConfig.group(m_id); return !group.hasKey("Orientation") && group.readEntry("View Count", 0) == 0; } struct DisableMainWindowUpdatesFromArea { - DisableMainWindowUpdatesFromArea(Sublime::Area* area) : m_area(area) { + explicit DisableMainWindowUpdatesFromArea(Sublime::Area* area) : m_area(area) { if(area) { foreach(Sublime::MainWindow* window, Core::self()->uiControllerInternal()->mainWindows()) { if(window->area() == area) { if(window->updatesEnabled()) { wasUpdatesEnabled.insert(window); window->setUpdatesEnabled(false); } } } } } ~DisableMainWindowUpdatesFromArea() { if(m_area) { foreach(Sublime::MainWindow* window, wasUpdatesEnabled) { window->setUpdatesEnabled(wasUpdatesEnabled.contains(window)); } } } Sublime::Area* m_area; QSet wasUpdatesEnabled; }; void loadFileList(QStringList& ret, KConfigGroup group) { if (group.hasKey("Orientation")) { QStringList subgroups = group.groupList(); if (subgroups.contains(QStringLiteral("0"))) { { KConfigGroup subgroup(&group, "0"); loadFileList(ret, subgroup); } if (subgroups.contains(QStringLiteral("1"))) { KConfigGroup subgroup(&group, "1"); loadFileList(ret, subgroup); } } } else { int viewCount = group.readEntry("View Count", 0); for (int i = 0; i < viewCount; ++i) { QString specifier = group.readEntry(QStringLiteral("View %1").arg(i), QString()); ret << specifier; } } } QStringList WorkingSet::fileList() const { QStringList ret; KConfigGroup setConfig(Core::self()->activeSession()->config(), "Working File Sets"); KConfigGroup group = setConfig.group(m_id); loadFileList(ret, group); return ret; } void WorkingSet::loadToArea(Sublime::Area* area, Sublime::AreaIndex* areaIndex) { PushValue enableLoading(m_loading, true); /// We cannot disable the updates here, because (probably) due to a bug in Qt, /// which causes the updates to stay disabled forever after some complex operations /// on the sub-views. This could be reproduced by creating two working-sets with complex /// split-view configurations and switching between them. Re-enabling the updates doesn't help. // DisableMainWindowUpdatesFromArea updatesDisabler(area); qCDebug(SHELL) << "loading working-set" << m_id << "into area" << area; QMultiMap recycle; foreach( Sublime::View* view, area->views() ) recycle.insert( view->document()->documentSpecifier(), area->removeView(view) ); qCDebug(SHELL) << "recycling" << recycle.size() << "old views"; Q_ASSERT( area->views().empty() ); KConfigGroup setConfig(Core::self()->activeSession()->config(), "Working File Sets"); KConfigGroup setGroup = setConfig.group(m_id); KConfigGroup areaGroup = setConfig.group(m_id + '|' + area->title()); loadToArea(area, areaIndex, setGroup, areaGroup, recycle); // Delete views which were not recycled qCDebug(SHELL) << "deleting " << recycle.size() << " old views"; qDeleteAll( recycle ); area->setActiveView(nullptr); //activate view in the working set /// @todo correctly select one out of multiple equal views QString activeView = areaGroup.readEntry("Active View", QString()); foreach (Sublime::View *v, area->views()) { if (v->document()->documentSpecifier() == activeView) { area->setActiveView(v); break; } } if( !area->activeView() && !area->views().isEmpty() ) area->setActiveView( area->views().at(0) ); if( area->activeView() ) { foreach(Sublime::MainWindow* window, Core::self()->uiControllerInternal()->mainWindows()) { if(window->area() == area) { window->activateView( area->activeView() ); } } } } void WorkingSet::loadToArea(Sublime::Area* area, Sublime::AreaIndex* areaIndex, KConfigGroup setGroup, KConfigGroup areaGroup, QMultiMap& recycle) { Q_ASSERT( !areaIndex->isSplit() ); if (setGroup.hasKey("Orientation")) { QStringList subgroups = setGroup.groupList(); /// @todo also save and restore the ratio if (subgroups.contains(QStringLiteral("0")) && subgroups.contains(QStringLiteral("1"))) { // qCDebug(SHELL) << "has zero, split:" << split; Qt::Orientation orientation = setGroup.readEntry("Orientation", "Horizontal") == QLatin1String("Vertical") ? Qt::Vertical : Qt::Horizontal; if(!areaIndex->isSplit()){ areaIndex->split(orientation); }else{ areaIndex->setOrientation(orientation); } loadToArea(area, areaIndex->first(), KConfigGroup(&setGroup, "0"), KConfigGroup(&areaGroup, "0"), recycle); loadToArea(area, areaIndex->second(), KConfigGroup(&setGroup, "1"), KConfigGroup(&areaGroup, "1"), recycle); if( areaIndex->first()->viewCount() == 0 ) areaIndex->unsplit(areaIndex->first()); else if( areaIndex->second()->viewCount() == 0 ) areaIndex->unsplit(areaIndex->second()); } } else { //Load all documents from the workingset into this areaIndex int viewCount = setGroup.readEntry("View Count", 0); QMap createdViews; for (int i = 0; i < viewCount; ++i) { QString specifier = setGroup.readEntry(QStringLiteral("View %1").arg(i), QString()); if (specifier.isEmpty()) { continue; } Sublime::View* previousView = area->views().empty() ? nullptr : area->views().at(area->views().size() - 1); QMultiMap::iterator it = recycle.find( specifier ); if( it != recycle.end() ) { area->addView( *it, areaIndex, previousView ); recycle.erase( it ); continue; } IDocument* doc = Core::self()->documentControllerInternal()->openDocument(QUrl::fromUserInput(specifier), KTextEditor::Cursor::invalid(), IDocumentController::DoNotActivate | IDocumentController::DoNotCreateView); Sublime::Document *document = dynamic_cast(doc); if (document) { Sublime::View* view = document->createView(); area->addView(view, areaIndex, previousView); createdViews[i] = view; } else { qWarning() << "Unable to create view" << specifier; } } //Load state for (int i = 0; i < viewCount; ++i) { QString state = areaGroup.readEntry(QStringLiteral("View %1 State").arg(i)); if (!state.isEmpty() && createdViews.contains(i)) createdViews[i]->setState(state); } } } void deleteGroupRecursive(KConfigGroup group) { // qCDebug(SHELL) << "deleting" << group.name(); foreach(const QString& entry, group.entryMap().keys()) { group.deleteEntry(entry); } Q_ASSERT(group.entryMap().isEmpty()); foreach(const QString& subGroup, group.groupList()) { deleteGroupRecursive(group.group(subGroup)); group.deleteGroup(subGroup); } //Why doesn't this work? // Q_ASSERT(group.groupList().isEmpty()); group.deleteGroup(); } void WorkingSet::deleteSet(bool force, bool silent) { if(m_areas.isEmpty() || force) { emit aboutToRemove(this); KConfigGroup setConfig(Core::self()->activeSession()->config(), "Working File Sets"); KConfigGroup group = setConfig.group(m_id); deleteGroupRecursive(group); #ifdef SYNC_OFTEN setConfig.sync(); #endif if(!silent) emit setChangedSignificantly(); } } void WorkingSet::saveFromArea(Sublime::Area* area, Sublime::AreaIndex* areaIndex) { qCDebug(SHELL) << "saving" << m_id << "from area"; bool wasPersistent = isPersistent(); KConfigGroup setConfig(Core::self()->activeSession()->config(), "Working File Sets"); KConfigGroup setGroup = setConfig.group(m_id); deleteGroupRecursive(setGroup); KConfigGroup areaGroup = setConfig.group(m_id + '|' + area->title()); QString lastActiveView = areaGroup.readEntry("Active View", ""); deleteGroupRecursive(areaGroup); if (area->activeView() && area->activeView()->document()) areaGroup.writeEntry("Active View", area->activeView()->document()->documentSpecifier()); else areaGroup.writeEntry("Active View", lastActiveView); saveFromArea(area, areaIndex, setGroup, areaGroup); if(isEmpty()) { deleteGroupRecursive(setGroup); deleteGroupRecursive(areaGroup); } setPersistent(wasPersistent); #ifdef SYNC_OFTEN setConfig.sync(); #endif emit setChangedSignificantly(); } void WorkingSet::areaViewAdded(Sublime::AreaIndex*, Sublime::View*) { Sublime::Area* area = qobject_cast(sender()); Q_ASSERT(area); Q_ASSERT(area->workingSet() == m_id); qCDebug(SHELL) << "added view in" << area << ", id" << m_id; if (m_loading) { qCDebug(SHELL) << "doing nothing because loading"; return; } changed(area); } void WorkingSet::areaViewRemoved(Sublime::AreaIndex*, Sublime::View* view) { Sublime::Area* area = qobject_cast(sender()); Q_ASSERT(area); Q_ASSERT(area->workingSet() == m_id); qCDebug(SHELL) << "removed view in" << area << ", id" << m_id; if (m_loading) { qCDebug(SHELL) << "doing nothing because loading"; return; } foreach(Sublime::Area* otherArea, m_areas) { if(otherArea == area) continue; bool hadDocument = false; foreach(Sublime::View* areaView, otherArea->views()) if(view->document() == areaView->document()) hadDocument = true; if(!hadDocument) { // We do this to prevent UI flicker. The view has already been removed from // one of the connected areas, so the working-set has already recorded the change. return; } } changed(area); } void WorkingSet::setPersistent(bool persistent) { KConfigGroup setConfig(Core::self()->activeSession()->config(), "Working File Sets"); KConfigGroup group = setConfig.group(m_id); group.writeEntry("persistent", persistent); #ifdef SYNC_OFTEN group.sync(); #endif qCDebug(SHELL) << "setting" << m_id << "persistent:" << persistent; } bool WorkingSet::isPersistent() const { KConfigGroup setConfig(Core::self()->activeSession()->config(), "Working File Sets"); KConfigGroup group = setConfig.group(m_id); return group.readEntry("persistent", false); } QIcon WorkingSet::icon() const { return m_icon; } bool WorkingSet::isConnected( Sublime::Area* area ) { return m_areas.contains( area ); } QString WorkingSet::id() const { return m_id; } bool WorkingSet::hasConnectedAreas() const { return !m_areas.isEmpty(); } bool WorkingSet::hasConnectedAreas( QList< Sublime::Area* > areas ) const { foreach( Sublime::Area* area, areas ) if ( m_areas.contains( area ) ) return true; return false; } void WorkingSet::connectArea( Sublime::Area* area ) { if ( m_areas.contains( area ) ) { qCDebug(SHELL) << "tried to double-connect area"; return; } qCDebug(SHELL) << "connecting" << m_id << "to area" << area; // Q_ASSERT(area->workingSet() == m_id); m_areas.push_back( area ); connect( area, &Sublime::Area::viewAdded, this, &WorkingSet::areaViewAdded ); connect( area, &Sublime::Area::viewRemoved, this, &WorkingSet::areaViewRemoved ); } void WorkingSet::disconnectArea( Sublime::Area* area ) { if ( !m_areas.contains( area ) ) { qCDebug(SHELL) << "tried to disconnect not connected area"; return; } qCDebug(SHELL) << "disconnecting" << m_id << "from area" << area; // Q_ASSERT(area->workingSet() == m_id); disconnect( area, &Sublime::Area::viewAdded, this, &WorkingSet::areaViewAdded ); disconnect( area, &Sublime::Area::viewRemoved, this, &WorkingSet::areaViewRemoved ); m_areas.removeAll( area ); } void WorkingSet::changed( Sublime::Area* area ) { if ( m_loading ) { return; } { //Do not capture changes done while loading PushValue enableLoading( m_loading, true ); qCDebug(SHELL) << "recording change done to" << m_id; saveFromArea( area, area->rootIndex() ); for ( QList< QPointer< Sublime::Area > >::iterator it = m_areas.begin(); it != m_areas.end(); ++it ) { if (( *it ) != area ) { loadToArea(( *it ), ( *it )->rootIndex() ); } } } emit setChangedSignificantly(); } diff --git a/shell/workingsets/workingset.h b/shell/workingsets/workingset.h index c214e7661..ac2aa0420 100644 --- a/shell/workingsets/workingset.h +++ b/shell/workingsets/workingset.h @@ -1,128 +1,128 @@ /* Copyright David Nolden This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef KDEVPLATFORM_WORKINGSET_H #define KDEVPLATFORM_WORKINGSET_H #include #include #include #include namespace Sublime { class Area; class AreaIndex; class View; } namespace KDevelop { /// Contains all significant parameters which control the appearance of a working set icon struct WorkingSetIconParameters { - WorkingSetIconParameters(const QString& id) + explicit WorkingSetIconParameters(const QString& id) : setId(qHash(id) % 268435459) , coloredCount((setId % 15 < 4) ? 1 : (setId % 15 < 10) ? 2 : (setId % 15 == 14) ? 4 : 3) , hue((setId % 273 * 83) % 360) , swapDiagonal(setId % 31 < 16) { }; // calculate layout and colors depending on the working set ID // modulo it so it's around 2^28, leaving some space before uint overflows const uint setId; // amount of colored squares in this icon (the rest is grey or whatever you set as default color) // use 4-6-4-1 weighting for 1, 2, 3, 4 squares, because that's the number of arrangements for each const uint coloredCount; const uint hue; bool swapDiagonal; // between 0 and 100, 100 = very similar, 0 = very different // 20 points should make a significantly different icon. uint similarity(const WorkingSetIconParameters& other) const { int sim = 100; uint hueDiff = qAbs(hue - other.hue); hueDiff = hueDiff > 180 ? 360 - hueDiff : hueDiff; sim -= hueDiff > 35 ? 50 : (hueDiff * 50) / 180; if ( coloredCount != other.coloredCount ) { sim -= 50; } else if ( coloredCount == 2 && swapDiagonal != other.swapDiagonal ) { sim -= 35; } return sim; }; }; class WorkingSet : public QObject { Q_OBJECT public: explicit WorkingSet(const QString& id); bool isConnected(Sublime::Area* area); QIcon icon() const; bool isPersistent() const; void setPersistent(bool persistent); QString id() const; QStringList fileList() const; bool isEmpty() const; ///Updates this working-set from the given area and area-index void saveFromArea(Sublime::Area* area, Sublime::AreaIndex * areaIndex); ///Loads this working-set directly from the configuration file, and stores it in the given area ///Does not ask the user, this should be done beforehand. void loadToArea(Sublime::Area* area, Sublime::AreaIndex* areaIndex); bool hasConnectedAreas() const; bool hasConnectedAreas(QList areas) const; void connectArea(Sublime::Area* area); void disconnectArea(Sublime::Area* area); void deleteSet(bool force, bool silent = false); private slots: void areaViewAdded(Sublime::AreaIndex* /*index*/, Sublime::View* /*view*/); void areaViewRemoved(Sublime::AreaIndex* /*index*/, Sublime::View* /*view*/); signals: void setChangedSignificantly(); void aboutToRemove(WorkingSet*); private: void changed(Sublime::Area* area); void saveFromArea(Sublime::Area* area, Sublime::AreaIndex *areaIndex, KConfigGroup setGroup, KConfigGroup areaGroup); void loadToArea(Sublime::Area* area, Sublime::AreaIndex *areaIndex, KConfigGroup setGroup, KConfigGroup areaGroup, QMultiMap& recycle); QString m_id; QIcon m_icon; QList > m_areas; static bool m_loading; }; } #endif // KDEVPLATFORM_WORKINGSET_H diff --git a/sublime/area.cpp b/sublime/area.cpp index 06566734c..73e5a590f 100644 --- a/sublime/area.cpp +++ b/sublime/area.cpp @@ -1,485 +1,485 @@ /*************************************************************************** * Copyright 2006-2007 Alexander Dymo * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "area.h" #include #include #include #include #include #include "view.h" #include "document.h" #include "areaindex.h" #include "controller.h" #include "sublimedebug.h" namespace Sublime { // struct AreaPrivate struct AreaPrivate { AreaPrivate() : rootIndex(new RootAreaIndex) , currentIndex(rootIndex.data()) , controller(nullptr) { } AreaPrivate(const AreaPrivate &p) : title(p.title) , rootIndex(new RootAreaIndex(*(p.rootIndex))) , currentIndex(rootIndex.data()) , controller(p.controller) , toolViewPositions() , desiredToolViews(p.desiredToolViews) , shownToolViews(p.shownToolViews) , iconName(p.iconName) , workingSet(p.workingSet) , m_actions(p.m_actions) { } ~AreaPrivate() { } struct ViewFinder { - ViewFinder(View *_view): view(_view), index(nullptr) {} + explicit ViewFinder(View *_view): view(_view), index(nullptr) {} Area::WalkerMode operator() (AreaIndex *idx) { if (idx->hasView(view)) { index = idx; return Area::StopWalker; } return Area::ContinueWalker; } View *view; AreaIndex *index; }; struct ViewLister { Area::WalkerMode operator()(AreaIndex *idx) { views += idx->views(); return Area::ContinueWalker; } QList views; }; QString title; QScopedPointer rootIndex; AreaIndex *currentIndex; Controller *controller; QList toolViews; QMap toolViewPositions; QMap desiredToolViews; QMap shownToolViews; QString iconName; QString workingSet; QPointer activeView; QList m_actions; }; // class Area Area::Area(Controller *controller, const QString &name, const QString &title) :QObject(controller), d( new AreaPrivate() ) { // FIXME: using objectName seems fishy. Introduce areaType method, // or some such. setObjectName(name); d->title = title; d->controller = controller; d->iconName = QStringLiteral("kdevelop"); d->workingSet.clear(); qCDebug(SUBLIME) << "initial working-set:" << d->workingSet; initialize(); } Area::Area(const Area &area) : QObject(area.controller()), d( new AreaPrivate( *(area.d) ) ) { setObjectName(area.objectName()); //clone toolviews d->toolViews.clear(); foreach (View *view, area.toolViews()) addToolView(view->document()->createView(), area.toolViewPosition(view)); initialize(); } void Area::initialize() { connect(this, &Area::viewAdded, d->controller, &Controller::notifyViewAdded); connect(this, &Area::aboutToRemoveView, d->controller, &Controller::notifyViewRemoved); connect(this, &Area::toolViewAdded, d->controller, &Controller::notifyToolViewAdded); connect(this, &Area::aboutToRemoveToolView, d->controller, &Controller::notifyToolViewRemoved); connect(this, &Area::toolViewMoved, d->controller, &Controller::toolViewMoved); /* In theory, ownership is passed to us, so should not bother detecting deletion outside. */ // Functor will be called after destructor has run -> capture controller pointer by value // otherwise we crash because we access the already freed pointer this->d auto controller = d->controller; connect(this, &Area::destroyed, controller, [controller] (QObject* obj) { controller->removeArea(static_cast(obj)); }); } Area::~Area() { delete d; } View* Area::activeView() { return d->activeView.data(); } void Area::setActiveView(View* view) { d->activeView = view; } void Area::addView(View *view, AreaIndex *index, View *after) { //View *after = 0; if (!after && controller()->openAfterCurrent()) { after = activeView(); } index->add(view, after); connect(view, &View::positionChanged, this, &Area::positionChanged); qCDebug(SUBLIME) << "view added in" << this; connect(this, &Area::destroyed, view, &View::deleteLater); emit viewAdded(index, view); } void Area::addView(View *view, View *after) { AreaIndex *index = d->currentIndex; if (after) { AreaIndex *i = indexOf(after); if (i) index = i; } addView(view, index); } void Area::addView(View *view, View *viewToSplit, Qt::Orientation orientation) { AreaIndex *indexToSplit = indexOf(viewToSplit); addView(view, indexToSplit, orientation); } void Area::addView(View* view, AreaIndex* indexToSplit, Qt::Orientation orientation) { indexToSplit->split(view, orientation); emit viewAdded(indexToSplit, view); connect(this, &Area::destroyed, view, &View::deleteLater); } View* Area::removeView(View *view) { AreaIndex *index = indexOf(view); if (!index) return nullptr; emit aboutToRemoveView(index, view); index->remove(view); emit viewRemoved(index, view); return view; } AreaIndex *Area::indexOf(View *view) { AreaPrivate::ViewFinder f(view); walkViews(f, d->rootIndex.data()); return f.index; } RootAreaIndex *Area::rootIndex() const { return d->rootIndex.data(); } void Area::addToolView(View *view, Position defaultPosition) { d->toolViews.append(view); const QString id = view->document()->documentSpecifier(); const Position position = d->desiredToolViews.value(id, defaultPosition); d->desiredToolViews[id] = position; d->toolViewPositions[view] = position; emit toolViewAdded(view, position); } void Sublime::Area::raiseToolView(View * toolView) { emit requestToolViewRaise(toolView); } View* Area::removeToolView(View *view) { if (!d->toolViews.contains(view)) return nullptr; emit aboutToRemoveToolView(view, d->toolViewPositions[view]); QString id = view->document()->documentSpecifier(); qCDebug(SUBLIME) << this << "removed tool view " << id; d->desiredToolViews.remove(id); d->toolViews.removeAll(view); d->toolViewPositions.remove(view); return view; } void Area::moveToolView(View *toolView, Position newPosition) { if (!d->toolViews.contains(toolView)) return; QString id = toolView->document()->documentSpecifier(); d->desiredToolViews[id] = newPosition; d->toolViewPositions[toolView] = newPosition; emit toolViewMoved(toolView, newPosition); } QList &Area::toolViews() const { return d->toolViews; } Position Area::toolViewPosition(View *toolView) const { return d->toolViewPositions[toolView]; } Controller *Area::controller() const { return d->controller; } QList Sublime::Area::views() { AreaPrivate::ViewLister lister; walkViews(lister, d->rootIndex.data()); return lister.views; } QString Area::title() const { return d->title; } void Area::setTitle(const QString &title) { d->title = title; } void Area::save(KConfigGroup& group) const { QStringList desired; QMap::iterator i, e; for (i = d->desiredToolViews.begin(), e = d->desiredToolViews.end(); i != e; ++i) { desired << i.key() + ':' + QString::number(static_cast(i.value())); } group.writeEntry("desired views", desired); qCDebug(SUBLIME) << "save " << this << "wrote" << group.readEntry("desired views", ""); group.writeEntry("view on left", shownToolViews(Sublime::Left)); group.writeEntry("view on right", shownToolViews(Sublime::Right)); group.writeEntry("view on top", shownToolViews(Sublime::Top)); group.writeEntry("view on bottom", shownToolViews(Sublime::Bottom)); group.writeEntry("working set", d->workingSet); } void Area::load(const KConfigGroup& group) { qCDebug(SUBLIME) << "loading areas config"; d->desiredToolViews.clear(); QStringList desired = group.readEntry("desired views", QStringList()); foreach (const QString &s, desired) { int i = s.indexOf(':'); if (i != -1) { QString id = s.left(i); int pos_i = s.midRef(i+1).toInt(); Sublime::Position pos = static_cast(pos_i); if (pos != Sublime::Left && pos != Sublime::Right && pos != Sublime::Top && pos != Sublime::Bottom) { pos = Sublime::Bottom; } d->desiredToolViews[id] = pos; } } setShownToolViews(Sublime::Left, group.readEntry("view on left", QStringList())); setShownToolViews(Sublime::Right, group.readEntry("view on right", QStringList())); setShownToolViews(Sublime::Top, group.readEntry("view on top", QStringList())); setShownToolViews(Sublime::Bottom, group.readEntry("view on bottom", QStringList())); setWorkingSet(group.readEntry("working set", d->workingSet)); } bool Area::wantToolView(const QString& id) { return (d->desiredToolViews.contains(id)); } void Area::setShownToolViews(Sublime::Position pos, const QStringList& ids) { d->shownToolViews[pos] = ids; } QStringList Area::shownToolViews(Sublime::Position pos) const { if (pos == Sublime::AllPositions) { QStringList allIds; std::for_each(d->shownToolViews.constBegin(), d->shownToolViews.constEnd(), [&](const QStringList& ids) { allIds << ids; }); return allIds; } return d->shownToolViews[pos]; } void Area::setDesiredToolViews( const QMap& desiredToolViews) { d->desiredToolViews = desiredToolViews; } QString Area::iconName() const { return d->iconName; } void Area::setIconName(const QString& iconName) { d->iconName = iconName; } void Area::positionChanged(View *view, int newPos) { qCDebug(SUBLIME) << view << newPos; AreaIndex *index = indexOf(view); index->views().move(index->views().indexOf(view), newPos); } QString Area::workingSet() const { return d->workingSet; } void Area::setWorkingSet(QString name) { if(name != d->workingSet) { qCDebug(SUBLIME) << this << "setting new working-set" << name; QString oldName = d->workingSet; emit changingWorkingSet(this, oldName, name); d->workingSet = name; emit changedWorkingSet(this, oldName, name); } } bool Area::closeView(View* view, bool silent) { QPointer doc = view->document(); // We don't just delete the view, because if silent is false, we might need to ask the user. if(doc && !silent) { // Do some counting to check whether we need to ask the user for feedback qCDebug(SUBLIME) << "Closing view for" << view->document()->documentSpecifier() << "views" << view->document()->views().size() << "in area" << this; int viewsInCurrentArea = 0; // Number of views for the same document in the current area int viewsInOtherAreas = 0; // Number of views for the same document in other areas int viewsInOtherWorkingSets = 0; // Number of views for the same document in areas with different working-set foreach(View* otherView, doc.data()->views()) { Area* area = controller()->areaForView(otherView); if(area == this) viewsInCurrentArea += 1; if(!area || (area != this)) viewsInOtherAreas += 1; if(area && area != this && area->workingSet() != workingSet()) viewsInOtherWorkingSets += 1; } if(viewsInCurrentArea == 1 && (viewsInOtherAreas == 0 || viewsInOtherWorkingSets == 0)) { // Time to ask the user for feedback, because the document will be completely closed // due to working-set synchronization if( !doc.data()->askForCloseFeedback() ) return false; } } // otherwise we can silently close the view, // the document will still have an opened view somewhere delete removeView(view); return true; } void Area::clearViews(bool silent) { foreach(Sublime::View* view, views()) { closeView(view, silent); } } void Area::clearDocuments() { if (views().isEmpty()) emit clearWorkingSet(this); else clearViews(true); } QList Area::actions() const { return d->m_actions; } void Area::addAction(QAction* action) { Q_ASSERT(!d->m_actions.contains(action)); connect(action, &QAction::destroyed, this, &Area::actionDestroyed); d->m_actions.append(action); } void Area::actionDestroyed(QObject* action) { d->m_actions.removeAll(qobject_cast(action)); } } diff --git a/sublime/areaindex.h b/sublime/areaindex.h index 5f7f569df..47582bb03 100644 --- a/sublime/areaindex.h +++ b/sublime/areaindex.h @@ -1,196 +1,196 @@ /*************************************************************************** * Copyright 2006-2007 Alexander Dymo * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef KDEVPLATFORM_SUBLIMEAREAINDEX_H #define KDEVPLATFORM_SUBLIMEAREAINDEX_H #include #include #include #include "sublimeexport.h" namespace Sublime { class View; /** @short Index denotes the position of the view in the split area. B-Tree alike structure is used to represent an area with split views. Area has a root index which can either contain one view or contain two child nodes (@p first and @p second). In the later case area is considered to be split into two parts. Each of those parts can in turn contain a view or be split (with first/second children). When a view at given index is split, then its index becomes an index of the splitter and the original view goes into the @p first part of the splitter. The new view goes as @p second part. For example, consider an area which was split once horizontally and then the second part of it was split vertically: @code 1. initial state: one view in the area |----------------| | | | 1 | | | |----------------| Indices: root_index (view 1) 2. the view is split horizontally |----------------| | | | | 1 | 2 | | | | |----------------| Indices: root_index (no view) | ---------------- | | view 1 view 2 3. the second view is split vertically |----------------| | | 2 | | 1 |--------| | | 3 | |----------------| Indices: root_index (horizontal splitter) | ---------------- | | view 1 vertical_splitter | ----------------- | | view 2 view 3 @endcode It is possible that several "stacked" views will have the same area index. Those views can be considered as the view stack from which only one view is visible at the time. @code |----------------| | | | 1,2,3,4 | | | |----------------| Indices: root_index (view1, view2, view3, view4) @endcode */ class KDEVPLATFORMSUBLIME_EXPORT AreaIndex { public: ~AreaIndex(); AreaIndex(const AreaIndex &index); /**@return the parent index, returns 0 for root index.*/ AreaIndex *parent() const; /**@return the first child index if there're any.*/ AreaIndex *first() const; /**@return the second child index if there're any.*/ AreaIndex *second() const; /**@return true if the index is split.*/ bool isSplit() const; /**@return the orientation of the splitter for this index.*/ Qt::Orientation orientation() const; /**Set the orientation of the splitter for this index.*/ void setOrientation(Qt::Orientation orientation) const; /**Adds view to the list of views in this position. Does nothing if the view is already split. @param after if not 0, new view will be placed after this one. @param view the view to be added.*/ void add(View *view, View *after = nullptr); /**Removes view and unsplits the parent index when no views are left at the current index.*/ void remove(View *view); /**Splits the view in this position by given @p orientation and adds the @p newView into the splitter. Does nothing if the view is already split. @p newView will be in the second child index.*/ void split(View *newView, Qt::Orientation orientation); /**Splits the view in this position by given @p orientation. * @p moveViewsToSecondChild Normally, the existing views in this index are moved to the first sub-index. * If this is true, the views are moved to the _second_ sub-index instead. * Does nothing if the view is already split.*/ void split(Qt::Orientation orientation, bool moveViewsToSecondChild = false); /**Unsplits the index removing the given @p childToRemove and moving the contents of another child to this index.*/ void unsplit(AreaIndex *childToRemove); /** Returns a text-representation of the architecture of this area index and sub-indices. */ QString print() const; /**@return the stacked view in @p position, returns 0 for splitter's indices and when there's no view at the @p position.*/ View *viewAt(int position) const; /**@return the number of stacked views.*/ int viewCount() const; /**@return true if there's a stacked @p view at this index.*/ bool hasView(View *view) const; /**@return the list of views at this index.*/ QList &views() const; protected: /**Constructor for Root index.*/ AreaIndex(); private: /**Constructor for indices other than root.*/ - AreaIndex(AreaIndex *parent); + explicit AreaIndex(AreaIndex *parent); /**Sets the parent for this index.*/ void setParent(AreaIndex *parent); /**Copies the data from this index to @p target.*/ void moveViewsTo(AreaIndex *target); /**Copies the children indices from this index to @p target.*/ void copyChildrenTo(AreaIndex *target); struct AreaIndexPrivate * const d; }; /** @short Root Area Index This is the special index class returned by @ref Area::rootIndex(). Doesn't provide any additional functionality beyond AreaIndex. */ class KDEVPLATFORMSUBLIME_EXPORT RootAreaIndex: public AreaIndex { public: RootAreaIndex(); private: class RootAreaIndexPrivate* const d; }; } #endif diff --git a/sublime/container.cpp b/sublime/container.cpp index 24fc2379a..cf1c0183f 100644 --- a/sublime/container.cpp +++ b/sublime/container.cpp @@ -1,688 +1,688 @@ /*************************************************************************** * Copyright 2006-2009 Alexander Dymo * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "container.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "view.h" #include "document.h" #include namespace Sublime { // struct ContainerPrivate class ContainerTabBar : public QTabBar { Q_OBJECT public: - ContainerTabBar(Container* container) + explicit ContainerTabBar(Container* container) : QTabBar(container), m_container(container) { if (QApplication::style()->objectName() == QLatin1String("macintosh")) { static QPointer qTabBarStyle = QStyleFactory::create("fusion"); if (qTabBarStyle) { setStyle(qTabBarStyle); } } // configure the QTabBar style so it behaves as appropriately as possible, // even if we end up using the native Macintosh style because the user's // Qt doesn't have the Fusion style installed. setDocumentMode(true); setUsesScrollButtons(true); setElideMode(Qt::ElideNone); installEventFilter(this); } bool event(QEvent* ev) override { if(ev->type() == QEvent::ToolTip) { ev->accept(); int tab = tabAt(mapFromGlobal(QCursor::pos())); if(tab != -1) { m_container->showTooltipForTab(tab); } return true; } return QTabBar::event(ev); } void mousePressEvent(QMouseEvent* event) override { if (event->button() == Qt::MidButton) { // just close on midbutton, drag can still be done with left mouse button int tab = tabAt(mapFromGlobal(QCursor::pos())); if (tab != -1) { emit tabCloseRequested(tab); } return; } QTabBar::mousePressEvent(event); } bool eventFilter(QObject* obj, QEvent* event) override { if (obj != this) { return QObject::eventFilter(obj, event); } // TODO Qt6: Move to mouseDoubleClickEvent when fixme in qttabbar.cpp is resolved // see "fixme Qt 6: move to mouseDoubleClickEvent(), here for BC reasons." in qtabbar.cpp if (event->type() == QEvent::MouseButtonDblClick) { // block tabBarDoubleClicked signals with RMB, see https://bugs.kde.org/show_bug.cgi?id=356016 auto mouseEvent = static_cast(event); if (mouseEvent->button() == Qt::MidButton) { return true; } } return QObject::eventFilter(obj, event); } Q_SIGNALS: void newTabRequested(); private: Container* m_container; }; bool sortViews(const View* const lhs, const View* const rhs) { return lhs->document()->title().compare(rhs->document()->title(), Qt::CaseInsensitive) < 0; } struct ContainerPrivate { QBoxLayout* layout; QHash viewForWidget; ContainerTabBar *tabBar; QStackedWidget *stack; KSqueezedTextLabel *fileNameCorner; QLabel *fileStatus; KSqueezedTextLabel *statusCorner; QPointer leftCornerWidget; QToolButton* documentListButton; QMenu* documentListMenu; QHash documentListActionForView; /** * Updates the context menu which is shown when * the document list button in the tab bar is clicked. * * It shall build a popup menu which contains all currently * enabled views using the title their document provides. */ void updateDocumentListPopupMenu() { qDeleteAll(documentListActionForView); documentListActionForView.clear(); documentListMenu->clear(); // create a lexicographically sorted list QVector views; views.reserve(viewForWidget.size()); foreach(View* view, viewForWidget){ views << view; } std::sort(views.begin(), views.end(), sortViews); for (int i = 0; i < views.size(); ++i) { View *view = views.at(i); QString visibleEntryTitle; // if filename is not unique, prepend containing directory if ((i < views.size() - 1 && view->document()->title() == views.at(i + 1)->document()->title()) || (i > 0 && view->document()->title() == views.at(i - 1)->document()->title()) ) { auto urlDoc = qobject_cast(view->document()); if (!urlDoc) { visibleEntryTitle = view->document()->title(); } else { auto url = urlDoc->url().toString(); int secondOffset = url.lastIndexOf('/'); secondOffset = url.lastIndexOf('/', secondOffset - 1); visibleEntryTitle = url.right(url.length() - url.lastIndexOf('/', secondOffset) - 1); } } else { visibleEntryTitle = view->document()->title(); } QAction* action = documentListMenu->addAction(visibleEntryTitle); action->setData(QVariant::fromValue(view)); documentListActionForView[view] = action; action->setIcon(view->document()->icon()); ///FIXME: push this code somehow into shell, such that we can access the project model for /// icons and also get a neat, short path like the document switcher. } } }; class UnderlinedLabel: public KSqueezedTextLabel { Q_OBJECT public: - UnderlinedLabel(QTabBar *tabBar, QWidget* parent = nullptr) + explicit UnderlinedLabel(QTabBar *tabBar, QWidget* parent = nullptr) :KSqueezedTextLabel(parent), m_tabBar(tabBar) { } protected: void paintEvent(QPaintEvent *ev) override { #ifndef Q_OS_OSX // getting the underlining right on OS X is tricky; omitting // the underlining attracts the eye less than not getting it // exactly right. if (m_tabBar->isVisible() && m_tabBar->count() > 0) { QStylePainter p(this); QStyleOptionTabBarBase optTabBase; optTabBase.init(m_tabBar); optTabBase.shape = m_tabBar->shape(); optTabBase.tabBarRect = m_tabBar->rect(); optTabBase.tabBarRect.moveRight(0); QStyleOptionTab tabOverlap; tabOverlap.shape = m_tabBar->shape(); int overlap = style()->pixelMetric(QStyle::PM_TabBarBaseOverlap, &tabOverlap, m_tabBar); if( overlap > 0 ) { QRect rect; rect.setRect(0, height()-overlap, width(), overlap); optTabBase.rect = rect; } if( m_tabBar->drawBase() ) { p.drawPrimitive(QStyle::PE_FrameTabBarBase, optTabBase); } } #endif KSqueezedTextLabel::paintEvent(ev); } QTabBar *m_tabBar; }; class StatusLabel: public UnderlinedLabel { Q_OBJECT public: - StatusLabel(QTabBar *tabBar, QWidget* parent = nullptr): + explicit StatusLabel(QTabBar *tabBar, QWidget* parent = nullptr): UnderlinedLabel(tabBar, parent) { setAlignment(Qt::AlignRight | Qt::AlignVCenter); setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Fixed); } QSize minimumSizeHint() const override { QRect rect = style()->itemTextRect(fontMetrics(), QRect(), Qt::AlignRight, true, i18n("Line: 00000 Col: 000")); rect.setHeight(m_tabBar->height()); return rect.size(); } }; // class Container Container::Container(QWidget *parent) :QWidget(parent), d(new ContainerPrivate()) { KAcceleratorManager::setNoAccel(this); QBoxLayout *l = new QBoxLayout(QBoxLayout::TopToBottom, this); l->setMargin(0); l->setSpacing(0); d->layout = new QBoxLayout(QBoxLayout::LeftToRight); d->layout->setMargin(0); d->layout->setSpacing(0); d->documentListMenu = new QMenu(this); d->documentListButton = new QToolButton(this); d->documentListButton->setIcon(QIcon::fromTheme(QStringLiteral("format-list-unordered"))); d->documentListButton->setMenu(d->documentListMenu); d->documentListButton->setPopupMode(QToolButton::InstantPopup); d->documentListButton->setAutoRaise(true); d->documentListButton->setToolTip(i18n("Show sorted list of opened documents")); d->documentListButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); d->layout->addWidget(d->documentListButton); d->tabBar = new ContainerTabBar(this); d->tabBar->setContextMenuPolicy(Qt::CustomContextMenu); d->layout->addWidget(d->tabBar); d->fileStatus = new QLabel( this ); d->fileStatus->setFixedSize( QSize( 16, 16 ) ); d->layout->addWidget(d->fileStatus); d->fileNameCorner = new UnderlinedLabel(d->tabBar, this); d->layout->addWidget(d->fileNameCorner); d->statusCorner = new StatusLabel(d->tabBar, this); d->layout->addWidget(d->statusCorner); l->addLayout(d->layout); d->stack = new QStackedWidget(this); l->addWidget(d->stack); connect(d->tabBar, &ContainerTabBar::currentChanged, this, &Container::widgetActivated); connect(d->tabBar, &ContainerTabBar::tabCloseRequested, this, static_cast(&Container::requestClose)); connect(d->tabBar, &ContainerTabBar::newTabRequested, this, &Container::newTabRequested); connect(d->tabBar, &ContainerTabBar::tabMoved, this, &Container::tabMoved); connect(d->tabBar, &ContainerTabBar::customContextMenuRequested, this, &Container::contextMenu); connect(d->tabBar, &ContainerTabBar::tabBarDoubleClicked, this, &Container::doubleClickTriggered); connect(d->documentListMenu, &QMenu::triggered, this, &Container::documentListActionTriggered); setTabBarHidden(!configTabBarVisible()); d->tabBar->setTabsClosable(true); d->tabBar->setMovable(true); d->tabBar->setExpanding(false); d->tabBar->setSelectionBehaviorOnRemove(QTabBar::SelectPreviousTab); } bool Container::configTabBarVisible() { KConfigGroup group = KSharedConfig::openConfig()->group("UiSettings"); return group.readEntry("TabBarVisibility", 1); } void Container::setLeftCornerWidget(QWidget* widget) { if(d->leftCornerWidget.data() == widget) { if(d->leftCornerWidget) d->leftCornerWidget.data()->setParent(nullptr); }else{ delete d->leftCornerWidget.data(); d->leftCornerWidget.clear(); } d->leftCornerWidget = widget; if(!widget) return; widget->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred); d->layout->insertWidget(0, widget); widget->show(); } Container::~Container() { delete d; } QList Container::views() const { return d->viewForWidget.values(); } void Container::requestClose(int idx) { emit requestClose(widget(idx)); } void Container::widgetActivated(int idx) { if (idx < 0) return; if (QWidget* w = d->stack->widget(idx)) { Sublime::View* view = d->viewForWidget.value(w); if(view) emit activateView(view); } } void Container::addWidget(View *view, int position) { QWidget *w = view->widget(this); int idx = 0; if (position != -1) { idx = d->stack->insertWidget(position, w); } else idx = d->stack->addWidget(w); d->tabBar->insertTab(idx, view->document()->statusIcon(), view->document()->title()); Q_ASSERT(view); d->viewForWidget[w] = view; // Update document list context menu. This has to be called before // setCurrentWidget, because we call the status icon and title update slots // already, which in turn need the document list menu to be setup. d->updateDocumentListPopupMenu(); setCurrentWidget(d->stack->currentWidget()); // This fixes a strange layouting bug, that could be reproduced like this: Open a few files in KDevelop, activate the rightmost tab. // Then temporarily switch to another area, and then switch back. After that, the tab-bar was gone. // The problem could only be fixed by closing/opening another view. d->tabBar->setMinimumHeight(d->tabBar->sizeHint().height()); connect(view, &View::statusChanged, this, &Container::statusChanged); connect(view->document(), &Document::statusIconChanged, this, &Container::statusIconChanged); connect(view->document(), &Document::titleChanged, this, &Container::documentTitleChanged); } void Container::statusChanged(Sublime::View* view) { d->statusCorner->setText(view->viewStatus()); } void Container::statusIconChanged(Document* doc) { QHashIterator it = d->viewForWidget; while (it.hasNext()) { if (it.next().value()->document() == doc) { d->fileStatus->setPixmap( doc->statusIcon().pixmap( QSize( 16,16 ) ) ); int tabIndex = d->stack->indexOf(it.key()); if (tabIndex != -1) { d->tabBar->setTabIcon(tabIndex, doc->statusIcon()); } // Update the document title's menu associated action // using the View* index map Q_ASSERT(d->documentListActionForView.contains(it.value())); d->documentListActionForView[it.value()]->setIcon(doc->icon()); break; } } } void Container::documentTitleChanged(Sublime::Document* doc) { QHashIterator it = d->viewForWidget; while (it.hasNext()) { Sublime::View* view = it.next().value(); if (view->document() == doc) { if (currentView() == view) { d->fileNameCorner->setText( doc->title(Document::Extended) + i18n(" (Press Ctrl+Tab to switch)") ); } int tabIndex = d->stack->indexOf(it.key()); if (tabIndex != -1) { d->tabBar->setTabText(tabIndex, doc->title()); } break; } } // Update document list popup title d->updateDocumentListPopupMenu(); } int Container::count() const { return d->stack->count(); } QWidget* Container::currentWidget() const { return d->stack->currentWidget(); } void Container::setCurrentWidget(QWidget* w) { d->stack->setCurrentWidget(w); //prevent from emitting activateView() signal on tabbar active tab change //this function is called from MainWindow::activateView() //which does the activation without any additional signals { QSignalBlocker blocker(d->tabBar); d->tabBar->setCurrentIndex(d->stack->indexOf(w)); } if (View* view = viewForWidget(w)) { statusChanged(view); if (!d->tabBar->isVisible()) { // repaint icon and document title only in tabbar-less mode // tabbar will do repainting for us statusIconChanged( view->document() ); documentTitleChanged( view->document() ); } } } QWidget* Container::widget(int i) const { return d->stack->widget(i); } int Container::indexOf(QWidget* w) const { return d->stack->indexOf(w); } void Container::removeWidget(QWidget *w) { if (w) { int widgetIdx = d->stack->indexOf(w); d->stack->removeWidget(w); d->tabBar->removeTab(widgetIdx); if (d->tabBar->currentIndex() != -1 && !d->tabBar->isVisible()) { // repaint icon and document title only in tabbar-less mode // tabbar will do repainting for us View* view = currentView(); if( view ) { statusIconChanged( view->document() ); documentTitleChanged( view->document() ); } } View* view = d->viewForWidget.take(w); if (view) { disconnect(view->document(), &Document::titleChanged, this, &Container::documentTitleChanged); disconnect(view->document(), &Document::statusIconChanged, this, &Container::statusIconChanged); disconnect(view, &View::statusChanged, this, &Container::statusChanged); // Update document list context menu Q_ASSERT(d->documentListActionForView.contains(view)); delete d->documentListActionForView.take(view); } } } bool Container::hasWidget(QWidget *w) { return d->stack->indexOf(w) != -1; } View *Container::viewForWidget(QWidget *w) const { return d->viewForWidget.value(w); } void Container::setTabBarHidden(bool hide) { if (hide) { d->tabBar->hide(); d->fileNameCorner->show(); d->fileStatus->show(); } else { d->fileNameCorner->hide(); d->fileStatus->hide(); d->tabBar->show(); } View* v = currentView(); if (v) { documentTitleChanged(v->document()); } } void Container::resetTabColors(const QColor& color) { for (int i = 0; i < count(); i++){ d->tabBar->setTabTextColor(i, color); } } void Container::setTabColor(const View* view, const QColor& color) { for (int i = 0; i < count(); i++){ if (view == viewForWidget(widget(i))) { d->tabBar->setTabTextColor(i, color); } } } void Container::setTabColors(const QHash& colors) { for (int i = 0; i < count(); i++) { auto view = viewForWidget(widget(i)); auto color = colors[view]; if (color.isValid()) { d->tabBar->setTabTextColor(i, color); } } } void Container::tabMoved(int from, int to) { QWidget *w = d->stack->widget(from); d->stack->removeWidget(w); d->stack->insertWidget(to, w); d->viewForWidget[w]->notifyPositionChanged(to); } void Container::contextMenu( const QPoint& pos ) { QWidget* senderWidget = qobject_cast(sender()); Q_ASSERT(senderWidget); int currentTab = d->tabBar->tabAt(pos); QMenu menu; Sublime::View* view = viewForWidget(widget(currentTab)); emit tabContextMenuRequested(view, &menu); menu.addSeparator(); QAction* copyPathAction = nullptr; QAction* closeTabAction = nullptr; QAction* closeOtherTabsAction = nullptr; if (view) { copyPathAction = menu.addAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("Copy Filename")); menu.addSeparator(); closeTabAction = menu.addAction(QIcon::fromTheme(QStringLiteral("document-close")), i18n("Close File")); closeOtherTabsAction = menu.addAction(QIcon::fromTheme(QStringLiteral("document-close")), i18n("Close Other Files")); } QAction* closeAllTabsAction = menu.addAction( QIcon::fromTheme(QStringLiteral("document-close")), i18n( "Close All Files" ) ); QAction* triggered = menu.exec(senderWidget->mapToGlobal(pos)); if (triggered) { if ( triggered == closeTabAction ) { requestClose(currentTab); } else if ( triggered == closeOtherTabsAction ) { // activate the remaining tab widgetActivated(currentTab); // first get the widgets to be closed since otherwise the indices will be wrong QList otherTabs; for ( int i = 0; i < count(); ++i ) { if ( i != currentTab ) { otherTabs << widget(i); } } // finally close other tabs foreach( QWidget* tab, otherTabs ) { requestClose(tab); } } else if ( triggered == closeAllTabsAction ) { // activate last tab widgetActivated(count() - 1); // close all for ( int i = 0; i < count(); ++i ) { requestClose(widget(i)); } } else if( triggered == copyPathAction ) { auto view = viewForWidget( widget( currentTab ) ); auto urlDocument = qobject_cast( view->document() ); if( urlDocument ) { QApplication::clipboard()->setText( urlDocument->url().toString() ); } } // else the action was handled by someone else } } void Container::showTooltipForTab(int tab) { emit tabToolTipRequested(viewForWidget(widget(tab)), this, tab); } bool Container::isCurrentTab(int tab) const { return d->tabBar->currentIndex() == tab; } QRect Container::tabRect(int tab) const { return d->tabBar->tabRect(tab).translated(d->tabBar->mapToGlobal(QPoint(0, 0))); } void Container::doubleClickTriggered(int tab) { if (tab == -1) { emit newTabRequested(); } else { emit tabDoubleClicked(viewForWidget(widget(tab))); } } void Container::documentListActionTriggered(QAction* action) { Sublime::View* view = action->data().value< Sublime::View* >(); Q_ASSERT(view); QWidget* widget = d->viewForWidget.key(view); Q_ASSERT(widget); setCurrentWidget(widget); } Sublime::View* Container::currentView() const { return d->viewForWidget.value(widget( d->tabBar->currentIndex() )); } } #include "container.moc" diff --git a/sublime/controller.cpp b/sublime/controller.cpp index fda3cd79c..a401127d7 100644 --- a/sublime/controller.cpp +++ b/sublime/controller.cpp @@ -1,418 +1,418 @@ /*************************************************************************** * Copyright 2006-2007 Alexander Dymo * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "controller.h" #include #include #include #include #include #include #include "area.h" #include "view.h" #include "document.h" #include "mainwindow.h" #include "sublimedebug.h" namespace Sublime { struct WidgetFinder { - WidgetFinder(QWidget *_w) :w(_w), view(nullptr) {} + explicit WidgetFinder(QWidget *_w) :w(_w), view(nullptr) {} Area::WalkerMode operator()(AreaIndex *index) { foreach (View *v, index->views()) { if (v->hasWidget() && (v->widget() == w)) { view = v; return Area::StopWalker; } } return Area::ContinueWalker; } QWidget *w; View *view; }; struct ToolWidgetFinder { - ToolWidgetFinder(QWidget *_w) :w(_w), view(nullptr) {} + explicit ToolWidgetFinder(QWidget *_w) :w(_w), view(nullptr) {} Area::WalkerMode operator()(View *v, Sublime::Position /*position*/) { if (v->hasWidget() && (v->widget() == w)) { view = v; return Area::StopWalker; } return Area::ContinueWalker; } QWidget *w; View *view; }; // struct ControllerPrivate struct ControllerPrivate { ControllerPrivate() { } QList documents; QList areas; QList allAreas; QMap namedAreas; // FIXME: remove this. QMap shownAreas; QList controlledWindows; QVector< QList > mainWindowAreas; bool openAfterCurrent; bool arrangeBuddies; }; // class Controller Controller::Controller(QObject *parent) :QObject(parent), MainWindowOperator(), d( new ControllerPrivate() ) { init(); } void Controller::init() { loadSettings(); qApp->installEventFilter(this); } Controller::~Controller() { qDeleteAll(d->controlledWindows); delete d; } void Controller::showArea(Area *area, MainWindow *mainWindow) { Area *areaToShow = nullptr; //if the area is already shown in another mainwindow then we need to clone it if (d->shownAreas.contains(area) && (mainWindow != d->shownAreas[area])) areaToShow = new Area(*area); else areaToShow = area; d->shownAreas[areaToShow] = mainWindow; showAreaInternal(areaToShow, mainWindow); } void Controller::showAreaInternal(Area* area, MainWindow *mainWindow) { /* Disconnect the previous area. We really don't want to mess with main window if an area not visible now is modified. Further, if showAreaInternal is called with the same area as is current now, we don't want to connect the same signals twice. */ MainWindowOperator::setArea(mainWindow, area); } void Controller::removeArea(Area *obj) { d->areas.removeAll(obj); } void Controller::removeDocument(Document *obj) { d->documents.removeAll(obj); } void Controller::showArea(const QString& areaTypeId, MainWindow *mainWindow) { int index = d->controlledWindows.indexOf(mainWindow); Q_ASSERT(index != -1); Area* area = nullptr; foreach (Area* a, d->mainWindowAreas[index]) { qCDebug(SUBLIME) << "Object name: " << a->objectName() << " id " << areaTypeId; if (a->objectName() == areaTypeId) { area = a; break; } } Q_ASSERT (area); showAreaInternal(area, mainWindow); } void Controller::resetCurrentArea(MainWindow *mainWindow) { QString id = mainWindow->area()->objectName(); int areaIndex = 0; Area* def = nullptr; foreach (Area* a, d->areas) { if (a->objectName() == id) { def = a; break; } ++areaIndex; } Q_ASSERT(def); int index = d->controlledWindows.indexOf(mainWindow); Q_ASSERT(index != -1); Area* prev = d->mainWindowAreas[index][areaIndex]; d->mainWindowAreas[index][areaIndex] = new Area(*def); showAreaInternal(d->mainWindowAreas[index][areaIndex], mainWindow); delete prev; } const QList &Controller::defaultAreas() const { return d->areas; } const QList< Area* >& Controller::areas(MainWindow* mainWindow) const { int index = d->controlledWindows.indexOf(mainWindow); Q_ASSERT(index != -1); return areas(index); } const QList &Controller::areas(int mainWindow) const { return d->mainWindowAreas[mainWindow]; } const QList &Controller::allAreas() const { return d->allAreas; } const QList &Controller::documents() const { return d->documents; } void Controller::addDefaultArea(Area *area) { d->areas.append(area); d->allAreas.append(area); d->namedAreas[area->objectName()] = area; emit areaCreated(area); } void Controller::addMainWindow(MainWindow* mainWindow) { Q_ASSERT(mainWindow); Q_ASSERT (!d->controlledWindows.contains(mainWindow)); d->controlledWindows << mainWindow; d->mainWindowAreas.resize(d->controlledWindows.size()); int index = d->controlledWindows.size()-1; foreach (Area* area, defaultAreas()) { Area *na = new Area(*area); d->allAreas.append(na); d->mainWindowAreas[index].push_back(na); emit areaCreated(na); } showAreaInternal(d->mainWindowAreas[index][0], mainWindow); emit mainWindowAdded( mainWindow ); } void Controller::addDocument(Document *document) { d->documents.append(document); } void Controller::areaReleased() { MainWindow *w = reinterpret_cast(sender()); qCDebug(SUBLIME) << "marking areas as mainwindow-free" << w << d->controlledWindows.contains(w) << d->shownAreas.keys(w); foreach (Area *area, d->shownAreas.keys(w)) { qCDebug(SUBLIME) << "" << area->objectName(); areaReleased(area); disconnect(area, nullptr, w, nullptr); } d->controlledWindows.removeAll(w); } void Controller::areaReleased(Sublime::Area *area) { d->shownAreas.remove(area); d->namedAreas.remove(area->objectName()); } Area *Controller::defaultArea(const QString &id) const { return d->namedAreas[id]; } Area *Controller::area(int mainWindow, const QString& id) const { foreach (Area* area, areas(mainWindow)) { if (area->objectName() == id) return area; } return nullptr; } Area* Controller::areaForView(View* view) const { foreach (Area* area, allAreas()) if(area->views().contains(view)) return area; return nullptr; } /*We need this to catch activation of views and toolviews so that we can always tell what view and toolview is active. "Active" doesn't mean focused. It means that it is focused now or was focused before and no other view/toolview wasn't focused after that."*/ //implementation is based upon KParts::PartManager::eventFilter bool Controller::eventFilter(QObject *obj, QEvent *ev) { if (ev->type() != QEvent::MouseButtonPress && ev->type() != QEvent::MouseButtonDblClick && ev->type() != QEvent::FocusIn) return false; //not a widget? - return if (!obj->isWidgetType()) return false; //is dialog or popup? - return QWidget *w = static_cast(obj); if (((w->windowFlags().testFlag(Qt::Dialog)) && w->isModal()) || (w->windowFlags().testFlag(Qt::Popup)) || (w->windowFlags().testFlag(Qt::Tool))) return false; //not a mouse button that should activate the widget? - return QMouseEvent *mev = nullptr; if (ev->type() == QEvent::MouseButtonPress || ev->type() == QEvent::MouseButtonDblClick) { mev = static_cast(ev); int activationButtonMask = Qt::LeftButton | Qt::MidButton | Qt::RightButton; if ((mev->button() & activationButtonMask) == 0) return false; } while (w) { //not inside sublime mainwindow MainWindow *mw = qobject_cast(w->topLevelWidget()); if (!mw || !d->controlledWindows.contains(mw)) return false; Area *area = mw->area(); ///@todo adymo: this is extra slow - optimize //find this widget in views WidgetFinder widgetFinder(w); area->walkViews(widgetFinder, area->rootIndex()); if (widgetFinder.view && widgetFinder.view != mw->activeView()) { setActiveView(mw, widgetFinder.view); ///@todo adymo: shall we filter out the event? return false; } //find this widget in toolviews ToolWidgetFinder toolFinder(w); area->walkToolViews(toolFinder, Sublime::AllPositions); if (toolFinder.view && toolFinder.view != mw->activeToolView()) { setActiveToolView(mw, toolFinder.view); ///@todo adymo: shall we filter out the event? return false; } w = w->parentWidget(); } return false; } const QList< MainWindow * > & Controller::mainWindows() const { return d->controlledWindows; } void Controller::notifyToolViewRemoved(Sublime::View *view, Sublime::Position) { emit aboutToRemoveToolView(view); } void Controller::notifyToolViewAdded(Sublime::View *view, Sublime::Position) { emit toolViewAdded(view); } void Controller::notifyViewRemoved(Sublime::AreaIndex*, Sublime::View *view) { emit aboutToRemoveView(view); } void Controller::notifyViewAdded(Sublime::AreaIndex*, Sublime::View *view) { emit viewAdded(view); } void Controller::setStatusIcon(Document * document, const QIcon & icon) { document->setStatusIcon(icon); } void Controller::loadSettings() { KConfigGroup uiGroup = KSharedConfig::openConfig()->group("UiSettings"); d->openAfterCurrent = (uiGroup.readEntry("TabBarOpenAfterCurrent", 1) == 1); d->arrangeBuddies = (uiGroup.readEntry("TabBarArrangeBuddies", 1) == 1); } bool Controller::openAfterCurrent() const { return d->openAfterCurrent; } bool Controller::arrangeBuddies() const { return d->arrangeBuddies; } } #include "moc_controller.cpp" diff --git a/sublime/document.cpp b/sublime/document.cpp index a64f5aa53..19a83a1dd 100644 --- a/sublime/document.cpp +++ b/sublime/document.cpp @@ -1,175 +1,175 @@ /*************************************************************************** * Copyright 2006-2007 Alexander Dymo * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "document.h" #include "view.h" #include "area.h" #include "controller.h" namespace Sublime { // struct DocumentPrivate struct DocumentPrivate { - DocumentPrivate(Document *doc): m_document(doc) {} + explicit DocumentPrivate(Document *doc): m_document(doc) {} void removeView(Sublime::View* view) { views.removeAll(view); //no need to keep empty document - we need to remove it if (views.count() == 0) { emit m_document->aboutToDelete(m_document); m_document->deleteLater(); } } Controller *controller; QList views; QIcon statusIcon; QString documentToolTip; Document *m_document; }; //class Document Document::Document(const QString &title, Controller *controller) :QObject(controller), d( new DocumentPrivate(this) ) { setObjectName(title); d->controller = controller; d->controller->addDocument(this); // Functor will be called after destructor has run -> capture controller pointer by value // otherwise we crash because we access the already freed pointer this->d connect(this, &Document::destroyed, d->controller, [controller] (QObject* obj) { controller->removeDocument(static_cast(obj)); }); } Document::~Document() { delete d; } Controller *Document::controller() const { return d->controller; } View *Document::createView() { View *view = newView(this); connect(view, &View::destroyed, this, [&] (QObject* obj) { d->removeView(static_cast(obj)); }); d->views.append(view); return view; } const QList &Document::views() const { return d->views; } QString Document::title(TitleType /*type*/) const { return objectName(); } QString Document::toolTip() const { return d->documentToolTip; } void Document::setTitle(const QString& newTitle) { setObjectName(newTitle); emit titleChanged(this); } void Document::setToolTip(const QString& newToolTip) { d->documentToolTip=newToolTip; } View *Document::newView(Document *doc) { //first create View, second emit the signal View *newView = new View(doc); return newView; } void Document::setStatusIcon(QIcon icon) { d->statusIcon = icon; emit statusIconChanged(this); } QIcon Document::statusIcon() const { return d->statusIcon; } void Document::closeViews() { foreach (Sublime::Area *area, controller()->allAreas()) { QList areaViews = area->views(); foreach (Sublime::View *view, areaViews) { if (views().contains(view)) { area->removeView(view); delete view; } } } Q_ASSERT(views().isEmpty()); } bool Document::askForCloseFeedback() { return true; } bool Document::closeDocument(bool silent) { if( !silent && !askForCloseFeedback() ) return false; closeViews(); deleteLater(); return true; } QIcon Document::icon() const { QIcon ret = statusIcon(); if (!ret.isNull()) { return ret; } return defaultIcon(); } QIcon Document::defaultIcon() const { return QIcon(); } } #include "moc_document.cpp" diff --git a/sublime/tests/test_areawalker.cpp b/sublime/tests/test_areawalker.cpp index a3df13dab..debaa6865 100644 --- a/sublime/tests/test_areawalker.cpp +++ b/sublime/tests/test_areawalker.cpp @@ -1,143 +1,143 @@ /*************************************************************************** * Copyright 2006-2007 Alexander Dymo * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "test_areawalker.h" #include #include #include #include #include #include #include "areaprinter.h" using namespace Sublime; struct AreaStopper { - AreaStopper(QString stopAt): m_stopAt(stopAt) {} + explicit AreaStopper(QString stopAt): m_stopAt(stopAt) {} Area::WalkerMode operator()(AreaIndex *index) { foreach (View *view, index->views()) { list << view->objectName(); if (view->objectName() == m_stopAt) return Area::StopWalker; } return Area::ContinueWalker; } Area::WalkerMode operator()(View *view, Sublime::Position) { list << view->objectName(); if (view->objectName() == m_stopAt) return Area::StopWalker; return Area::ContinueWalker; } QStringList list; QString m_stopAt; }; void TestAreaWalker::viewWalkerModes() { Controller *controller = new Controller(this); Document *doc = new UrlDocument(controller, QUrl::fromLocalFile(QStringLiteral("~/foo.cpp"))); Area *area = new Area(controller, QStringLiteral("Area")); View *view1 = doc->createView(); view1->setObjectName(QStringLiteral("1")); area->addView(view1); View *view2 = doc->createView(); view2->setObjectName(QStringLiteral("2")); area->addView(view2, view1, Qt::Vertical); View *view3 = doc->createView(); view3->setObjectName(QStringLiteral("3")); area->addView(view3, view1, Qt::Vertical); View *view4 = doc->createView(); view4->setObjectName(QStringLiteral("4")); area->addView(view4, view1, Qt::Vertical); AreaViewsPrinter p; area->walkViews(p, area->rootIndex()); QCOMPARE(p.result, QStringLiteral("\n\ [ vertical splitter ]\n\ [ vertical splitter ]\n\ [ vertical splitter ]\n\ [ 1 ]\n\ [ 4 ]\n\ [ 3 ]\n\ [ 2 ]\n\ ")); AreaStopper stopper(QStringLiteral("1")); area->walkViews(stopper, area->rootIndex()); QCOMPARE(stopper.list.join(QStringLiteral(" ")), QStringLiteral("1")); AreaStopper stopper2(QStringLiteral("2")); area->walkViews(stopper2, area->rootIndex()); QCOMPARE(stopper2.list.join(QStringLiteral(" ")), QStringLiteral("1 4 3 2")); AreaStopper stopper3(QStringLiteral("3")); area->walkViews(stopper3, area->rootIndex()); QCOMPARE(stopper3.list.join(QStringLiteral(" ")), QStringLiteral("1 4 3")); AreaStopper noStopper(QStringLiteral("X")); area->walkViews(noStopper, area->rootIndex()); QCOMPARE(noStopper.list.join(QStringLiteral(" ")), QStringLiteral("1 4 3 2")); delete area; delete doc; delete controller; } void TestAreaWalker::toolViewWalkerModes() { Controller *controller = new Controller(this); Document *doc = new UrlDocument(controller, QUrl::fromLocalFile(QStringLiteral("~/foo.cpp"))); Area *area = new Area(controller, QStringLiteral("Area")); View *view = doc->createView(); view->setObjectName(QStringLiteral("1")); area->addToolView(view, Sublime::Left); view = doc->createView(); view->setObjectName(QStringLiteral("2")); area->addToolView(view, Sublime::Left); view = doc->createView(); view->setObjectName(QStringLiteral("3")); area->addToolView(view, Sublime::Bottom); AreaStopper stopper1(QStringLiteral("1")); area->walkToolViews(stopper1, Sublime::AllPositions); QCOMPARE(stopper1.list.join(QStringLiteral(" ")), QStringLiteral("1")); AreaStopper stopper2(QStringLiteral("2")); area->walkToolViews(stopper2, Sublime::AllPositions); QCOMPARE(stopper2.list.join(QStringLiteral(" ")), QStringLiteral("1 2")); AreaStopper stopper3(QStringLiteral("3")); area->walkToolViews(stopper3, Sublime::AllPositions); QCOMPARE(stopper3.list.join(QStringLiteral(" ")), QStringLiteral("1 2 3")); AreaStopper noStopper(QStringLiteral("X")); area->walkToolViews(noStopper, Sublime::AllPositions); QCOMPARE(noStopper.list.join(QStringLiteral(" ")), QStringLiteral("1 2 3")); delete area; delete doc; delete controller; } QTEST_MAIN(TestAreaWalker) diff --git a/sublime/tests/test_toolviewtoolbar.cpp b/sublime/tests/test_toolviewtoolbar.cpp index 67d314763..6b0b11363 100644 --- a/sublime/tests/test_toolviewtoolbar.cpp +++ b/sublime/tests/test_toolviewtoolbar.cpp @@ -1,134 +1,134 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2008 Andreas Pakulat * * Copyright 2008 Manuel Breugelmans * * * * 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 "test_toolviewtoolbar.h" #include #include #include #include #include #include #include #include #include #include #include using namespace Sublime; class ToolViewToolBarFactory : public SimpleToolWidgetFactory { public: - ToolViewToolBarFactory(const QString &id): SimpleToolWidgetFactory(id) {} + explicit ToolViewToolBarFactory(const QString &id): SimpleToolWidgetFactory(id) {} QList toolBarActions( QWidget* ) const override { QAction* action = new QAction(actionText, nullptr); return QList() << action; } QString actionText; }; void TestToolViewToolBar::init() { // this is starting to become a GeneralFixture controller = new Controller(this); area = new Area( controller, QStringLiteral("Area") ); MainWindow* mw = new MainWindow(controller); // a horizontal tool with toolbar ToolViewToolBarFactory* factoryT1 = new ToolViewToolBarFactory(QStringLiteral("tool1factory")); actionTextT1 = QStringLiteral("Tool1Action"); factoryT1->actionText = actionTextT1; tool1 = new ToolDocument( QStringLiteral("tool1"), controller, factoryT1 ); viewT11 = tool1->createView(); area->addToolView( viewT11, Sublime::Bottom ); // a vertical tool with toolbar ToolViewToolBarFactory* factoryT2 = new ToolViewToolBarFactory(QStringLiteral("tool2factory")); actionTextT2 = QStringLiteral("Tool2Action"); factoryT2->actionText = actionTextT2; tool2 = new ToolDocument( QStringLiteral("tool2"), controller, factoryT2 ); viewT21 = tool2->createView(); area->addToolView( viewT21, Sublime::Left ); controller->showArea(area, mw); } void TestToolViewToolBar::cleanup() { delete controller; } QToolBar* TestToolViewToolBar::fetchToolBarFor(Sublime::View* view) { QWidget* toolWidget = view->widget(); const char* loc = "fetchToolBarFor"; Q_UNUSED(loc); Q_ASSERT_X(toolWidget, loc, "Tool refuses to create widget (null)."); Q_ASSERT(toolWidget->parent()); QMainWindow* toolWin = dynamic_cast(toolWidget->parent()); Q_ASSERT_X(toolWin, loc, "Tool widget's parent is not a QMainWindow."); QList toolBars = toolWin->findChildren(); int barCount = toolBars.count(); char* failMsg = qstrdup(QStringLiteral("Expected to find a toolbar but found %1").arg(barCount).toLatin1().data()); Q_UNUSED(failMsg); Q_ASSERT_X(barCount == 1, loc, failMsg); return toolBars.at(0); } void TestToolViewToolBar::assertGoodBar(QToolBar* toolbar, QString actionText) { QVERIFY( toolbar ); QVERIFY( !toolbar->isFloatable() ); QCOMPARE( toolbar->iconSize(), QSize( 16, 16 ) ); QList actions = toolbar->actions(); QCOMPARE( actions.count(), 1 ); QCOMPARE( actions.at(0)->text(), actionText); QCOMPARE( toolbar->orientation(), Qt::Horizontal ); } void TestToolViewToolBar::horizontalTool() { // viewT11 was added with Sublime::Bottom, so it should have a horizontal bar QToolBar* bar = fetchToolBarFor(viewT11); assertGoodBar(bar, actionTextT1); } void TestToolViewToolBar::verticalTool() { // viewT21 was added with Sublime::Left, so it should have a vertical bar QToolBar* bar = fetchToolBarFor(viewT21); assertGoodBar(bar, actionTextT2); } void TestToolViewToolBar::toolViewMove() { area->moveToolView( viewT11, Sublime::Right ); area->moveToolView( viewT21, Sublime::Bottom ); QToolBar* barT1 = fetchToolBarFor(viewT11); QToolBar* barT2 = fetchToolBarFor(viewT21); assertGoodBar(barT1, actionTextT1); assertGoodBar(barT2, actionTextT2); } QTEST_MAIN(TestToolViewToolBar) diff --git a/sublime/tests/test_view.cpp b/sublime/tests/test_view.cpp index f4df89302..f34dfcfc6 100644 --- a/sublime/tests/test_view.cpp +++ b/sublime/tests/test_view.cpp @@ -1,73 +1,73 @@ /*************************************************************************** * Copyright 2007 Alexander Dymo * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "test_view.h" #include #include #include #include #include using namespace Sublime; void TestView::widgetDeletion() { Controller controller; Document *doc = new ToolDocument(QStringLiteral("tool"), &controller, new SimpleToolWidgetFactory(QStringLiteral("tool"))); View *view = doc->createView(); //create the widget view->widget(); QVERIFY(view->hasWidget()); QCOMPARE(view->widget()->metaObject()->className(), "QTextEdit"); //delete the widget and check that view knows about that delete view->widget(); QVERIFY(!view->hasWidget()); } class Test: public View { Q_OBJECT public: - Test(Document *doc): View(doc) {} + explicit Test(Document *doc): View(doc) {} }; class TestDocument: public Document { Q_OBJECT public: - TestDocument(Controller *controller): Document(QStringLiteral("TestDocument"), controller) {} + explicit TestDocument(Controller *controller): Document(QStringLiteral("TestDocument"), controller) {} QString documentType() const override { return QStringLiteral("Test"); } QString documentSpecifier() const override { return QString(); } protected: QWidget *createViewWidget(QWidget *parent = nullptr) override { return new QWidget(parent); } View *newView(Document *doc) override { return new Test(doc); } }; void TestView::viewReimplementation() { Controller controller; Document *doc = new TestDocument(&controller); View *view = doc->createView(); QVERIFY(dynamic_cast(view) != nullptr); } QTEST_MAIN(TestView) #include "test_view.moc" diff --git a/sublime/tests/test_viewactivation.cpp b/sublime/tests/test_viewactivation.cpp index beb8d0e08..2058a5e65 100644 --- a/sublime/tests/test_viewactivation.cpp +++ b/sublime/tests/test_viewactivation.cpp @@ -1,227 +1,227 @@ /*************************************************************************** * Copyright 2007 Alexander Dymo * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "test_viewactivation.h" #include #include #include #include #include #include #include #include #include #include #include using namespace Sublime; template class SpecialWidgetFactory: public SimpleToolWidgetFactory { public: - SpecialWidgetFactory(const QString &id): SimpleToolWidgetFactory(id) {} + explicit SpecialWidgetFactory(const QString &id): SimpleToolWidgetFactory(id) {} QWidget* create(ToolDocument *doc, QWidget *parent = nullptr) override { QWidget *w = new QWidget(parent); Widget *inner = new Widget(w); inner->setObjectName(doc->title()+"_inner"); w->setObjectName(doc->title()+"_outer"); return w; } }; void TestViewActivation::initTestCase() { qRegisterMetaType("View*"); } void TestViewActivation::init() { controller = new Controller(this); doc1 = new ToolDocument(QStringLiteral("doc1"), controller, new SimpleToolWidgetFactory(QStringLiteral("doc1"))); //this document will create special widgets - QListView nested in QWidget doc2 = new ToolDocument(QStringLiteral("doc2"), controller, new SpecialWidgetFactory(QStringLiteral("doc2"))); doc3 = new ToolDocument(QStringLiteral("doc3"), controller, new SimpleToolWidgetFactory(QStringLiteral("doc3"))); doc4 = new ToolDocument(QStringLiteral("doc4"), controller, new SimpleToolWidgetFactory(QStringLiteral("doc4"))); tool1 = new ToolDocument(QStringLiteral("tool1"), controller, new SimpleToolWidgetFactory(QStringLiteral("tool1"))); tool2 = new ToolDocument(QStringLiteral("tool2"), controller, new SimpleToolWidgetFactory(QStringLiteral("tool2"))); tool3 = new ToolDocument(QStringLiteral("tool3"), controller, new SimpleToolWidgetFactory(QStringLiteral("tool3"))); area = new Area(controller, QStringLiteral("Area")); view211 = doc1->createView(); view211->setObjectName(QStringLiteral("view211")); area->addView(view211); view212 = doc1->createView(); view212->setObjectName(QStringLiteral("view212")); area->addView(view212); view221 = doc2->createView(); area->addView(view221, view211, Qt::Vertical); view231 = doc3->createView(); area->addView(view231, view221, Qt::Horizontal); view241 = doc4->createView(); area->addView(view241, view212, Qt::Vertical); viewT11 = tool1->createView(); area->addToolView(viewT11, Sublime::Bottom); viewT21 = tool2->createView(); area->addToolView(viewT21, Sublime::Right); viewT31 = tool3->createView(); area->addToolView(viewT31, Sublime::Top); viewT32 = tool3->createView(); area->addToolView(viewT32, Sublime::Top); } void TestViewActivation::cleanup() { delete controller; } void TestViewActivation::signalsOnViewCreationAndDeletion() { Controller *controller = new Controller(this); ToolDocument *doc1 = new ToolDocument(QStringLiteral("doc1"), controller, new SimpleToolWidgetFactory(QStringLiteral("doc1"))); Area *area = new Area(controller, QStringLiteral("Area")); QSignalSpy spy(controller, SIGNAL(viewAdded(Sublime::View*))); View *v = doc1->createView(); area->addView(v); QCOMPARE(spy.count(), 1); QSignalSpy spy2(controller, SIGNAL(aboutToRemoveView(Sublime::View*))); area->removeView(v); QCOMPARE(spy2.count(), 1); QSignalSpy spy3(controller, SIGNAL(toolViewAdded(Sublime::View*))); v = doc1->createView(); area->addToolView(v, Sublime::Bottom); QCOMPARE(spy3.count(), 1); QSignalSpy spy4(controller, SIGNAL(aboutToRemoveToolView(Sublime::View*))); area->removeToolView(v); QCOMPARE(spy4.count(), 1); delete controller; } void TestViewActivation::viewActivation() { MainWindow* mw = new MainWindow(controller); controller->addDefaultArea(area); // Q_ASSERT without this. controller->addMainWindow(mw); controller->showArea(area, mw); //we should have an active view immediatelly after the area is shown QCOMPARE(mw->activeView(), view211); //add some widgets that are not in layout QTextEdit *breaker = new QTextEdit(mw); breaker->setObjectName(QStringLiteral("breaker")); QTextEdit *toolBreaker = new QTextEdit(mw); toolBreaker->setObjectName(QStringLiteral("toolBreaker")); QDockWidget *dock = new QDockWidget(mw); dock->setWidget(toolBreaker); mw->addDockWidget(Qt::LeftDockWidgetArea, dock); //now post events to the widgets and see if mainwindow has the right active views //activate view qApp->sendEvent(view212->widget(), new QFocusEvent(QEvent::FocusIn)); QString failMsg = QStringLiteral("\nWas expecting %1 to be active but got %2"). arg(view212->objectName(), mw->activeView()->objectName()); QVERIFY2(mw->activeView() == view212, failMsg.toLatin1().data()); //activate toolview and check that both view and toolview are active qApp->sendEvent(viewT31->widget(), new QFocusEvent(QEvent::FocusIn)); QCOMPARE(mw->activeView(), view212); QCOMPARE(mw->activeToolView(), viewT31); //active another view qApp->sendEvent(view241->widget(), new QFocusEvent(QEvent::FocusIn)); QCOMPARE(mw->activeView(), view241); QCOMPARE(mw->activeToolView(), viewT31); //focus a widget not in the area qApp->sendEvent(breaker, new QFocusEvent(QEvent::FocusIn)); QCOMPARE(mw->activeView(), view241); QCOMPARE(mw->activeToolView(), viewT31); //focus a dock not in the area qApp->sendEvent(toolBreaker, new QFocusEvent(QEvent::FocusIn)); QCOMPARE(mw->activeView(), view241); QCOMPARE(mw->activeToolView(), viewT31); //focus inner widget for view221 QListView *inner = mw->findChild(QStringLiteral("doc2_inner")); QVERIFY(inner); qApp->sendEvent(inner, new QFocusEvent(QEvent::FocusIn)); QCOMPARE(mw->activeView(), view221); QCOMPARE(mw->activeToolView(), viewT31); } void TestViewActivation::activationInMultipleMainWindows() { MainWindow mw(controller); controller->showArea(area, &mw); QCOMPARE(mw.activeView(), view211); //check that new mainwindow always have active view right after displaying area MainWindow mw2(controller); controller->showArea(area, &mw2); QVERIFY(mw2.activeView()); QCOMPARE(mw2.activeView()->document(), doc1); } void TestViewActivation::activationAfterViewRemoval() { MainWindow mw(controller); controller->showArea(area, &mw); QCOMPARE(mw.activeView(), view211); //check what happens if we remove a view which is not the only one in container delete area->removeView(view211); QCOMPARE(mw.activeView(), view212); //check what happens if we remove a view which is alone in container mw.activateView(view231); QCOMPARE(mw.activeView(), view231); delete area->removeView(view231); QCOMPARE(mw.activeView(), view221); } void TestViewActivation::activationAfterRemovalSimplestCase() { //we don't have split views - just two views in one area index MainWindow mw(controller); Area *area = new Area(controller, QStringLiteral("Area")); View *v1 = doc1->createView(); View *v2 = doc2->createView(); area->addView(v1); area->addView(v2, v1); controller->showArea(area, &mw); mw.activateView(v2); //delete active view and check that previous is activated delete area->removeView(v2); QCOMPARE(mw.activeView(), v1); } QTEST_MAIN(TestViewActivation) diff --git a/sublime/tooldocument.h b/sublime/tooldocument.h index fcfd14a4e..a8509bc91 100644 --- a/sublime/tooldocument.h +++ b/sublime/tooldocument.h @@ -1,91 +1,91 @@ /*************************************************************************** * Copyright 2006-2007 Alexander Dymo * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef KDEVPLATFORM_SUBLIMETOOLDOCUMENT_H #define KDEVPLATFORM_SUBLIMETOOLDOCUMENT_H #include "document.h" #include "sublimeexport.h" class QAction; namespace Sublime { class ToolDocument; /** @short Factory to create widgets for toolviews */ class KDEVPLATFORMSUBLIME_EXPORT ToolFactory { public: virtual ~ToolFactory() {} virtual QWidget* create(ToolDocument *doc, QWidget *parent = nullptr) = 0; virtual QList toolBarActions( QWidget* viewWidget ) const = 0; virtual QList contextMenuActions( QWidget* viewWidget ) const = 0; virtual QString id() const = 0; }; /** @short Simple factory that just creates a new widget of given type */ template class SimpleToolWidgetFactory: public ToolFactory { public: - SimpleToolWidgetFactory(const QString &id): ToolFactory(), m_id(id) {} + explicit SimpleToolWidgetFactory(const QString &id): ToolFactory(), m_id(id) {} QWidget* create(ToolDocument * /*doc*/, QWidget *parent = nullptr) override { return new Widget(parent); } QList toolBarActions( QWidget* ) const override { return QList(); } QList< QAction* > contextMenuActions(QWidget* /*viewWidget*/) const override { return QList(); } QString id() const override { return m_id; } virtual bool viewsWantProgressIndicator() const { return false; } private: QString m_id; }; /** @short Document to represent and manage widgets as toolviews */ class KDEVPLATFORMSUBLIME_EXPORT ToolDocument: public Document { Q_OBJECT public: /**Initializes tool document with given @p factory. Document takes ownership over the factory and deletes it together with itself*/ ToolDocument(const QString &title, Controller *controller, ToolFactory *factory); ~ToolDocument() override; QString documentType() const override; QString documentSpecifier() const override; protected: QWidget *createViewWidget(QWidget *parent = nullptr) override; ToolFactory *factory() const; private: struct ToolDocumentPrivate * const d; friend class View; }; } #endif diff --git a/sublime/view.h b/sublime/view.h index 7580660f1..6f34cdae6 100644 --- a/sublime/view.h +++ b/sublime/view.h @@ -1,112 +1,112 @@ /*************************************************************************** * Copyright 2006-2007 Alexander Dymo * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef KDEVPLATFORM_SUBLIMEVIEW_H #define KDEVPLATFORM_SUBLIMEVIEW_H #include #include #include "sublimeexport.h" class QAction; namespace Sublime { class Document; /** @short View - the wrapper to the widget that knows about its document Views are the convenient way to manage a widget. It is specifically designed to be light and fast. Use @ref Document::createView() to get the new view for the document and call @ref View::widget() to create and get the actual widget. It is not possible to create a view by hand. You need either subclass it or use a Document. If you create a subclass of View you need to override Sublime::View::createWidget to provide a custom widget for your view. */ class KDEVPLATFORMSUBLIME_EXPORT View: public QObject { Q_OBJECT public: enum WidgetOwnership { TakeOwnership, DoNotTakeOwnerShip }; ~View() override; /**@return the toolbar actions for this view, this needs to be called _after_ the first call to widget() */ QList toolBarActions() const; /**@return the toolbar actions for this view, this needs to be called _after_ the first call to widget() */ QList contextMenuActions() const; /**@return the document for this view.*/ Document *document() const; /**@return widget for this view (creates it if it's not yet created).*/ QWidget *widget(QWidget *parent = nullptr); /**@return true if this view has an initialized widget.*/ bool hasWidget() const; /// Retrieve information to be placed in the status bar. virtual QString viewStatus() const; /// Retrieve view state for saving into configuration. virtual QString viewState() const; /// Restore view state from configuration virtual void setState(const QString& state); void notifyPositionChanged(int newPositionInArea); Q_SIGNALS: void raise(Sublime::View*); /// Notify that the status for this document has changed void statusChanged(Sublime::View*); void positionChanged(Sublime::View*, int); public Q_SLOTS: void requestRaise(); protected: - View(Document *doc, WidgetOwnership ws = DoNotTakeOwnerShip ); + explicit View(Document *doc, WidgetOwnership ws = DoNotTakeOwnerShip ); /** * override this function to create a custom widget in your View subclass * @param parent the parent widget * @returns a new widget which is used for this view */ virtual QWidget *createWidget(QWidget *parent); private: Q_PRIVATE_SLOT(d, void unsetWidget()) //copy is not allowed, create a new view from the document instead View(const View &v); struct ViewPrivate *const d; friend class Document; }; } Q_DECLARE_METATYPE(Sublime::View*) #endif diff --git a/tests/autotestshell.h b/tests/autotestshell.h index 7c2d106f0..0efa56ad4 100644 --- a/tests/autotestshell.h +++ b/tests/autotestshell.h @@ -1,81 +1,81 @@ /*************************************************************************** * Copyright 2008 Harald Fernengel * * Copyright 2013 Milian Wolff * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU 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_AUTOTESTSHELL_H #define KDEVPLATFORM_AUTOTESTSHELL_H #include #include "testsexport.h" #include #include /* This is a dummy shell for unit tests. It basically does nothing :) You can initialize it in initTestCase() to get a minimal shell to run your autotests. Example of a minimal KDevPlatform unit test: void Mytest::initTestCase() { AutoTestShell::init(); TestCore::initialize(); } */ namespace KDevelop { class KDEVPLATFORMTESTS_EXPORT AutoTestShell : public KDevelop::ShellExtension { public: - AutoTestShell(const QStringList& plugins); + explicit AutoTestShell(const QStringList& plugins); QString xmlFile() override { return QString(); } QString binaryPath() override { return QString(); }; QString defaultProfile() { return QStringLiteral("kdevtest"); } KDevelop::AreaParams defaultArea() override { KDevelop::AreaParams params; params.name = QStringLiteral("test"); params.title = QStringLiteral("Test"); return params; } QString projectFileExtension() override { return QString(); } QString projectFileDescription() override { return QString(); } QStringList defaultPlugins() override { return m_plugins; } /** * Initialize the AutoTestShell and set the global instance. * * @p plugins A list of default global plugins which should be loaded. * By default, all global plugins are loaded. */ static void init(const QStringList& plugins = QStringList()); private: QStringList m_plugins; }; } #endif diff --git a/tests/json/delayedoutput.h b/tests/json/delayedoutput.h index cce92e213..ba07f3b70 100644 --- a/tests/json/delayedoutput.h +++ b/tests/json/delayedoutput.h @@ -1,55 +1,55 @@ /* This file is part of KDevelop Copyright 2012 Olivier de Gaalon 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_DELAYEDOUTPUT_H #define KDEVPLATFORM_DELAYEDOUTPUT_H #include #include #include namespace KDevelop { ///Used to invert and visually nest error output generated by nested/recursive functions ///Used in TestSuite.h, use only at your own, singleton educated risk class DelayedOutputPrivate; class KDEVPLATFORMTESTS_EXPORT DelayedOutput { public: class KDEVPLATFORMTESTS_EXPORT Delay { public: - Delay(DelayedOutput* output); + explicit Delay(DelayedOutput* output); ~Delay(); private: DelayedOutput *m_output; }; ~DelayedOutput(); static DelayedOutput& self(); void push(const QString &output); private: DelayedOutput(); Q_DISABLE_COPY(DelayedOutput) const QScopedPointer d; }; } #endif //KDEVPLATFORM_DELAYEDOUTPUT_H diff --git a/tests/modeltest.h b/tests/modeltest.h index 5ddf1a048..cdd558d4f 100644 --- a/tests/modeltest.h +++ b/tests/modeltest.h @@ -1,78 +1,78 @@ /**************************************************************************** ** ** Copyright (C) 2007 Trolltech ASA. All rights reserved. ** ** This file is part of the Qt Concurrent project on Trolltech Labs. ** ** This file may be used under the terms of the GNU General Public ** License version 2.0 as published by the Free Software Foundation ** and appearing in the file LICENSE.GPL included in the packaging of ** this file. Please review the following information to ensure GNU ** General Public Licensing requirements will be met: ** http://www.trolltech.com/products/qt/opensource.html ** ** If you are unsure which license is appropriate for your use, please ** review the following information: ** http://www.trolltech.com/products/qt/licensing.html or contact the ** sales department at sales@trolltech.com. ** ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. ** ****************************************************************************/ #ifndef KDEVPLATFORM_MODELTEST_H #define KDEVPLATFORM_MODELTEST_H #include #include #include #include "testsexport.h" class KDEVPLATFORMTESTS_EXPORT ModelTest : public QObject { Q_OBJECT public: - ModelTest(QAbstractItemModel *model, QObject *parent = nullptr); + explicit ModelTest(QAbstractItemModel *model, QObject *parent = nullptr); private Q_SLOTS: void nonDestructiveBasicTest(); void rowCount(); void columnCount(); void hasIndex(); void index(); void parent(); void data(); protected Q_SLOTS: void runAllTests(); void layoutAboutToBeChanged(); void layoutChanged(); void rowsAboutToBeInserted(const QModelIndex &parent, int start, int end); void rowsInserted(const QModelIndex & parent, int start, int end); void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end); void rowsRemoved(const QModelIndex & parent, int start, int end); private: void checkChildren(const QModelIndex &parent, int currentDepth = 0); QAbstractItemModel *model; struct Changing { QModelIndex parent; int oldSize; QVariant last; QVariant next; }; QStack insert; QStack remove; bool fetchingMore; QList changing; }; #endif diff --git a/tests/testlanguagecontroller.h b/tests/testlanguagecontroller.h index ccb2f1772..37ab73f06 100644 --- a/tests/testlanguagecontroller.h +++ b/tests/testlanguagecontroller.h @@ -1,45 +1,45 @@ /* * This file is part of KDevelop * * Copyright 2012 Milian Wolff * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_TESTLANGUAGECONTROLLER_H #define KDEVPLATFORM_TESTLANGUAGECONTROLLER_H #include "testsexport.h" #include namespace KDevelop { class ILanguageSupport; class KDEVPLATFORMTESTS_EXPORT TestLanguageController : public LanguageController { Q_OBJECT public: - TestLanguageController(QObject* parent); + explicit TestLanguageController(QObject* parent); ~TestLanguageController() override; void addTestLanguage(KDevelop::ILanguageSupport* languageSupport, const QStringList& mimetypes); }; } #endif // KDEVPLATFORM_TESTLANGUAGECONTROLLER_H diff --git a/tests/testplugincontroller.h b/tests/testplugincontroller.h index 1a987543a..aa2fe4fc9 100644 --- a/tests/testplugincontroller.h +++ b/tests/testplugincontroller.h @@ -1,55 +1,55 @@ /* * This file is part of KDevelop * * Copyright 2012 Milian Wolff * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_TESTPLUGINCONTROLLER_H #define KDEVPLATFORM_TESTPLUGINCONTROLLER_H #include "testsexport.h" #include #include namespace KDevelop { /** * Dummy controller that disables plugins. */ class KDEVPLATFORMTESTS_EXPORT TestPluginController : public PluginController { Q_OBJECT public: - TestPluginController(KDevelop::Core* core); + explicit TestPluginController(KDevelop::Core* core); QList< KDevelop::IPlugin* > allPluginsForExtension(const QString& extension, const QVariantMap& constraints = QVariantMap()) override; QList< KDevelop::IPlugin* > loadedPlugins() const override; KDevelop::IPlugin* pluginForExtension(const QString& extension, const QString& pluginName = {}, const QVariantMap& constraints = QVariantMap()) override; KDevelop::IPlugin* loadPlugin(const QString& pluginName) override; KPluginMetaData pluginInfo(const KDevelop::IPlugin*) const override; QList< KDevelop::ContextMenuExtension > queryPluginsForContextMenuExtensions(KDevelop::Context* context)const override ; QVector queryExtensionPlugins(const QString& extension, const QVariantMap& constraints = QVariantMap()) const override; bool unloadPlugin(const QString& plugin) override; void initialize() override; }; } #endif // KDEVPLATFORM_TESTPLUGINCONTROLLER_H diff --git a/tests/testproject.h b/tests/testproject.h index 10dd2eecc..628d9bb4d 100644 --- a/tests/testproject.h +++ b/tests/testproject.h @@ -1,102 +1,102 @@ /*************************************************************************** * Copyright 2010 Niko Sams * * Copyright 2012 Milian Wolff * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef KDEVPLATFORM_TEST_PROJECT_H #define KDEVPLATFORM_TEST_PROJECT_H #include #include #include #include #include "testsexport.h" #include namespace KDevelop { /** * Dummy Project than can be used for Unit Tests. * * Currently only FileSet methods are implemented. */ class KDEVPLATFORMTESTS_EXPORT TestProject : public IProject { Q_OBJECT public: /** * @p url Path to project directory. */ - TestProject(const Path& url = Path(), QObject* parent = nullptr); + explicit TestProject(const Path& url = Path(), QObject* parent = nullptr); ~TestProject() override; IProjectFileManager* projectFileManager() const override { return nullptr; } IBuildSystemManager* buildSystemManager() const override { return nullptr; } IPlugin* managerPlugin() const override { return nullptr; } IPlugin* versionControlPlugin() const override { return nullptr; } ProjectFolderItem* projectItem() const override; void setProjectItem(ProjectFolderItem* item); int fileCount() const { return 0; } ProjectFileItem* fileAt( int ) const { return nullptr; } QList files() const; QList< ProjectBaseItem* > itemsForPath(const IndexedString&) const override { return QList< ProjectBaseItem* >(); } QList< ProjectFileItem* > filesForPath(const IndexedString&) const override; QList< ProjectFolderItem* > foldersForPath(const IndexedString&) const override { return QList(); } void reloadModel() override { } void close() override {} Path projectFile() const override; KSharedConfigPtr projectConfiguration() const override { return m_projectConfiguration; } void addToFileSet( ProjectFileItem* file) override; void removeFromFileSet( ProjectFileItem* file) override; QSet fileSet() const override { return m_fileSet; } bool isReady() const override { return true; } void setPath(const Path& path); Path path() const override; QString name() const override { return QStringLiteral("Test Project"); } bool inProject(const IndexedString& path) const override; void setReloadJob(KJob* ) override {} private: QSet m_fileSet; Path m_path; ProjectFolderItem* m_root; KSharedConfigPtr m_projectConfiguration; }; /** * ProjectController that can clear open projects. Useful in Unit Tests. */ class KDEVPLATFORMTESTS_EXPORT TestProjectController : public ProjectController { Q_OBJECT public: - TestProjectController(Core* core) : ProjectController(core) {} + explicit TestProjectController(Core* core) : ProjectController(core) {} public: using ProjectController::addProject; using ProjectController::takeProject; void initialize() override; }; } #endif diff --git a/util/commandexecutor.cpp b/util/commandexecutor.cpp index 578a6bf48..5da92c915 100644 --- a/util/commandexecutor.cpp +++ b/util/commandexecutor.cpp @@ -1,167 +1,167 @@ /* 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 "commandexecutor.h" #include "processlinemaker.h" #include #include #include #include #include namespace KDevelop { class CommandExecutorPrivate { public: - CommandExecutorPrivate( CommandExecutor* cmd ) + explicit CommandExecutorPrivate( CommandExecutor* cmd ) : m_exec(cmd), m_useShell(false) { } CommandExecutor* m_exec; KProcess* m_process; ProcessLineMaker* m_lineMaker; QString m_command; QStringList m_args; QString m_workDir; QMap m_env; bool m_useShell; void procError( QProcess::ProcessError error ) { Q_UNUSED(error) m_lineMaker->flushBuffers(); emit m_exec->failed( error ); } void procFinished( int code, QProcess::ExitStatus status ) { m_lineMaker->flushBuffers(); if( status == QProcess::NormalExit ) emit m_exec->completed(code); } }; CommandExecutor::CommandExecutor( const QString& command, QObject* parent ) : QObject(parent), d(new CommandExecutorPrivate(this)) { d->m_process = new KProcess(this); d->m_process->setOutputChannelMode( KProcess::SeparateChannels ); d->m_lineMaker = new ProcessLineMaker( d->m_process ); d->m_command = command; connect( d->m_lineMaker, &ProcessLineMaker::receivedStdoutLines, this, &CommandExecutor::receivedStandardOutput ); connect( d->m_lineMaker, &ProcessLineMaker::receivedStderrLines, this, &CommandExecutor::receivedStandardError ); connect( d->m_process, static_cast(&KProcess::error), this, [&] (QProcess::ProcessError error) { d->procError(error); } ); connect( d->m_process, static_cast(&KProcess::finished), this, [&] (int code, QProcess::ExitStatus status) { d->procFinished(code, status); } ); } CommandExecutor::~CommandExecutor() { delete d->m_process; delete d->m_lineMaker; delete d; } void CommandExecutor::setEnvironment( const QMap& env ) { d->m_env = env; } void CommandExecutor::setEnvironment( const QStringList& env ) { QMap envmap; foreach( const QString& var, env ) { int sep = var.indexOf( '=' ); envmap.insert( var.left( sep ), var.mid( sep + 1 ) ); } d->m_env = envmap; } void CommandExecutor::setArguments( const QStringList& args ) { d->m_args = args; } void CommandExecutor::setWorkingDirectory( const QString& dir ) { d->m_workDir = dir; } bool CommandExecutor::useShell() const { return d->m_useShell; } void CommandExecutor::setUseShell( bool shell ) { d->m_useShell = shell; } void CommandExecutor::start() { for(auto it = d->m_env.constBegin(), itEnd = d->m_env.constEnd(); it!=itEnd; ++it) { d->m_process->setEnv( it.key(), it.value() ); } d->m_process->setWorkingDirectory( d->m_workDir ); if( !d->m_useShell ) { d->m_process->setProgram( d->m_command, d->m_args ); } else { QStringList arguments; Q_FOREACH( const QString &a, d->m_args ) arguments << KShell::quoteArg( a ); d->m_process->setShellCommand(d->m_command + ' ' + arguments.join(QLatin1Char(' '))); } d->m_process->start(); } void CommandExecutor::setCommand( const QString& command ) { d->m_command = command; } void CommandExecutor::kill() { d->m_process->kill(); } QString CommandExecutor::command() const { return d->m_command; } QStringList CommandExecutor::arguments() const { return d->m_args; } QString CommandExecutor::workingDirectory() const { return d->m_workDir; } } #include "moc_commandexecutor.cpp" diff --git a/util/convenientfreelist.h b/util/convenientfreelist.h index 8f4d3b35d..1876c9cf1 100644 --- a/util/convenientfreelist.h +++ b/util/convenientfreelist.h @@ -1,743 +1,743 @@ /* 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_CONVENIENTFREELIST_H #define KDEVPLATFORM_CONVENIENTFREELIST_H #include #include #include "embeddedfreetree.h" #include "kdevvarlengtharray.h" namespace KDevelop { template class ConvenientEmbeddedSetIterator; template class ConvenientEmbeddedSetFilterIterator; ///A convenience-class for accessing the data in a set managed by the EmbeddedFreeTree algorithms. template class ConstantConvenientEmbeddedSet { public: ConstantConvenientEmbeddedSet() : m_data(nullptr), m_dataSize(0), m_centralFreeItem(-1) { } ConstantConvenientEmbeddedSet(const Data* data, uint count, int centralFreeItem) : m_data(data), m_dataSize(count), m_centralFreeItem(centralFreeItem) { } ///Returns whether the item is contained in this set bool contains(const Data& data) const { return indexOf(data) != -1; } ///Returns the position of the item in the underlying array, or -1 if it is not contained int indexOf(const Data& data) const { EmbeddedTreeAlgorithms alg(m_data, m_dataSize, m_centralFreeItem); return alg.indexOf(data); } ///Returns the size of the underlying array uint dataSize() const { return m_dataSize; } uint countFreeItems() { EmbeddedTreeAlgorithms alg(m_data, m_dataSize, m_centralFreeItem); return alg.countFreeItems(); } void verify() { EmbeddedTreeAlgorithms alg(m_data, m_dataSize, m_centralFreeItem); alg.verifyTreeConsistent(); alg.verifyOrder(); } ///Returns the underlying array. That array may contain invalid/free items. const Data* data() const { return m_data; } ///Returns the first valid index that has a data-value larger or equal to @p data. ///Returns -1 if nothing is found. int lowerBound(const Data& data) const { EmbeddedTreeAlgorithms alg(m_data, m_dataSize, m_centralFreeItem); return alg.lowerBound(data, 0, m_dataSize); } ///Returns the first valid index that has a data-value larger or equal to @p data, ///and that is in the range [start, end). ///Returns -1 if nothing is found. int lowerBound(const Data& data, uint start, uint end) const { EmbeddedTreeAlgorithms alg(m_data, m_dataSize, m_centralFreeItem); return alg.lowerBound(data, start, end); } ///Finds a valid most central in the range [start, end). ///Returns -1 if no such item exists. int validMiddle(uint start, uint end) { if(end <= start) return -1; int firstTry = ((end-start)/2) + start; int thisTry = firstTry; while(thisTry < (int)end && Handler::isFree(m_data[thisTry])) ++thisTry; if(thisTry != (int)end) return thisTry; //Nothing find on right side of middle, try the other direction thisTry = firstTry-1; while(thisTry >= (int)start && Handler::isFree(m_data[thisTry])) --thisTry; if(thisTry >= (int)start) return thisTry; else return -1; } ///Returns the first valid item in the range [pos, end), or -1 int firstValidItem(int start, int end = -1) const { if(end == -1) end = (int)m_dataSize; for(; start < end; ++start) if(!Handler::isFree(m_data[start])) return start; return -1; } ///Returns the last valid item in the range [pos, end), or -1 int lastValidItem(int start = 0, int end = -1) const { if(end == -1) end = (int)m_dataSize; --end; for(; end >= start; --end) if(!Handler::isFree(m_data[end])) return end; return -1; } typedef ConvenientEmbeddedSetIterator Iterator; ConvenientEmbeddedSetIterator iterator() const; // protected: const Data* m_data; uint m_dataSize; int m_centralFreeItem; }; ///Convenient iterator that automatically skips invalid/free items in the array. template class ConvenientEmbeddedSetIterator : public ConstantConvenientEmbeddedSet { public: - ConvenientEmbeddedSetIterator(const Data* data = nullptr, uint count = 0, int centralFreeItem = -1) : ConstantConvenientEmbeddedSet(data, count, centralFreeItem), m_pos(0) { + explicit ConvenientEmbeddedSetIterator(const Data* data = nullptr, uint count = 0, int centralFreeItem = -1) : ConstantConvenientEmbeddedSet(data, count, centralFreeItem), m_pos(0) { //Move to first valid position moveToValid(); } ///Returns true of this iterator has a value to return operator bool() const { return m_pos != this->m_dataSize; } const Data* operator->() const { return &this->m_data[m_pos]; } const Data& operator*() const { return this->m_data[m_pos]; } ConvenientEmbeddedSetIterator& operator++() { ++m_pos; moveToValid(); return *this; } protected: inline void moveToValid() { while(this->m_pos < this->m_dataSize && (Handler::isFree(this->m_data[this->m_pos]))) ++this->m_pos; } uint m_pos; }; ///An iterator that allows efficient matching between two lists with different data type. ///Important: It must be possible to extract the data-type of the second list from the items in the first list ///The second list must be sorted by that data. ///The first list must primarily be sorted by that data, but is allowed to ///be sub-ordered by something else, and multiple items in the first list are allowed to match one item in the second. ///This iterator iterates through all items in the first list that have extracted key-data that is in represented in the second. template class ConvenientEmbeddedSetFilterIterator : public ConvenientEmbeddedSetIterator { public: ConvenientEmbeddedSetFilterIterator() : m_match(-1) { } ConvenientEmbeddedSetFilterIterator(const ConvenientEmbeddedSetIterator& base, const ConvenientEmbeddedSetIterator& rhs) : ConvenientEmbeddedSetIterator(base), m_rhs(rhs), m_match(-1) { boundStack.append( qMakePair( qMakePair(0u, this->m_dataSize), qMakePair(0u, rhs.m_dataSize) ) ); go(); } operator bool() const { return m_match != -1; } const Data* operator->() const { Q_ASSERT(m_match != -1); return &this->m_data[m_match]; } const Data& operator*() const { Q_ASSERT(m_match != -1); return this->m_data[m_match]; } ConvenientEmbeddedSetFilterIterator& operator++() { Q_ASSERT(m_match != -1); go(); return *this; } #define CHECK_BOUNDS Q_ASSERT(boundStack.back().first.first < 100000 && boundStack.back().first.second < 10000 && boundStack.back().second.first < 100000 && boundStack.back().second.second < 10000 ); private: void go() { m_match = -1; boundsUp: if(boundStack.isEmpty()) return; CHECK_BOUNDS QPair, QPair > currentBounds = boundStack.back(); boundStack.pop_back(); uint ownStart = currentBounds.first.first, ownEnd = currentBounds.first.second; uint rhsStart = currentBounds.second.first, rhsEnd = currentBounds.second.second; #if 0 //This code works, but it doesn't give a speedup int ownFirstValid = this->firstValidItem(ownStart, ownEnd), ownLastValid = this->lastValidItem(ownStart, ownEnd); int rhsFirstValid = m_rhs.firstValidItem(rhsStart, rhsEnd), rhsLastValid = m_rhs.lastValidItem(rhsStart, rhsEnd); if(ownFirstValid == -1 || rhsFirstValid == -1) goto boundsUp; Data2 ownFirstValidData = KeyExtractor::extract(this->m_data[ownFirstValid]); Data2 ownLastValidData = KeyExtractor::extract(this->m_data[ownLastValid]); Data2 commonStart = ownFirstValidData; Data2 commonLast = ownLastValidData; //commonLast is also still valid if(commonStart < m_rhs.m_data[rhsFirstValid]) commonStart = m_rhs.m_data[rhsFirstValid]; if(m_rhs.m_data[rhsLastValid] < commonLast) commonLast = m_rhs.m_data[rhsLastValid]; if(commonLast < commonStart) goto boundsUp; #endif while(true) { if(ownStart == ownEnd) goto boundsUp; int ownMiddle = this->validMiddle(ownStart, ownEnd); Q_ASSERT(ownMiddle < 100000); if(ownMiddle == -1) goto boundsUp; //No valid items in the range Data2 currentData2 = KeyExtractor::extract(this->m_data[ownMiddle]); Q_ASSERT(!Handler2::isFree(currentData2)); int bound = m_rhs.lowerBound(currentData2, rhsStart, rhsEnd); if(bound == -1) { //Release second half of the own range // Q_ASSERT(ownEnd > ownMiddle); ownEnd = ownMiddle; continue; } if(currentData2 == m_rhs.m_data[bound]) { //We have a match this->m_match = ownMiddle; //Append the ranges that need to be matched next, without the matched item boundStack.append( qMakePair( qMakePair( (uint)ownMiddle+1, ownEnd ), qMakePair((uint)bound, rhsEnd)) ); if(ownMiddle) boundStack.append( qMakePair( qMakePair( ownStart, (uint)ownMiddle ), qMakePair(rhsStart, (uint)bound+1)) ); return; } if(bound == m_rhs.firstValidItem(rhsStart)) { //The bound is the first valid item of the second range. //Discard left side and the matched left item, and continue. ownStart = ownMiddle+1; rhsStart = bound; continue; } //Standard: Split both sides into 2 ranges that will be checked next boundStack.append( qMakePair( qMakePair( (uint)ownMiddle+1, ownEnd ), qMakePair((uint)bound, rhsEnd)) ); // Q_ASSERT(ownMiddle <= ownEnd); ownEnd = ownMiddle; //We loose the item at 'middle' here, but that's fine, since it hasn't found a match. rhsEnd = bound+1; } } //Bounds that yet need to be matched. KDevVarLengthArray, QPair > > boundStack; ConvenientEmbeddedSetIterator m_rhs; int m_match; }; ///Filters a list-embedded set by a binary tree set as managed by the SetRepository data structures template class ConvenientEmbeddedSetTreeFilterIterator : public ConvenientEmbeddedSetIterator { public: ConvenientEmbeddedSetTreeFilterIterator() : m_match(-1) { } ///@param noFiltering whether the given input is pre-filtered. If this is true, base will be iterated without skipping any items. ConvenientEmbeddedSetTreeFilterIterator(const ConvenientEmbeddedSetIterator& base, const TreeSet& rhs, bool noFiltering = false) : ConvenientEmbeddedSetIterator(base), m_rhs(rhs), m_match(-1), m_noFiltering(noFiltering) { if(rhs.node().isValid()) { //Correctly initialize the initial bounds int ownStart = lowerBound(rhs.node().firstItem(), 0, this->m_dataSize); if(ownStart == -1) return; int ownEnd = lowerBound(rhs.node().lastItem(), ownStart, this->m_dataSize); if(ownEnd == -1) ownEnd = this->m_dataSize; else ownEnd += 1; boundStack.append( qMakePair( qMakePair((uint)ownStart, (uint)ownEnd), rhs.node() ) ); } go(); } operator bool() const { return m_match != -1; } const Data* operator->() const { Q_ASSERT(m_match != -1); return &this->m_data[m_match]; } const Data& operator*() const { Q_ASSERT(m_match != -1); return this->m_data[m_match]; } ConvenientEmbeddedSetTreeFilterIterator& operator++() { Q_ASSERT(m_match != -1); go(); return *this; } #define CHECK_BOUNDS Q_ASSERT(boundStack.back().first.first < 100000 && boundStack.back().first.second < 10000 && boundStack.back().second.first < 100000 && boundStack.back().second.second < 10000 ); private: void go() { if(m_noFiltering) { ++m_match; if((uint)m_match >= this->m_dataSize) m_match = -1; return; } if(m_match != -1) { //Match multiple items in this list to one in the tree m_match = this->firstValidItem(m_match+1, this->m_dataSize); if(m_match != -1 && KeyExtractor::extract(this->m_data[m_match]) == m_matchingTo) return; } m_match = -1; boundsUp: if(boundStack.isEmpty()) return; QPair, typename TreeSet::Node > currentBounds = boundStack.back(); boundStack.pop_back(); uint ownStart = currentBounds.first.first, ownEnd = currentBounds.first.second; typename TreeSet::Node currentNode = currentBounds.second; if(ownStart >= ownEnd) goto boundsUp; if(!currentNode.isValid()) goto boundsUp; while(true) { if(ownStart == ownEnd) goto boundsUp; if(currentNode.isFinalNode()) { // qCDebug(UTIL) << ownStart << ownEnd << "final node" << currentNode.start() * extractor_div_with << currentNode.end() * extractor_div_with; //Check whether the item is contained int bound = lowerBound(*currentNode, ownStart, ownEnd); // qCDebug(UTIL) << "bound:" << bound << (KeyExtractor::extract(this->m_data[bound]) == *currentNode); if(bound != -1 && KeyExtractor::extract(this->m_data[bound]) == *currentNode) { //Got a match m_match = bound; m_matchingTo = *currentNode; m_matchBound = ownEnd; return; }else{ //Mismatch goto boundsUp; } }else{ // qCDebug(UTIL) << ownStart << ownEnd << "node" << currentNode.start() * extractor_div_with << currentNode.end() * extractor_div_with; //This is not a final node, split up the search into the sub-nodes typename TreeSet::Node leftNode = currentNode.leftChild(); typename TreeSet::Node rightNode = currentNode.rightChild(); Q_ASSERT(leftNode.isValid()); Q_ASSERT(rightNode.isValid()); Data2 leftLastItem = leftNode.lastItem(); int rightSearchStart = lowerBound(rightNode.firstItem(), ownStart, ownEnd); if(rightSearchStart == -1) rightSearchStart = ownEnd; int leftSearchLast = lowerBound(leftLastItem, ownStart, rightSearchStart != -1 ? rightSearchStart : ownEnd); if(leftSearchLast == -1) leftSearchLast = rightSearchStart-1; bool recurseLeft = false; if(leftSearchLast > (int)ownStart) { recurseLeft = true; //There must be something in the range ownStart -> leftSearchLast that matches the range }else if((int)ownStart == leftSearchLast) { //Check if the one item item under leftSearchStart is contained in the range Data2 leftFoundStartData = KeyExtractor::extract(this->m_data[ownStart]); recurseLeft = leftFoundStartData < leftLastItem || leftFoundStartData == leftLastItem; } bool recurseRight = false; if(rightSearchStart < (int)ownEnd) recurseRight = true; if(recurseLeft && recurseRight) { //Push the right branch onto the stack, and work in the left one boundStack.append( qMakePair( qMakePair( (uint)rightSearchStart, ownEnd ), rightNode) ); } if(recurseLeft) { currentNode = leftNode; if(leftSearchLast != -1) ownEnd = leftSearchLast+1; }else if(recurseRight) { currentNode = rightNode; ownStart = rightSearchStart; }else{ goto boundsUp; } } } } ///Returns the first valid index that has an extracted data-value larger or equal to @p data. ///Returns -1 if nothing is found. int lowerBound(const Data2& data, int start, int end) { int currentBound = -1; while(1) { if(start >= end) return currentBound; int center = (start + end)/2; //Skip free items, since they cannot be used for ordering for(; center < end; ) { if(!Handler::isFree(this->m_data[center])) break; ++center; } if(center == end) { end = (start + end)/2; //No non-free items found in second half, so continue search in the other }else{ Data2 centerData = KeyExtractor::extract(this->m_data[center]); //Even if the data equals we must continue searching to the left, since there may be multiple matching if(data == centerData || data < centerData) { currentBound = center; end = (start + end)/2; }else{ //Continue search in second half start = center+1; } } } } //Bounds that yet need to be matched. Always a range in the own vector, and a node that all items in the range are contained in KDevVarLengthArray, typename TreeSet::Node > > boundStack; TreeSet m_rhs; int m_match, m_matchBound; Data2 m_matchingTo; bool m_noFiltering; }; ///Same as above, except that it visits all filtered items with a visitor, instead of iterating over them. ///This is more efficient. The visiting is done directly from within the constructor. template class ConvenientEmbeddedSetTreeFilterVisitor : public ConvenientEmbeddedSetIterator { public: ConvenientEmbeddedSetTreeFilterVisitor() { } typedef QPair, typename TreeSet::Node > Bounds; struct Bound { inline Bound(uint s, uint e, const typename TreeSet::Node& n) : start(s), end(e), node(n) { } Bound() { } uint start; uint end; typename TreeSet::Node node; }; ///@param noFiltering whether the given input is pre-filtered. If this is true, base will be iterated without skipping any items. ConvenientEmbeddedSetTreeFilterVisitor(Visitor& visitor, const ConvenientEmbeddedSetIterator& base, const TreeSet& rhs, bool noFiltering = false) : ConvenientEmbeddedSetIterator(base), m_visitor(visitor), m_rhs(rhs), m_noFiltering(noFiltering) { if(m_noFiltering) { for(uint a = 0; a < this->m_dataSize; ++a) visitor(this->m_data[a]); return; } if(rhs.node().isValid()) { //Correctly initialize the initial bounds int ownStart = lowerBound(rhs.node().firstItem(), 0, this->m_dataSize); if(ownStart == -1) return; int ownEnd = lowerBound(rhs.node().lastItem(), ownStart, this->m_dataSize); if(ownEnd == -1) ownEnd = this->m_dataSize; else ownEnd += 1; go( Bound((uint)ownStart, (uint)ownEnd, rhs.node()) ); } } private: void go( Bound bound ) { KDevVarLengthArray bounds; while(true) { if(bound.start >= bound.end) goto nextBound; if(bound.node.isFinalNode()) { //Check whether the item is contained int b = lowerBound(*bound.node, bound.start, bound.end); if(b != -1) { const Data2& matchTo(*bound.node); if(KeyExtractor::extract(this->m_data[b]) == matchTo) { while(1) { m_visitor(this->m_data[b]); b = this->firstValidItem(b+1, this->m_dataSize); if(b < (int)this->m_dataSize && b != -1 && KeyExtractor::extract(this->m_data[b]) == matchTo) continue; else break; } } } goto nextBound; }else{ //This is not a final node, split up the search into the sub-nodes typename TreeSet::Node leftNode = bound.node.leftChild(); typename TreeSet::Node rightNode = bound.node.rightChild(); Q_ASSERT(leftNode.isValid()); Q_ASSERT(rightNode.isValid()); Data2 leftLastItem = leftNode.lastItem(); int rightSearchStart = lowerBound(rightNode.firstItem(), bound.start, bound.end); if(rightSearchStart == -1) rightSearchStart = bound.end; int leftSearchLast = lowerBound(leftLastItem, bound.start, rightSearchStart != -1 ? rightSearchStart : bound.end); if(leftSearchLast == -1) leftSearchLast = rightSearchStart-1; bool recurseLeft = false; if(leftSearchLast > (int)bound.start) { recurseLeft = true; //There must be something in the range bound.start -> leftSearchLast that matches the range }else if((int)bound.start == leftSearchLast) { //Check if the one item item under leftSearchStart is contained in the range Data2 leftFoundStartData = KeyExtractor::extract(this->m_data[bound.start]); recurseLeft = leftFoundStartData < leftLastItem || leftFoundStartData == leftLastItem; } bool recurseRight = false; if(rightSearchStart < (int)bound.end) recurseRight = true; if(recurseLeft && recurseRight) bounds.append( Bound(rightSearchStart, bound.end, rightNode) ); if(recurseLeft) { bound.node = leftNode; if(leftSearchLast != -1) bound.end = leftSearchLast+1; }else if(recurseRight) { bound.node = rightNode; bound.start = rightSearchStart; }else{ goto nextBound; } continue; } nextBound: if(bounds.isEmpty()) { return; }else{ bound = bounds.back(); bounds.pop_back(); } } } ///Returns the first valid index that has an extracted data-value larger or equal to @p data. ///Returns -1 if nothing is found. int lowerBound(const Data2& data, int start, int end) { int currentBound = -1; while(1) { if(start >= end) return currentBound; int center = (start + end)/2; //Skip free items, since they cannot be used for ordering for(; center < end; ) { if(!Handler::isFree(this->m_data[center])) break; ++center; } if(center == end) { end = (start + end)/2; //No non-free items found in second half, so continue search in the other }else{ Data2 centerData = KeyExtractor::extract(this->m_data[center]); //Even if the data equals we must continue searching to the left, since there may be multiple matching if(data == centerData || data < centerData) { currentBound = center; end = (start + end)/2; }else{ //Continue search in second half start = center+1; } } } } //Bounds that yet need to be matched. Always a range in the own vector, and a node that all items in the range are contained in Visitor& m_visitor; TreeSet m_rhs; bool m_noFiltering; }; template ConvenientEmbeddedSetIterator ConstantConvenientEmbeddedSet::iterator() const { return ConvenientEmbeddedSetIterator(m_data, m_dataSize, m_centralFreeItem); } ///This is a simple set implementation based on the embedded free tree algorithms. ///The core advantage of the whole thing is that the wole set is represented by a consecutive ///memory-area, and thus can be stored or copied using a simple memcpy. ///However in many cases it's better using the algorithms directly in such cases. /// ///However even for normal tasks this implementation does have some advantages over std::set: ///- Many times faster iteration through contained data ///- Lower memory-usage if the objects are small, since there is no heap allocation overhead ///- Can be combined with other embedded-free-list based sets using algorithms in ConstantConvenientEmbeddedSet ///Disadvantages: ///- Significantly slower insertion template class ConvenientFreeListSet { public: typedef ConvenientEmbeddedSetIterator Iterator; ConvenientFreeListSet() : m_centralFree(-1) { } ///Re-construct a set from its components ConvenientFreeListSet(int centralFreeItem, QVector data) : m_data(data), m_centralFree(centralFreeItem) { } ///You can use this to store the set to disk and later give it together with data() to the constructor, thus reconstructing it. int centralFreeItem() const { return m_centralFree; } const QVector& data() const { return m_data; } void insert(const Data& item) { if(contains(item)) return; KDevelop::EmbeddedTreeAddItem add(m_data.data(), m_data.size(), m_centralFree, item); if((int)add.newItemCount() != (int)m_data.size()) { QVector newData; newData.resize(add.newItemCount()); add.transferData(newData.data(), newData.size()); m_data = newData; } } Iterator iterator() const { return Iterator(m_data.data(), m_data.size(), m_centralFree); } bool contains(const Data& item) const { KDevelop::EmbeddedTreeAlgorithms alg(m_data.data(), m_data.size(), m_centralFree); return alg.indexOf(Data(item)) != -1; } void remove(const Data& item) { KDevelop::EmbeddedTreeRemoveItem remove(m_data.data(), m_data.size(), m_centralFree, item); if((int)remove.newItemCount() != (int)m_data.size()) { QVector newData; newData.resize(remove.newItemCount()); remove.transferData(newData.data(), newData.size()); m_data = newData; } } private: int m_centralFree; QVector m_data; }; } #endif diff --git a/util/environmentselectionwidget.cpp b/util/environmentselectionwidget.cpp index f381bb85a..4a7d15b98 100644 --- a/util/environmentselectionwidget.cpp +++ b/util/environmentselectionwidget.cpp @@ -1,105 +1,105 @@ /* This file is part of KDevelop Copyright 2007 Dukju Ahn 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 "environmentselectionwidget.h" #include "environmentgrouplist.h" #include "environmentselectionmodel.h" #include #include #include namespace KDevelop { struct EnvironmentSelectionWidgetPrivate { KComboBox* comboBox; EnvironmentSelectionModel* model; EnvironmentSelectionWidget* owner; - EnvironmentSelectionWidgetPrivate( EnvironmentSelectionWidget* _owner ) + explicit EnvironmentSelectionWidgetPrivate( EnvironmentSelectionWidget* _owner ) : comboBox( new KComboBox( _owner ) ) , model( new EnvironmentSelectionModel( _owner ) ) , owner( _owner ) { comboBox->setModel( model ); comboBox->setEditable( false ); } }; EnvironmentSelectionWidget::EnvironmentSelectionWidget( QWidget *parent ) : QWidget( parent ), d( new EnvironmentSelectionWidgetPrivate( this ) ) { // Taken from kdelibs/kdeui/dialogs/kconfigdialogmanager.cpp (no idea whether this is documented) // Commits d44186bce4670d2985fb6aba8dba59bbd2c4c77a and 8edc1932ecc62370d9a31836dfa9b2bd0175a293 // introduced a regression in kdelibs to fix problems running some apps against Qt4.8. Unfortunately // this fix breaks exactly our use-case, which is to store the text-value in kconfig instead of // the index even though the combobox is editable. Since that change the special combobox-code in // kconfigdialogmanager.cpp is run before check a user-property and hence our user-property is // ignored. Setting this special kcfg_property to the name of our user-property again overrides // the hardcoded combobox-behaviour - until the next one breaks things in kdelibs :| setProperty("kcfg_property", QByteArray("currentProfile")); setLayout( new QHBoxLayout( this ) ); layout()->addWidget( d->comboBox ); layout()->setMargin( 0 ); setCurrentProfile( QString() ); // select the default profile connect(d->comboBox, &QComboBox::currentTextChanged, this, &EnvironmentSelectionWidget::currentProfileChanged); } EnvironmentSelectionWidget::~EnvironmentSelectionWidget() { delete d; } QString EnvironmentSelectionWidget::currentProfile() const { return d->model->index( d->comboBox->currentIndex(), 0 ).data( Qt::EditRole ).toString(); } void EnvironmentSelectionWidget::setCurrentProfile( const QString& profile ) { d->comboBox->setCurrentIndex( d->comboBox->findData( profile, Qt::EditRole ) ); emit currentProfileChanged(profile); } void EnvironmentSelectionWidget::reconfigure() { QString selectedProfile = currentProfile(); d->model->reload(); setCurrentProfile( d->model->reloadSelectedItem( selectedProfile ) ); } QString EnvironmentSelectionWidget::effectiveProfileName() const { return d->model->index( d->comboBox->currentIndex(), 0 ).data( EnvironmentSelectionModel::EffectiveNameRole ).toString(); } EnvironmentGroupList EnvironmentSelectionWidget::environment() const { return d->model->environment(); } } diff --git a/util/ksharedobject.h b/util/ksharedobject.h index 6780495e3..115958840 100644 --- a/util/ksharedobject.h +++ b/util/ksharedobject.h @@ -1,74 +1,74 @@ /* 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. */ #ifndef KDEVPLATFORM_KSHAREDOBJECT_H #define KDEVPLATFORM_KSHAREDOBJECT_H #include #include namespace KDevelop { struct FakeAtomic { inline FakeAtomic(QObject& object, QSharedData& real) : m_object(object), m_real(real) { } inline operator int() const { const int value = m_real.ref.loadAcquire(); if(value == 0) return 1; //Always return true, because we handle the deleting by ourself using deleteLater return value; } inline bool ref() { return m_real.ref.ref(); } inline bool deref() { bool ret = m_real.ref.deref(); if(!ret) m_object.deleteLater(); return true; //Always return true, because we handle the deleting by ourself } inline int load() const { return m_real.ref.load(); } QObject& m_object; QSharedData& m_real; }; /** * Wrapper around QSharedData for use with KSharedPtr when the object is based on QObject as well. * Instead of deleting the object once the reference-count reaches zero, QObject::deleteLater() is called. * This prevents a possible crash when the reference-count reaches zero during event-processing while the queue * contains events to be delivered to that object. * * Notice however that the object will not be deleted immediately, which may lead to unintended behavior. */ struct KSharedObject : public QSharedData { - inline KSharedObject(QObject& object) : ref(object, *this) { + inline explicit KSharedObject(QObject& object) : ref(object, *this) { } mutable FakeAtomic ref; }; } #endif diff --git a/util/multilevellistview.cpp b/util/multilevellistview.cpp index 1e1742c6d..d1aefac45 100644 --- a/util/multilevellistview.cpp +++ b/util/multilevellistview.cpp @@ -1,455 +1,455 @@ /* 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 "multilevellistview.h" #include #include #include #include #include /** * Interface to set the label of a model. */ class LabeledProxy { public: virtual ~LabeledProxy() { } void setLabel(const QString& label) { m_label = label; } QVariant header(QAbstractItemModel* model, int section, Qt::Orientation orientation, int role) const { if (model && section == 0 && orientation == Qt::Horizontal && role == Qt::DisplayRole) { return m_label; } else { return QVariant(); } } protected: QString m_label; }; /** * The left-most view's model which only contains the root nodes of the source model. */ class RootProxyModel : public QSortFilterProxyModel, public LabeledProxy { Q_OBJECT public: - RootProxyModel( QObject* parent = nullptr ) + explicit RootProxyModel( QObject* parent = nullptr ) : QSortFilterProxyModel( parent ) { } bool filterAcceptsRow( int /*source_row*/, const QModelIndex& source_parent ) const override { return !source_parent.isValid(); } QVariant headerData( int section, Qt::Orientation orientation, int role ) const override { return header(sourceModel(), section, orientation, role); } }; /** * A class that automatically updates its contents based on the selection in another view. */ class SubTreeProxyModel : public KSelectionProxyModel, public LabeledProxy { Q_OBJECT public: explicit SubTreeProxyModel( QItemSelectionModel* selectionModel, QObject* parent = nullptr ) : KSelectionProxyModel( selectionModel, parent ) {} QVariant headerData( int section, Qt::Orientation orientation, int role ) const override { return header(sourceModel(), section, orientation, role); } Qt::ItemFlags flags(const QModelIndex& index) const override { Qt::ItemFlags ret = KSelectionProxyModel::flags(index); if (filterBehavior() == KSelectionProxyModel::SubTreesWithoutRoots && hasChildren(index)) { // we want to select child items ret &= ~Qt::ItemIsSelectable; } return ret; } }; using namespace KDevelop; class KDevelop::MultiLevelListViewPrivate { public: - MultiLevelListViewPrivate(MultiLevelListView* view); + explicit MultiLevelListViewPrivate(MultiLevelListView* view); ~MultiLevelListViewPrivate(); void viewSelectionChanged(const QModelIndex& current, const QModelIndex& previous); void lastViewsContentsChanged(); void ensureViewSelected(QTreeView* view); /** * @param index index in any of our proxy models * @return an index in the source model */ QModelIndex mapToSource(QModelIndex index); /** * @param index an index in the source model * @return an index in the view's model at level @p level */ QModelIndex mapFromSource(QModelIndex index, int level); MultiLevelListView* view; int levels; QList views; QList proxies; QList layouts; QAbstractItemModel* model; }; MultiLevelListViewPrivate::MultiLevelListViewPrivate(MultiLevelListView* view_) : view(view_) , levels(0) , model(nullptr) { } MultiLevelListViewPrivate::~MultiLevelListViewPrivate() { } void MultiLevelListViewPrivate::viewSelectionChanged(const QModelIndex& current, const QModelIndex& previous) { if (!current.isValid()) { // ignore, as we should always have some kind of selection return; } // figure out which proxy this signal belongs to QAbstractProxyModel* proxy = qobject_cast( const_cast(current.model())); Q_ASSERT(proxy); // what level is this proxy in int level = -1; for(int i = 0; i < levels; ++i) { if (views.at(i)->model() == proxy) { level = i; break; } } Q_ASSERT(level >= 0 && level < levels); if (level + 1 == levels) { // right-most view if (current.child(0, 0).isValid()) { // select the first leaf node for this view QModelIndex idx = current; QModelIndex child = idx.child(0, 0); while(child.isValid()) { idx = child; child = idx.child(0, 0); } views.last()->setCurrentIndex(idx); return; } // signal that our actual selection has changed emit view->currentIndexChanged(mapToSource(current), mapToSource(previous)); } else { // some leftish view // ensure the next view's first item is selected QTreeView* treeView = views.at(level + 1); // we need to delay the call, because at this point the child view // will still have its old data which is going to be invalidated // right after this method exits // be we must not set the index to 0,0 here directly, since e.g. // MultiLevelListView::setCurrentIndex might have been used, which // sets a proper index already. QMetaObject::invokeMethod(view, "ensureViewSelected", Qt::QueuedConnection, Q_ARG(QTreeView*, treeView)); } } void MultiLevelListViewPrivate::lastViewsContentsChanged() { views.last()->expandAll(); } void MultiLevelListViewPrivate::ensureViewSelected(QTreeView* view) { if (!view->currentIndex().isValid()) { view->setCurrentIndex(view->model()->index(0, 0)); } } QModelIndex MultiLevelListViewPrivate::mapToSource(QModelIndex index) { if (!index.isValid()) { return index; } while(index.model() != model) { QAbstractProxyModel* proxy = qobject_cast( const_cast(index.model())); Q_ASSERT(proxy); index = proxy->mapToSource(index); Q_ASSERT(index.isValid()); } return index; } QModelIndex MultiLevelListViewPrivate::mapFromSource(QModelIndex index, int level) { if (!index.isValid()) { return index; } Q_ASSERT(index.model() == model); QAbstractProxyModel* proxy = qobject_cast(views.at(level)->model()); Q_ASSERT(proxy); // find all proxies between the source and our view QVector proxies; proxies << proxy; forever { QAbstractProxyModel* child = qobject_cast(proxy->sourceModel()); if (child) { proxy = child; proxies << proxy; } else { Q_ASSERT(proxy->sourceModel() == model); break; } } // iterate in reverse order to find the view's index for(int i = proxies.size() - 1; i >= 0; --i) { proxy = proxies.at(i); index = proxy->mapFromSource(index); Q_ASSERT(index.isValid()); } return index; } MultiLevelListView::MultiLevelListView(QWidget* parent, Qt::WindowFlags f) : QWidget(parent, f) , d(new MultiLevelListViewPrivate(this)) { delete layout(); setLayout(new QHBoxLayout()); layout()->setContentsMargins(0, 0, 0, 0); qRegisterMetaType("QTreeView*"); } MultiLevelListView::~MultiLevelListView() { delete d; } int MultiLevelListView::levels() const { return d->levels; } void MultiLevelListView::setLevels(int levels) { qDeleteAll(d->views); qDeleteAll(d->proxies); qDeleteAll(d->layouts); d->views.clear(); d->proxies.clear(); d->layouts.clear(); d->levels = levels; QTreeView* previousView = nullptr; for (int i = 0; i < d->levels; ++i) { QVBoxLayout* levelLayout = new QVBoxLayout(); QTreeView* view = new QTreeView(this); view->setContentsMargins(0, 0, 0, 0); // only the right-most view is decorated view->setRootIsDecorated(i + 1 == d->levels); view->setHeaderHidden(false); view->setSelectionMode(QAbstractItemView::SingleSelection); if (!previousView) { // the root, i.e. left-most view RootProxyModel* root = new RootProxyModel(this); root->setDynamicSortFilter(true); d->proxies << root; root->setSourceModel(d->model); view->setModel(root); } else { SubTreeProxyModel* subTreeProxy = new SubTreeProxyModel(previousView->selectionModel(), this); if (i + 1 < d->levels) { // middel views only shows children of selection subTreeProxy->setFilterBehavior(KSelectionProxyModel::ChildrenOfExactSelection); } else { // right-most view shows the rest subTreeProxy->setFilterBehavior(KSelectionProxyModel::SubTreesWithoutRoots); } d->proxies << subTreeProxy; subTreeProxy->setSourceModel(d->model); // sorting requires another proxy in-between QSortFilterProxyModel* sortProxy = new QSortFilterProxyModel(subTreeProxy); sortProxy->setSourceModel(subTreeProxy); sortProxy->setDynamicSortFilter(true); view->setModel(sortProxy); } // view->setModel creates the selection model connect(view->selectionModel(), &QItemSelectionModel::currentChanged, this, [&] (const QModelIndex& current, const QModelIndex& previous) { d->viewSelectionChanged(current, previous); }); if (i + 1 == d->levels) { connect(view->model(), &QAbstractItemModel::rowsInserted, this, [&] { d->lastViewsContentsChanged(); }); } view->setSortingEnabled(true); view->sortByColumn(0, Qt::AscendingOrder); levelLayout->addWidget(view); layout()->addItem(levelLayout); d->layouts << levelLayout; d->views << view; previousView = view; } setModel(d->model); } QAbstractItemModel* MultiLevelListView::model() const { return d->model; } void MultiLevelListView::setModel(QAbstractItemModel* model) { d->model = model; foreach (LabeledProxy* proxy, d->proxies) { dynamic_cast(proxy)->setSourceModel(model); } if (model && !d->views.isEmpty()) { d->views.first()->setCurrentIndex(d->views.first()->model()->index(0, 0)); } } QTreeView* MultiLevelListView::viewForLevel( int level ) const { return d->views[level]; } void MultiLevelListView::addWidget(int level, QWidget* widget) { Q_ASSERT(level < d->levels); d->layouts[level]->addWidget(widget); } QModelIndex MultiLevelListView::currentIndex() const { return d->mapToSource(d->views.last()->currentIndex()); } void MultiLevelListView::setCurrentIndex(const QModelIndex& index) { // incoming index is for the original model Q_ASSERT(!index.isValid() || index.model() == d->model); const QModelIndex previous = currentIndex(); QModelIndex idx(index); QVector indexes; while (idx.isValid()) { indexes.prepend(idx); idx = idx.parent(); } for (int i = 0; i < d->levels; ++i) { QTreeView* view = d->views.at(i); if (indexes.size() <= i) { // select first item by default view->setCurrentIndex(view->model()->index(0, 0)); continue; } QModelIndex index; if (i + 1 == d->levels) { // select the very last index in the list (i.e. might be deep down in the actual tree) index = indexes.last(); } else { // select the first index for that level index = indexes.at(i); } view->setCurrentIndex(d->mapFromSource(index, i)); } emit currentIndexChanged(index, previous); } void MultiLevelListView::setRootIndex(const QModelIndex& index) { Q_ASSERT(!index.isValid() || index.model() == d->model); d->views.first()->setRootIndex(index); } void MultiLevelListView::setHeaderLabels(const QStringList& labels) { int n = qMin(d->levels, labels.size()); for (int i = 0; i < n; ++i) { d->proxies.at(i)->setLabel(labels[i]); } } void MultiLevelListView::setLastModelsFilterBehavior(KSelectionProxyModel::FilterBehavior filter) { if (d->proxies.isEmpty()) { return; } dynamic_cast(d->proxies.last())->setFilterBehavior(filter); } #include "multilevellistview.moc" #include "moc_multilevellistview.cpp" diff --git a/util/objectlist.h b/util/objectlist.h index a6923f53b..b978e50da 100644 --- a/util/objectlist.h +++ b/util/objectlist.h @@ -1,137 +1,137 @@ /* * Copyright 2014 Kevin Funk * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #ifndef KDEVPLATFORM_OBJECTLIST_H #define KDEVPLATFORM_OBJECTLIST_H #include #include #include "utilexport.h" namespace KDevelop { /** * @brief Class for tracking a set of alive objects inheriting from QObject. * * Add individual objects via the append() method to have their lifetime being monitored by this class. * In case one of the tracked objects is destroyed, it is removed from the list. * * This means this class always contains a set of valid pointers to QObject instances. * The up-to-date list can be access via the data() method. * * @note You are *not* being notified if an item is removed from the list. * The purpose of this class is to provide a simple mechanism to keep track of a set of *alive* objects * * @sa append() * @sa data() */ class KDEVPLATFORMUTIL_EXPORT ObjectListTracker : public QObject { Q_OBJECT public: enum CleanupBehavior { NoCleanupWhenDone, ///< Do nothing when this object is destructed CleanupWhenDone ///< Delete list of items when this object is destructed }; explicit ObjectListTracker(CleanupBehavior behavior = NoCleanupWhenDone, QObject* parent = nullptr); ~ObjectListTracker() override; /** * Append and track object @p object * * In case @p object is destroyed, it gets removed from the list * * @note If @p object is already tracked, this operation is a no-op */ void append(QObject* object); /** * Remove and no longer track object @p object * * @return False if object @p object wasn't tracked or null, otherwise true */ bool remove(QObject* object); /** * Delete all objects currently tracked and clears the list */ void deleteAll(); /** * Accessor towards to the internal list of currently tracked objects */ const QList& data() const; private: void objectDestroyed(QObject*); private: struct Private; QScopedPointer const d; }; /** * @brief Template-based wrapper around ObjectListTracker for tracking a set of objects inheriting from QObject * * Provides a type-safe way to access and mutate ObjectListTracker * * @sa KDevelop::ObjectListTracker */ template class ObjectList { public: - ObjectList(ObjectListTracker::CleanupBehavior behavior = ObjectListTracker::NoCleanupWhenDone) + explicit ObjectList(ObjectListTracker::CleanupBehavior behavior = ObjectListTracker::NoCleanupWhenDone) : m_tracker(behavior) { }; void append(T* object) { m_tracker.append(object); } bool remove(T* object) { return m_tracker.remove(object); } void deleteAll() { m_tracker.deleteAll(); } /** * Accessor to the up-to-date list inside the object tracker */ QList data() const { // This is considered safe, as QList and QList have the same memory layout // also see http://comments.gmane.org/gmane.comp.lib.qt.general/38943 return *reinterpret_cast*>(&m_tracker.data()); } private: ObjectListTracker m_tracker; }; } #endif // KDEVPLATFORM_OBJECTLIST_H diff --git a/util/placeholderitemproxymodel.cpp b/util/placeholderitemproxymodel.cpp index 9d052f294..8493a1730 100644 --- a/util/placeholderitemproxymodel.cpp +++ b/util/placeholderitemproxymodel.cpp @@ -1,212 +1,212 @@ /* * Copyright 2013 Kevin Funk * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "placeholderitemproxymodel.h" #include #include using namespace KDevelop; struct PlaceholderItemProxyModel::Private { - Private(PlaceholderItemProxyModel* qq) + explicit Private(PlaceholderItemProxyModel* qq) : q(qq) {} inline int sourceRowCount() { return q->sourceModel() ? q->sourceModel()->rowCount() : 0; } inline bool isPlaceholderRow(const QModelIndex& index) const { if (!q->sourceModel()) { return false; } return index.row() == q->sourceModel()->rowCount(); } PlaceholderItemProxyModel* const q; /// column -> hint mapping QMap m_columnHints; }; PlaceholderItemProxyModel::PlaceholderItemProxyModel(QObject* parent) : QIdentityProxyModel(parent) , d(new Private(this)) {} PlaceholderItemProxyModel::~PlaceholderItemProxyModel() { } QVariant PlaceholderItemProxyModel::columnHint(int column) const { return d->m_columnHints.value(column); } void PlaceholderItemProxyModel::setColumnHint(int column, const QVariant& hint) { if (column < 0) { return; } d->m_columnHints[column] = hint; const int row = d->sourceRowCount(); emit dataChanged(index(row, 0), index(row, columnCount())); } Qt::ItemFlags PlaceholderItemProxyModel::flags(const QModelIndex& index) const { if (d->isPlaceholderRow(index)) { Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled; const int column = index.column(); // if the column doesn't provide a hint we assume that we can't edit this field if (d->m_columnHints.contains(column)) { flags |= Qt::ItemIsEditable; } return flags; } return QIdentityProxyModel::flags(index); } void PlaceholderItemProxyModel::setSourceModel(QAbstractItemModel* sourceModel) { QIdentityProxyModel::setSourceModel(sourceModel); // TODO: Listen to layoutDataChanged signals? } int PlaceholderItemProxyModel::rowCount(const QModelIndex& parent) const { if (!sourceModel()) return 0; // only flat models supported for now, assert early in case that's not true Q_ASSERT(!parent.isValid()); Q_UNUSED(parent); return sourceModel()->rowCount() + 1; } bool KDevelop::PlaceholderItemProxyModel::hasChildren(const QModelIndex& parent) const { if ( !parent.isValid() ) { return true; } return QIdentityProxyModel::hasChildren(parent); } QVariant PlaceholderItemProxyModel::data(const QModelIndex& proxyIndex, int role) const { const int column = proxyIndex.column(); if (d->isPlaceholderRow(proxyIndex)) { switch (role) { case Qt::DisplayRole: return columnHint(column); case Qt::ForegroundRole: { const KColorScheme scheme(QPalette::Normal); return scheme.foreground(KColorScheme::InactiveText); } default: return QVariant(); } } return QIdentityProxyModel::data(proxyIndex, role); } QModelIndex PlaceholderItemProxyModel::parent(const QModelIndex& child) const { if (d->isPlaceholderRow(child)) { return QModelIndex(); } return QIdentityProxyModel::parent(child); } QModelIndex PlaceholderItemProxyModel::buddy(const QModelIndex& index) const { if (d->isPlaceholderRow(index)) { return index; } return QIdentityProxyModel::buddy(index); } QModelIndex PlaceholderItemProxyModel::sibling(int row, int column, const QModelIndex& idx) const { const bool isPlaceHolderRow = (sourceModel() ? row == sourceModel()->rowCount() : false); if (isPlaceHolderRow) { return index(row, column, QModelIndex()); } return QIdentityProxyModel::sibling(row, column, idx); } QModelIndex PlaceholderItemProxyModel::mapToSource(const QModelIndex& proxyIndex) const { if (d->isPlaceholderRow(proxyIndex)) { return QModelIndex(); } return QIdentityProxyModel::mapToSource(proxyIndex); } bool PlaceholderItemProxyModel::setData(const QModelIndex& index, const QVariant& value, int role) { const int column = index.column(); if (d->isPlaceholderRow(index) && role == Qt::EditRole && d->m_columnHints.contains(column)) { const bool accept = validateRow(index, value); // if validation fails, clear the complete line if (!accept) { emit dataChanged(index, index); return false; } // update view emit dataChanged(index, index); // notify observers emit dataInserted(column, value); return true; } return QIdentityProxyModel::setData(index, value, role); } QModelIndex PlaceholderItemProxyModel::index(int row, int column, const QModelIndex& parent) const { Q_ASSERT(!parent.isValid()); Q_UNUSED(parent); const bool isPlaceHolderRow = (sourceModel() ? row == sourceModel()->rowCount() : false); if (isPlaceHolderRow) { return createIndex(row, column); } return QIdentityProxyModel::index(row, column, parent); } bool PlaceholderItemProxyModel::validateRow(const QModelIndex& index, const QVariant& value) const { Q_UNUSED(index); return !value.toString().isEmpty(); } #include "moc_placeholderitemproxymodel.cpp" diff --git a/util/processlinemaker.cpp b/util/processlinemaker.cpp index 91e1fe854..0abc23237 100644 --- a/util/processlinemaker.cpp +++ b/util/processlinemaker.cpp @@ -1,133 +1,133 @@ /* This file is part of the KDE project Copyright 2002 John Firebaugh Copyright 2007 Andreas Pakulat Copyright 2007 Oswald Buddenhagen 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 "processlinemaker.h" #include #include #include namespace KDevelop { class ProcessLineMakerPrivate { public: QByteArray stdoutbuf; QByteArray stderrbuf; ProcessLineMaker* p; QProcess* m_proc; - ProcessLineMakerPrivate( ProcessLineMaker* maker ) + explicit ProcessLineMakerPrivate( ProcessLineMaker* maker ) : p(maker) { } void slotReadyReadStdout() { stdoutbuf += m_proc->readAllStandardOutput(); processStdOut(); } static QStringList streamToStrings(QByteArray &data) { QStringList lineList; int pos; while ( (pos = data.indexOf('\n')) != -1) { if (pos > 0 && data.at(pos - 1) == '\r') lineList << QString::fromLocal8Bit(data, pos - 1); else lineList << QString::fromLocal8Bit(data, pos); data.remove(0, pos+1); } return lineList; } void processStdOut() { emit p->receivedStdoutLines(streamToStrings(stdoutbuf)); } void slotReadyReadStderr() { stderrbuf += m_proc->readAllStandardError(); processStdErr(); } void processStdErr() { emit p->receivedStderrLines(streamToStrings(stderrbuf)); } }; ProcessLineMaker::ProcessLineMaker(QObject* parent) : QObject(parent) , d( new ProcessLineMakerPrivate( this ) ) { } ProcessLineMaker::ProcessLineMaker( QProcess* proc, QObject* parent ) : QObject(parent) , d( new ProcessLineMakerPrivate( this ) ) { d->m_proc = proc; connect(proc, &QProcess::readyReadStandardOutput, this, [&] { d->slotReadyReadStdout(); } ); connect(proc, &QProcess::readyReadStandardError, this, [&] { d->slotReadyReadStderr(); } ); } ProcessLineMaker::~ProcessLineMaker() { delete d; } void ProcessLineMaker::slotReceivedStdout( const QByteArray& buffer ) { d->stdoutbuf += buffer; d->processStdOut(); } void ProcessLineMaker::slotReceivedStderr( const QByteArray& buffer ) { d->stderrbuf += buffer; d->processStdErr(); } void ProcessLineMaker::discardBuffers( ) { d->stderrbuf.truncate(0); d->stdoutbuf.truncate(0); } void ProcessLineMaker::flushBuffers() { if (!d->stdoutbuf.isEmpty()) emit receivedStdoutLines(QStringList(QString::fromLocal8Bit(d->stdoutbuf))); if (!d->stderrbuf.isEmpty()) emit receivedStderrLines(QStringList(QString::fromLocal8Bit(d->stderrbuf))); discardBuffers(); } } #include "moc_processlinemaker.cpp" diff --git a/util/projecttestjob.cpp b/util/projecttestjob.cpp index d79d7c93f..917a3a408 100644 --- a/util/projecttestjob.cpp +++ b/util/projecttestjob.cpp @@ -1,126 +1,126 @@ /* * This file is part of KDevelop * * Copyright 2012 Miha Čančula * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU 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 "projecttestjob.h" #include #include #include #include #include using namespace KDevelop; struct ProjectTestJob::Private { - Private(ProjectTestJob* q) + explicit Private(ProjectTestJob* q) : q(q) , m_currentJob(nullptr) , m_currentSuite(nullptr) {} void runNext(); void gotResult(ITestSuite* suite, const TestResult& result); ProjectTestJob* q; QList m_suites; KJob* m_currentJob; ITestSuite* m_currentSuite; ProjectTestResult m_result; }; void ProjectTestJob::Private::runNext() { m_currentSuite = m_suites.takeFirst(); m_currentJob = m_currentSuite->launchAllCases(ITestSuite::Silent); m_currentJob->start(); } void ProjectTestJob::Private::gotResult(ITestSuite* suite, const TestResult& result) { if (suite == m_currentSuite) { m_result.total++; q->emitPercent(m_result.total, m_result.total + m_suites.size()); switch (result.suiteResult) { case TestResult::Passed: m_result.passed++; break; case TestResult::Failed: m_result.failed++; break; case TestResult::Error: m_result.error++; break; default: break; } if (m_suites.isEmpty()) { q->emitResult(); } else { runNext(); } } } ProjectTestJob::ProjectTestJob(IProject* project, QObject* parent) : KJob(parent) , d(new Private(this)) { setCapabilities(Killable); setObjectName(i18n("Run all tests in %1", project->name())); d->m_suites = ICore::self()->testController()->testSuitesForProject(project); connect(ICore::self()->testController(), &ITestController::testRunFinished, this, [&] (ITestSuite* suite, const TestResult& result) { d->gotResult(suite, result); }); } ProjectTestJob::~ProjectTestJob() { } void ProjectTestJob::start() { d->runNext(); } bool ProjectTestJob::doKill() { if (d->m_currentJob) { d->m_currentJob->kill(); } else { d->m_suites.clear(); } return true; } ProjectTestResult ProjectTestJob::testResult() { return d->m_result; } #include "moc_projecttestjob.cpp" diff --git a/util/pushvalue.h b/util/pushvalue.h index d33a2a877..020ae9aab 100644 --- a/util/pushvalue.h +++ b/util/pushvalue.h @@ -1,61 +1,61 @@ /* 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_PUSHVALUE_H #define KDEVPLATFORM_PUSHVALUE_H #include /** * A simple helper-class that does the following: * Backup the given reference-value given through @p ptr, * replace it with the value given through @p push, * restore the backed up value back on destruction. * * NOTE: This is _not_ a direct alias of QScopedValueRollback, * the behavior of the constructor is different: * PushValue will *always* push, PushPositiveValue will only * push if the value evaluates to true. QScopedValueRollback * will *always* push with the ctor that takes a new value, * and *never* with the ctor that just takes a ref. **/ template class PushValue : public QScopedValueRollback { public: - PushValue(Value& ref, const Value& newValue = Value()) + explicit PushValue(Value& ref, const Value& newValue = Value()) : QScopedValueRollback(ref, newValue) { } }; ///Only difference to PushValue: The value is only replaced if the new value is positive template class PushPositiveValue : public QScopedValueRollback { public: - PushPositiveValue(Value& ref, const Value& newValue = Value()) + explicit PushPositiveValue(Value& ref, const Value& newValue = Value()) : QScopedValueRollback(ref) { if (newValue) { ref = newValue; } } }; #endif diff --git a/util/tests/test_embeddedfreetree.cpp b/util/tests/test_embeddedfreetree.cpp index 6abdda43f..5dfdb5b98 100644 --- a/util/tests/test_embeddedfreetree.cpp +++ b/util/tests/test_embeddedfreetree.cpp @@ -1,609 +1,609 @@ /* 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. */ unsigned int extractor_div_with = 0; #include #include #include #include #include #include #include #include #include #include #include #include struct TestItem { explicit TestItem(uint _value = 0) : value(_value), leftChild(-1), rightChild(-1) { } uint value; int leftChild; int rightChild; bool operator==(const TestItem& rhs) const { return value == rhs.value; } bool operator<(const TestItem& item) const { return value < item.value; } }; struct TestItemConversion { static uint toIndex(const TestItem& item) { return item.value; } static TestItem toItem(uint index) { return TestItem(index); } }; struct Extractor{ static TestItem extract(const TestItem& item) { return TestItem(item.value/extractor_div_with); } }; clock_t std_insertion = 0, std_removal = 0, std_contains = 0, std_iteration = 0, emb_insertion = 0, emb_removal = 0, emb_contains = 0, emb_iteration = 0; QString toString(std::set set) { QString ret; for(std::set::const_iterator it = set.begin(); it != set.end(); ++it) ret += QStringLiteral("%1 ").arg(*it); return ret; } bool operator==(const std::set a, const std::set b) { if(a.size() != b.size()) { qDebug() << "size mismatch" << toString(a) << ": " << toString(b); return false; } std::set::const_iterator aIt = a.begin(); std::set::const_iterator bIt = b.begin(); for(; aIt != a.end(); ++aIt, ++bIt) if(*aIt != *bIt) { qDebug() << "mismatch" << toString(a) << ": " << toString(b); return false; } return true; } struct TestItemHandler { public: static int rightChild(const TestItem& m_data) { return m_data.rightChild; } static int leftChild(const TestItem& m_data) { return m_data.leftChild; } static void setLeftChild(TestItem& m_data, int child) { m_data.leftChild = child; } static void setRightChild(TestItem& m_data, int child) { m_data.rightChild = child; } //Copies this item into the given one static void copyTo(const TestItem& m_data, TestItem& data) { data = m_data; } static void createFreeItem(TestItem& data) { data = TestItem(); } static inline bool isFree(const TestItem& m_data) { return !m_data.value; } static const TestItem& data(const TestItem& m_data) { return m_data; } inline static bool equals(const TestItem& m_data, const TestItem& rhs) { return m_data.value == rhs.value; } }; class TestItemBasedSet { public: TestItemBasedSet() : m_centralFree(-1) { } void insert(uint i) { TestItem item(i); KDevelop::EmbeddedTreeAddItem add(data.data(), data.size(), m_centralFree, item); if((int)add.newItemCount() != (int)data.size()) { QVector newData; newData.resize(add.newItemCount()); add.transferData(newData.data(), newData.size()); data = newData; } } bool contains(uint item) { KDevelop::EmbeddedTreeAlgorithms alg(data.data(), data.size(), m_centralFree); return alg.indexOf(TestItem(item)) != -1; } void remove(uint i) { TestItem item(i); KDevelop::EmbeddedTreeRemoveItem remove(data.data(), data.size(), m_centralFree, item); if((int)remove.newItemCount() != (int)data.size()) { QVector newData; newData.resize(remove.newItemCount()); remove.transferData(newData.data(), newData.size()); data = newData; } } std::set toSet() const { std::set ret; for(int a = 0; a < data.size(); ++a) if(data[a].value) ret.insert(data[a].value); return ret; } void verify() { //1. verify order uint last = 0; uint freeCount = 0; for(int a = 0; a < data.size(); ++a) { if(data[a].value) { QVERIFY(last < data[a].value); last = data[a].value; }else{ ++freeCount; } } KDevelop::EmbeddedTreeAlgorithms algorithms(data.data(), data.size(), m_centralFree); uint countFree = algorithms.countFreeItems(); QCOMPARE(freeCount, countFree); algorithms.verifyTreeConsistent(); } uint getItem(uint number) const { Q_ASSERT(number < (uint)data.size()); uint ret = 0; uint size = (uint)data.size(); uint current = 0; for(uint a = 0; a < size; ++a) { if(data[a].value) { //Only count the non-free items if(current == number) ret = data[a].value; ++current; }else{ //This is a free item } } return ret; } private: int m_centralFree; QVector data; }; class TestSet { public: void add(uint i) { if(realSet.find(i) != realSet.end()) { QVERIFY(set.contains(i)); return; }else{ QVERIFY(!set.contains(i)); } clock_t start = clock(); realSet.insert(i); std_insertion += clock() - start; start = clock(); set.insert(i); emb_insertion += clock() - start; start = clock(); bool contained = realSet.find(i) != realSet.end(); std_contains += clock() - start; start = clock(); set.contains(i); emb_contains += clock() - start; QVERIFY(set.contains(i)); QVERIFY(contained); set.verify(); } void remove(uint i) { if(realSet.find(i) != realSet.end()) { QVERIFY(set.contains(i)); }else{ QVERIFY(!set.contains(i)); return; } clock_t start = clock(); set.remove(i); emb_removal += clock() - start; start = clock(); realSet.erase(i); std_removal += clock() - start; QVERIFY(!set.contains(i)); } uint size() const { return realSet.size(); } uint getItem(uint number) const { Q_ASSERT(number < size()); uint current = 0; uint ret = 0; clock_t start = clock(); for(std::set::const_iterator it = realSet.begin(); it != realSet.end(); ++it) { if(current == number) { ret = *it; } ++current; } std_iteration += clock() - start; start = clock(); set.getItem(number); emb_iteration += clock() - start; Q_ASSERT(ret); return ret; } void verify() { QVERIFY(realSet == set.toSet()); set.verify(); } private: std::set realSet; TestItemBasedSet set; }; float toSeconds(clock_t time) { return ((float)time) / CLOCKS_PER_SEC; } struct StaticRepository { static Utils::BasicSetRepository* repository() { static Utils::BasicSetRepository repository(QStringLiteral("test repository")); return &repository; } }; struct UintSetVisitor { std::set& s; - UintSetVisitor(std::set& _s) : s(_s) { + explicit UintSetVisitor(std::set& _s) : s(_s) { } inline bool operator() (const TestItem& item) { s.insert(item.value); return true; } }; struct NothingDoVisitor { inline bool operator() (const TestItem& item) { Q_UNUSED(item); return true; } }; class TestEmbeddedFreeTree : public QObject { Q_OBJECT private slots: void initTestCase() { KDevelop::AutoTestShell::init(); KDevelop::TestCore::initialize(KDevelop::Core::NoUi); } void cleanupTestCase() { KDevelop::TestCore::shutdown(); } void randomizedTest() { const int cycles = 10000; const int valueRange = 1000; const int removeProbability = 40; //Percent TestSet set; srand(time(nullptr)); for(int a = 0; a < cycles; ++a) { if(a % (cycles / 10) == 0) { qDebug() << "cycle" << a; } bool remove = (rand() % 100) < removeProbability; if(remove && set.size()) { set.remove(set.getItem(rand() % set.size())); }else{ int value = (rand() % valueRange) + 1; set.add(value); } set.verify(); } qDebug() << "Performance embedded list: insertion:" << toSeconds(emb_insertion) << "removal:" << toSeconds(emb_removal) << "contains:" << toSeconds(emb_contains) << "iteration:" << toSeconds(emb_iteration); qDebug() << "Performance std::set: insertion:" << toSeconds(std_insertion) << "removal:" << toSeconds(std_removal) << "contains:" << toSeconds(std_contains) << "iteration:" << toSeconds(std_iteration); } void sequentialTest() { TestSet set; set.add(5); set.verify(); set.remove(5); set.verify(); set.add(3); set.verify(); set.add(4); set.verify(); set.add(7); set.verify(); set.remove(3); set.verify(); set.remove(7); set.verify(); set.add(6); set.verify(); set.add(1); set.verify(); set.add(9); set.verify(); set.remove(4); set.verify(); set.remove(9); set.verify(); set.add(1); set.verify(); set.add(2); set.verify(); set.add(3); set.verify(); set.add(4); set.verify(); set.add(5); set.verify(); set.remove(1); set.verify(); set.remove(3); set.verify(); set.add(15); set.verify(); set.add(16); set.verify(); set.add(17); set.verify(); set.add(18); set.verify(); set.remove(18); set.verify(); set.remove(17); set.verify(); set.add(9); set.verify(); } void testFiltering() { clock_t stdTime = 0; clock_t algoTime = 0; clock_t treeAlgoTime = 0; clock_t treeAlgoVisitorTime = 0; clock_t insertionStdTime = 0; clock_t insertionAlgoTime = 0; clock_t insertionTreeAlgoTime = 0; typedef Utils::StorableSet RepositorySet; const uint cycles = 3000; const uint setSize = 1500; uint totalItems = 0, totalFilteredItems = 0; srand(time(nullptr)); for(uint a = 0; a < cycles; ++a) { KDevelop::ConvenientFreeListSet set1; std::set testSet1; KDevelop::ConvenientFreeListSet set2; std::set testSet2; RepositorySet repSet2; if(a % (cycles / 10) == 0) { qDebug() << "cycle" << a; } //Build the sets extractor_div_with = (rand() % 10) + 1; for(uint a = 0; a < setSize; ++a) { uint value = rand() % 3000; uint divValue = value/extractor_div_with; if(!divValue) continue; // qDebug() << "inserting" << value; std::set::const_iterator it = testSet1.lower_bound(value); int pos = set1.iterator().lowerBound(TestItem(value)); //This tests the upperBound functionality if (pos != -1) { QVERIFY(it != testSet1.end()); QVERIFY(set1.data()[pos].value == *it); } else { QVERIFY(it == testSet1.end()); } if((rand() % 10) == 0) { set1.insert(TestItem(value)); testSet1.insert(value); } //This is tuned so in the end, about 99% of all declarations are filtered out, like in the symbol table. if((rand() % (extractor_div_with*100)) == 0) { clock_t start = clock(); set2.insert(TestItem(divValue)); insertionStdTime += clock() - start; start = clock(); testSet2.insert(divValue); insertionAlgoTime += clock() - start; start = clock(); repSet2.insert(TestItem(divValue)); insertionTreeAlgoTime += clock() - start; start = clock(); } } std::set verifySet1; for(KDevelop::ConvenientFreeListSet::Iterator it = set1.iterator(); it; ++it) verifySet1.insert(it->value); std::set verifySet2; for(KDevelop::ConvenientFreeListSet::Iterator it = set2.iterator(); it; ++it) verifySet2.insert(it->value); std::set verifyRepSet2; for(RepositorySet::Iterator it = repSet2.iterator(); it; ++it) verifyRepSet2.insert((*it).value); QCOMPARE(verifySet1, testSet1); QCOMPARE(verifySet2, testSet2); QCOMPARE(verifyRepSet2, testSet2); std::set algoFiltered; std::set treeAlgoFiltered; std::set treeAlgoVisitorFiltered; { //Do the filtering once without actions on the filtered items, just for calculating the time clock_t start = clock(); { KDevelop::ConvenientEmbeddedSetFilterIterator filterIterator(set1.iterator(), set2.iterator()); while(filterIterator) ++filterIterator; algoTime += clock() - start; } start = clock(); { KDevelop::ConvenientEmbeddedSetTreeFilterIterator filterIterator(set1.iterator(), repSet2); while(filterIterator) ++filterIterator; treeAlgoTime += clock() - start; } { start = clock(); NothingDoVisitor v; KDevelop::ConvenientEmbeddedSetTreeFilterVisitor visit(v, set1.iterator(), repSet2); treeAlgoVisitorTime += clock() - start; } start = clock(); for(std::set::const_iterator it = testSet1.begin(); it != testSet1.end(); ++it) { if(testSet2.count((*it) / extractor_div_with) == 1) { } } stdTime += clock() - start; } { KDevelop::ConvenientEmbeddedSetFilterIterator filterIterator(set1.iterator(), set2.iterator()); while(filterIterator) { algoFiltered.insert(filterIterator->value); ++filterIterator; } } { KDevelop::ConvenientEmbeddedSetTreeFilterIterator filterIterator(set1.iterator(), repSet2); while(filterIterator) { treeAlgoFiltered.insert((*filterIterator).value); ++filterIterator; } } { UintSetVisitor v(treeAlgoVisitorFiltered); KDevelop::ConvenientEmbeddedSetTreeFilterVisitor visit(v, set1.iterator(), repSet2); } totalItems += testSet1.size(); totalFilteredItems += algoFiltered.size(); std::set stdFiltered; for(std::set::const_iterator it = testSet1.begin(); it != testSet1.end(); ++it) { if(testSet2.count((*it) / extractor_div_with) == 1) { stdFiltered.insert(*it); } } QCOMPARE(algoFiltered, stdFiltered); QCOMPARE(treeAlgoFiltered, stdFiltered); QCOMPARE(treeAlgoVisitorFiltered, stdFiltered); } qDebug() << "Filtering performance: embedded-list filtering:" << toSeconds(algoTime) << "set-repository filtering:" << toSeconds(treeAlgoTime) << "set-repository visitor filtering:" << toSeconds(treeAlgoVisitorTime) << "std::set filtering:" << toSeconds(stdTime) << "Normal -> Embedded speedup ratio:" << (toSeconds(stdTime) / toSeconds(algoTime)) << "Normal -> Repository speedup ratio:" << (toSeconds(stdTime) / toSeconds(treeAlgoVisitorTime)) << "total processed items:" << totalItems << "total items after filtering:" << totalFilteredItems; qDebug() << "Insertion: embedded-list:" << toSeconds(insertionAlgoTime) << "set-repository:" << toSeconds(insertionTreeAlgoTime) << "std::set:" << toSeconds(insertionStdTime); } }; #include "test_embeddedfreetree.moc" QTEST_MAIN(TestEmbeddedFreeTree) diff --git a/util/tests/test_executecompositejob.cpp b/util/tests/test_executecompositejob.cpp index dbee85b88..b03cba9c0 100644 --- a/util/tests/test_executecompositejob.cpp +++ b/util/tests/test_executecompositejob.cpp @@ -1,118 +1,118 @@ /* * This file is part of KDevelop * Copyright 2014 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 "test_executecompositejob.h" #include #include #include QTEST_MAIN(TestExecuteCompositeJob); using namespace KDevelop; struct JobSpy { - JobSpy(KJob* job) + explicit JobSpy(KJob* job) : finished(job, SIGNAL(finished(KJob*))) , result(job, SIGNAL(result(KJob*))) {} QSignalSpy finished; QSignalSpy result; }; void TestExecuteCompositeJob::runOneJob() { QPointer slave(new TestJob); QPointer master(new ExecuteCompositeJob(nullptr, {slave.data()})); JobSpy masterSpy(master.data()); QSignalSpy startedSpy(slave.data(), SIGNAL(started(KJob*))); JobSpy slaveSpy(slave.data()); master->start(); QCOMPARE(startedSpy.count(), 1); QCOMPARE(slaveSpy.finished.count(), 0); QCOMPARE(masterSpy.finished.count(), 0); slave->callEmitResult(); QCOMPARE(masterSpy.finished.count(), 1); QCOMPARE(masterSpy.result.count(), 1); QCOMPARE(startedSpy.count(), 1); QCOMPARE(slaveSpy.finished.count(), 1); QCOMPARE(slaveSpy.result.count(), 1); QTest::qWait(10); QVERIFY(!slave); QVERIFY(!master); } void TestExecuteCompositeJob::runTwoJobs() { QPointer slave1(new TestJob); QPointer slave2(new TestJob); QPointer master(new ExecuteCompositeJob(nullptr, {slave1.data(), slave2.data()})); JobSpy masterSpy(master.data()); QSignalSpy started1Spy(slave1.data(), SIGNAL(started(KJob*))); QSignalSpy started2Spy(slave2.data(), SIGNAL(started(KJob*))); JobSpy slave1Spy(slave1.data()); JobSpy slave2Spy(slave2.data()); master->start(); QCOMPARE(started1Spy.count(), 1); QCOMPARE(slave1Spy.finished.count(), 0); QCOMPARE(started2Spy.count(), 0); QCOMPARE(slave2Spy.finished.count(), 0); QCOMPARE(masterSpy.finished.count(), 0); slave1->callEmitResult(); QCOMPARE(started1Spy.count(), 1); QCOMPARE(slave1Spy.finished.count(), 1); QCOMPARE(started2Spy.count(), 1); QCOMPARE(slave2Spy.finished.count(), 0); QCOMPARE(masterSpy.finished.count(), 0); slave2->callEmitResult(); QCOMPARE(masterSpy.finished.count(), 1); QCOMPARE(masterSpy.result.count(), 1); QCOMPARE(started1Spy.count(), 1); QCOMPARE(slave1Spy.finished.count(), 1); QCOMPARE(slave1Spy.result.count(), 1); QCOMPARE(started2Spy.count(), 1); QCOMPARE(slave2Spy.finished.count(), 1); QCOMPARE(slave2Spy.result.count(), 1); QTest::qWait(10); QVERIFY(!slave1); QVERIFY(!slave2); QVERIFY(!master); } diff --git a/vcs/dvcs/ui/dvcsimportmetadatawidget.cpp b/vcs/dvcs/ui/dvcsimportmetadatawidget.cpp index c60b06b54..f5d5194bd 100644 --- a/vcs/dvcs/ui/dvcsimportmetadatawidget.cpp +++ b/vcs/dvcs/ui/dvcsimportmetadatawidget.cpp @@ -1,98 +1,98 @@ /*************************************************************************** * Copyright 2007 Robert Gruber * * Copyright 2007 Andreas Pakulat * * * * Adapted for Git * * Copyright 2008 Evgeniy Ivanov * * * * Pimpl-ed and exported * * Copyright 2014 Maciej Poleski * * * * 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 "dvcsimportmetadatawidget.h" #include #include "ui_dvcsimportmetadatawidget.h" class DvcsImportMetadataWidgetPrivate { friend class DvcsImportMetadataWidget; - DvcsImportMetadataWidgetPrivate(Ui::DvcsImportMetadataWidget* ui) : m_ui(ui) {} + explicit DvcsImportMetadataWidgetPrivate(Ui::DvcsImportMetadataWidget* ui) : m_ui(ui) {} ~DvcsImportMetadataWidgetPrivate() { delete m_ui; } Ui::DvcsImportMetadataWidget* m_ui; }; DvcsImportMetadataWidget::DvcsImportMetadataWidget(QWidget *parent) : KDevelop::VcsImportMetadataWidget(parent), d_ptr(new DvcsImportMetadataWidgetPrivate(new Ui::DvcsImportMetadataWidget)) { Q_D(DvcsImportMetadataWidget); d->m_ui->setupUi(this); d->m_ui->sourceLoc->setEnabled( false ); d->m_ui->sourceLoc->setMode( KFile::Directory ); connect( d->m_ui->sourceLoc, &KUrlRequester::textChanged, this, &DvcsImportMetadataWidget::changed ); connect( d->m_ui->sourceLoc, &KUrlRequester::urlSelected, this, &DvcsImportMetadataWidget::changed ); } DvcsImportMetadataWidget::~DvcsImportMetadataWidget() { delete d_ptr; } QUrl DvcsImportMetadataWidget::source() const { Q_D(const DvcsImportMetadataWidget); return d->m_ui->sourceLoc->url(); } KDevelop::VcsLocation DvcsImportMetadataWidget::destination() const { // Used for compatibility with import Q_D(const DvcsImportMetadataWidget); KDevelop::VcsLocation dest; dest.setRepositoryServer(d->m_ui->sourceLoc->url().url()); return dest; } QString DvcsImportMetadataWidget::message( ) const { return QString(); } void DvcsImportMetadataWidget::setSourceLocation( const KDevelop::VcsLocation& url ) { Q_D(const DvcsImportMetadataWidget); d->m_ui->sourceLoc->setUrl( url.localUrl() ); } void DvcsImportMetadataWidget::setSourceLocationEditable( bool enable ) { Q_D(const DvcsImportMetadataWidget); d->m_ui->sourceLoc->setEnabled( enable ); } bool DvcsImportMetadataWidget::hasValidData() const { Q_D(const DvcsImportMetadataWidget); return !d->m_ui->sourceLoc->text().isEmpty(); } diff --git a/vcs/interfaces/icontentawareversioncontrol.cpp b/vcs/interfaces/icontentawareversioncontrol.cpp index 76a5bd9e8..a248a5496 100644 --- a/vcs/interfaces/icontentawareversioncontrol.cpp +++ b/vcs/interfaces/icontentawareversioncontrol.cpp @@ -1,58 +1,58 @@ /* This file is part of KDevelop * * Copyright 2013 Sven Brauch * * 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 "icontentawareversioncontrol.h" namespace KDevelop { struct CheckInRepositoryJobPrivate { - CheckInRepositoryJobPrivate(KTextEditor::Document* document) + explicit CheckInRepositoryJobPrivate(KTextEditor::Document* document) : document(document) { }; friend class CheckInRepositoryJob; KTextEditor::Document* document; }; CheckInRepositoryJob::CheckInRepositoryJob(KTextEditor::Document* document) : KJob() , d(new CheckInRepositoryJobPrivate(document)) { connect(this, &CheckInRepositoryJob::finished, this, &CheckInRepositoryJob::deleteLater); setCapabilities(Killable); }; KTextEditor::Document* CheckInRepositoryJob::document() const { return d->document; } CheckInRepositoryJob::~CheckInRepositoryJob() { delete d; } void CheckInRepositoryJob::abort() { kill(); } } // namespace KDevelop diff --git a/vcs/models/brancheslistmodel.cpp b/vcs/models/brancheslistmodel.cpp index 60fd01f8d..e05dd457a 100644 --- a/vcs/models/brancheslistmodel.cpp +++ b/vcs/models/brancheslistmodel.cpp @@ -1,212 +1,212 @@ /*************************************************************************** * Copyright 2008 Evgeniy Ivanov * * Copyright 2012 Aleix Pol Gonzalez * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "brancheslistmodel.h" #include "../debug.h" #include #include #include #include #include #include #include #include #include "util/path.h" #include #include #include #include using namespace std; using namespace KDevelop; class KDevelop::BranchesListModelPrivate { public: BranchesListModelPrivate() { } IBranchingVersionControl * dvcsplugin; QUrl repo; }; class BranchItem : public QStandardItem { public: - BranchItem(const QString& name, bool current=false) : + explicit BranchItem(const QString& name, bool current=false) : QStandardItem(name) { setEditable(true); setCurrent(current); } void setCurrent(bool current) { setData(current, BranchesListModel::CurrentRole); setIcon(QIcon::fromTheme( current ? QStringLiteral("arrow-right") : QStringLiteral(""))); } void setData(const QVariant& value, int role = Qt::UserRole + 1) override { if(role==Qt::EditRole && value.toString()!=text()) { QString newBranch = value.toString(); BranchesListModel* bmodel = qobject_cast(model()); if(!bmodel->findItems(newBranch).isEmpty()) { KMessageBox::messageBox(nullptr, KMessageBox::Sorry, i18n("Branch \"%1\" already exists.", newBranch)); return; } int ret = KMessageBox::messageBox(nullptr, KMessageBox::WarningYesNo, i18n("Are you sure you want to rename \"%1\" to \"%2\"?", text(), newBranch)); if (ret == KMessageBox::No) { return; // ignore event } KDevelop::VcsJob *branchJob = bmodel->interface()->renameBranch(bmodel->repository(), newBranch, text()); ret = branchJob->exec(); qCDebug(VCS) << "Renaming " << text() << " to " << newBranch << ':' << ret; if (!ret) { return; // ignore event } } QStandardItem::setData(value, role); } }; static QVariant runSynchronously(KDevelop::VcsJob* job) { job->setVerbosity(KDevelop::OutputJob::Silent); QVariant ret; if(job->exec() && job->status()==KDevelop::VcsJob::JobSucceeded) { ret = job->fetchResults(); } delete job; return ret; } BranchesListModel::BranchesListModel(QObject* parent) : QStandardItemModel(parent), d(new BranchesListModelPrivate()) { } BranchesListModel::~BranchesListModel() { } QHash BranchesListModel::roleNames() const { auto roles = QAbstractItemModel::roleNames(); roles.insert(CurrentRole, "isCurrent"); return roles; } void BranchesListModel::createBranch(const QString& baseBranch, const QString& newBranch) { qCDebug(VCS) << "Creating " << baseBranch << " based on " << newBranch; KDevelop::VcsRevision rev; rev.setRevisionValue(baseBranch, KDevelop::VcsRevision::GlobalNumber); KDevelop::VcsJob* branchJob = d->dvcsplugin->branch(d->repo, rev, newBranch); qCDebug(VCS) << "Adding new branch"; if (branchJob->exec()) appendRow(new BranchItem(newBranch)); } void BranchesListModel::removeBranch(const QString& branch) { KDevelop::VcsJob *branchJob = d->dvcsplugin->deleteBranch(d->repo, branch); qCDebug(VCS) << "Removing branch:" << branch; if (branchJob->exec()) { QList< QStandardItem* > items = findItems(branch); foreach(QStandardItem* item, items) removeRow(item->row()); } } QUrl BranchesListModel::repository() const { return d->repo; } KDevelop::IBranchingVersionControl* BranchesListModel::interface() { return d->dvcsplugin; } void BranchesListModel::initialize(KDevelop::IBranchingVersionControl* branching, const QUrl& r) { d->dvcsplugin = branching; d->repo = r; refresh(); } void BranchesListModel::refresh() { QStringList branches = runSynchronously(d->dvcsplugin->branches(d->repo)).toStringList(); QString curBranch = runSynchronously(d->dvcsplugin->currentBranch(d->repo)).toString(); foreach(const QString& branch, branches) appendRow(new BranchItem(branch, branch == curBranch)); } void BranchesListModel::resetCurrent() { refresh(); emit currentBranchChanged(); } QString BranchesListModel::currentBranch() const { return runSynchronously(d->dvcsplugin->currentBranch(d->repo)).toString(); } KDevelop::IProject* BranchesListModel::project() const { return KDevelop::ICore::self()->projectController()->findProjectForUrl(d->repo); } void BranchesListModel::setProject(KDevelop::IProject* p) { if(!p || !p->versionControlPlugin()) { qCDebug(VCS) << "null or invalid project" << p; return; } KDevelop::IBranchingVersionControl* branching = p->versionControlPlugin()->extension(); if(branching) { initialize(branching, p->path().toUrl()); } else qCDebug(VCS) << "not a branching vcs project" << p->name(); } void BranchesListModel::setCurrentBranch(const QString& branch) { KDevelop::VcsJob* job = d->dvcsplugin->switchBranch(d->repo, branch); connect(job, &VcsJob::finished, this, &BranchesListModel::currentBranchChanged); KDevelop::ICore::self()->runController()->registerJob(job); } diff --git a/vcs/models/vcsannotationmodel.cpp b/vcs/models/vcsannotationmodel.cpp index 27bec6cd0..f27ae8bc9 100644 --- a/vcs/models/vcsannotationmodel.cpp +++ b/vcs/models/vcsannotationmodel.cpp @@ -1,147 +1,147 @@ /*************************************************************************** * 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 "vcsannotationmodel.h" #include "../vcsannotation.h" #include "../vcsrevision.h" #include "../vcsjob.h" #include #include #include #include #include #include #include #include #include #include namespace KDevelop { class VcsAnnotationModelPrivate { public: - VcsAnnotationModelPrivate( VcsAnnotationModel* q_ ) : q(q_) {} + explicit VcsAnnotationModelPrivate( VcsAnnotationModel* q_ ) : q(q_) {} KDevelop::VcsAnnotation m_annotation; QHash m_brushes; VcsAnnotationModel* q; VcsJob* job; void addLines( KDevelop::VcsJob* job ) { if( job == this->job ) { foreach( const QVariant& v, job->fetchResults().toList() ) { if( v.canConvert() ) { VcsAnnotationLine l = v.value(); if( !m_brushes.contains( l.revision() ) ) { const int background_y = q->background.red()*0.299 + 0.587*q->background.green() + 0.114*q->background.blue(); int u = ( float(qrand()) / RAND_MAX ) * 255; int v = ( float(qrand()) / RAND_MAX ) * 255; float r = qMin(255.0, qMax(0.0, background_y + 1.402*(v-128))); float g = qMin(255.0, qMax(0.0, background_y - 0.344*(u-128) - 0.714*(v-128))); float b = qMin(255.0, qMax(0.0, background_y + 1.772*(u-128))); m_brushes.insert( l.revision(), QBrush( QColor( r, g, b ) ) ); } m_annotation.insertLine( l.lineNumber(), l ); emit q->lineChanged( l.lineNumber() ); } } } } }; VcsAnnotationModel::VcsAnnotationModel(VcsJob *job, const QUrl& url, QObject* parent, const QColor &foreground, const QColor &background) : d( new VcsAnnotationModelPrivate( this ) ) , foreground(foreground) , background(background) { setParent( parent ); d->m_annotation.setLocation( url ); d->job = job; qsrand( QDateTime().toTime_t() ); connect( d->job, &VcsJob::resultsReady,this, [&] (VcsJob* job) { d->addLines(job); } ); ICore::self()->runController()->registerJob( d->job ); } VcsAnnotationModel::~VcsAnnotationModel() { delete d; } static QString abbreviateLastName(const QString& author) { auto parts = author.split(' '); bool onlyOneFragment = parts.size() == 1 || ( parts.size() == 2 && parts.at(1).isEmpty() ); return onlyOneFragment ? parts.first() : parts.first() + QStringLiteral(" %1.").arg(parts.last()[0]); } QVariant VcsAnnotationModel::data( int line, Qt::ItemDataRole role ) const { if( line < 0 || !d->m_annotation.containsLine( line ) ) { return QVariant(); } KDevelop::VcsAnnotationLine aline = d->m_annotation.line( line ); if( role == Qt::ForegroundRole ) { return QVariant( QPen( foreground ) ); } if( role == Qt::BackgroundRole ) { return QVariant( d->m_brushes[aline.revision()] ); } else if( role == Qt::DisplayRole ) { return QVariant( QStringLiteral("%1 ").arg(aline.date().date().year()) + abbreviateLastName(aline.author()) ); } else if( role == Qt::UserRole ) // TODO KDE5: replace by KTextEditor::AnnotationModel::GroupIdentifierRole { return aline.revision().revisionValue(); } else if( role == Qt::ToolTipRole ) { return QVariant( i18n("Author: %1\nDate: %2\nCommit Message: %3", aline.author(), QLocale().toString( aline.date() ), aline.commitMessage() ) ); } return QVariant(); } VcsRevision VcsAnnotationModel::revisionForLine( int line ) const { ///FIXME: update the annotation bar on edit/reload somehow ///BUG: https://bugs.kde.org/show_bug.cgi?id=269757 if (!d->m_annotation.containsLine(line)) { return VcsRevision(); } Q_ASSERT(line > 0 && d->m_annotation.containsLine( line )); return d->m_annotation.line( line ).revision(); } } #include "moc_vcsannotationmodel.cpp" diff --git a/vcs/models/vcsfilechangesmodel.cpp b/vcs/models/vcsfilechangesmodel.cpp index 7991a15d8..2157279fc 100644 --- a/vcs/models/vcsfilechangesmodel.cpp +++ b/vcs/models/vcsfilechangesmodel.cpp @@ -1,270 +1,270 @@ /* This file is part of KDevelop Copyright 2010 Aleix Pol Split into separate class Copyright 2011 Andrey Batyiev 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 "vcsfilechangesmodel.h" #include #include #include #include #include #include namespace KDevelop { static QString stateToString(KDevelop::VcsStatusInfo::State state) { switch(state) { case KDevelop::VcsStatusInfo::ItemAdded: return i18nc("file was added to versioncontrolsystem", "Added"); case KDevelop::VcsStatusInfo::ItemDeleted: return i18nc("file was deleted from versioncontrolsystem", "Deleted"); case KDevelop::VcsStatusInfo::ItemHasConflicts: return i18nc("file is confilicting (versioncontrolsystem)", "Has Conflicts"); case KDevelop::VcsStatusInfo::ItemModified: return i18nc("version controlled file was modified", "Modified"); case KDevelop::VcsStatusInfo::ItemUpToDate: return i18nc("file is up to date in versioncontrolsystem", "Up To Date"); case KDevelop::VcsStatusInfo::ItemUnknown: case KDevelop::VcsStatusInfo::ItemUserState: return i18nc("file is not known to versioncontrolsystem", "Unknown"); } return i18nc("Unknown VCS file status, probably a backend error", "?"); } static QIcon stateToIcon(KDevelop::VcsStatusInfo::State state) { switch(state) { case KDevelop::VcsStatusInfo::ItemAdded: return QIcon::fromTheme(QStringLiteral("vcs-added")); case KDevelop::VcsStatusInfo::ItemDeleted: return QIcon::fromTheme(QStringLiteral("vcs-removed")); case KDevelop::VcsStatusInfo::ItemHasConflicts: return QIcon::fromTheme(QStringLiteral("vcs-conflicting")); case KDevelop::VcsStatusInfo::ItemModified: return QIcon::fromTheme(QStringLiteral("vcs-locally-modified")); case KDevelop::VcsStatusInfo::ItemUpToDate: return QIcon::fromTheme(QStringLiteral("vcs-normal")); case KDevelop::VcsStatusInfo::ItemUnknown: case KDevelop::VcsStatusInfo::ItemUserState: return QIcon::fromTheme(QStringLiteral("unknown")); } return QIcon::fromTheme(QStringLiteral("dialog-error")); } VcsFileChangesSortProxyModel::VcsFileChangesSortProxyModel(QObject* parent) : QSortFilterProxyModel(parent) { } bool VcsFileChangesSortProxyModel::lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const { const auto leftStatus = source_left.data(VcsFileChangesModel::StateRole).value(); const auto rightStatus = source_right.data(VcsFileChangesModel::StateRole).value(); if (leftStatus != rightStatus) { return leftStatus < rightStatus; } const QString leftPath = source_left.data(VcsFileChangesModel::UrlRole).toString(); const QString rightPath = source_right.data(VcsFileChangesModel::UrlRole).toString(); return QString::localeAwareCompare(leftPath, rightPath) < 0; } class VcsStatusInfoItem : public QStandardItem { public: - VcsStatusInfoItem(const VcsStatusInfo& info) + explicit VcsStatusInfoItem(const VcsStatusInfo& info) : QStandardItem() , m_info(info) {} void setStatus(const VcsStatusInfo& info) { m_info = info; emitDataChanged(); } QVariant data(int role) const override { switch(role) { case Qt::DisplayRole: return stateToString(m_info.state()); case Qt::DecorationRole: return stateToIcon(m_info.state()); case VcsFileChangesModel::VcsStatusInfoRole: return QVariant::fromValue(m_info); case VcsFileChangesModel::UrlRole: return m_info.url(); case VcsFileChangesModel::StateRole: return QVariant::fromValue(m_info.state()); } return {}; } private: VcsStatusInfo m_info; }; class VcsFileChangesModelPrivate { public: bool allowSelection; }; VcsFileChangesModel::VcsFileChangesModel(QObject *parent, bool allowSelection) : QStandardItemModel(parent), d(new VcsFileChangesModelPrivate {allowSelection} ) { setColumnCount(2); setHeaderData(0, Qt::Horizontal, i18n("Filename")); setHeaderData(1, Qt::Horizontal, i18n("Status")); } VcsFileChangesModel::~VcsFileChangesModel() { } int VcsFileChangesModel::updateState(QStandardItem *parent, const KDevelop::VcsStatusInfo &status) { if(status.state()==VcsStatusInfo::ItemUnknown || status.state()==VcsStatusInfo::ItemUpToDate) { removeUrl(status.url()); return -1; } else { QStandardItem* item = fileItemForUrl(parent, status.url()); if(!item) { QString path = ICore::self()->projectController()->prettyFileName(status.url(), KDevelop::IProjectController::FormatPlain); QMimeType mime = status.url().isLocalFile() ? QMimeDatabase().mimeTypeForFile(status.url().toLocalFile(), QMimeDatabase::MatchExtension) : QMimeDatabase().mimeTypeForUrl(status.url()); QIcon icon = QIcon::fromTheme(mime.iconName()); item = new QStandardItem(icon, path); auto itStatus = new VcsStatusInfoItem(status); if(d->allowSelection) { item->setCheckable(true); item->setCheckState(status.state() == VcsStatusInfo::ItemUnknown ? Qt::Unchecked : Qt::Checked); } parent->appendRow({ item, itStatus }); } else { QStandardItem *parent = item->parent(); if(parent == nullptr) parent = invisibleRootItem(); auto statusInfoItem = static_cast(parent->child(item->row(), 1)); statusInfoItem->setStatus(status); } return item->row(); } } QVariant VcsFileChangesModel::data(const QModelIndex &index, int role) const { if (role >= VcsStatusInfoRole && index.column()==0) { return QStandardItemModel::data(index.sibling(index.row(), 1), role); } return QStandardItemModel::data(index, role); } QStandardItem* VcsFileChangesModel::fileItemForUrl(QStandardItem* parent, const QUrl& url) const { for(int i=0, c=parent->rowCount(); ichild(i); if(indexFromItem(item).data(UrlRole).toUrl() == url) { return parent->child(i); } } return nullptr; } void VcsFileChangesModel::setAllChecked(bool checked) { if(!d->allowSelection) return; QStandardItem* parent = invisibleRootItem(); for(int i = 0, c = parent->rowCount(); i < c; i++) { QStandardItem* item = parent->child(i); item->setCheckState(checked ? Qt::Checked : Qt::Unchecked); } } QList VcsFileChangesModel::checkedUrls(QStandardItem *parent) const { QList ret; for(int i = 0, c = parent->rowCount(); i < c; i++) { QStandardItem* item = parent->child(i); if(!d->allowSelection || item->checkState() == Qt::Checked) { ret << indexFromItem(item).data(UrlRole).toUrl(); } } return ret; } QList VcsFileChangesModel::urls(QStandardItem *parent) const { QList ret; for(int i = 0, c = parent->rowCount(); i < c; i++) { QStandardItem* item = parent->child(i); ret << indexFromItem(item).data(UrlRole).toUrl(); } return ret; } void VcsFileChangesModel::checkUrls(QStandardItem *parent, const QList& urls) const { QSet urlSet(urls.toSet()); if(!d->allowSelection) return; for(int i = 0, c = parent->rowCount(); i < c; i++) { QStandardItem* item = parent->child(i); item->setCheckState(urlSet.contains(indexFromItem(item).data(UrlRole).toUrl()) ? Qt::Checked : Qt::Unchecked); } } void VcsFileChangesModel::setIsCheckbable(bool checkable) { d->allowSelection = checkable; } bool VcsFileChangesModel::isCheckable() const { return d->allowSelection; } bool VcsFileChangesModel::removeUrl(const QUrl& url) { const auto matches = match(index(0, 0), UrlRole, url, 1, Qt::MatchExactly); if (matches.isEmpty()) return false; const auto& idx = matches.first(); return removeRow(idx.row(), idx.parent()); } } diff --git a/vcs/models/vcsfilechangesmodel.h b/vcs/models/vcsfilechangesmodel.h index 50113b467..4d08001aa 100644 --- a/vcs/models/vcsfilechangesmodel.h +++ b/vcs/models/vcsfilechangesmodel.h @@ -1,155 +1,155 @@ /* This file is part of KDevelop Copyright 2010 Aleix Pol Split into separate class Copyright 2011 Andrey Batyiev 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_VCSFILECHANGESMODEL_H #define KDEVPLATFORM_VCSFILECHANGESMODEL_H #include #include #include #include class QUrl; namespace KDevelop { class VcsStatusInfo; class KDEVPLATFORMVCS_EXPORT VcsFileChangesSortProxyModel : public QSortFilterProxyModel { Q_OBJECT public: - VcsFileChangesSortProxyModel(QObject* parent = nullptr); + explicit VcsFileChangesSortProxyModel(QObject* parent = nullptr); bool lessThan(const QModelIndex& rLeft, const QModelIndex& rRight) const override; }; /** * This class holds and represents information about changes in files. * Also it is possible to provide tree like models by inheriting this class, see protected members. * All stuff should be pulled in by @p updateState. */ class VcsFileChangesModelPrivate; class KDEVPLATFORMVCS_EXPORT VcsFileChangesModel : public QStandardItemModel { Q_OBJECT public: enum ItemRoles { VcsStatusInfoRole = Qt::UserRole+1, UrlRole, StateRole, LastItemRole }; enum Column { PathColumn = 0, StatusColumn = 1 }; /** * Constructor for class. * @param isCheckable if true, model will show checkboxes on items. */ explicit VcsFileChangesModel(QObject *parent = nullptr, bool isCheckable = false); ~VcsFileChangesModel() override; QVariant data(const QModelIndex &index, int role) const override; /** * Returns list of currently checked urls. */ QList checkedUrls() const { return checkedUrls(invisibleRootItem()); } /** * Returns urls of all files * */ QList urls() const { return urls(invisibleRootItem()); } /** * Set the checked urls * */ void setCheckedUrls(const QList& urls) const { return checkUrls(invisibleRootItem(), urls); } /** * Returns the item for the specified url * */ QStandardItem* itemForUrl(const QUrl &url) const { return fileItemForUrl(invisibleRootItem(), url); } /** * Changes the check-state of all files to the given state * */ void setAllChecked(bool checked); void setIsCheckbable(bool checkable); bool isCheckable() const; bool removeUrl(const QUrl& url); public slots: /** * Used to post update of status of some file. Any status except UpToDate * and Unknown will update (or add) item representation. */ void updateState(const KDevelop::VcsStatusInfo &status) { updateState(invisibleRootItem(), status); } protected: /** * Post update of status of some file. * @return changed row or -1 if row is deleted */ int updateState(QStandardItem *parent, const KDevelop::VcsStatusInfo &status); /** * Returns list of currently checked urls. */ QList checkedUrls(QStandardItem *parent) const; /** * Checks the given urls, unchecks all others. * */ void checkUrls(QStandardItem *parent, const QList& urls) const; /** * Returns all urls * */ QList urls(QStandardItem *parent) const; /** * Returns item for particular url. */ QStandardItem* fileItemForUrl(QStandardItem *parent, const QUrl &url) const; private: QScopedPointer const d; }; } Q_DECLARE_METATYPE(KDevelop::VcsStatusInfo::State) #endif // KDEVPLATFORM_VCSFILECHANGESMODEL_H diff --git a/vcs/widgets/vcsdiffwidget.cpp b/vcs/widgets/vcsdiffwidget.cpp index d873321d1..82c79b3d1 100644 --- a/vcs/widgets/vcsdiffwidget.cpp +++ b/vcs/widgets/vcsdiffwidget.cpp @@ -1,104 +1,104 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2007 Andreas Pakulat * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "vcsdiffwidget.h" #include #include #include #include "../vcsjob.h" #include "../vcsrevision.h" #include "../vcsdiff.h" #include "../debug.h" #include "ui_vcsdiffwidget.h" #include "vcsdiffpatchsources.h" namespace KDevelop { class VcsDiffWidgetPrivate { public: Ui::VcsDiffWidget* m_ui; VcsJob* m_job; VcsDiffWidget* q; - VcsDiffWidgetPrivate(VcsDiffWidget* _q) : q(_q) { + explicit VcsDiffWidgetPrivate(VcsDiffWidget* _q) : q(_q) { } void diffReady( KDevelop::VcsJob* job ) { if( job != m_job ) return; KDevelop::VcsDiff diff = m_job->fetchResults().value(); // Try using the patch-review plugin if possible VCSDiffPatchSource* patch = new VCSDiffPatchSource(diff); if(showVcsDiff(patch)) { q->deleteLater(); return; }else{ delete patch; } qCDebug(VCS) << "diff:" << diff.leftTexts().count(); qCDebug(VCS) << "diff:" << diff.diff(); qCDebug(VCS) << "diff:" << diff.type(); qCDebug(VCS) << "diff:" << diff.contentType(); m_ui->diffDisplay->setPlainText( diff.diff() ); m_ui->diffDisplay->setReadOnly( true ); } }; VcsDiffWidget::VcsDiffWidget( KDevelop::VcsJob* job, QWidget* parent ) : QWidget( parent ), d(new VcsDiffWidgetPrivate(this)) { d->m_job = job; d->m_ui = new Ui::VcsDiffWidget(); d->m_ui->setupUi( this ); connect( d->m_job, &VcsJob::resultsReady, this, [&] (VcsJob* job) { d->diffReady(job); } ); ICore::self()->runController()->registerJob( d->m_job ); } VcsDiffWidget::~VcsDiffWidget() { delete d->m_ui; delete d; } void VcsDiffWidget::setRevisions( const KDevelop::VcsRevision& first, const KDevelop::VcsRevision& second ) { d->m_ui->revLabel->setText( i18n("Difference between revision %1 and %2:", first.prettyValue(), second.prettyValue() ) ); } } #include "moc_vcsdiffwidget.cpp" diff --git a/vcs/widgets/vcseventwidget.cpp b/vcs/widgets/vcseventwidget.cpp index 2d14a6eaa..60199aa11 100644 --- a/vcs/widgets/vcseventwidget.cpp +++ b/vcs/widgets/vcseventwidget.cpp @@ -1,235 +1,235 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2007 Dukju Ahn * * Copyright 2007 Andreas Pakulat * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "vcseventwidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "ui_vcseventwidget.h" #include "vcsdiffwidget.h" #include "../interfaces/ibasicversioncontrol.h" #include "../models/vcseventmodel.h" #include "../models/vcsitemeventmodel.h" #include "../debug.h" #include "../vcsevent.h" #include "../vcsjob.h" #include "../vcslocation.h" #include "../vcsrevision.h" namespace KDevelop { class VcsEventWidgetPrivate { public: - VcsEventWidgetPrivate( VcsEventWidget* w ) + explicit VcsEventWidgetPrivate( VcsEventWidget* w ) : q( w ) { m_copyAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("Copy revision number"), q); m_copyAction->setShortcut(Qt::ControlModifier+Qt::Key_C); QObject::connect(m_copyAction, &QAction::triggered, q, [&] { copyRevision(); }); } Ui::VcsEventWidget* m_ui; VcsItemEventModel* m_detailModel; VcsEventModel *m_logModel; QUrl m_url; QModelIndex m_contextIndex; VcsEventWidget* q; QAction* m_copyAction; IBasicVersionControl* m_iface; void eventViewCustomContextMenuRequested( const QPoint &point ); void eventViewClicked( const QModelIndex &index ); void jobReceivedResults( KDevelop::VcsJob* job ); void copyRevision(); void diffToPrevious(); void diffRevisions(); void currentRowChanged(const QModelIndex& start, const QModelIndex& end); }; void VcsEventWidgetPrivate::eventViewCustomContextMenuRequested( const QPoint &point ) { m_contextIndex = m_ui->eventView->indexAt( point ); if( !m_contextIndex.isValid() ){ qCDebug(VCS) << "contextMenu is not in TreeView"; return; } QMenu menu( m_ui->eventView ); menu.addAction(m_copyAction); menu.addAction(i18n("Diff to previous revision"), q, SLOT(diffToPrevious())); QAction* action = menu.addAction(i18n("Diff between revisions"), q, SLOT(diffRevisions())); action->setEnabled(m_ui->eventView->selectionModel()->selectedRows().size()>=2); menu.exec( m_ui->eventView->viewport()->mapToGlobal(point) ); } void VcsEventWidgetPrivate::currentRowChanged(const QModelIndex& start, const QModelIndex& end) { Q_UNUSED(end); if(start.isValid()) eventViewClicked(start); } void VcsEventWidgetPrivate::eventViewClicked( const QModelIndex &index ) { KDevelop::VcsEvent ev = m_logModel->eventForIndex( index ); m_detailModel->removeRows(0, m_detailModel->rowCount()); if( ev.revision().revisionType() != KDevelop::VcsRevision::Invalid ) { m_ui->itemEventView->setEnabled(true); m_ui->message->setEnabled(true); m_ui->message->setPlainText( ev.message() ); m_detailModel->addItemEvents( ev.items() ); }else { m_ui->itemEventView->setEnabled(false); m_ui->message->setEnabled(false); m_ui->message->clear(); } QHeaderView* header = m_ui->itemEventView->header(); header->setSectionResizeMode(QHeaderView::ResizeToContents); header->setStretchLastSection(true); } void VcsEventWidgetPrivate::copyRevision() { qApp->clipboard()->setText(m_contextIndex.sibling(m_contextIndex.row(), 0).data().toString()); } void VcsEventWidgetPrivate::diffToPrevious() { KDevelop::VcsEvent ev = m_logModel->eventForIndex( m_contextIndex ); KDevelop::VcsRevision prev = KDevelop::VcsRevision::createSpecialRevision(KDevelop::VcsRevision::Previous); KDevelop::VcsJob* job = m_iface->diff( m_url, prev, ev.revision() ); VcsDiffWidget* widget = new VcsDiffWidget( job ); widget->setRevisions( prev, ev.revision() ); QDialog* dlg = new QDialog( q ); widget->connect(widget, &VcsDiffWidget::destroyed, dlg, &QDialog::deleteLater); dlg->setWindowTitle( i18n("Difference To Previous") ); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok); auto mainWidget = new QWidget; QVBoxLayout *mainLayout = new QVBoxLayout; dlg->setLayout(mainLayout); mainLayout->addWidget(mainWidget); QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); dlg->connect(buttonBox, &QDialogButtonBox::accepted, dlg, &QDialog::accept); dlg->connect(buttonBox, &QDialogButtonBox::rejected, dlg, &QDialog::reject); mainLayout->addWidget(widget); mainLayout->addWidget(buttonBox); dlg->show(); } void VcsEventWidgetPrivate::diffRevisions() { QModelIndexList l = m_ui->eventView->selectionModel()->selectedRows(); KDevelop::VcsEvent ev1 = m_logModel->eventForIndex( l.first() ); KDevelop::VcsEvent ev2 = m_logModel->eventForIndex( l.last() ); KDevelop::VcsJob* job = m_iface->diff( m_url, ev1.revision(), ev2.revision() ); VcsDiffWidget* widget = new VcsDiffWidget( job ); widget->setRevisions( ev1.revision(), ev2.revision() ); auto dlg = new QDialog( q ); dlg->setWindowTitle( i18n("Difference between Revisions") ); widget->connect(widget, &VcsDiffWidget::destroyed, dlg, &QDialog::deleteLater); auto mainLayout = new QVBoxLayout(dlg); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok); auto okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); dlg->connect(buttonBox, &QDialogButtonBox::accepted, dlg, &QDialog::accept); dlg->connect(buttonBox, &QDialogButtonBox::rejected, dlg, &QDialog::reject); mainLayout->addWidget(buttonBox); mainLayout->addWidget(widget); dlg->show(); } VcsEventWidget::VcsEventWidget( const QUrl& url, const VcsRevision& rev, KDevelop::IBasicVersionControl* iface, QWidget* parent ) : QWidget(parent), d(new VcsEventWidgetPrivate(this) ) { d->m_iface = iface; d->m_url = url; d->m_ui = new Ui::VcsEventWidget(); d->m_ui->setupUi(this); d->m_logModel = new VcsEventModel(iface, rev, url, this); d->m_ui->eventView->setModel( d->m_logModel ); d->m_ui->eventView->sortByColumn(0, Qt::DescendingOrder); d->m_ui->eventView->setContextMenuPolicy( Qt::CustomContextMenu ); QHeaderView* header = d->m_ui->eventView->header(); header->setSectionResizeMode( 0, QHeaderView::ResizeToContents ); header->setSectionResizeMode( 1, QHeaderView::Stretch ); header->setSectionResizeMode( 2, QHeaderView::ResizeToContents ); header->setSectionResizeMode( 3, QHeaderView::ResizeToContents ); // Select first row as soon as the model got populated connect(d->m_logModel, &QAbstractItemModel::rowsInserted, this, [this]() { auto view = d->m_ui->eventView; view->setCurrentIndex(view->model()->index(0, 0)); }); d->m_detailModel = new VcsItemEventModel(this); d->m_ui->itemEventView->setModel( d->m_detailModel ); connect( d->m_ui->eventView, &QTreeView::clicked, this, [&] (const QModelIndex& index) { d->eventViewClicked(index); } ); connect( d->m_ui->eventView->selectionModel(), &QItemSelectionModel::currentRowChanged, this, [&] (const QModelIndex& start, const QModelIndex& end) { d->currentRowChanged(start, end); }); connect( d->m_ui->eventView, &QTreeView::customContextMenuRequested, this, [&] (const QPoint& point) { d->eventViewCustomContextMenuRequested(point); } ); } VcsEventWidget::~VcsEventWidget() { delete d->m_ui; delete d; } } #include "moc_vcseventwidget.cpp"