diff --git a/debugger/breakpoint/breakpointdetails.cpp b/debugger/breakpoint/breakpointdetails.cpp index 1f5a56415..3f9024537 100644 --- a/debugger/breakpoint/breakpointdetails.cpp +++ b/debugger/breakpoint/breakpointdetails.cpp @@ -1,165 +1,165 @@ /* * This file is part of KDevelop * * Copyright 2008 Vladimir Prus * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "breakpointdetails.h" #include #include #include #include #include #include #include "../breakpoint/breakpoint.h" #include "../interfaces/idebugsession.h" #include "../../interfaces/icore.h" #include "../interfaces/ibreakpointcontroller.h" #include "../../interfaces/idebugcontroller.h" using namespace KDevelop; BreakpointDetails::BreakpointDetails(QWidget *parent) : QWidget(parent), m_currentBreakpoint(0) { QVBoxLayout* layout = new QVBoxLayout(this); layout->setContentsMargins(11, 0, 0, 11); m_status = new QLabel(this); connect(m_status, &QLabel::linkActivated, this, &BreakpointDetails::showExplanation); layout->addWidget(m_status); QGridLayout* hitsLayout = new QGridLayout(); layout->addLayout(hitsLayout); hitsLayout->setContentsMargins(0, 0, 0, 0); m_hits = new QLabel(i18n("Not hit yet"), this); m_hits->setWordWrap(true); hitsLayout->addWidget(m_hits, 0, 0, 1, 3); QFrame* frame = new QFrame(this); frame->setFrameShape(QFrame::HLine); hitsLayout->addWidget(frame, 1, 0, 1, 3); QLabel *l2 = new QLabel(i18n("Ignore"), this); hitsLayout->addWidget(l2, 2, 0); m_ignore = new QSpinBox(this); hitsLayout->addWidget(m_ignore, 2, 1); m_ignore->setRange(0, 99999); connect(m_ignore, static_cast(&QSpinBox::valueChanged), this, &BreakpointDetails::setIgnoreHits); QLabel *l3 = new QLabel(i18n("next hits"), this); hitsLayout->addWidget(l3, 2, 2); layout->addStretch(); setItem(0); //initialize with no breakpoint active } void KDevelop::BreakpointDetails::setIgnoreHits(int ignoreHits) { if (!m_currentBreakpoint) return; m_currentBreakpoint->setIgnoreHits(ignoreHits); } void BreakpointDetails::setItem(Breakpoint *b) { m_currentBreakpoint = b; if (!b) { m_status->hide(); m_hits->hide(); m_ignore->setEnabled(false); return; } m_ignore->setValue(b->ignoreHits()); if (b->state() == Breakpoint::NotStartedState) { m_status->hide(); m_hits->hide(); m_ignore->setEnabled(true); return; } m_status->show(); m_hits->show(); m_ignore->setEnabled(true); if (b->errorText().isEmpty()) { switch (b->state()) { case Breakpoint::NotStartedState: Q_ASSERT(0); break; case Breakpoint::PendingState: m_status->setText(i18n("Breakpoint is pending")); break; case Breakpoint::DirtyState: m_status->setText(i18n("Breakpoint is dirty")); break; case Breakpoint::CleanState: m_status->setText(i18n("Breakpoint is active")); break; } if (b->hitCount() == -1) - m_hits->setText(""); + m_hits->clear(); else if (b->hitCount()) m_hits->setText(i18np("Hit %1 time", "Hit %1 times", b->hitCount())); else m_hits->setText(i18n("Not hit yet")); } else { m_status->setText(i18n("Breakpoint has errors")); m_hits->setText(b->errorText()); } } void BreakpointDetails::showExplanation(const QString& link) { QPoint pos = m_status->mapToGlobal(m_status->geometry().topLeft()); - if (link == "pending") + if (link == QLatin1String("pending")) { QWhatsThis::showText(pos, i18n("Breakpoint is pending" "

Pending breakpoints are those that have " "been passed to GDB, but which are not yet " "installed in the target, because GDB cannot " "find the function or file to which the breakpoint " "refers. The most common case is a breakpoint " "in a shared library: GDB will insert this " "breakpoint only when the library is loaded.

"), m_status); } - else if (link == "dirty") + else if (link == QLatin1String("dirty")) { QWhatsThis::showText(pos, i18n("Breakpoint is dirty" "

The breakpoint has not yet been passed " "to the debugger.

"), m_status); } } diff --git a/debugger/breakpoint/breakpointwidget.cpp b/debugger/breakpoint/breakpointwidget.cpp index 30a7a4c58..70dd88f27 100644 --- a/debugger/breakpoint/breakpointwidget.cpp +++ b/debugger/breakpoint/breakpointwidget.cpp @@ -1,313 +1,313 @@ /* * This file is part of KDevelop * * Copyright 2008 Vladimir Prus * Copyright 2013 Vlas Puhov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "breakpointwidget.h" #include #include #include #include #include #include #include #include #include #include "breakpointdetails.h" #include "../breakpoint/breakpoint.h" #include "../breakpoint/breakpointmodel.h" #include "util/debug.h" #include #include #define IF_DEBUG(x) #include #include #include using namespace KDevelop; BreakpointWidget::BreakpointWidget(IDebugController *controller, QWidget *parent) : AutoOrientedSplitter(parent), m_firstShow(true), m_debugController(controller), m_breakpointDisableAllAction(0), m_breakpointEnableAllAction(0), m_breakpointRemoveAll(0) { setWindowTitle(i18nc("@title:window", "Debugger Breakpoints")); setWhatsThis(i18nc("@info:whatsthis", "Displays a list of breakpoints with " "their current status. Clicking on a " "breakpoint item allows you to change " "the breakpoint and will take you " "to the source in the editor window.")); setWindowIcon( QIcon::fromTheme( QStringLiteral( "media-playback-pause"), windowIcon() ) ); m_breakpointsView = new QTableView(this); m_breakpointsView->setSelectionBehavior(QAbstractItemView::SelectRows); m_breakpointsView->setSelectionMode(QAbstractItemView::SingleSelection); m_breakpointsView->horizontalHeader()->setHighlightSections(false); m_breakpointsView->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft | Qt::AlignVCenter); m_details = new BreakpointDetails(this); setStretchFactor(0, 2); m_breakpointsView->verticalHeader()->hide(); PlaceholderItemProxyModel* proxyModel = new PlaceholderItemProxyModel(this); proxyModel->setSourceModel(m_debugController->breakpointModel()); proxyModel->setColumnHint(Breakpoint::LocationColumn, i18n("New code breakpoint ...")); proxyModel->setColumnHint(Breakpoint::ConditionColumn, i18n("Enter condition ...")); m_breakpointsView->setModel(proxyModel); connect(proxyModel, &PlaceholderItemProxyModel::dataInserted, this, &BreakpointWidget::slotDataInserted); m_proxyModel = proxyModel; connect(m_breakpointsView, &QTableView::activated, this, &BreakpointWidget::slotOpenFile); connect(m_breakpointsView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &BreakpointWidget::slotUpdateBreakpointDetail); connect(m_debugController->breakpointModel(), &BreakpointModel::rowsInserted, this, &BreakpointWidget::slotUpdateBreakpointDetail); connect(m_debugController->breakpointModel(), &BreakpointModel::rowsRemoved, this, &BreakpointWidget::slotUpdateBreakpointDetail); connect(m_debugController->breakpointModel(), &BreakpointModel::modelReset, this, &BreakpointWidget::slotUpdateBreakpointDetail); connect(m_debugController->breakpointModel(), &BreakpointModel::dataChanged, this, &BreakpointWidget::slotUpdateBreakpointDetail); connect(m_debugController->breakpointModel(), &BreakpointModel::hit, this, &BreakpointWidget::breakpointHit); connect(m_debugController->breakpointModel(), &BreakpointModel::error, this, &BreakpointWidget::breakpointError); setupPopupMenu(); } void BreakpointWidget::setupPopupMenu() { m_popup = new QMenu(this); QMenu* newBreakpoint = m_popup->addMenu( i18nc("New breakpoint", "&New") ); newBreakpoint->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); QAction* action = newBreakpoint->addAction( i18nc("Code breakpoint", "&Code"), this, SLOT(slotAddBlankBreakpoint()) ); // Use this action also to provide a local shortcut action->setShortcut(QKeySequence(Qt::Key_B + Qt::CTRL, Qt::Key_C)); addAction(action); newBreakpoint->addAction( i18nc("Data breakpoint", "Data &Write"), this, SLOT(slotAddBlankWatchpoint())); newBreakpoint->addAction( i18nc("Data read breakpoint", "Data &Read"), this, SLOT(slotAddBlankReadWatchpoint())); newBreakpoint->addAction( i18nc("Data access breakpoint", "Data &Access"), this, SLOT(slotAddBlankAccessWatchpoint())); QAction* breakpointDelete = m_popup->addAction( QIcon::fromTheme(QStringLiteral("edit-delete")), i18n( "&Delete" ), this, SLOT(slotRemoveBreakpoint())); breakpointDelete->setShortcut(Qt::Key_Delete); breakpointDelete->setShortcutContext(Qt::WidgetWithChildrenShortcut); addAction(breakpointDelete); m_popup->addSeparator(); m_breakpointDisableAllAction = m_popup->addAction(i18n("Disable &All"), this, SLOT(slotDisableAllBreakpoints())); m_breakpointEnableAllAction = m_popup->addAction(i18n("&Enable All"), this, SLOT(slotEnableAllBreakpoints())); m_breakpointRemoveAll = m_popup->addAction(i18n("&Remove All"), this, SLOT(slotRemoveAllBreakpoints())); connect(m_popup,&QMenu::aboutToShow, this, &BreakpointWidget::slotPopupMenuAboutToShow); } void BreakpointWidget::contextMenuEvent(QContextMenuEvent* event) { m_popup->popup(event->globalPos()); } void BreakpointWidget::slotPopupMenuAboutToShow() { if (m_debugController->breakpointModel()->rowCount() < 2) { m_breakpointDisableAllAction->setDisabled(true); m_breakpointEnableAllAction->setDisabled(true); m_breakpointRemoveAll->setDisabled(true); } else { m_breakpointRemoveAll->setEnabled(true); bool allDisabled = true; bool allEnabled = true; for (int i = 0; i < m_debugController->breakpointModel()->rowCount() - 1 ; i++) { Breakpoint *bp = m_debugController->breakpointModel()->breakpoint(i); if (bp->enabled()) allDisabled = false; else allEnabled = false; } m_breakpointDisableAllAction->setDisabled(allDisabled); m_breakpointEnableAllAction->setDisabled(allEnabled); } } void BreakpointWidget::showEvent(QShowEvent *) { if (m_firstShow) { QHeaderView* header = m_breakpointsView->horizontalHeader(); for (int i = 0; i < m_breakpointsView->model()->columnCount(); ++i) { if(i == Breakpoint::LocationColumn){ continue; } m_breakpointsView->resizeColumnToContents(i); } //for some reasons sometimes width can be very small about 200... But it doesn't matter as we use tooltip anyway. int width = m_breakpointsView->size().width(); header->resizeSection(Breakpoint::LocationColumn, width > 400 ? width/2 : header->sectionSize(Breakpoint::LocationColumn)*2 ); m_firstShow = false; } } void BreakpointWidget::edit(KDevelop::Breakpoint *n) { QModelIndex index = m_proxyModel->mapFromSource(m_debugController->breakpointModel()->breakpointIndex(n, Breakpoint::LocationColumn)); m_breakpointsView->setCurrentIndex(index); m_breakpointsView->edit(index); } void BreakpointWidget::slotDataInserted(int column, const QVariant& value) { Breakpoint* breakpoint = m_debugController->breakpointModel()->addCodeBreakpoint(); breakpoint->setData(column, value); } void BreakpointWidget::slotAddBlankBreakpoint() { edit(m_debugController->breakpointModel()->addCodeBreakpoint()); } void BreakpointWidget::slotAddBlankWatchpoint() { edit(m_debugController->breakpointModel()->addWatchpoint()); } void BreakpointWidget::slotAddBlankReadWatchpoint() { edit(m_debugController->breakpointModel()->addReadWatchpoint()); } void KDevelop::BreakpointWidget::slotAddBlankAccessWatchpoint() { edit(m_debugController->breakpointModel()->addAccessWatchpoint()); } void BreakpointWidget::slotRemoveBreakpoint() { QItemSelectionModel* sel = m_breakpointsView->selectionModel(); QModelIndexList selected = sel->selectedIndexes(); IF_DEBUG( qCDebug(DEBUGGER) << selected; ) if (!selected.isEmpty()) { m_debugController->breakpointModel()->removeRow(selected.first().row()); } } void BreakpointWidget::slotRemoveAllBreakpoints() { m_debugController->breakpointModel()->removeRows(0, m_debugController->breakpointModel()->rowCount()); } void BreakpointWidget::slotUpdateBreakpointDetail() { QModelIndexList selected = m_breakpointsView->selectionModel()->selectedIndexes(); IF_DEBUG( qCDebug(DEBUGGER) << selected; ) if (selected.isEmpty()) { m_details->setItem(0); } else { m_details->setItem(m_debugController->breakpointModel()->breakpoint(selected.first().row())); } } void BreakpointWidget::breakpointHit(int row) { const QModelIndex index = m_proxyModel->mapFromSource(m_debugController->breakpointModel()->index(row, 0)); m_breakpointsView->selectionModel()->select( index, QItemSelectionModel::Rows | QItemSelectionModel::ClearAndSelect); } void BreakpointWidget::breakpointError(int row, const QString& msg) { // FIXME: we probably should prevent this error notification during // initial setting of breakpoint, to avoid a cloud of popups. if (!m_breakpointsView->isVisible()) return; const QModelIndex index = m_proxyModel->mapFromSource( m_debugController->breakpointModel()->index(row, BreakpointModel::LocationColumn)); QPoint p = m_breakpointsView->visualRect(index).topLeft(); p = m_breakpointsView->mapToGlobal(p); KPassivePopup *pop = new KPassivePopup(m_breakpointsView); pop->setPopupStyle(KPassivePopup::Boxed); pop->setAutoDelete(true); // FIXME: the icon, too. - pop->setView("", msg); + pop->setView(QString(), msg); pop->setTimeout(-1); pop->show(p); } void BreakpointWidget::slotOpenFile(const QModelIndex& breakpointIdx) { if (breakpointIdx.column() != Breakpoint::LocationColumn){ return; } Breakpoint *bp = m_debugController->breakpointModel()->breakpoint(breakpointIdx.row()); if (!bp || bp->line() == -1 || bp->url().isEmpty() ){ return; } ICore::self()->documentController()->openDocument(bp->url(), KTextEditor::Cursor(bp->line(), 0), IDocumentController::DoNotFocus); } void BreakpointWidget::slotDisableAllBreakpoints() { for (int i = 0; i < m_debugController->breakpointModel()->rowCount() - 1 ; i++) { Breakpoint *bp = m_debugController->breakpointModel()->breakpoint(i); bp->setData(Breakpoint::EnableColumn, Qt::Unchecked); } } void BreakpointWidget::slotEnableAllBreakpoints() { for (int i = 0; i < m_debugController->breakpointModel()->rowCount() - 1 ; i++) { Breakpoint *bp = m_debugController->breakpointModel()->breakpoint(i); bp->setData(Breakpoint::EnableColumn, Qt::Checked); } } diff --git a/debugger/util/treeitem.cpp b/debugger/util/treeitem.cpp index c58798fa6..9ea619779 100644 --- a/debugger/util/treeitem.cpp +++ b/debugger/util/treeitem.cpp @@ -1,263 +1,265 @@ /* * This file is part of KDevelop * * Copyright 2008 Vladimir Prus * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "treeitem.h" #include #include #include "debug.h" #include "treemodel.h" using namespace KDevelop; TreeItem::TreeItem(TreeModel* model, TreeItem *parent) : model_(model), more_(false), ellipsis_(0), expanded_(false) { parentItem = parent; } TreeItem::~TreeItem() { foreach (TreeItem *it, childItems) delete it; delete ellipsis_; } void TreeItem::setData(const QVector &data) { itemData=data; } void TreeItem::appendChild(TreeItem *item, bool initial) { QModelIndex index = model_->indexForItem(this, 0); // Note that we need emit beginRemoveRows, even if we're replacing // ellipsis item with the real one. The number of rows does not change // with the result, but the last item is different. The address of the // item is stored inside QModelIndex, so just replacing the item, and // deleting the old one, will lead to crash. if (more_) { if (!initial) model_->beginRemoveRows(index, childItems.size(), childItems.size()); more_ = false; delete ellipsis_; ellipsis_ = 0; if (!initial) model_->endRemoveRows(); } if (!initial) model_->beginInsertRows(index, childItems.size(), childItems.size()); childItems.append(item); if (!initial) model_->endInsertRows(); } void TreeItem::insertChild(int position, TreeItem *child, bool initial) { QModelIndex index = model_->indexForItem(this, 0); if (!initial) model_->beginInsertRows(index, position, position); childItems.insert(position, child); if (!initial) model_->endInsertRows(); } void TreeItem::reportChange() { QModelIndex index = model_->indexForItem(this, 0); QModelIndex index2 = model_->indexForItem(this, itemData.size()-1); emit model_->dataChanged(index, index2); } void KDevelop::TreeItem::reportChange(int column) { QModelIndex index = model_->indexForItem(this, column); emit model_->dataChanged(index, index); } void TreeItem::removeChild(int index) { QModelIndex modelIndex = model_->indexForItem(this, 0); model_->beginRemoveRows(modelIndex, index, index); childItems.erase(childItems.begin() + index); model_->endRemoveRows(); } void TreeItem::removeSelf() { QModelIndex modelIndex = model_->indexForItem(this, 0); parentItem->removeChild(modelIndex.row()); } void TreeItem::deleteChildren() { QVector copy = childItems; clear(); // Only delete the children after removing them // from model. Otherwise, the model will touch // deleted things, with undefined results. for (int i = 0; i < childItems.size(); ++i) { TreeItem* v = child(i); delete v; } } void TreeItem::clear() { if (!childItems.isEmpty() || more_) { QModelIndex index = model_->indexForItem(this, 0); model_->beginRemoveRows(index, 0, childItems.size()-1+more_); childItems.clear(); more_ = false; delete ellipsis_; ellipsis_ = 0; model_->endRemoveRows(); } } TreeItem *TreeItem::child(int row) { if (row < childItems.size()) return childItems.value(row); else if (row == childItems.size() && more_) return ellipsis_; else return NULL; } int TreeItem::childCount() const { return childItems.count() + more_; } int TreeItem::columnCount() const { return itemData.count(); } QVariant TreeItem::data(int column, int role) const { if (role == Qt::DecorationRole) return icon(column); else if (role==Qt::DisplayRole || role == Qt::EditRole) return itemData.value(column); return QVariant(); } TreeItem *TreeItem::parent() { return parentItem; } int TreeItem::row() const { if (parentItem) return parentItem->childItems.indexOf(const_cast(this)); return 0; } class EllipsisItem : public TreeItem { + Q_OBJECT public: EllipsisItem(TreeModel *model, TreeItem *parent) : TreeItem(model, parent) { QVector data; data.push_back("..."); for (int i = 1; i < model->columnCount(QModelIndex()); ++i) data.push_back(""); setData(data); } void clicked() override { qCDebug(DEBUGGER) << "Ellipsis item clicked"; /* FIXME: restore Q_ASSERT (parentItem->hasMore()); */ parentItem->fetchMoreChildren(); } void fetchMoreChildren() override {} }; void TreeItem::setHasMore(bool more) { /* FIXME: this will crash if used in ctor of root item, where the model is not associated with item or something. */ QModelIndex index = model_->indexForItem(this, 0); if (more && !more_) { model_->beginInsertRows(index, childItems.size(), childItems.size()); ellipsis_ = new EllipsisItem (model(), this); more_ = more; model_->endInsertRows(); } else if (!more && more_) { model_->beginRemoveRows(index, childItems.size(), childItems.size()); delete ellipsis_; ellipsis_ = 0; more_ = more; model_->endRemoveRows(); } } void TreeItem::emitAllChildrenFetched() { emit allChildrenFetched(); } void TreeItem::setHasMoreInitial(bool more) { more_ = more; if (more) { ellipsis_ = new EllipsisItem (model(), this); } } QVariant KDevelop::TreeItem::icon(int column) const { Q_UNUSED(column); return QVariant(); } void KDevelop::TreeItem::setExpanded(bool b) { if (expanded_ != b) { expanded_ = b; if (expanded_) emit expanded(); else emit collapsed(); } } +#include "treeitem.moc" diff --git a/debugger/variable/variablecollection.cpp b/debugger/variable/variablecollection.cpp index 2ade9071d..41e4a9a16 100644 --- a/debugger/variable/variablecollection.cpp +++ b/debugger/variable/variablecollection.cpp @@ -1,542 +1,542 @@ /* * KDevelop Debugger Support * * Copyright 2007 Hamish Rodda * Copyright 2008 Vladimir Prus * Copyright 2009 Niko Sams * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "variablecollection.h" #include #include #include #include #include #include #include #include #include "../../interfaces/icore.h" #include "../../interfaces/idocumentcontroller.h" #include "../../interfaces/iuicontroller.h" #include "../../sublime/controller.h" #include "../../sublime/view.h" #include "../../interfaces/idebugcontroller.h" #include "../interfaces/idebugsession.h" #include "../interfaces/ivariablecontroller.h" #include "util/debug.h" #include "util/texteditorhelpers.h" #include "variabletooltip.h" #include namespace KDevelop { IDebugSession* currentSession() { return ICore::self()->debugController()->currentSession(); } IDebugSession::DebuggerState currentSessionState() { if (!currentSession()) return IDebugSession::NotStartedState; return currentSession()->state(); } bool hasStartedSession() { IDebugSession::DebuggerState s = currentSessionState(); return s != IDebugSession::NotStartedState && s != IDebugSession::EndedState; } Variable::Variable(TreeModel* model, TreeItem* parent, const QString& expression, const QString& display) : TreeItem(model, parent), inScope_(true), topLevel_(true), changed_(false), showError_(false), m_format(Natural) { expression_ = expression; // FIXME: should not duplicate the data, instead overload 'data' // and return expression_ directly. if (display.isEmpty()) setData(QVector() << expression << QString() << QString()); else setData(QVector() << display << QString() << QString()); } QString Variable::expression() const { return expression_; } bool Variable::inScope() const { return inScope_; } void Variable::setValue(const QString& v) { itemData[VariableCollection::ValueColumn] = v; reportChange(); } QString Variable::value() const { return itemData[VariableCollection::ValueColumn].toString(); } void Variable::setType(const QString& type) { itemData[VariableCollection::TypeColumn] = type; reportChange(); } QString Variable::type() const { return itemData[VariableCollection::TypeColumn].toString(); } void Variable::setTopLevel(bool v) { topLevel_ = v; } void Variable::setInScope(bool v) { inScope_ = v; for (int i=0; i < childCount(); ++i) { if (Variable *var = dynamic_cast(child(i))) { var->setInScope(v); } } reportChange(); } void Variable::setShowError (bool v) { showError_ = v; reportChange(); } bool Variable::showError() { return showError_; } Variable::~Variable() { } void Variable::die() { removeSelf(); deleteLater(); } void Variable::setChanged(bool c) { changed_=c; reportChange(); } void Variable::resetChanged() { setChanged(false); for (int i=0; i(childItem)) { static_cast(childItem)->resetChanged(); } } } Variable::format_t Variable::str2format(const QString& str) { - if(str=="Binary" || str=="binary") return Binary; - if(str=="Octal" || str=="octal") return Octal; - if(str=="Decimal" || str=="decimal") return Decimal; - if(str=="Hexadecimal" || str=="hexadecimal")return Hexadecimal; + if(str==QLatin1String("Binary") || str==QLatin1String("binary")) return Binary; + if(str==QLatin1String("Octal") || str==QLatin1String("octal")) return Octal; + if(str==QLatin1String("Decimal") || str==QLatin1String("decimal")) return Decimal; + if(str==QLatin1String("Hexadecimal") || str==QLatin1String("hexadecimal"))return Hexadecimal; return Natural; // maybe most reasonable default } QString Variable::format2str(format_t format) { switch(format) { - case Natural: return "natural"; - case Binary: return "binary"; - case Octal: return "octal"; - case Decimal: return "decimal"; - case Hexadecimal: return "hexadecimal"; - default: return ""; + case Natural: return QStringLiteral("natural"); + case Binary: return QStringLiteral("binary"); + case Octal: return QStringLiteral("octal"); + case Decimal: return QStringLiteral("decimal"); + case Hexadecimal: return QStringLiteral("hexadecimal"); + default: return QString(); } } void Variable::setFormat(Variable::format_t format) { if (m_format != format) { m_format = format; formatChanged(); } } void Variable::formatChanged() { } bool Variable::isPotentialProblematicValue() const { const auto value = data(VariableCollection::ValueColumn, Qt::DisplayRole).toString(); return value == QLatin1String("0x0"); } QVariant Variable::data(int column, int role) const { if (showError_) { if (role == Qt::FontRole) { QVariant ret = TreeItem::data(column, role); QFont font = ret.value(); font.setStyle(QFont::StyleItalic); return font; } else if (column == 1 && role == Qt::DisplayRole) { return i18n("Error"); } } if (column == 1 && role == Qt::TextColorRole) { KColorScheme scheme(QPalette::Active); if (!inScope_) { return scheme.foreground(KColorScheme::InactiveText).color(); } else if (isPotentialProblematicValue()) { return scheme.foreground(KColorScheme::NegativeText).color(); } else if (changed_) { return scheme.foreground(KColorScheme::NeutralText).color(); } } if (role == Qt::ToolTipRole) { return TreeItem::data(column, Qt::DisplayRole); } return TreeItem::data(column, role); } Watches::Watches(TreeModel* model, TreeItem* parent) : TreeItem(model, parent), finishResult_(0) { setData(QVector() << i18n("Auto") << QString()); } Variable* Watches::add(const QString& expression) { if (!hasStartedSession()) return 0; Variable* v = currentSession()->variableController()->createVariable( model(), this, expression); appendChild(v); v->attachMaybe(); if (childCount() == 1 && !isExpanded()) { setExpanded(true); } return v; } Variable *Watches::addFinishResult(const QString& convenienceVarible) { if( finishResult_ ) { removeFinishResult(); } finishResult_ = currentSession()->variableController()->createVariable( model(), this, convenienceVarible, QStringLiteral("$ret")); appendChild(finishResult_); finishResult_->attachMaybe(); if (childCount() == 1 && !isExpanded()) { setExpanded(true); } return finishResult_; } void Watches::removeFinishResult() { if (finishResult_) { finishResult_->die(); finishResult_ = 0; } } void Watches::resetChanged() { for (int i=0; i(childItem)) { static_cast(childItem)->resetChanged(); } } } QVariant Watches::data(int column, int role) const { #if 0 if (column == 0 && role == Qt::FontRole) { /* FIXME: is creating font again and agian efficient? */ QFont f = font(); f.setBold(true); return f; } #endif return TreeItem::data(column, role); } void Watches::reinstall() { for (int i = 0; i < childItems.size(); ++i) { Variable* v = static_cast(child(i)); v->attachMaybe(); } } Locals::Locals(TreeModel* model, TreeItem* parent, const QString &name) : TreeItem(model, parent) { setData(QVector() << name << QString()); } QList Locals::updateLocals(QStringList locals) { QSet existing, current; for (int i = 0; i < childItems.size(); i++) { Q_ASSERT(dynamic_cast(child(i))); Variable* var= static_cast(child(i)); existing << var->expression(); } foreach (const QString& var, locals) { current << var; // If we currently don't display this local var, add it. if( !existing.contains( var ) ) { // FIXME: passing variableCollection this way is awkward. // In future, variableCollection probably should get a // method to create variable. Variable* v = currentSession()->variableController()->createVariable( ICore::self()->debugController()->variableCollection(), this, var ); appendChild( v, false ); } } for (int i = 0; i < childItems.size(); ++i) { KDevelop::Variable* v = static_cast(child(i)); if (!current.contains(v->expression())) { removeChild(i); --i; // FIXME: check that -var-delete is sent. delete v; } } if (hasMore()) { setHasMore(false); } // Variables which changed just value are updated by a call to -var-update. // Variables that changed type -- likewise. QList ret; foreach (TreeItem *i, childItems) { Q_ASSERT(dynamic_cast(i)); ret << static_cast(i); } return ret; } void Locals::resetChanged() { for (int i=0; i(childItem)) { static_cast(childItem)->resetChanged(); } } } VariablesRoot::VariablesRoot(TreeModel* model) : TreeItem(model) { watches_ = new Watches(model, this); appendChild(watches_, true); } Locals* VariablesRoot::locals(const QString& name) { if (!locals_.contains(name)) { locals_[name] = new Locals(model(), this, name); appendChild(locals_[name]); } return locals_[name]; } QHash VariablesRoot::allLocals() const { return locals_; } void VariablesRoot::resetChanged() { watches_->resetChanged(); foreach (Locals *l, locals_) { l->resetChanged(); } } VariableCollection::VariableCollection(IDebugController* controller) : TreeModel(QVector() << i18n( "Name" ) << i18n( "Value" ) << i18n("Type"), controller), m_widgetVisible(false) { universe_ = new VariablesRoot(this); setRootItem(universe_); //new ModelTest(this); connect (ICore::self()->documentController(), &IDocumentController::textDocumentCreated, this, &VariableCollection::textDocumentCreated ); connect(controller, &IDebugController::currentSessionChanged, this, &VariableCollection::updateAutoUpdate); // Qt5 signal slot syntax does not support default arguments auto callUpdateAutoUpdate = [&] { updateAutoUpdate(); }; connect(locals(), &Locals::expanded, this, callUpdateAutoUpdate); connect(locals(), &Locals::collapsed, this, callUpdateAutoUpdate); connect(watches(), &Watches::expanded, this, callUpdateAutoUpdate); connect(watches(), &Watches::collapsed, this, callUpdateAutoUpdate); } void VariableCollection::variableWidgetHidden() { m_widgetVisible = false; updateAutoUpdate(); } void VariableCollection::variableWidgetShown() { m_widgetVisible = true; updateAutoUpdate(); } void VariableCollection::updateAutoUpdate(IDebugSession* session) { if (!session) session = currentSession(); qCDebug(DEBUGGER) << session; if (!session) return; if (!m_widgetVisible) { session->variableController()->setAutoUpdate(IVariableController::UpdateNone); } else { QFlags t = IVariableController::UpdateNone; if (locals()->isExpanded()) t |= IVariableController::UpdateLocals; if (watches()->isExpanded()) t |= IVariableController::UpdateWatches; session->variableController()->setAutoUpdate(t); } } VariableCollection::~ VariableCollection() { } void VariableCollection::textDocumentCreated(IDocument* doc) { connect( doc->textDocument(), &KTextEditor::Document::viewCreated, this, &VariableCollection::viewCreated ); foreach( KTextEditor::View* view, doc->textDocument()->views() ) viewCreated( doc->textDocument(), view ); } void VariableCollection::viewCreated(KTextEditor::Document* doc, KTextEditor::View* view) { Q_UNUSED(doc); using namespace KTextEditor; TextHintInterface *iface = dynamic_cast(view); if( !iface ) return; iface->registerTextHintProvider(new VariableProvider(this)); } VariableProvider::VariableProvider(VariableCollection* collection) : KTextEditor::TextHintProvider() , m_collection(collection) { } QString VariableProvider::textHint(KTextEditor::View* view, const KTextEditor::Cursor& cursor) { if (!hasStartedSession()) return QString(); - if (ICore::self()->uiController()->activeArea()->objectName() != "debug") + if (ICore::self()->uiController()->activeArea()->objectName() != QLatin1String("debug")) return QString(); //TODO: These keyboardModifiers should also hide already opened tooltip, and show another one for code area. if (QApplication::keyboardModifiers() == Qt::ControlModifier || QApplication::keyboardModifiers() == Qt::AltModifier){ return QString(); } KTextEditor::Document* doc = view->document(); KTextEditor::Range expressionRange = currentSession()->variableController()->expressionRangeUnderCursor(doc, cursor); if (!expressionRange.isValid()) return QString(); QString expression = doc->text(expressionRange).trimmed(); // Don't do anything if there's already an open tooltip with matching range if (m_collection->activeTooltip_ && m_collection->activeTooltip_->variable()->expression() == expression) return QString(); if (expression.isEmpty()) return QString(); QPoint local = view->cursorToCoordinate(cursor); QPoint global = view->mapToGlobal(local); QWidget* w = view->childAt(local); if (!w) w = view; m_collection->activeTooltip_ = new VariableToolTip(w, global+QPoint(30,30), expression); m_collection->activeTooltip_->setHandleRect(getItemBoundingRect(view, expressionRange)); return QString(); } } diff --git a/debugger/variable/variablecollection.h b/debugger/variable/variablecollection.h index 252094436..fb24d3a30 100644 --- a/debugger/variable/variablecollection.h +++ b/debugger/variable/variablecollection.h @@ -1,249 +1,253 @@ /* * KDevelop Debugger Support * * Copyright 2007 Hamish Rodda * Copyright 2008 Vladimir Prus * Copyright 2009 Niko Sams * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_VARIABLECOLLECTION_H #define KDEVPLATFORM_VARIABLECOLLECTION_H #include #include #include #include #include #include #include "../util/treemodel.h" #include "../util/treeitem.h" #include "../../interfaces/idocument.h" #include "../interfaces/idebugsession.h" #include "../../interfaces/idebugcontroller.h" namespace GDBDebugger { class GdbTest; } namespace KDevelop { class VariableToolTip; class KDEVPLATFORMDEBUGGER_EXPORT Variable : public TreeItem { Q_OBJECT friend class GDBDebugger::GdbTest; public: protected: Variable(TreeModel* model, TreeItem* parent, const QString& expression, const QString& display = {}); public: enum format_t { Natural, Binary, Octal, Decimal, Hexadecimal }; static format_t str2format(const QString& str); static QString format2str(format_t format); QString expression() const; bool inScope() const; void setInScope(bool v); void setValue(const QString &v); QString value() const; void setType(const QString& type); QString type() const; void setTopLevel(bool v); void setShowError(bool v); bool showError(); using TreeItem::setHasMore; using TreeItem::setHasMoreInitial; using TreeItem::appendChild; using TreeItem::deleteChildren; using TreeItem::isExpanded; using TreeItem::parent; using TreeItem::model; ~Variable() override; /* Connects this variable to debugger, fetching the current value and otherwise preparing this variable to be displayed everywhere. The attempt may fail, for example if the expression is invalid. Calls slot 'callbackMethod' in 'callback' to notify of the result. The slot should be taking 'bool ok' parameter. */ virtual void attachMaybe(QObject *callback = 0, const char *callbackMethod = 0) = 0; virtual bool canSetFormat() const { return false; } void setFormat(format_t format); format_t format() const { return m_format; } virtual void formatChanged(); // get/set 'changed' state, if the variable changed it will be highlighted bool isChanged() const { return changed_; } void setChanged(bool c); void resetChanged(); public slots: void die(); protected: bool topLevel() const { return topLevel_; } private: // TreeItem overrides QVariant data(int column, int role) const override; private: bool isPotentialProblematicValue() const; QString expression_; bool inScope_; bool topLevel_; bool changed_; bool showError_; format_t m_format; }; class KDEVPLATFORMDEBUGGER_EXPORT TooltipRoot : public TreeItem { + Q_OBJECT public: explicit TooltipRoot(TreeModel* model) : TreeItem(model) {} void init(Variable *var) { appendChild(var); } void fetchMoreChildren() override {} }; class KDEVPLATFORMDEBUGGER_EXPORT Watches : public TreeItem { + Q_OBJECT friend class GDBDebugger::GdbTest; public: Watches(TreeModel* model, TreeItem* parent); Variable* add(const QString& expression); void reinstall(); Variable *addFinishResult(const QString& convenienceVarible); void removeFinishResult(); void resetChanged(); using TreeItem::childCount; friend class VariableCollection; friend class IVariableController; private: QVariant data(int column, int role) const override; void fetchMoreChildren() override {} Variable* finishResult_; }; class KDEVPLATFORMDEBUGGER_EXPORT Locals : public TreeItem { + Q_OBJECT public: Locals(TreeModel* model, TreeItem* parent, const QString &name); QList updateLocals(QStringList locals); void resetChanged(); using TreeItem::deleteChildren; using TreeItem::setHasMore; friend class VariableCollection; friend class IVariableController; private: void fetchMoreChildren() override {} }; class KDEVPLATFORMDEBUGGER_EXPORT VariablesRoot : public TreeItem { + Q_OBJECT public: explicit VariablesRoot(TreeModel* model); Watches *watches() const { return watches_; } - Locals *locals(const QString &name = QLatin1String("Locals")); + Locals *locals(const QString &name = QStringLiteral("Locals")); QHash allLocals() const; void fetchMoreChildren() override {} void resetChanged(); private: Watches *watches_; QHash locals_; }; class VariableProvider : public KTextEditor::TextHintProvider { public: explicit VariableProvider(VariableCollection* collection); QString textHint(KTextEditor::View* view, const KTextEditor::Cursor& position) override; private: VariableCollection* m_collection; }; class KDEVPLATFORMDEBUGGER_EXPORT VariableCollection : public TreeModel { Q_OBJECT public: enum Column { NameColumn, ValueColumn, TypeColumn }; explicit VariableCollection(IDebugController* parent); ~VariableCollection() override; VariablesRoot* root() const { return universe_; } Watches* watches() const { return universe_->watches(); } Locals* locals(const QString &name = i18n("Locals")) const { return universe_->locals(name); } QHash allLocals() const { return universe_->allLocals(); } public Q_SLOTS: void variableWidgetShown(); void variableWidgetHidden(); private Q_SLOTS: void updateAutoUpdate(KDevelop::IDebugSession* session = 0); void textDocumentCreated( KDevelop::IDocument*); void viewCreated(KTextEditor::Document*, KTextEditor::View*); private: VariablesRoot* universe_; QPointer activeTooltip_; bool m_widgetVisible; friend class VariableProvider; }; } #endif diff --git a/debugger/variable/variabletooltip.cpp b/debugger/variable/variabletooltip.cpp index ce95f4617..fb1a14195 100644 --- a/debugger/variable/variabletooltip.cpp +++ b/debugger/variable/variabletooltip.cpp @@ -1,218 +1,220 @@ /* * 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) { 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() ); model_ = new TreeModel(QVector() << i18n("Name") << i18n("Value") << i18n("Type"), this); TooltipRoot* tr = new TooltipRoot(model_); model_->setRootItem(tr); var_ = ICore::self()->debugController()->currentSession()-> variableController()->createVariable( model_, tr, identifier); tr->init(var_); var_->attachMaybe(this, "variableCreated"); QVBoxLayout* l = new QVBoxLayout(this); l->setContentsMargins(0, 0, 0, 0); // setup proxy model proxy_ = new QSortFilterProxyModel; view_ = new AsyncTreeView(model_, proxy_, this); proxy_->setSourceModel(model_); view_->setModel(proxy_); view_->header()->resizeSection(0, 150); view_->header()->resizeSection(1, 90); view_->setSelectionBehavior(QAbstractItemView::SelectRows); view_->setSelectionMode(QAbstractItemView::SingleSelection); view_->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); view_->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Expanding); l->addWidget(view_); itemHeight_ = view_->indexRowSizeHint(model_->indexForItem(var_, 0)); connect(view_->verticalScrollBar(), &QScrollBar::rangeChanged, this, &VariableToolTip::slotRangeChanged); selection_ = view_->selectionModel(); selection_->select(model_->indexForItem(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) { view_->resizeColumns(); if (hasValue) { ActiveToolTip::showToolTip(this, 0.0); } else { close(); } } void VariableToolTip::slotLinkActivated(const QString& link) { Variable* v = var_; QItemSelection s = selection_->selection(); if (!s.empty()) { QModelIndex index = s.front().topLeft(); TreeItem *item = model_->itemForIndex(index); if (item) { Variable* v2 = dynamic_cast(item); if (v2) v = v2; } } IDebugSession *session = ICore::self()->debugController()->currentSession(); if (session && session->state() != IDebugSession::NotStartedState && session->state() != IDebugSession::EndedState) { - if (link == "add_watch") { + if (link == QLatin1String("add_watch")) { session->variableController()->addWatch(v); - } else if (link == "add_watchpoint") { + } 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*itemHeight_ < rect.bottom()) resize(width(), height() + max*itemHeight_); else { // Oh, well, I'm sorry, but here's the scrollbar you was // longing to see view_->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); } } } +#include "variabletooltip.moc" diff --git a/interfaces/contextmenuextension.cpp b/interfaces/contextmenuextension.cpp index 191e1832d..8db7f9d2f 100644 --- a/interfaces/contextmenuextension.cpp +++ b/interfaces/contextmenuextension.cpp @@ -1,189 +1,189 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2008 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 "contextmenuextension.h" #include #include #include #include namespace KDevelop { -const QString ContextMenuExtension::FileGroup = "FileGroup"; -const QString ContextMenuExtension::RefactorGroup = "RefactorGroup"; -const QString ContextMenuExtension::BuildGroup = "BuildGroup"; -const QString ContextMenuExtension::RunGroup = "RunGroup"; -const QString ContextMenuExtension::DebugGroup = "DebugGroup"; -const QString ContextMenuExtension::EditGroup = "EditGroup"; -const QString ContextMenuExtension::VcsGroup = "VcsGroup"; -const QString ContextMenuExtension::ProjectGroup = "ProjectGroup"; -const QString ContextMenuExtension::OpenEmbeddedGroup = "OpenEmbeddedGroup"; -const QString ContextMenuExtension::OpenExternalGroup = "OpenExternalGroup"; -const QString ContextMenuExtension::ExtensionGroup = "ExtensionGroup"; +const QString ContextMenuExtension::FileGroup = QStringLiteral("FileGroup"); +const QString ContextMenuExtension::RefactorGroup = QStringLiteral("RefactorGroup"); +const QString ContextMenuExtension::BuildGroup = QStringLiteral("BuildGroup"); +const QString ContextMenuExtension::RunGroup = QStringLiteral("RunGroup"); +const QString ContextMenuExtension::DebugGroup = QStringLiteral("DebugGroup"); +const QString ContextMenuExtension::EditGroup = QStringLiteral("EditGroup"); +const QString ContextMenuExtension::VcsGroup = QStringLiteral("VcsGroup"); +const QString ContextMenuExtension::ProjectGroup = QStringLiteral("ProjectGroup"); +const QString ContextMenuExtension::OpenEmbeddedGroup = QStringLiteral("OpenEmbeddedGroup"); +const QString ContextMenuExtension::OpenExternalGroup = QStringLiteral("OpenExternalGroup"); +const QString ContextMenuExtension::ExtensionGroup = QStringLiteral("ExtensionGroup"); class ContextMenuExtensionPrivate { public: QMap > extensions; }; ContextMenuExtension::ContextMenuExtension() : d(new ContextMenuExtensionPrivate) { } ContextMenuExtension::~ContextMenuExtension() { delete d; } ContextMenuExtension::ContextMenuExtension( const ContextMenuExtension& rhs ) : d( new ContextMenuExtensionPrivate ) { d->extensions = rhs.d->extensions; } ContextMenuExtension& ContextMenuExtension::operator=( const ContextMenuExtension& rhs ) { if( this == &rhs ) return *this; d->extensions = rhs.d->extensions; return *this; } QList ContextMenuExtension::actions( const QString& group ) const { return d->extensions.value( group, QList() ); } void ContextMenuExtension::addAction( const QString& group, QAction* action ) { if( !d->extensions.contains( group ) ) { d->extensions.insert( group, QList() << action ); } else { d->extensions[group].append( action ); } } void ContextMenuExtension::populateMenu(QMenu* menu, const QList& extensions) { QList buildActions; QList vcsActions; QList extActions; QList refactorActions; QList debugActions; foreach( const ContextMenuExtension &ext, extensions ) { foreach( QAction* act, ext.actions( ContextMenuExtension::BuildGroup ) ) { buildActions << act; } foreach( QAction* act, ext.actions( ContextMenuExtension::VcsGroup ) ) { vcsActions << act; } foreach( QAction* act, ext.actions( ContextMenuExtension::ExtensionGroup ) ) { extActions << act; } foreach( QAction* act, ext.actions( ContextMenuExtension::RefactorGroup ) ) { refactorActions << act; } foreach( QAction* act, ext.actions( ContextMenuExtension::DebugGroup ) ) { debugActions << act; } } if(!buildActions.isEmpty()) { foreach(QAction* action, buildActions) menu->addAction(action); menu->addSeparator(); } foreach( const ContextMenuExtension &ext, extensions ) { foreach( QAction* act, ext.actions( ContextMenuExtension::FileGroup ) ) { menu->addAction( act ); } menu->addSeparator(); foreach( QAction* act, ext.actions( ContextMenuExtension::EditGroup ) ) { menu->addAction( act ); } } QMenu* debugmenu = menu; if( debugActions.count() > 1 ) { debugmenu = menu->addMenu( i18n("Debug") ); } foreach( QAction* act, debugActions ) { debugmenu->addAction( act ); } menu->addSeparator(); QMenu* refactormenu = menu; if( refactorActions.count() > 1 ) { refactormenu = menu->addMenu( i18n("Refactor") ); } foreach( QAction* act, refactorActions ) { refactormenu->addAction( act ); } menu->addSeparator(); QMenu* vcsmenu = menu; if( vcsActions.count() > 1 ) { vcsmenu = menu->addMenu( i18n("Version Control") ); } foreach( QAction* act, vcsActions ) { vcsmenu->addAction( act ); } menu->addSeparator(); foreach( QAction* act, extActions ) { menu->addAction( act ); } } } diff --git a/interfaces/ipartcontroller.cpp b/interfaces/ipartcontroller.cpp index 5948371b1..80a2002bf 100644 --- a/interfaces/ipartcontroller.cpp +++ b/interfaces/ipartcontroller.cpp @@ -1,98 +1,98 @@ /*************************************************************************** * 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 "ipartcontroller.h" #include #include #include #include #include namespace KDevelop { IPartController::IPartController( QWidget* toplevel ) : KParts::PartManager( toplevel, 0 ) { } KPluginFactory* IPartController::findPartFactory ( const QString& mimetype, const QString& parttype, const QString& preferredName ) { // parttype may be a interface type not derived from KParts/ReadOnlyPart const KService::List offers = KMimeTypeTrader::self()->query( mimetype, - QString::fromLatin1( "KParts/ReadOnlyPart" ), - QString::fromLatin1( "'%1' in ServiceTypes" ).arg( parttype ) ); + QStringLiteral( "KParts/ReadOnlyPart" ), + QStringLiteral( "'%1' in ServiceTypes" ).arg( parttype ) ); if ( ! offers.isEmpty() ) { KService::Ptr ptr; // if there is a preferred plugin we'll take it if ( !preferredName.isEmpty() ) { KService::List::ConstIterator it; for ( it = offers.constBegin(); it != offers.constEnd(); ++it ) { if ( ( *it ) ->desktopEntryName() == preferredName ) { ptr = ( *it ); break; } } } // else we just take the first in the list if ( !ptr ) { ptr = offers.first(); } KPluginLoader loader( ptr->library() ); return loader.factory(); } return 0; } KParts::Part* IPartController::createPart ( const QString& mimetype, const QString& prefName ) { const uint length = 1; static const char* const services[length] = { // Disable read/write parts until we can support them /*"KParts/ReadWritePart",*/ "KParts/ReadOnlyPart" }; KParts::Part* part = 0; for ( uint i = 0; i < length; ++i ) { KPluginFactory* editorFactory = findPartFactory( mimetype, QString::fromLatin1(services[ i ]), prefName ); if ( editorFactory ) { part = editorFactory->create( 0, this ); break; } } return part; } } diff --git a/interfaces/iplugin.cpp b/interfaces/iplugin.cpp index 76da996db..b0994f5c3 100644 --- a/interfaces/iplugin.cpp +++ b/interfaces/iplugin.cpp @@ -1,213 +1,213 @@ /* 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) : 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(QLatin1String("has_project"), reverse); + q->stateChanged(QStringLiteral("has_project"), reverse); } IPlugin *q; ICore *core; QVector m_extensions; }; 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. 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; } } QVector KDevelop::IPlugin::extensions( ) const { return d->m_extensions; } void KDevelop::IPlugin::addExtension( const QByteArray& ext ) { d->m_extensions << ext; } KDevelop::ContextMenuExtension KDevelop::IPlugin::contextMenuExtension( KDevelop::Context* ) { return KDevelop::ContextMenuExtension(); } void KDevelop::IPlugin::initializeGuiState() { } class CustomXmlGUIClient : public KXMLGUIClient { public: CustomXmlGUIClient(const QString& componentName) { // TODO KF5: Get rid off this setComponentName(componentName, componentName); } void setXmlFile(QString file) { KXMLGUIClient::setXMLFile(file); } }; KXMLGUIClient* KDevelop::IPlugin::createGUIForMainWindow(Sublime::MainWindow* window) { CustomXmlGUIClient* ret = new CustomXmlGUIClient(componentName()); QString file; createActionsForMainWindow(window, file, *ret->actionCollection()); if(!ret->actionCollection()->isEmpty()) { Q_ASSERT(!file.isEmpty()); //A file must have been set ret->setXmlFile(file); } else { delete ret; ret = 0; } return ret; } void KDevelop::IPlugin::createActionsForMainWindow( Sublime::MainWindow* /*window*/, QString& /*xmlFile*/, KActionCollection& /*actions*/ ) { } bool KDevelop::IPlugin::hasError() const { return false; } QString KDevelop::IPlugin::errorDescription() const { return QString(); } 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/interfaces/isourceformatter.cpp b/interfaces/isourceformatter.cpp index 7a295ca2a..14ba47b77 100644 --- a/interfaces/isourceformatter.cpp +++ b/interfaces/isourceformatter.cpp @@ -1,197 +1,197 @@ /* This file is part of KDevelop Copyright (C) 2008 Cédric Pasteur This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "isourceformatter.h" #include #include namespace KDevelop { SettingsWidget::SettingsWidget(QWidget *parent) : QWidget(parent) { } SettingsWidget::~SettingsWidget() { } ISourceFormatter::~ISourceFormatter() { } SourceFormatterStyle::SourceFormatterStyle() : m_usePreview(false) { }; SourceFormatterStyle::SourceFormatterStyle(const QString &name) : m_usePreview(false) , m_name(name) { } void SourceFormatterStyle::setContent(const QString &content) { m_content = content; } void SourceFormatterStyle::setCaption(const QString &caption) { m_caption = caption; } QString SourceFormatterStyle::content() const { return m_content; } QString SourceFormatterStyle::caption() const { return m_caption; } QString SourceFormatterStyle::name() const { return m_name; } QString SourceFormatterStyle::description() const { return m_description; } void SourceFormatterStyle::setDescription(const QString &desc) { m_description = desc; } bool SourceFormatterStyle::usePreview() const { return m_usePreview; } void SourceFormatterStyle::setUsePreview(bool use) { m_usePreview = use; } void SourceFormatterStyle::setMimeTypes(const SourceFormatterStyle::MimeList& types) { m_mimeTypes = types; } void SourceFormatterStyle::setMimeTypes(const QStringList& types) { - for ( auto t: types ) { + foreach ( auto& t, types ) { auto items = t.split('|'); if ( items.size() != 2 ) { continue; } m_mimeTypes << MimeHighlightPair{items.at(0), items.at(1)}; } } void SourceFormatterStyle::setOverrideSample(const QString &sample) { m_overrideSample = sample; } QString SourceFormatterStyle::overrideSample() const { return m_overrideSample; } SourceFormatterStyle::MimeList SourceFormatterStyle::mimeTypes() const { return m_mimeTypes; } QVariant SourceFormatterStyle::mimeTypesVariant() const { QStringList result; for ( const auto& item: m_mimeTypes ) { result << item.mimeType + "|" + item.highlightMode; } return QVariant::fromValue(result); } bool SourceFormatterStyle::supportsLanguage(const QString &language) const { for ( const auto& item: m_mimeTypes ) { if ( item.highlightMode == language ) { return true; } } return false; } QString SourceFormatterStyle::modeForMimetype(const QMimeType& mime) const { - for (const auto& item : mimeTypes()) { + foreach (const auto& item, mimeTypes()) { if (mime.inherits(item.mimeType)) { return item.highlightMode; } } return QString(); } void SourceFormatterStyle::copyDataFrom(SourceFormatterStyle *other) { m_content = other->content(); m_mimeTypes = other->mimeTypes(); m_overrideSample = other->overrideSample(); } QString ISourceFormatter::optionMapToString(const QMap &map) { QString options; QMap::const_iterator it = map.constBegin(); for (; it != map.constEnd(); ++it) { options += it.key(); options += '='; options += it.value().toString(); options += ','; } return options; } QMap ISourceFormatter::stringToOptionMap(const QString &options) { QMap map; QStringList pairs = options.split(',', QString::SkipEmptyParts); QStringList::const_iterator it; for (it = pairs.constBegin(); it != pairs.constEnd(); ++it) { QStringList bits = (*it).split('='); map[bits[0]] = bits[1]; } return map; } QString ISourceFormatter::missingExecutableMessage(const QString &name) { return i18n("The executable %1 cannot be found. Please make sure" " it is installed and can be executed.
" "The plugin will not work until you fix this problem.", "" + name + ""); } } // kate: indent-mode cstyle; space-indent off; tab-width 4; diff --git a/language/assistant/renameaction.cpp b/language/assistant/renameaction.cpp index 8d1c383b1..994ec46dc 100644 --- a/language/assistant/renameaction.cpp +++ b/language/assistant/renameaction.cpp @@ -1,114 +1,114 @@ /* Copyright 2012 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 "renameaction.h" #include #include #include #include #include #include #include #include #include using namespace KDevelop; QVector RevisionedFileRanges::convert(const QMap >& uses) { QVector ret(uses.size()); auto insertIt = ret.begin(); for (auto it = uses.constBegin(); it != uses.constEnd(); ++it, ++insertIt) { insertIt->file = it.key(); insertIt->ranges = it.value(); DocumentChangeTracker* tracker = ICore::self()->languageController()->backgroundParser()->trackerForUrl(it.key()); if (tracker) { insertIt->revision = tracker->revisionAtLastReset(); } } return ret; } struct RenameAction::Private { Private() {} Identifier m_oldDeclarationName; QString m_newDeclarationName; QVector m_oldDeclarationUses; }; RenameAction::RenameAction(const Identifier& oldDeclarationName, const QString& newDeclarationName, const QVector& oldDeclarationUses) : d(new Private) { d->m_oldDeclarationName = oldDeclarationName; d->m_newDeclarationName = newDeclarationName; d->m_oldDeclarationUses = oldDeclarationUses; } RenameAction::~RenameAction() { } QString RenameAction::description() const { return i18n("Rename \"%1\" to \"%2\"", d->m_oldDeclarationName.toString(), d->m_newDeclarationName); } QString RenameAction::newDeclarationName() const { return d->m_newDeclarationName; } QString RenameAction::oldDeclarationName() const { return d->m_oldDeclarationName.toString(); } void RenameAction::execute() { DocumentChangeSet changes; foreach(const RevisionedFileRanges& ranges, d->m_oldDeclarationUses) { - foreach (const RangeInRevision &range, ranges.ranges) { + foreach (const RangeInRevision range, ranges.ranges) { KTextEditor::Range currentRange; if (ranges.revision && ranges.revision->valid()) { currentRange = ranges.revision->transformToCurrentRevision(range); } else { currentRange = range.castToSimpleRange(); } DocumentChange useRename(ranges.file, currentRange, d->m_oldDeclarationName.toString(), d->m_newDeclarationName); changes.addChange( useRename ); changes.setReplacementPolicy(DocumentChangeSet::WarnOnFailedChange); } } DocumentChangeSet::ChangeResult result = changes.applyAllChanges(); if (!result) { KMessageBox::error(0, i18n("Failed to apply changes: %1", result.m_failureReason)); } emit executed(this); } diff --git a/language/assistant/renameassistant.cpp b/language/assistant/renameassistant.cpp index 62f58a0cb..048f2d1ac 100644 --- a/language/assistant/renameassistant.cpp +++ b/language/assistant/renameassistant.cpp @@ -1,223 +1,223 @@ /* 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::View* view, const KTextEditor::Range& changed) { const KTextEditor::Cursor cursor(changed.start()); Declaration* declaration = DUChainUtils::itemUnderCursor(view->document()->url(), cursor); //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(view->document()->url(), KTextEditor::Cursor(cursor.line(), cursor.column()-1)); } //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 0; } return declaration; } } struct RenameAssistant::Private { 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; }; 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::View* view, const KTextEditor::Range& invocationRange, const QString& removedText) { clearActions(); if (!supportedLanguage()->refactoring()) { qCWarning(LANGUAGE) << "Refactoring not supported. Aborting."; return; } if (!view) 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(view->document()->text(invocationRange))) { d->reset(); return; } const QUrl url = view->document()->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(view, 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()) { + foreach(const RangeInRevision range, it.value()) { KTextEditor::Range currentRange = declAtCursor->transformFromLocalRevision(range); if(currentRange.isEmpty() || view->document()->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 = view->document()->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(); } #include "moc_renameassistant.cpp" diff --git a/language/backgroundparser/backgroundparser.cpp b/language/backgroundparser/backgroundparser.cpp index affd601a9..eecbfbbed 100644 --- a/language/backgroundparser/backgroundparser.cpp +++ b/language/backgroundparser/backgroundparser.cpp @@ -1,831 +1,831 @@ /* * This file is part of KDevelop * * Copyright 2006 Adam Treat * Copyright 2007 Kris Wong * Copyright 2007-2008 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "backgroundparser.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "util/debug.h" #include "parsejob.h" #include const bool separateThreadForHighPriority = true; /** * Elides string in @p path, e.g. "VEEERY/LONG/PATH" -> ".../LONG/PATH" * - probably much faster than QFontMetrics::elidedText() * - we dont need a widget context * - takes path separators into account * * @p width Maximum number of characters * * TODO: Move to kdevutil? */ static QString elidedPathLeft(const QString& path, int width) { static const QChar separator = QDir::separator(); - static const QString placeholder = "..."; + static const QString placeholder = QStringLiteral("..."); if (path.size() <= width) { return path; } int start = (path.size() - width) + placeholder.size(); int pos = path.indexOf(separator, start); if (pos == -1) { pos = start; // no separator => just cut off the path at the beginning } Q_ASSERT(path.size() - pos >= 0 && path.size() - pos <= width); QStringRef elidedText = path.rightRef(path.size() - pos); QString result = placeholder; result.append(elidedText); return result; } namespace { /** * @return true if @p url is non-empty, valid and has a clean path, false otherwise. */ inline bool isValidURL(const KDevelop::IndexedString& url) { if (url.isEmpty()) { return false; } QUrl original = url.toUrl(); if (!original.isValid() || original.isRelative() || original.fileName().isEmpty()) { qCWarning(LANGUAGE) << "INVALID URL ENCOUNTERED:" << url << original; return false; } QUrl cleaned = original.adjusted(QUrl::NormalizePathSegments); return original == cleaned; } } namespace KDevelop { class BackgroundParserPrivate { public: BackgroundParserPrivate(BackgroundParser *parser, ILanguageController *languageController) :m_parser(parser), m_languageController(languageController), m_shuttingDown(false), m_mutex(QMutex::Recursive) { parser->d = this; //Set this so we can safely call back BackgroundParser from within loadSettings() m_timer.setSingleShot(true); m_delay = 500; m_threads = 1; m_doneParseJobs = 0; m_maxParseJobs = 0; m_neededPriority = BackgroundParser::WorstPriority; ThreadWeaver::setDebugLevel(true, 1); QObject::connect(&m_timer, &QTimer::timeout, m_parser, &BackgroundParser::parseDocuments); } void startTimerThreadSafe() { QMetaObject::invokeMethod(m_parser, "startTimer", Qt::QueuedConnection); } ~BackgroundParserPrivate() { // Release dequeued jobs QHashIterator it = m_parseJobs; while (it.hasNext()) { it.next(); delete it.value(); } } // Non-mutex guarded functions, only call with m_mutex acquired. void parseDocumentsInternal() { if(m_shuttingDown) return; // Create delayed jobs, that is, jobs for documents which have been changed // by the user. QList jobs; // Before starting a new job, first wait for all higher-priority ones to finish. // That way, parse job priorities can be used for dependency handling. int bestRunningPriority = BackgroundParser::WorstPriority; foreach (const ThreadWeaver::QObjectDecorator* decorator, m_parseJobs) { const ParseJob* parseJob = dynamic_cast(decorator->job()); Q_ASSERT(parseJob); if (parseJob->respectsSequentialProcessing() && parseJob->parsePriority() < bestRunningPriority) { bestRunningPriority = parseJob->parsePriority(); } } bool done = false; for (QMap >::Iterator it1 = m_documentsForPriority.begin(); it1 != m_documentsForPriority.end(); ++it1 ) { if(it1.key() > m_neededPriority) break; //The priority is not good enough to be processed right now for(QSet::Iterator it = it1.value().begin(); it != it1.value().end();) { //Only create parse-jobs for up to thread-count * 2 documents, so we don't fill the memory unnecessarily if(m_parseJobs.count() >= m_threads+1 || (m_parseJobs.count() >= m_threads && !separateThreadForHighPriority) ) break; if(m_parseJobs.count() >= m_threads && it1.key() > BackgroundParser::NormalPriority && !specialParseJob) break; //The additional parsing thread is reserved for higher priority parsing // When a document is scheduled for parsing while it is being parsed, it will be parsed // again once the job finished, but not now. if (m_parseJobs.contains(*it) ) { ++it; continue; } Q_ASSERT(m_documents.contains(*it)); const DocumentParsePlan& parsePlan = m_documents[*it]; // If the current job requires sequential processing, but not all jobs with a better priority have been // completed yet, it will not be created now. if ( parsePlan.sequentialProcessingFlags() & ParseJob::RequiresSequentialProcessing && parsePlan.priority() > bestRunningPriority ) { ++it; continue; } qCDebug(LANGUAGE) << "creating parse-job" << *it << "new count of active parse-jobs:" << m_parseJobs.count() + 1; const QString elidedPathString = elidedPathLeft(it->str(), 70); emit m_parser->showMessage(m_parser, i18n("Parsing: %1", elidedPathString)); ThreadWeaver::QObjectDecorator* decorator = createParseJob(*it, parsePlan.features(), parsePlan.notifyWhenReady(), parsePlan.priority()); if(m_parseJobs.count() == m_threads+1 && !specialParseJob) specialParseJob = decorator; //This parse-job is allocated into the reserved thread if (decorator) { ParseJob* parseJob = dynamic_cast(decorator->job()); parseJob->setSequentialProcessingFlags(parsePlan.sequentialProcessingFlags()); jobs.append(ThreadWeaver::JobPointer(decorator)); // update the currently best processed priority, if the created job respects sequential processing if ( parsePlan.sequentialProcessingFlags() & ParseJob::RespectsSequentialProcessing && parsePlan.priority() < bestRunningPriority) { bestRunningPriority = parsePlan.priority(); } } // Remove all mentions of this document. foreach(const DocumentParseTarget& target, parsePlan.targets) { if (target.priority != it1.key()) { m_documentsForPriority[target.priority].remove(*it); } } m_documents.remove(*it); it = it1.value().erase(it); --m_maxParseJobs; //We have added one when putting the document into m_documents if(!m_documents.isEmpty()) { // Only try creating one parse-job at a time, else we might iterate through thousands of files // without finding a language-support, and block the UI for a long time. // If there are more documents to parse, instantly re-try. QMetaObject::invokeMethod(m_parser, "parseDocuments", Qt::QueuedConnection); done = true; break; } } if ( done ) break; } // Ok, enqueueing is fine because m_parseJobs contains all of the jobs now - foreach (ThreadWeaver::JobPointer job, jobs) + foreach (const ThreadWeaver::JobPointer& job, jobs) m_weaver.enqueue(job); m_parser->updateProgressBar(); //We don't hide the progress-bar in updateProgressBar, so it doesn't permanently flash when a document is reparsed again and again. if(m_doneParseJobs == m_maxParseJobs || (m_neededPriority == BackgroundParser::BestPriority && m_weaver.queueLength() == 0)) { emit m_parser->hideProgress(m_parser); } } ThreadWeaver::QObjectDecorator* createParseJob(const IndexedString& url, TopDUContext::Features features, const QList >& notifyWhenReady, int priority = 0) { ///FIXME: use IndexedString in the other APIs as well! Esp. for createParseJob! QUrl qUrl = url.toUrl(); auto languages = m_languageController->languagesForUrl(qUrl); - foreach (const auto& language, languages) { + foreach (const auto language, languages) { if(!language) { qCWarning(LANGUAGE) << "got zero language for" << qUrl; continue; } ParseJob* job = language->createParseJob(url); if (!job) { continue; // Language part did not produce a valid ParseJob. } job->setParsePriority(priority); job->setMinimumFeatures(features); job->setNotifyWhenReady(notifyWhenReady); ThreadWeaver::QObjectDecorator* decorator = new ThreadWeaver::QObjectDecorator(job); QObject::connect(decorator, &ThreadWeaver::QObjectDecorator::done, m_parser, &BackgroundParser::parseComplete); QObject::connect(decorator, &ThreadWeaver::QObjectDecorator::failed, m_parser, &BackgroundParser::parseComplete); QObject::connect(job, &ParseJob::progress, m_parser, &BackgroundParser::parseProgress, Qt::QueuedConnection); m_parseJobs.insert(url, decorator); ++m_maxParseJobs; // TODO more thinking required here to support multiple parse jobs per url (where multiple language plugins want to parse) return decorator; } if(languages.isEmpty()) qCDebug(LANGUAGE) << "found no languages for url" << qUrl; else qCDebug(LANGUAGE) << "could not create parse-job for url" << qUrl; //Notify that we failed typedef QPointer Notify; foreach(const Notify& n, notifyWhenReady) if(n) QMetaObject::invokeMethod(n.data(), "updateReady", Qt::QueuedConnection, Q_ARG(KDevelop::IndexedString, url), Q_ARG(KDevelop::ReferencedTopDUContext, ReferencedTopDUContext())); return nullptr; } void loadSettings() { ///@todo re-load settings when they have been changed! Q_ASSERT(ICore::self()->activeSession()); KConfigGroup config(ICore::self()->activeSession()->config(), "Background Parser"); // stay backwards compatible KConfigGroup oldConfig(KSharedConfig::openConfig(), "Background Parser"); #define BACKWARDS_COMPATIBLE_ENTRY(entry, default) \ config.readEntry(entry, oldConfig.readEntry(entry, default)) m_delay = BACKWARDS_COMPATIBLE_ENTRY("Delay", 500); m_timer.setInterval(m_delay); m_threads = 0; m_parser->setThreadCount(BACKWARDS_COMPATIBLE_ENTRY("Number of Threads", QThread::idealThreadCount())); resume(); if (BACKWARDS_COMPATIBLE_ENTRY("Enabled", true)) { m_parser->enableProcessing(); } else { m_parser->disableProcessing(); } } void suspend() { bool s = m_weaver.state()->stateId() == ThreadWeaver::Suspended || m_weaver.state()->stateId() == ThreadWeaver::Suspending; if (s) { // Already suspending return; } m_timer.stop(); m_weaver.suspend(); } void resume() { bool s = m_weaver.state()->stateId() == ThreadWeaver::Suspended || m_weaver.state()->stateId() == ThreadWeaver::Suspending; if (m_timer.isActive() && !s) { // Not suspending return; } m_timer.start(m_delay); m_weaver.resume(); } BackgroundParser *m_parser; ILanguageController* m_languageController; //Current parse-job that is executed in the additional thread QPointer specialParseJob; QTimer m_timer; int m_delay; int m_threads; bool m_shuttingDown; struct DocumentParseTarget { QPointer notifyWhenReady; int priority; TopDUContext::Features features; ParseJob::SequentialProcessingFlags sequentialProcessingFlags; bool operator==(const DocumentParseTarget& rhs) const { return notifyWhenReady == rhs.notifyWhenReady && priority == rhs.priority && features == rhs.features; } }; struct DocumentParsePlan { QSet targets; ParseJob::SequentialProcessingFlags sequentialProcessingFlags() const { //Pick the strictest possible flags ParseJob::SequentialProcessingFlags ret = ParseJob::IgnoresSequentialProcessing; foreach(const DocumentParseTarget &target, targets) { ret |= target.sequentialProcessingFlags; } return ret; } int priority() const { //Pick the best priority int ret = BackgroundParser::WorstPriority; foreach(const DocumentParseTarget &target, targets) { if(target.priority < ret) { ret = target.priority; } } return ret; } TopDUContext::Features features() const { //Pick the best features TopDUContext::Features ret = (TopDUContext::Features)0; foreach(const DocumentParseTarget &target, targets) { ret = (TopDUContext::Features) (ret | target.features); } return ret; } QList > notifyWhenReady() const { QList > ret; foreach(const DocumentParseTarget &target, targets) if(target.notifyWhenReady) ret << target.notifyWhenReady; return ret; } }; // A list of documents that are planned to be parsed, and their priority QHash m_documents; // The documents ordered by priority QMap > m_documentsForPriority; // Currently running parse jobs QHash m_parseJobs; // A change tracker for each managed document QHash m_managed; // The url for each managed document. Those may temporarily differ from the real url. QHash m_managedTextDocumentUrls; // Projects currently in progress of loading QSet m_loadingProjects; ThreadWeaver::Queue m_weaver; QMutex m_mutex; int m_maxParseJobs; int m_doneParseJobs; QHash m_jobProgress; int m_neededPriority; //The minimum priority needed for processed jobs }; inline uint qHash(const BackgroundParserPrivate::DocumentParseTarget& target) { return target.features * 7 + target.priority * 13 + target.sequentialProcessingFlags * 17 + reinterpret_cast(target.notifyWhenReady.data()); }; BackgroundParser::BackgroundParser(ILanguageController *languageController) : QObject(languageController), d(new BackgroundParserPrivate(this, languageController)) { Q_ASSERT(ICore::self()->documentController()); connect(ICore::self()->documentController(), &IDocumentController::documentLoaded, this, &BackgroundParser::documentLoaded); connect(ICore::self()->documentController(), &IDocumentController::documentUrlChanged, this, &BackgroundParser::documentUrlChanged); connect(ICore::self()->documentController(), &IDocumentController::documentClosed, this, &BackgroundParser::documentClosed); connect(ICore::self(), &ICore::aboutToShutdown, this, &BackgroundParser::aboutToQuit); bool connected = QObject::connect(ICore::self()->projectController(), &IProjectController::projectAboutToBeOpened, this, &BackgroundParser::projectAboutToBeOpened); Q_ASSERT(connected); connected = QObject::connect(ICore::self()->projectController(), &IProjectController::projectOpened, this, &BackgroundParser::projectOpened); Q_ASSERT(connected); connected = QObject::connect(ICore::self()->projectController(), &IProjectController::projectOpeningAborted, this, &BackgroundParser::projectOpeningAborted); Q_ASSERT(connected); Q_UNUSED(connected); } void BackgroundParser::aboutToQuit() { d->m_shuttingDown = true; } BackgroundParser::~BackgroundParser() { delete d; } QString BackgroundParser::statusName() const { return i18n("Background Parser"); } void BackgroundParser::loadSettings() { d->loadSettings(); } void BackgroundParser::parseProgress(KDevelop::ParseJob* job, float value, QString text) { Q_UNUSED(text) d->m_jobProgress[job] = value; updateProgressBar(); } void BackgroundParser::revertAllRequests(QObject* notifyWhenReady) { QMutexLocker lock(&d->m_mutex); for(QHash::iterator it = d->m_documents.begin(); it != d->m_documents.end(); ) { d->m_documentsForPriority[it.value().priority()].remove(it.key()); foreach ( const BackgroundParserPrivate::DocumentParseTarget& target, (*it).targets ) { if ( notifyWhenReady && target.notifyWhenReady.data() == notifyWhenReady ) { (*it).targets.remove(target); } } if((*it).targets.isEmpty()) { it = d->m_documents.erase(it); --d->m_maxParseJobs; continue; } d->m_documentsForPriority[it.value().priority()].insert(it.key()); ++it; } } void BackgroundParser::addDocument(const IndexedString& url, TopDUContext::Features features, int priority, QObject* notifyWhenReady, ParseJob::SequentialProcessingFlags flags) { // qCDebug(LANGUAGE) << "BackgroundParser::addDocument" << url.toUrl(); Q_ASSERT(isValidURL(url)); QMutexLocker lock(&d->m_mutex); { BackgroundParserPrivate::DocumentParseTarget target; target.priority = priority; target.features = features; target.sequentialProcessingFlags = flags; target.notifyWhenReady = QPointer(notifyWhenReady); QHash::iterator it = d->m_documents.find(url); if (it != d->m_documents.end()) { //Update the stored plan d->m_documentsForPriority[it.value().priority()].remove(url); it.value().targets << target; d->m_documentsForPriority[it.value().priority()].insert(url); }else{ // qCDebug(LANGUAGE) << "BackgroundParser::addDocument: queuing" << cleanedUrl; d->m_documents[url].targets << target; d->m_documentsForPriority[d->m_documents[url].priority()].insert(url); ++d->m_maxParseJobs; //So the progress-bar waits for this document } d->startTimerThreadSafe(); } } void BackgroundParser::removeDocument(const IndexedString& url, QObject* notifyWhenReady) { Q_ASSERT(isValidURL(url)); QMutexLocker lock(&d->m_mutex); if(d->m_documents.contains(url)) { d->m_documentsForPriority[d->m_documents[url].priority()].remove(url); foreach(const BackgroundParserPrivate::DocumentParseTarget& target, d->m_documents[url].targets) { if(target.notifyWhenReady.data() == notifyWhenReady) { d->m_documents[url].targets.remove(target); } } if(d->m_documents[url].targets.isEmpty()) { d->m_documents.remove(url); --d->m_maxParseJobs; }else{ //Insert with an eventually different priority d->m_documentsForPriority[d->m_documents[url].priority()].insert(url); } } } void BackgroundParser::parseDocuments() { if (!d->m_loadingProjects.empty()) { startTimer(); return; } QMutexLocker lock(&d->m_mutex); d->parseDocumentsInternal(); } void BackgroundParser::parseComplete(const ThreadWeaver::JobPointer& job) { auto decorator = dynamic_cast(job.data()); Q_ASSERT(decorator); ParseJob* parseJob = dynamic_cast(decorator->job()); Q_ASSERT(parseJob); emit parseJobFinished(parseJob); { QMutexLocker lock(&d->m_mutex); d->m_parseJobs.remove(parseJob->document()); d->m_jobProgress.remove(parseJob); ++d->m_doneParseJobs; updateProgressBar(); } //Continue creating more parse-jobs QMetaObject::invokeMethod(this, "parseDocuments", Qt::QueuedConnection); } void BackgroundParser::disableProcessing() { setNeededPriority(BestPriority); } void BackgroundParser::enableProcessing() { setNeededPriority(WorstPriority); } int BackgroundParser::priorityForDocument(const IndexedString& url) const { Q_ASSERT(isValidURL(url)); QMutexLocker lock(&d->m_mutex); return d->m_documents[url].priority(); } bool BackgroundParser::isQueued(const IndexedString& url) const { Q_ASSERT(isValidURL(url)); QMutexLocker lock(&d->m_mutex); return d->m_documents.contains(url); } int BackgroundParser::queuedCount() const { QMutexLocker lock(&d->m_mutex); return d->m_documents.count(); } bool BackgroundParser::isIdle() const { QMutexLocker lock(&d->m_mutex); return d->m_documents.isEmpty() && d->m_weaver.isIdle(); } void BackgroundParser::setNeededPriority(int priority) { QMutexLocker lock(&d->m_mutex); d->m_neededPriority = priority; d->startTimerThreadSafe(); } void BackgroundParser::suspend() { d->suspend(); emit hideProgress(this); } void BackgroundParser::resume() { d->resume(); updateProgressBar(); } void BackgroundParser::updateProgressBar() { if (d->m_doneParseJobs >= d->m_maxParseJobs) { if(d->m_doneParseJobs > d->m_maxParseJobs) { qCDebug(LANGUAGE) << "m_doneParseJobs larger than m_maxParseJobs:" << d->m_doneParseJobs << d->m_maxParseJobs; } d->m_doneParseJobs = 0; d->m_maxParseJobs = 0; } else { float additionalProgress = 0; for(QHash::const_iterator it = d->m_jobProgress.constBegin(); it != d->m_jobProgress.constEnd(); ++it) additionalProgress += *it; emit showProgress(this, 0, d->m_maxParseJobs*1000, (additionalProgress + d->m_doneParseJobs)*1000); } } ParseJob* BackgroundParser::parseJobForDocument(const IndexedString& document) const { Q_ASSERT(isValidURL(document)); QMutexLocker lock(&d->m_mutex); auto decorator = d->m_parseJobs.value(document); return decorator ? dynamic_cast(decorator->job()) : nullptr; } void BackgroundParser::setThreadCount(int threadCount) { if (d->m_threads != threadCount) { d->m_threads = threadCount; d->m_weaver.setMaximumNumberOfThreads(d->m_threads+1); //1 Additional thread for high-priority parsing } } int BackgroundParser::threadCount() const { return d->m_threads; } void BackgroundParser::setDelay(int miliseconds) { if (d->m_delay != miliseconds) { d->m_delay = miliseconds; d->m_timer.setInterval(d->m_delay); } } QList< IndexedString > BackgroundParser::managedDocuments() { QMutexLocker l(&d->m_mutex); return d->m_managed.keys(); } DocumentChangeTracker* BackgroundParser::trackerForUrl(const KDevelop::IndexedString& url) const { if (url.isEmpty()) { // this happens e.g. when setting the final location of a problem that is not // yet associated with a top ctx. return 0; } Q_ASSERT(isValidURL(url)); QMutexLocker l(&d->m_mutex); return d->m_managed.value(url, 0); } void BackgroundParser::documentClosed(IDocument* document) { QMutexLocker l(&d->m_mutex); if(document->textDocument()) { KTextEditor::Document* textDocument = document->textDocument(); if(!d->m_managedTextDocumentUrls.contains(textDocument)) return; // Probably the document had an invalid url, and thus it wasn't added to the background parser Q_ASSERT(d->m_managedTextDocumentUrls.contains(textDocument)); IndexedString url(d->m_managedTextDocumentUrls[textDocument]); Q_ASSERT(d->m_managed.contains(url)); qCDebug(LANGUAGE) << "removing" << url.str() << "from background parser"; delete d->m_managed[url]; d->m_managedTextDocumentUrls.remove(textDocument); d->m_managed.remove(url); } } void BackgroundParser::documentLoaded( IDocument* document ) { QMutexLocker l(&d->m_mutex); if(document->textDocument() && document->textDocument()->url().isValid()) { KTextEditor::Document* textDocument = document->textDocument(); IndexedString url(document->url()); // Some debugging because we had issues with this if(d->m_managed.contains(url) && d->m_managed[url]->document() == textDocument) { qCDebug(LANGUAGE) << "Got redundant documentLoaded from" << document->url() << textDocument; return; } qCDebug(LANGUAGE) << "Creating change tracker for " << document->url(); Q_ASSERT(!d->m_managed.contains(url)); Q_ASSERT(!d->m_managedTextDocumentUrls.contains(textDocument)); d->m_managedTextDocumentUrls[textDocument] = url; d->m_managed.insert(url, new DocumentChangeTracker(textDocument)); }else{ qCDebug(LANGUAGE) << "NOT creating change tracker for" << document->url(); } } void BackgroundParser::documentUrlChanged(IDocument* document) { documentClosed(document); // Only call documentLoaded if the file wasn't renamed to a filename that is already tracked. if(document->textDocument() && !d->m_managed.contains(IndexedString(document->textDocument()->url()))) documentLoaded(document); } void BackgroundParser::startTimer() { d->m_timer.start(d->m_delay); } void BackgroundParser::projectAboutToBeOpened(IProject* project) { d->m_loadingProjects.insert(project); } void BackgroundParser::projectOpened(IProject* project) { d->m_loadingProjects.remove(project); } void BackgroundParser::projectOpeningAborted(IProject* project) { d->m_loadingProjects.remove(project); } } Q_DECLARE_TYPEINFO(KDevelop::BackgroundParserPrivate::DocumentParseTarget, Q_MOVABLE_TYPE); Q_DECLARE_TYPEINFO(KDevelop::BackgroundParserPrivate::DocumentParsePlan, Q_MOVABLE_TYPE); diff --git a/language/backgroundparser/documentchangetracker.cpp b/language/backgroundparser/documentchangetracker.cpp index cbb6359c7..4ddded520 100644 --- a/language/backgroundparser/documentchangetracker.cpp +++ b/language/backgroundparser/documentchangetracker.cpp @@ -1,518 +1,518 @@ /* * This file is part of KDevelop * * Copyright 2010 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "documentchangetracker.h" #include #include #include #include #include #include #include #include #include #include "backgroundparser.h" #include "util/debug.h" #include // Can be used to disable the 'clever' updating logic that ignores whitespace-only changes and such. // #define ALWAYS_UPDATE using namespace KTextEditor; /** * @todo Track the exact changes to the document, and then: * Dont reparse if: * - Comment added/changed * - Newlines added/changed (ready) * Complete the document for validation: * - Incomplete for-loops * - ... * Only reparse after a statement was completed (either with statement-completion or manually), or after the cursor was switched away * Incremental parsing: * - All changes within a local function (or function parameter context): Update only the context (and all its importers) * * @todo: Prevent recursive updates after insignificant changes * (whitespace changes, or changes that don't affect publically visible stuff, eg. local incremental changes) * -> Maybe alter the file-modification caches directly * */ namespace KDevelop { DocumentChangeTracker::DocumentChangeTracker( KTextEditor::Document* document ) : m_needUpdate(false), m_document(document), m_moving(0), m_whitespaceSensitivity(ILanguageSupport::Insensitive) { m_url = IndexedString(document->url()); Q_ASSERT(document); Q_ASSERT(document->url().isValid()); // Check whether a language plugin is tracking the document which belongs to a // whitespace-sensitive language (e.g. python) - foreach (const auto& lang, ICore::self()->languageController()->languagesForUrl(document->url())) { + foreach (const auto lang, ICore::self()->languageController()->languagesForUrl(document->url())) { if (!lang) { continue; } if (lang->whitespaceSensititivy() >= m_whitespaceSensitivity) { m_whitespaceSensitivity = lang->whitespaceSensititivy(); } } connect(document, &Document::textInserted, this, &DocumentChangeTracker::textInserted); connect(document, &Document::textRemoved, this, &DocumentChangeTracker::textRemoved); connect(document, &Document::destroyed, this, &DocumentChangeTracker::documentDestroyed); connect(document, &Document::documentSavedOrUploaded, this, &DocumentChangeTracker::documentSavedOrUploaded); m_moving = dynamic_cast(document); Q_ASSERT(m_moving); // can't use new connect syntax here, MovingInterface is not a QObject connect(m_document, SIGNAL(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document*)), this, SLOT(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document*))); ModificationRevision::setEditorRevisionForFile(m_url, m_moving->revision()); reset(); } QList< QPair< KTextEditor::Range, QString > > DocumentChangeTracker::completions() const { VERIFY_FOREGROUND_LOCKED QList< QPair< KTextEditor::Range , QString > > ret; return ret; } void DocumentChangeTracker::reset() { VERIFY_FOREGROUND_LOCKED // We don't reset the insertion here, as it may continue m_needUpdate = false; m_revisionAtLastReset = acquireRevision(m_moving->revision()); Q_ASSERT(m_revisionAtLastReset); } RevisionReference DocumentChangeTracker::currentRevision() { VERIFY_FOREGROUND_LOCKED return acquireRevision(m_moving->revision()); } RevisionReference DocumentChangeTracker::revisionAtLastReset() const { VERIFY_FOREGROUND_LOCKED return m_revisionAtLastReset; } bool DocumentChangeTracker::needUpdate() const { VERIFY_FOREGROUND_LOCKED return m_needUpdate; } bool DocumentChangeTracker::checkMergeTokens(const KTextEditor::Range& range) { ///@todo Improve this so that it notices when we wrapped in/out of a line-comment ///@todo Improve this so that it really checks whether some merge-able tokens have been moved together if(m_document->documentRange().contains(range)) { - if(range.start().column() == 0 || m_document->text(KTextEditor::Range(range.start().line(), range.start().column()-1, range.start().line(), range.start().column()))[0].isSpace()) + if(range.start().column() == 0 || m_document->text(KTextEditor::Range(range.start().line(), range.start().column()-1, range.start().line(), range.start().column())).at(0).isSpace()) return true; if(range.end().column() >= m_document->lineLength(range.end().line()) || m_document->text(KTextEditor::Range(range.end().line(), range.end().column(), range.end().line(), range.end().column()+1))[0].isSpace()) return true; } return false; } void DocumentChangeTracker::updateChangedRange() { // Q_ASSERT(m_moving->revision() != m_revisionAtLastReset->revision()); // May happen after reload // When reloading, textRemoved is called with an invalid m_document->url(). For that reason, we use m_url instead. ModificationRevision::setEditorRevisionForFile(m_url, m_moving->revision()); if(needUpdate()) ICore::self()->languageController()->backgroundParser()->addDocument(m_url, TopDUContext::AllDeclarationsContextsAndUses); } static Cursor cursorAdd(Cursor c, const QString& text) { c.setLine(c.line() + text.count('\n')); c.setColumn(c.column() + (text.length() - qMin(0, text.lastIndexOf('\n')))); return c; } static bool whitespaceOnly(const QString& string) { for (QChar c : string) { if (!c.isSpace()) { return false; } } return true; } void DocumentChangeTracker::textInserted( Document* document, const Cursor& cursor, const QString& text) { if ( m_whitespaceSensitivity == ILanguageSupport::Sensitive ) { m_needUpdate = true; } /// TODO: get this data from KTextEditor directly, make its signal public Range range(cursor, cursorAdd(cursor, text)); if ( ! m_needUpdate ) { bool changeIsWhitespaceOnly = whitespaceOnly(text) && checkMergeTokens(range); if ( m_whitespaceSensitivity == ILanguageSupport::IndentOnly && changeIsWhitespaceOnly ) { // The language requires a document to be re-parsed if the indentation changes (e.g. python), // so do some special checks here to see if that is the case. if ( changeIsWhitespaceOnly ) { QString fromLineBeginning = document->text(Range(Cursor(cursor.line(), 0), range.end() - Cursor{0, 1})); if ( fromLineBeginning.trimmed().isEmpty() ) { m_needUpdate = true; } } } if ( ! changeIsWhitespaceOnly ) { // If we've inserted something else than whitespace, an update is required for all languages. m_needUpdate = true; } } #ifdef ALWAYS_UPDATE m_needUpdate = true; #endif if(!m_lastInsertionPosition.isValid() || m_lastInsertionPosition == cursor) { m_currentCleanedInsertion.append(text); m_lastInsertionPosition = range.end(); } updateChangedRange(); } void DocumentChangeTracker::textRemoved( Document* document, const Range& oldRange, const QString& oldText ) { if (whitespaceOnly(oldText) && checkMergeTokens(Range(oldRange.start(), oldRange.start()))) { // Only whitespace was changed, no update is required eventually if ( m_whitespaceSensitivity == ILanguageSupport::Sensitive ) { m_needUpdate = true; } else if ( m_whitespaceSensitivity == ILanguageSupport::IndentOnly ) { // check text from the beginning of the line if ( document->text({{oldRange.start().line(), 0}, oldRange.end()}).trimmed().isEmpty() ) { m_needUpdate = true; } } } else { m_needUpdate = true; // If we've inserted something else than whitespace, an update is required } #ifdef ALWAYS_UPDATE m_needUpdate = true; #endif m_currentCleanedInsertion.clear(); m_lastInsertionPosition = KTextEditor::Cursor::invalid(); updateChangedRange(); } void DocumentChangeTracker::documentSavedOrUploaded(KTextEditor::Document* doc,bool) { ModificationRevision::clearModificationCache(IndexedString(doc->url())); } void DocumentChangeTracker::documentDestroyed( QObject* ) { m_document = 0; m_moving = 0; } DocumentChangeTracker::~DocumentChangeTracker() { Q_ASSERT(m_document); ModificationRevision::clearEditorRevisionForFile(KDevelop::IndexedString(m_document->url())); } Document* DocumentChangeTracker::document() const { return m_document; } MovingInterface* DocumentChangeTracker::documentMovingInterface() const { return m_moving; } void DocumentChangeTracker::aboutToInvalidateMovingInterfaceContent ( Document* ) { // Release all revisions! They must not be used any more. qCDebug(LANGUAGE) << "clearing all revisions"; m_revisionLocks.clear(); m_revisionAtLastReset = RevisionReference(); ModificationRevision::setEditorRevisionForFile(m_url, 0); } KDevelop::RangeInRevision DocumentChangeTracker::transformBetweenRevisions(KDevelop::RangeInRevision range, qint64 fromRevision, qint64 toRevision) const { VERIFY_FOREGROUND_LOCKED if((fromRevision == -1 || holdingRevision(fromRevision)) && (toRevision == -1 || holdingRevision(toRevision))) { m_moving->transformCursor(range.start.line, range.start.column, KTextEditor::MovingCursor::MoveOnInsert, fromRevision, toRevision); m_moving->transformCursor(range.end.line, range.end.column, KTextEditor::MovingCursor::StayOnInsert, fromRevision, toRevision); } return range; } KDevelop::CursorInRevision DocumentChangeTracker::transformBetweenRevisions(KDevelop::CursorInRevision cursor, qint64 fromRevision, qint64 toRevision, KTextEditor::MovingCursor::InsertBehavior behavior) const { VERIFY_FOREGROUND_LOCKED if((fromRevision == -1 || holdingRevision(fromRevision)) && (toRevision == -1 || holdingRevision(toRevision))) { m_moving->transformCursor(cursor.line, cursor.column, behavior, fromRevision, toRevision); } return cursor; } RangeInRevision DocumentChangeTracker::transformToRevision(KTextEditor::Range range, qint64 toRevision) const { return transformBetweenRevisions(RangeInRevision::castFromSimpleRange(range), -1, toRevision); } CursorInRevision DocumentChangeTracker::transformToRevision(KTextEditor::Cursor cursor, qint64 toRevision, MovingCursor::InsertBehavior behavior) const { return transformBetweenRevisions(CursorInRevision::castFromSimpleCursor(cursor), -1, toRevision, behavior); } KTextEditor::Range DocumentChangeTracker::transformToCurrentRevision(RangeInRevision range, qint64 fromRevision) const { return transformBetweenRevisions(range, fromRevision, -1).castToSimpleRange(); } KTextEditor::Cursor DocumentChangeTracker::transformToCurrentRevision(CursorInRevision cursor, qint64 fromRevision, MovingCursor::InsertBehavior behavior) const { return transformBetweenRevisions(cursor, fromRevision, -1, behavior).castToSimpleCursor(); } RevisionLockerAndClearerPrivate::RevisionLockerAndClearerPrivate(DocumentChangeTracker* tracker, qint64 revision) : m_tracker(tracker), m_revision(revision) { VERIFY_FOREGROUND_LOCKED moveToThread(QApplication::instance()->thread()); // Lock the revision m_tracker->lockRevision(revision); } RevisionLockerAndClearerPrivate::~RevisionLockerAndClearerPrivate() { if (m_tracker) m_tracker->unlockRevision(m_revision); } RevisionLockerAndClearer::~RevisionLockerAndClearer() { m_p->deleteLater(); // Will be deleted in the foreground thread, as the object was re-owned to the foreground } RevisionReference DocumentChangeTracker::acquireRevision(qint64 revision) { VERIFY_FOREGROUND_LOCKED if(!holdingRevision(revision) && revision != m_moving->revision()) return RevisionReference(); RevisionReference ret(new RevisionLockerAndClearer); ret->m_p = new RevisionLockerAndClearerPrivate(this, revision); return ret; } bool DocumentChangeTracker::holdingRevision(qint64 revision) const { VERIFY_FOREGROUND_LOCKED return m_revisionLocks.contains(revision); } void DocumentChangeTracker::lockRevision(qint64 revision) { VERIFY_FOREGROUND_LOCKED QMap< qint64, int >::iterator it = m_revisionLocks.find(revision); if(it != m_revisionLocks.end()) ++(*it); else { m_revisionLocks.insert(revision, 1); m_moving->lockRevision(revision); } } void DocumentChangeTracker::unlockRevision(qint64 revision) { VERIFY_FOREGROUND_LOCKED QMap< qint64, int >::iterator it = m_revisionLocks.find(revision); if(it == m_revisionLocks.end()) { qCDebug(LANGUAGE) << "cannot unlock revision" << revision << ", probably the revisions have been cleared"; return; } --(*it); if(*it == 0) { m_moving->unlockRevision(revision); m_revisionLocks.erase(it); } } qint64 RevisionLockerAndClearer::revision() const { return m_p->revision(); } RangeInRevision RevisionLockerAndClearer::transformToRevision(const KDevelop::RangeInRevision& range, const KDevelop::RevisionLockerAndClearer::Ptr& to) const { VERIFY_FOREGROUND_LOCKED if(!m_p->m_tracker || !valid() || (to && !to->valid())) return range; qint64 fromRevision = revision(); qint64 toRevision = -1; if(to) toRevision = to->revision(); return m_p->m_tracker->transformBetweenRevisions(range, fromRevision, toRevision); } CursorInRevision RevisionLockerAndClearer::transformToRevision(const KDevelop::CursorInRevision& cursor, const KDevelop::RevisionLockerAndClearer::Ptr& to, MovingCursor::InsertBehavior behavior) const { VERIFY_FOREGROUND_LOCKED if(!m_p->m_tracker || !valid() || (to && !to->valid())) return cursor; qint64 fromRevision = revision(); qint64 toRevision = -1; if(to) toRevision = to->revision(); return m_p->m_tracker->transformBetweenRevisions(cursor, fromRevision, toRevision, behavior); } RangeInRevision RevisionLockerAndClearer::transformFromRevision(const KDevelop::RangeInRevision& range, const KDevelop::RevisionLockerAndClearer::Ptr& from) const { VERIFY_FOREGROUND_LOCKED if(!m_p->m_tracker || !valid()) return range; qint64 toRevision = revision(); qint64 fromRevision = -1; if(from) fromRevision = from->revision(); return m_p->m_tracker->transformBetweenRevisions(range, fromRevision, toRevision); } CursorInRevision RevisionLockerAndClearer::transformFromRevision(const KDevelop::CursorInRevision& cursor, const KDevelop::RevisionLockerAndClearer::Ptr& from, MovingCursor::InsertBehavior behavior) const { VERIFY_FOREGROUND_LOCKED if(!m_p->m_tracker) return cursor; qint64 toRevision = revision(); qint64 fromRevision = -1; if(from) fromRevision = from->revision(); return m_p->m_tracker->transformBetweenRevisions(cursor, fromRevision, toRevision, behavior); } KTextEditor::Range RevisionLockerAndClearer::transformToCurrentRevision(const KDevelop::RangeInRevision& range) const { return transformToRevision(range, KDevelop::RevisionLockerAndClearer::Ptr()).castToSimpleRange(); } KTextEditor::Cursor RevisionLockerAndClearer::transformToCurrentRevision(const KDevelop::CursorInRevision& cursor, MovingCursor::InsertBehavior behavior) const { return transformToRevision(cursor, KDevelop::RevisionLockerAndClearer::Ptr(), behavior).castToSimpleCursor(); } RangeInRevision RevisionLockerAndClearer::transformFromCurrentRevision(const KTextEditor::Range& range) const { return transformFromRevision(RangeInRevision::castFromSimpleRange(range), RevisionReference()); } CursorInRevision RevisionLockerAndClearer::transformFromCurrentRevision(const KTextEditor::Cursor& cursor, MovingCursor::InsertBehavior behavior) const { return transformFromRevision(CursorInRevision::castFromSimpleCursor(cursor), RevisionReference(), behavior); } bool RevisionLockerAndClearer::valid() const { VERIFY_FOREGROUND_LOCKED if(!m_p->m_tracker) return false; if(revision() == -1) return true; // The 'current' revision is always valid return m_p->m_tracker->holdingRevision(revision()); } RevisionReference DocumentChangeTracker::diskRevision() const { ///@todo Track which revision was last saved to disk return RevisionReference(); } } diff --git a/language/backgroundparser/parsejob.cpp b/language/backgroundparser/parsejob.cpp index b38ae36c2..4e02d1242 100644 --- a/language/backgroundparser/parsejob.cpp +++ b/language/backgroundparser/parsejob.cpp @@ -1,521 +1,521 @@ /* * This file is part of KDevelop * * Copyright 2006 Adam Treat * Copyright 2006-2008 Hamish Rodda * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "parsejob.h" #include #include #include #include #include #include #include #include #include #include "backgroundparser.h" #include "util/debug.h" #include "duchain/topducontext.h" #include "duchain/duchainlock.h" #include "duchain/duchain.h" #include "duchain/parsingenvironment.h" #include #include #include #include #include #include #include #include using namespace KTextEditor; static QMutex minimumFeaturesMutex; static QHash > staticMinimumFeatures; namespace KDevelop { class ParseJobPrivate { public: ParseJobPrivate(const IndexedString& url_, ILanguageSupport* languageSupport_) : url( url_ ) , languageSupport( languageSupport_ ) , abortRequested( 0 ) , hasReadContents( false ) , aborted( false ) , features( TopDUContext::VisibleDeclarationsAndContexts ) , parsePriority( 0 ) , sequentialProcessingFlags( ParseJob::IgnoresSequentialProcessing ) { } ~ParseJobPrivate() { } ReferencedTopDUContext duContext; IndexedString url; ILanguageSupport* languageSupport; ParseJob::Contents contents; QAtomicInt abortRequested; bool hasReadContents : 1; bool aborted : 1; TopDUContext::Features features; QList > notify; QPointer tracker; RevisionReference revision; RevisionReference previousRevision; int parsePriority; ParseJob::SequentialProcessingFlags sequentialProcessingFlags; }; ParseJob::ParseJob( const IndexedString& url, KDevelop::ILanguageSupport* languageSupport ) : ThreadWeaver::Sequence(), d(new ParseJobPrivate(url, languageSupport)) { } ParseJob::~ParseJob() { typedef QPointer QObjectPointer; foreach(const QObjectPointer &p, d->notify) { if(p) { QMetaObject::invokeMethod(p.data(), "updateReady", Qt::QueuedConnection, Q_ARG(KDevelop::IndexedString, d->url), Q_ARG(KDevelop::ReferencedTopDUContext, d->duContext)); } } delete d; } ILanguageSupport* ParseJob::languageSupport() const { return d->languageSupport; } void ParseJob::setParsePriority(int priority) { d->parsePriority = priority; } int ParseJob::parsePriority() const { return d->parsePriority; } bool ParseJob::requiresSequentialProcessing() const { return d->sequentialProcessingFlags & RequiresSequentialProcessing; } bool ParseJob::respectsSequentialProcessing() const { return d->sequentialProcessingFlags & RespectsSequentialProcessing; } void ParseJob::setSequentialProcessingFlags(SequentialProcessingFlags flags) { d->sequentialProcessingFlags = flags; } IndexedString ParseJob::document() const { return d->url; } bool ParseJob::success() const { return !d->aborted; } void ParseJob::setMinimumFeatures(TopDUContext::Features features) { d->features = features; } bool ParseJob::hasStaticMinimumFeatures() { QMutexLocker lock(&minimumFeaturesMutex); - return ::staticMinimumFeatures.size(); + return !::staticMinimumFeatures.isEmpty(); } TopDUContext::Features ParseJob::staticMinimumFeatures(const IndexedString& url) { QMutexLocker lock(&minimumFeaturesMutex); TopDUContext::Features features = (TopDUContext::Features)0; if(::staticMinimumFeatures.contains(url)) - foreach(const TopDUContext::Features &f, ::staticMinimumFeatures[url]) + foreach(const TopDUContext::Features f, ::staticMinimumFeatures[url]) features = (TopDUContext::Features)(features | f); return features; } TopDUContext::Features ParseJob::minimumFeatures() const { return (TopDUContext::Features)(d->features | staticMinimumFeatures(d->url)); } void ParseJob::setDuChain(ReferencedTopDUContext duChain) { d->duContext = duChain; } ReferencedTopDUContext ParseJob::duChain() const { return d->duContext; } bool ParseJob::abortRequested() const { return d->abortRequested.load(); } void ParseJob::requestAbort() { d->abortRequested = 1; } void ParseJob::abortJob() { d->aborted = true; setStatus(Status_Aborted); } void ParseJob::setNotifyWhenReady(const QList >& notify ) { d->notify = notify; } void ParseJob::setStaticMinimumFeatures(const IndexedString& url, TopDUContext::Features features) { QMutexLocker lock(&minimumFeaturesMutex); ::staticMinimumFeatures[url].append(features); } void ParseJob::unsetStaticMinimumFeatures(const IndexedString& url, TopDUContext::Features features) { QMutexLocker lock(&minimumFeaturesMutex); ::staticMinimumFeatures[url].removeOne(features); if(::staticMinimumFeatures[url].isEmpty()) ::staticMinimumFeatures.remove(url); } KDevelop::ProblemPointer ParseJob::readContents() { Q_ASSERT(!d->hasReadContents); d->hasReadContents = true; QString localFile(document().toUrl().toLocalFile()); QFileInfo fileInfo( localFile ); QDateTime lastModified = fileInfo.lastModified(); d->tracker = ICore::self()->languageController()->backgroundParser()->trackerForUrl(document()); //Try using an artificial code-representation, which overrides everything else if(artificialCodeRepresentationExists(document())) { CodeRepresentation::Ptr repr = createCodeRepresentation(document()); d->contents.contents = repr->text().toUtf8(); qCDebug(LANGUAGE) << "took contents for " << document().str() << " from artificial code-representation"; return KDevelop::ProblemPointer(); } bool hadTracker = false; if(d->tracker) { ForegroundLock lock; if(DocumentChangeTracker* t = d->tracker.data()) { // The file is open in an editor d->previousRevision = t->revisionAtLastReset(); t->reset(); // Reset the tracker to the current revision Q_ASSERT(t->revisionAtLastReset()); d->contents.contents = t->document()->text().toUtf8(); d->contents.modification = KDevelop::ModificationRevision( lastModified, t->revisionAtLastReset()->revision() ); d->revision = t->acquireRevision(d->contents.modification.revision); hadTracker = true; } } if (!hadTracker) { // We have to load the file from disk static const int maximumFileSize = 5 * 1024 * 1024; // 5 MB if (fileInfo.size() > maximumFileSize) { KFormat f; KDevelop::ProblemPointer p(new Problem()); p->setSource(IProblem::Disk); p->setDescription(i18nc("%1: filename", "Skipped file that is too large: '%1'", localFile )); p->setExplanation(i18nc("%1: file size, %2: limit file size", "The file is %1 and exceeds the limit of %2.", f.formatByteSize(fileInfo.size()), f.formatByteSize(maximumFileSize))); p->setFinalLocation(DocumentRange(document(), KTextEditor::Range::invalid())); qCWarning(LANGUAGE) << p->description() << p->explanation(); return p; } QFile file( localFile ); if ( !file.open( QIODevice::ReadOnly ) ) { KDevelop::ProblemPointer p(new Problem()); p->setSource(IProblem::Disk); p->setDescription(i18n( "Could not open file '%1'", localFile )); switch (file.error()) { case QFile::ReadError: p->setExplanation(i18n("File could not be read from disk.")); break; case QFile::OpenError: p->setExplanation(i18n("File could not be opened.")); break; case QFile::PermissionsError: p->setExplanation(i18n("File could not be read from disk due to permissions.")); break; default: break; } p->setFinalLocation(DocumentRange(document(), KTextEditor::Range::invalid())); qCWarning(LANGUAGE) << "Could not open file" << document().str() << "(path" << localFile << ")" ; return p; } d->contents.contents = file.readAll(); ///@todo Convert from local encoding to utf-8 if they don't match d->contents.modification = KDevelop::ModificationRevision(lastModified); file.close(); } return KDevelop::ProblemPointer(); } const KDevelop::ParseJob::Contents& ParseJob::contents() const { Q_ASSERT(d->hasReadContents); return d->contents; } struct MovingRangeTranslator : public DUChainVisitor { MovingRangeTranslator(qint64 _source, qint64 _target, MovingInterface* _moving) : source(_source), target(_target), moving(_moving) { } void visit(DUContext* context) override { translateRange(context); ///@todo Also map import-positions // Translate uses uint usesCount = context->usesCount(); for(uint u = 0; u < usesCount; ++u) { RangeInRevision r = context->uses()[u].m_range; translateRange(r); context->changeUseRange(u, r); } } void visit(Declaration* declaration) override { translateRange(declaration); } void translateRange(DUChainBase* object) { RangeInRevision r = object->range(); translateRange(r); object->setRange(r); } void translateRange(RangeInRevision& r) { // PHP and python use top contexts that start at (0, 0) end at INT_MAX, so make sure that doesn't overflow // or translate the start of the top context away from (0, 0) if ( r.start.line != 0 || r.start.column != 0 ) { moving->transformCursor(r.start.line, r.start.column, MovingCursor::MoveOnInsert, source, target); } if ( r.end.line != std::numeric_limits::max() || r.end.column != std::numeric_limits::max() ) { moving->transformCursor(r.end.line, r.end.column, MovingCursor::StayOnInsert, source, target); } } KTextEditor::Range range; qint64 source; qint64 target; MovingInterface* moving; }; void ParseJob::translateDUChainToRevision(TopDUContext* context) { qint64 targetRevision = d->contents.modification.revision; if(targetRevision == -1) { qCDebug(LANGUAGE) << "invalid target revision" << targetRevision; return; } qint64 sourceRevision; { DUChainReadLocker duChainLock; Q_ASSERT(context->parsingEnvironmentFile()); // Cannot map if there is no source revision sourceRevision = context->parsingEnvironmentFile()->modificationRevision().revision; if(sourceRevision == -1) { qCDebug(LANGUAGE) << "invalid source revision" << sourceRevision; return; } } if(sourceRevision > targetRevision) { qCDebug(LANGUAGE) << "for document" << document().str() << ": source revision is higher than target revision:" << sourceRevision << " > " << targetRevision; return; } ForegroundLock lock; if(DocumentChangeTracker* t = d->tracker.data()) { if(!d->previousRevision) { qCDebug(LANGUAGE) << "not translating because there is no valid predecessor-revision"; return; } if(sourceRevision != d->previousRevision->revision() || !d->previousRevision->valid()) { qCDebug(LANGUAGE) << "not translating because the document revision does not match the tracker start revision (maybe the document was cleared)"; return; } if(!t->holdingRevision(sourceRevision) || !t->holdingRevision(targetRevision)) { qCDebug(LANGUAGE) << "lost one of the translation revisions, not doing the map"; return; } // Perform translation MovingInterface* moving = t->documentMovingInterface(); DUChainWriteLocker wLock; MovingRangeTranslator translator(sourceRevision, targetRevision, moving); context->visit(translator); QList< ProblemPointer > problems = context->problems(); for(QList< ProblemPointer >::iterator problem = problems.begin(); problem != problems.end(); ++problem) { RangeInRevision r = (*problem)->range(); translator.translateRange(r); (*problem)->setRange(r); } // Update the modification revision in the meta-data ModificationRevision modRev = context->parsingEnvironmentFile()->modificationRevision(); modRev.revision = targetRevision; context->parsingEnvironmentFile()->setModificationRevision(modRev); } } bool ParseJob::isUpdateRequired(const IndexedString& languageString) { if (abortRequested()) { return false; } if (minimumFeatures() & TopDUContext::ForceUpdate) { return true; } DUChainReadLocker lock; if (abortRequested()) { return false; } foreach(const ParsingEnvironmentFilePointer &file, DUChain::self()->allEnvironmentFiles(document())) { if (file->language() != languageString) { continue; } if (!file->needsUpdate(environment()) && file->featuresSatisfied(minimumFeatures())) { qCDebug(LANGUAGE) << "Already up to date" << document().str(); setDuChain(file->topContext()); lock.unlock(); highlightDUChain(); return false; } break; } return !abortRequested(); } const ParsingEnvironment* ParseJob::environment() const { return nullptr; } void ParseJob::highlightDUChain() { ENSURE_CHAIN_NOT_LOCKED if (!d->languageSupport->codeHighlighting() || !duChain() || abortRequested()) { // language doesn't support highlighting return; } if (!d->hasReadContents && !d->tracker) { d->tracker = ICore::self()->languageController()->backgroundParser()->trackerForUrl(document()); } if (d->tracker) { d->languageSupport->codeHighlighting()->highlightDUChain(duChain()); } } ControlFlowGraph* ParseJob::controlFlowGraph() { return nullptr; } DataAccessRepository* ParseJob::dataAccessInformation() { return nullptr; } bool ParseJob::hasTracker() const { return d->tracker; } } diff --git a/language/backgroundparser/parseprojectjob.cpp b/language/backgroundparser/parseprojectjob.cpp index 1417ac40d..398486864 100644 --- a/language/backgroundparser/parseprojectjob.cpp +++ b/language/backgroundparser/parseprojectjob.cpp @@ -1,157 +1,157 @@ /* Copyright 2009 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "parseprojectjob.h" #include "util/debug.h" #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; bool ParseProjectJob::doKill() { qCDebug(LANGUAGE) << "stopping project parse job"; deleteLater(); return true; } ParseProjectJob::~ParseProjectJob() { ICore::self()->languageController()->backgroundParser()->revertAllRequests(this); if(ICore::self()->runController()->currentJobs().contains(this)) ICore::self()->runController()->unregisterJob(this); } ParseProjectJob::ParseProjectJob(IProject* project, bool forceUpdate) : m_updated(0) , m_forceUpdate(forceUpdate) , m_project(project) { connect(project, &IProject::destroyed, this, &ParseProjectJob::deleteNow); if (!ICore::self()->projectController()->parseAllProjectSources()) { // In case we don't want to parse the whole project, still add all currently open files that belong to the project to the background-parser - for (auto document: ICore::self()->documentController()->openDocuments()) { + foreach (auto document, ICore::self()->documentController()->openDocuments()) { const auto path = IndexedString(document->url()); if (project->fileSet().contains(path)) { m_filesToParse.insert(path); } } } else { m_filesToParse = project->fileSet(); } setCapabilities(Killable); setObjectName(i18np("Process 1 file in %2","Process %1 files in %2", m_filesToParse.size(), m_project->name())); } void ParseProjectJob::deleteNow() { delete this; } void ParseProjectJob::updateProgress() { } void ParseProjectJob::updateReady(const IndexedString& url, ReferencedTopDUContext topContext) { Q_UNUSED(url); Q_UNUSED(topContext); ++m_updated; if(m_updated % ((m_filesToParse.size() / 100)+1) == 0) updateProgress(); if(m_updated >= m_filesToParse.size()) deleteLater(); } void ParseProjectJob::start() { if (ICore::self()->shuttingDown()) { return; } if (m_filesToParse.isEmpty()) { deleteLater(); return; } qCDebug(LANGUAGE) << "starting project parse job"; TopDUContext::Features processingLevel = m_filesToParse.size() < ICore::self()->languageController()->completionSettings()->minFilesForSimplifiedParsing() ? TopDUContext::VisibleDeclarationsAndContexts : TopDUContext::SimplifiedVisibleDeclarationsAndContexts; if (m_forceUpdate) { if (processingLevel & TopDUContext::VisibleDeclarationsAndContexts) { processingLevel = TopDUContext::AllDeclarationsContextsAndUses; } processingLevel = (TopDUContext::Features)(TopDUContext::ForceUpdate | processingLevel); } if (auto currentDocument = ICore::self()->documentController()->activeDocument()) { const auto path = IndexedString(currentDocument->url()); if (m_filesToParse.contains(path)) { ICore::self()->languageController()->backgroundParser()->addDocument(path, TopDUContext::AllDeclarationsContextsAndUses, BackgroundParser::BestPriority, this); m_filesToParse.remove(path); } } // Add all currently open files that belong to the project to the background-parser, so that they'll be parsed first of all - for (auto document: ICore::self()->documentController()->openDocuments()) { + foreach (auto document, ICore::self()->documentController()->openDocuments()) { const auto path = IndexedString(document->url()); if (m_filesToParse.contains(path)) { ICore::self()->languageController()->backgroundParser()->addDocument(path, TopDUContext::AllDeclarationsContextsAndUses, 10, this ); m_filesToParse.remove(path); } } if (!ICore::self()->projectController()->parseAllProjectSources()) { return; } // prevent UI-lockup by processing events after some files // esp. noticeable when dealing with huge projects const int processAfter = 1000; int processed = 0; // guard against reentrancy issues, see also bug 345480 auto crashGuard = QPointer{this}; foreach(const IndexedString& url, m_filesToParse) { ICore::self()->languageController()->backgroundParser()->addDocument( url, processingLevel, BackgroundParser::InitialParsePriority, this ); ++processed; if (processed == processAfter) { QApplication::processEvents(); if (!crashGuard) { return; } processed = 0; } } } diff --git a/language/backgroundparser/tests/test_backgroundparser.cpp b/language/backgroundparser/tests/test_backgroundparser.cpp index e91182cb0..9ecdd9778 100644 --- a/language/backgroundparser/tests/test_backgroundparser.cpp +++ b/language/backgroundparser/tests/test_backgroundparser.cpp @@ -1,301 +1,301 @@ /* * This file is part of KDevelop * * Copyright 2012 by Sven Brauch * Copyright 2012 by Milian Wolff * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "test_backgroundparser.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "testlanguagesupport.h" #include "testparsejob.h" QTEST_MAIN(TestBackgroundparser) #define QVERIFY_RETURN(statement, retval) \ do { if (!QTest::qVerify((statement), #statement, "", __FILE__, __LINE__)) return retval; } while (0) using namespace KDevelop; JobPlan::JobPlan() { } void JobPlan::addJob(const JobPrototype& job) { m_jobs << job; } void JobPlan::clear() { m_jobs.clear(); m_finishedJobs.clear(); m_createdJobs.clear(); } void JobPlan::parseJobCreated(ParseJob* job) { // e.g. for the benchmark if (m_jobs.isEmpty()) { return; } TestParseJob* testJob = dynamic_cast(job); Q_ASSERT(testJob); qDebug() << "assigning propierties for created job" << testJob->document().toUrl(); testJob->duration_ms = jobForUrl(testJob->document()).m_duration; m_createdJobs.append(testJob->document()); } bool JobPlan::runJobs(int timeoutMS) { // add parse jobs foreach(const JobPrototype& job, m_jobs) { ICore::self()->languageController()->backgroundParser()->addDocument( job.m_url, TopDUContext::Empty, job.m_priority, this, job.m_flags ); } ICore::self()->languageController()->backgroundParser()->parseDocuments(); QElapsedTimer t; t.start(); while ( !t.hasExpired(timeoutMS) && m_jobs.size() != m_finishedJobs.size() ) { QTest::qWait(50); } QVERIFY_RETURN(m_jobs.size() == m_createdJobs.size(), false); QVERIFY_RETURN(m_finishedJobs.size() == m_jobs.size(), false); // verify they're started in the right order int currentBestPriority = BackgroundParser::BestPriority; foreach ( const IndexedString& url, m_createdJobs ) { const JobPrototype p = jobForUrl(url); QVERIFY_RETURN(p.m_priority >= currentBestPriority, false); currentBestPriority = p.m_priority; } return true; } JobPrototype JobPlan::jobForUrl(const IndexedString& url) { foreach(const JobPrototype& job, m_jobs) { if (job.m_url == url) { return job; } } return JobPrototype(); } void JobPlan::updateReady(const IndexedString& url, const ReferencedTopDUContext& /*context*/) { qDebug() << "update ready on " << url.toUrl(); const JobPrototype job = jobForUrl(url); QVERIFY(job.m_url.toUrl().isValid()); if (job.m_flags & ParseJob::RequiresSequentialProcessing) { // ensure that all jobs that respect sequential processing // with lower priority have been run foreach(const JobPrototype& otherJob, m_jobs) { if (otherJob.m_url == job.m_url) { continue; } if (otherJob.m_flags & ParseJob::RespectsSequentialProcessing && otherJob.m_priority < job.m_priority) { QVERIFY(m_finishedJobs.contains(otherJob.m_url)); } } } QVERIFY(!m_finishedJobs.contains(job.m_url)); m_finishedJobs << job.m_url; } void TestBackgroundparser::initTestCase() { AutoTestShell::init(); TestCore* core = TestCore::initialize(Core::NoUi); DUChain::self()->disablePersistentStorage(); TestLanguageController* langController = new TestLanguageController(core); core->setLanguageController(langController); langController->backgroundParser()->setThreadCount(4); TestLanguageSupport* testLang = new TestLanguageSupport(); connect(testLang, &TestLanguageSupport::parseJobCreated, &m_jobPlan, &JobPlan::parseJobCreated); - langController->addTestLanguage(testLang, QStringList() << "text/plain"); + langController->addTestLanguage(testLang, QStringList() << QStringLiteral("text/plain")); - const auto languages = langController->languagesForUrl(QUrl::fromLocalFile("/foo.txt")); + const auto languages = langController->languagesForUrl(QUrl::fromLocalFile(QStringLiteral("/foo.txt"))); QCOMPARE(languages.size(), 1); QCOMPARE(languages.first(), testLang); } void TestBackgroundparser::cleanupTestCase() { TestCore::shutdown(); } void TestBackgroundparser::init() { m_jobPlan.clear(); } void TestBackgroundparser::testParseOrdering_foregroundThread() { m_jobPlan.clear(); // prove that background parsing happens with sequential flags although there is a high-priority // foreground thread (active document being edited, ...) running all the time. // the long-running high-prio job - m_jobPlan.addJob(JobPrototype(QUrl::fromLocalFile("/test_fgt_hp.txt"), -500, ParseJob::IgnoresSequentialProcessing, 630)); + m_jobPlan.addJob(JobPrototype(QUrl::fromLocalFile(QStringLiteral("/test_fgt_hp.txt")), -500, ParseJob::IgnoresSequentialProcessing, 630)); // several small background jobs for ( int i = 0; i < 10; i++ ) { m_jobPlan.addJob(JobPrototype(QUrl::fromLocalFile("/test_fgt_lp__" + QString::number(i) + ".txt"), i, ParseJob::FullSequentialProcessing, 40)); } // not enough time if the small jobs run after the large one QVERIFY(m_jobPlan.runJobs(700)); } void TestBackgroundparser::testParseOrdering_noSequentialProcessing() { m_jobPlan.clear(); for ( int i = 0; i < 20; i++ ) { // create jobs with no sequential processing, and different priorities m_jobPlan.addJob(JobPrototype(QUrl::fromLocalFile("/test_nsp1__" + QString::number(i) + ".txt"), i, ParseJob::IgnoresSequentialProcessing, i)); } for ( int i = 0; i < 8; i++ ) { // create a few more jobs with the same priority m_jobPlan.addJob(JobPrototype(QUrl::fromLocalFile("/test_nsp2__" + QString::number(i) + ".txt"), 10, ParseJob::IgnoresSequentialProcessing, i)); } QVERIFY(m_jobPlan.runJobs(1000)); } void TestBackgroundparser::testParseOrdering_lockup() { m_jobPlan.clear(); for ( int i = 3; i > 0; i-- ) { // add 3 jobs which do not care about sequential processing, at 4 threads it should take no more than 1s to process them m_jobPlan.addJob(JobPrototype(QUrl::fromLocalFile("/test" + QString::number(i) + ".txt"), i, ParseJob::IgnoresSequentialProcessing, 200)); } // add one job which requires sequential processing with high priority - m_jobPlan.addJob(JobPrototype(QUrl::fromLocalFile("/test_hp.txt"), -200, ParseJob::FullSequentialProcessing, 200)); + m_jobPlan.addJob(JobPrototype(QUrl::fromLocalFile(QStringLiteral("/test_hp.txt")), -200, ParseJob::FullSequentialProcessing, 200)); // verify that the low-priority nonsequential jobs are run simultaneously with the other one. QVERIFY(m_jobPlan.runJobs(700)); } void TestBackgroundparser::testParseOrdering_simple() { m_jobPlan.clear(); for ( int i = 20; i > 0; i-- ) { // the job with priority i should be at place i in the finished list // (lower priority value -> should be parsed first) ParseJob::SequentialProcessingFlags flags = ParseJob::FullSequentialProcessing; m_jobPlan.addJob(JobPrototype(QUrl::fromLocalFile("/test" + QString::number(i) + ".txt"), i, flags)); } // also add a few jobs which ignore the processing for ( int i = 0; i < 5; ++i ) { m_jobPlan.addJob(JobPrototype(QUrl::fromLocalFile("/test2-" + QString::number(i) + ".txt"), BackgroundParser::NormalPriority, ParseJob::IgnoresSequentialProcessing)); } QVERIFY(m_jobPlan.runJobs(1000)); } void TestBackgroundparser::benchmark() { const int jobs = 10000; QVector jobUrls; jobUrls.reserve(jobs); for ( int i = 0; i < jobs; ++i ) { jobUrls << IndexedString("/test" + QString::number(i) + ".txt"); } QBENCHMARK { foreach ( const IndexedString& url, jobUrls ) { ICore::self()->languageController()->backgroundParser()->addDocument(url); } ICore::self()->languageController()->backgroundParser()->parseDocuments(); while ( ICore::self()->languageController()->backgroundParser()->queuedCount() ) { QTest::qWait(50); } } } void TestBackgroundparser::benchmarkDocumentChanges() { KTextEditor::Editor* editor = KTextEditor::Editor::instance(); QVERIFY(editor); KTextEditor::Document* doc = editor->createDocument(this); QVERIFY(doc); QTemporaryFile file; QVERIFY(file.open()); doc->saveAs(QUrl::fromLocalFile(file.fileName())); DocumentChangeTracker tracker(doc); - doc->setText("hello world"); + doc->setText(QStringLiteral("hello world")); // required for proper benchmark results doc->createView(0); QBENCHMARK { for ( int i = 0; i < 5000; i++ ) { { KTextEditor::Document::EditingTransaction t(doc); - doc->insertText(KTextEditor::Cursor(0, 0), "This is a test line.\n"); + doc->insertText(KTextEditor::Cursor(0, 0), QStringLiteral("This is a test line.\n")); } QApplication::processEvents(); } } doc->clear(); doc->save(); } diff --git a/language/backgroundparser/tests/testlanguagesupport.cpp b/language/backgroundparser/tests/testlanguagesupport.cpp index 8b8109bd6..82426ce84 100644 --- a/language/backgroundparser/tests/testlanguagesupport.cpp +++ b/language/backgroundparser/tests/testlanguagesupport.cpp @@ -1,42 +1,42 @@ /* * This file is part of KDevelop * * Copyright 2012 by Sven Brauch * * 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 "testlanguagesupport.h" #include "testparsejob.h" #include "test_backgroundparser.h" #include using namespace KDevelop; ParseJob* TestLanguageSupport::createParseJob(const IndexedString& url) { qDebug() << "creating test language support parse job"; TestParseJob* job = new TestParseJob(url, this); emit parseJobCreated(job); return job; } QString TestLanguageSupport::name() const { - return "TestLanguageSupport"; + return QStringLiteral("TestLanguageSupport"); } diff --git a/language/classmodel/classmodel.cpp b/language/classmodel/classmodel.cpp index 74e4f02e2..67b35ff7f 100644 --- a/language/classmodel/classmodel.cpp +++ b/language/classmodel/classmodel.cpp @@ -1,278 +1,278 @@ /* * KDevelop Class Browser * * Copyright 2007-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 "classmodel.h" #include "classmodelnode.h" #include "allclassesfolder.h" #include "projectfolder.h" #include "../duchain/declaration.h" #include #include "../../interfaces/icore.h" #include "../../interfaces/iproject.h" #include "../../interfaces/iprojectcontroller.h" using namespace KDevelop; using namespace ClassModelNodes; ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// NodesModelInterface::~NodesModelInterface() { } ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// ClassModel::ClassModel() : m_features(NodesModelInterface::AllProjectsClasses | NodesModelInterface::BaseAndDerivedClasses | NodesModelInterface::ClassInternals) { - m_topNode = new FolderNode("Top Node", this); + m_topNode = new FolderNode(QStringLiteral("Top Node"), this); if ( features().testFlag(NodesModelInterface::AllProjectsClasses) ) { m_allClassesNode = new FilteredAllClassesFolder(this); m_topNode->addNode( m_allClassesNode ); } connect(ICore::self()->projectController(), &IProjectController::projectClosing, this, &ClassModel::removeProjectNode); connect(ICore::self()->projectController(), &IProjectController::projectOpened, this, &ClassModel::addProjectNode); foreach ( IProject* project, ICore::self()->projectController()->projects() ) { addProjectNode(project); } } ClassModel::~ClassModel() { delete m_topNode; } void ClassModel::updateFilterString(QString a_newFilterString) { m_allClassesNode->updateFilterString(a_newFilterString); foreach ( ClassModelNodes::FilteredProjectFolder* folder, m_projectNodes ) { folder->updateFilterString(a_newFilterString); } } void ClassModel::collapsed(const QModelIndex& index) { Node* node = static_cast(index.internalPointer()); node->collapse(); } void ClassModel::expanded(const QModelIndex& index) { Node* node = static_cast(index.internalPointer()); node->expand(); } QFlags< Qt::ItemFlag > ClassModel::flags(const QModelIndex&) const { return Qt::ItemIsSelectable | Qt::ItemIsEnabled; } int ClassModel::rowCount(const QModelIndex& parent) const { Node* node = m_topNode; if ( parent.isValid() ) node = static_cast(parent.internalPointer()); return node->getChildren().size(); } QVariant ClassModel::data(const QModelIndex& index, int role) const { if ( !index.isValid() ) return QVariant(); Node* node = static_cast(index.internalPointer()); if ( role == Qt::DisplayRole ) return node->displayName(); if ( role == Qt::DecorationRole ) { QIcon icon = node->getCachedIcon(); return icon.isNull() ? QVariant() : icon; } return QVariant(); } QVariant ClassModel::headerData(int, Qt::Orientation, int role) const { if ( role == Qt::DisplayRole ) return "Class"; return QVariant(); } int ClassModel::columnCount(const QModelIndex&) const { return 1; } bool ClassModel::hasChildren(const QModelIndex& parent) const { if ( !parent.isValid() ) return true; Node* node = static_cast(parent.internalPointer()); return node->hasChildren(); } QModelIndex ClassModel::index(int row, int column, const QModelIndex& parent) const { if (row < 0 || column != 0) return QModelIndex(); Node* node = m_topNode; if ( parent.isValid() ) node = static_cast(parent.internalPointer()); if ( row >= node->getChildren().size() ) return QModelIndex(); return index(node->getChildren()[row]); } QModelIndex ClassModel::parent(const QModelIndex& childIndex) const { if ( !childIndex.isValid() ) return QModelIndex(); Node* childNode = static_cast(childIndex.internalPointer()); if ( childNode->getParent() == m_topNode ) return QModelIndex(); return index( childNode->getParent() ); } QModelIndex ClassModel::index(ClassModelNodes::Node* a_node) const { if (!a_node) { return QModelIndex(); } // If no parent exists, we have an invalid index (root node or not part of a model). if ( a_node->getParent() == 0 ) return QModelIndex(); return createIndex(a_node->row(), 0, a_node); } KDevelop::DUChainBase* ClassModel::duObjectForIndex(const QModelIndex& a_index) { if ( !a_index.isValid() ) return 0; Node* node = static_cast(a_index.internalPointer()); if ( IdentifierNode* identifierNode = dynamic_cast(node) ) return identifierNode->getDeclaration(); // Non was found. return 0; } QModelIndex ClassModel::getIndexForIdentifier(const KDevelop::IndexedQualifiedIdentifier& a_id) { ClassNode* node = m_allClassesNode->findClassNode(a_id); if ( node == 0 ) return QModelIndex(); return index(node); } void ClassModel::nodesLayoutAboutToBeChanged(ClassModelNodes::Node*) { emit layoutAboutToBeChanged(); } void ClassModel::nodesLayoutChanged(ClassModelNodes::Node*) { QModelIndexList oldIndexList = persistentIndexList(); QModelIndexList newIndexList; foreach(const QModelIndex& oldIndex, oldIndexList) { Node* node = static_cast(oldIndex.internalPointer()); if ( node ) { // Re-map the index. newIndexList << createIndex(node->row(), 0, node); } else newIndexList << oldIndex; } changePersistentIndexList(oldIndexList, newIndexList); emit layoutChanged(); } void ClassModel::nodesRemoved(ClassModelNodes::Node* a_parent, int a_first, int a_last) { beginRemoveRows(index(a_parent), a_first, a_last); endRemoveRows(); } void ClassModel::nodesAboutToBeAdded(ClassModelNodes::Node* a_parent, int a_pos, int a_size) { beginInsertRows(index(a_parent), a_pos, a_pos + a_size - 1); } void ClassModel::nodesAdded(ClassModelNodes::Node*) { endInsertRows(); } void ClassModel::addProjectNode( IProject* project ) { m_projectNodes[project] = new ClassModelNodes::FilteredProjectFolder(this, project); nodesLayoutAboutToBeChanged(m_projectNodes[project]); m_topNode->addNode(m_projectNodes[project]); nodesLayoutChanged(m_projectNodes[project]); } void ClassModel::removeProjectNode( IProject* project ) { m_topNode->removeNode(m_projectNodes[project]); m_projectNodes.remove(project); } // kate: space-indent on; indent-width 2; tab-width 4; replace-tabs on; auto-insert-doxygen on diff --git a/language/classmodel/classmodelnode.cpp b/language/classmodel/classmodelnode.cpp index 9d7faab2f..6ac0bf6ee 100644 --- a/language/classmodel/classmodelnode.cpp +++ b/language/classmodel/classmodelnode.cpp @@ -1,610 +1,610 @@ /* * KDevelop Class Browser * * Copyright 2007-2009 Hamish Rodda * Copyright 2009 Lior Mualem * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "classmodelnode.h" #include "debug.h" #include #include #include "../duchain/duchainlock.h" #include "../duchain/duchain.h" #include "../duchain/persistentsymboltable.h" #include "../duchain/duchainutils.h" #include "../duchain/classdeclaration.h" #include "../duchain/classfunctiondeclaration.h" #include "../duchain/types/functiontype.h" #include "../duchain/types/enumerationtype.h" #include "util/debug.h" using namespace KDevelop; using namespace ClassModelNodes; IdentifierNode::IdentifierNode(KDevelop::Declaration* a_decl, NodesModelInterface* a_model, const QString& a_displayName) : DynamicNode(a_displayName.isEmpty() ? a_decl->identifier().toString() : a_displayName, a_model) , m_identifier(a_decl->qualifiedIdentifier()) , m_indexedDeclaration(a_decl) , m_cachedDeclaration(a_decl) { } Declaration* IdentifierNode::getDeclaration() { if ( !m_cachedDeclaration ) m_cachedDeclaration = m_indexedDeclaration.declaration(); return m_cachedDeclaration.data(); } bool IdentifierNode::getIcon(QIcon& a_resultIcon) { DUChainReadLocker readLock(DUChain::lock()); Declaration* decl = getDeclaration(); if ( decl ) a_resultIcon = DUChainUtils::iconForDeclaration(decl); return !a_resultIcon.isNull(); } ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// EnumNode::EnumNode(KDevelop::Declaration* a_decl, NodesModelInterface* a_model) : IdentifierNode(a_decl, a_model) { // Set display name for anonymous enums if ( m_displayName.isEmpty() ) - m_displayName = "*Anonymous*"; + m_displayName = QStringLiteral("*Anonymous*"); } bool EnumNode::getIcon(QIcon& a_resultIcon) { DUChainReadLocker readLock(DUChain::lock()); ClassMemberDeclaration* decl = dynamic_cast(getDeclaration()); if ( decl == 0 ) { - static QIcon Icon = QIcon::fromTheme("enum"); + static QIcon Icon = QIcon::fromTheme(QStringLiteral("enum")); a_resultIcon = Icon; } else { if ( decl->accessPolicy() == Declaration::Protected ) { - static QIcon Icon = QIcon::fromTheme("protected_enum"); + static QIcon Icon = QIcon::fromTheme(QStringLiteral("protected_enum")); a_resultIcon = Icon; } else if ( decl->accessPolicy() == Declaration::Private ) { - static QIcon Icon = QIcon::fromTheme("private_enum"); + static QIcon Icon = QIcon::fromTheme(QStringLiteral("private_enum")); a_resultIcon = Icon; } else { - static QIcon Icon = QIcon::fromTheme("enum"); + static QIcon Icon = QIcon::fromTheme(QStringLiteral("enum")); a_resultIcon = Icon; } } return true; } void EnumNode::populateNode() { DUChainReadLocker readLock(DUChain::lock()); Declaration* decl = getDeclaration(); if ( decl->internalContext() ) foreach( Declaration* enumDecl, decl->internalContext()->localDeclarations() ) addNode( new EnumNode(enumDecl, m_model) ); } ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// ClassNode::ClassNode(Declaration* a_decl, NodesModelInterface* a_model) : IdentifierNode(a_decl, a_model) { } ClassNode::~ClassNode() { if ( !m_cachedUrl.isEmpty() ) { ClassModelNodesController::self().unregisterForChanges(m_cachedUrl, this); m_cachedUrl = IndexedString(); } } void ClassNode::populateNode() { DUChainReadLocker readLock(DUChain::lock()); if ( m_model->features().testFlag(NodesModelInterface::ClassInternals) ) { if ( updateClassDeclarations() ) { m_cachedUrl = getDeclaration()->url(); ClassModelNodesController::self().registerForChanges(m_cachedUrl, this); } } // Add special folders if (m_model->features().testFlag(NodesModelInterface::BaseAndDerivedClasses)) addBaseAndDerived(); } template <> inline bool qMapLessThanKey(const IndexedIdentifier &key1, const IndexedIdentifier &key2) { return key1.getIndex() < key2.getIndex(); } bool ClassNode::updateClassDeclarations() { bool hadChanges = false; SubIdentifiersMap existingIdentifiers = m_subIdentifiers; ClassDeclaration* klass = dynamic_cast(getDeclaration()); if ( klass ) { foreach(Declaration* decl, klass->internalContext()->localDeclarations()) { // Ignore forward declarations. if ( decl->isForwardDeclaration() ) continue; // Don't add existing declarations. if ( existingIdentifiers.contains( decl->ownIndex() ) ) { existingIdentifiers.remove(decl->ownIndex()); continue; } Node* newNode = 0; if ( EnumerationType::Ptr enumType = decl->type() ) newNode = new EnumNode( decl, m_model ); else if ( decl->isFunctionDeclaration() ) newNode = new FunctionNode( decl, m_model ); else if ( ClassDeclaration* classDecl = dynamic_cast(decl) ) newNode = new ClassNode(classDecl, m_model); else if ( ClassMemberDeclaration* memDecl = dynamic_cast(decl) ) newNode = new ClassMemberNode( memDecl, m_model ); else { // Debug - for reference. qCDebug(LANGUAGE) << "class: " << klass->toString() << "name: " << decl->toString() << " - unknown declaration type: " << typeid(*decl).name(); } if ( newNode ) { addNode(newNode); // Also remember the identifier. m_subIdentifiers.insert(decl->ownIndex(), newNode); hadChanges = true; } } } // Remove old existing identifiers for ( SubIdentifiersMap::iterator iter = existingIdentifiers.begin(); iter != existingIdentifiers.end(); ++iter ) { iter.value()->removeSelf(); m_subIdentifiers.remove(iter.key()); hadChanges = true; } return hadChanges; } bool ClassNode::addBaseAndDerived() { bool added = false; BaseClassesFolderNode *baseClassesNode = new BaseClassesFolderNode( m_model ); addNode( baseClassesNode ); if ( !baseClassesNode->hasChildren() ) removeNode( baseClassesNode ); else added = true; DerivedClassesFolderNode *derivedClassesNode = new DerivedClassesFolderNode( m_model ); addNode( derivedClassesNode ); if ( !derivedClassesNode->hasChildren() ) removeNode( derivedClassesNode ); else added = true; return added; } void ClassNode::nodeCleared() { if ( !m_cachedUrl.isEmpty() ) { ClassModelNodesController::self().unregisterForChanges(m_cachedUrl, this); m_cachedUrl = IndexedString(); } m_subIdentifiers.clear(); } void ClassModelNodes::ClassNode::documentChanged(const KDevelop::IndexedString&) { DUChainReadLocker readLock(DUChain::lock()); if ( updateClassDeclarations() ) recursiveSort(); } ClassNode* ClassNode::findSubClass(const KDevelop::IndexedQualifiedIdentifier& a_id) { // Make sure we have sub nodes. performPopulateNode(); /// @todo This is slow - we go over all the sub identifiers but the assumption is that /// this function call is rare and the list is not that long. foreach(Node* item, m_subIdentifiers) { ClassNode* classNode = dynamic_cast(item); if ( classNode == 0 ) continue; if ( classNode->getIdentifier() == a_id ) return classNode; } return 0; } ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// FunctionNode::FunctionNode(Declaration* a_decl, NodesModelInterface* a_model) : IdentifierNode(a_decl, a_model) { // Append the argument signature to the identifier's name (which is what the displayName is. if (FunctionType::Ptr type = a_decl->type()) m_displayName += type->partToString(FunctionType::SignatureArguments); // Add special values for ctor / dtor to sort first ClassFunctionDeclaration* classmember = dynamic_cast(a_decl); if ( classmember ) { if ( classmember->isConstructor() || classmember->isDestructor() ) m_sortableString = '0' + m_displayName; else m_sortableString = '1' + m_displayName; } else { m_sortableString = m_displayName; } } ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// ClassMemberNode::ClassMemberNode(KDevelop::ClassMemberDeclaration* a_decl, NodesModelInterface* a_model) : IdentifierNode(a_decl, a_model) { } bool ClassMemberNode::getIcon(QIcon& a_resultIcon) { DUChainReadLocker readLock(DUChain::lock()); ClassMemberDeclaration* decl = dynamic_cast(getDeclaration()); if ( decl == 0 ) return false; if ( decl->isTypeAlias() ) { - static QIcon Icon = QIcon::fromTheme("typedef"); + static QIcon Icon = QIcon::fromTheme(QStringLiteral("typedef")); a_resultIcon = Icon; } else if ( decl->accessPolicy() == Declaration::Protected ) { - static QIcon Icon = QIcon::fromTheme("protected_field"); + static QIcon Icon = QIcon::fromTheme(QStringLiteral("protected_field")); a_resultIcon = Icon; } else if ( decl->accessPolicy() == Declaration::Private ) { - static QIcon Icon = QIcon::fromTheme("private_field"); + static QIcon Icon = QIcon::fromTheme(QStringLiteral("private_field")); a_resultIcon = Icon; } else { - static QIcon Icon = QIcon::fromTheme("field"); + static QIcon Icon = QIcon::fromTheme(QStringLiteral("field")); a_resultIcon = Icon; } return true; } ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// DynamicFolderNode::DynamicFolderNode(const QString& a_displayName, NodesModelInterface* a_model) : DynamicNode(a_displayName, a_model) { } bool DynamicFolderNode::getIcon(QIcon& a_resultIcon) { - static QIcon folderIcon = QIcon::fromTheme("folder"); + static QIcon folderIcon = QIcon::fromTheme(QStringLiteral("folder")); a_resultIcon = folderIcon; return true; } ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// FolderNode::FolderNode(const QString& a_displayName, NodesModelInterface* a_model) : Node(a_displayName, a_model) { } bool FolderNode::getIcon(QIcon& a_resultIcon) { - static QIcon folderIcon = QIcon::fromTheme("folder"); + static QIcon folderIcon = QIcon::fromTheme(QStringLiteral("folder")); a_resultIcon = folderIcon; return true; } ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// BaseClassesFolderNode::BaseClassesFolderNode(NodesModelInterface* a_model) : DynamicFolderNode(i18n("Base classes"), a_model) { } void BaseClassesFolderNode::populateNode() { DUChainReadLocker readLock(DUChain::lock()); ClassDeclaration* klass = dynamic_cast( static_cast(getParent())->getDeclaration() ); if ( klass ) { // I use the imports instead of the baseClasses in the ClassDeclaration because I need // to get to the base class identifier which is not directly accessible through the // baseClasses function. foreach( const DUContext::Import& import, klass->internalContext()->importedParentContexts() ) { DUContext* baseContext = import.context( klass->topContext() ); if ( baseContext && baseContext->type() == DUContext::Class ) { Declaration* baseClassDeclaration = baseContext->owner(); if ( baseClassDeclaration ) { // Add the base class. addNode( new ClassNode(baseClassDeclaration, m_model) ); } } } } } ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// DerivedClassesFolderNode::DerivedClassesFolderNode(NodesModelInterface* a_model) : DynamicFolderNode(i18n("Derived classes"), a_model) { } void DerivedClassesFolderNode::populateNode() { DUChainReadLocker readLock(DUChain::lock()); ClassDeclaration* klass = dynamic_cast( static_cast(getParent())->getDeclaration() ); if ( klass ) { uint steps = 10000; QList< Declaration* > inheriters = DUChainUtils::getInheriters(klass, steps, true); foreach( Declaration* decl, inheriters ) { addNode( new ClassNode(decl, m_model) ); } } } ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// Node::Node(const QString& a_displayName, NodesModelInterface* a_model) : m_parentNode(0) , m_displayName(a_displayName) , m_model(a_model) { } Node::~Node() { // Notify the model about the removal of this nodes' children. if ( !m_children.empty() && m_model ) m_model->nodesRemoved(this, 0, m_children.size()-1); clear(); } void Node::clear() { qDeleteAll(m_children); m_children.clear(); } void Node::addNode(Node* a_child) { /// @note This is disabled for performance reasons - we add them to the bottom and a /// sort usually follows which causes a layout change to be fired. // m_model->nodesAboutToBeAdded(this, m_children.size(), 1); a_child->m_parentNode = this; m_children.push_back(a_child); // m_model->nodesAdded(this); } void Node::removeNode(Node* a_child) { int row = a_child->row(); m_children.removeAt(row); m_model->nodesRemoved(this, row, row ); delete a_child; } // Sort algorithm for the nodes. struct SortNodesFunctor { bool operator() (Node* a_lhs, Node* a_rhs) { if ( a_lhs->getScore() == a_rhs->getScore() ) { return a_lhs->getSortableString() < a_rhs->getSortableString(); } else return a_lhs->getScore() < a_rhs->getScore(); } }; void Node::recursiveSortInternal() { // Sort my nodes. std::sort(m_children.begin(), m_children.end(), SortNodesFunctor()); // Tell each node to sort it self. foreach (Node* node, m_children) node->recursiveSortInternal(); } void Node::recursiveSort() { m_model->nodesLayoutAboutToBeChanged(this); recursiveSortInternal(); m_model->nodesLayoutChanged(this); } int Node::row() { if ( m_parentNode == 0 ) return -1; return m_parentNode->m_children.indexOf(this); } QIcon ClassModelNodes::Node::getCachedIcon() { // Load the cached icon if it's null. if ( m_cachedIcon.isNull() ) { if ( !getIcon(m_cachedIcon) ) m_cachedIcon = QIcon(); } return m_cachedIcon; } ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// DynamicNode::DynamicNode(const QString& a_displayName, NodesModelInterface* a_model) : Node(a_displayName, a_model) , m_populated(false) { } void DynamicNode::collapse() { performNodeCleanup(); } void DynamicNode::expand() { performPopulateNode(); } void DynamicNode::performNodeCleanup() { if ( !m_populated ) return; if ( !m_children.empty() ) { // Notify model for this node. m_model->nodesRemoved(this, 0, m_children.size()-1); } // Clear sub-nodes. clear(); // This shouldn't be called from clear since clear is called also from the d-tor // and the function is virtual. nodeCleared(); // Mark the fact that we've been collapsed m_populated = false; } void DynamicNode::performPopulateNode(bool a_forceRepopulate) { if ( m_populated ) { if ( a_forceRepopulate ) performNodeCleanup(); else return; } populateNode(); // We're populated. m_populated = true; // Sort the list. recursiveSort(); } bool DynamicNode::hasChildren() const { // To get a true status, we'll need to populate the node. const_cast(this)->performPopulateNode(); return !m_children.empty(); } // kate: space-indent on; indent-width 2; tab-width 4; replace-tabs on; auto-insert-doxygen on diff --git a/language/classmodel/documentclassesfolder.cpp b/language/classmodel/documentclassesfolder.cpp index 0d8750c03..56a8a611e 100644 --- a/language/classmodel/documentclassesfolder.cpp +++ b/language/classmodel/documentclassesfolder.cpp @@ -1,451 +1,451 @@ /* * KDevelop Class Browser * * 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 "documentclassesfolder.h" #include "../duchain/declaration.h" #include "../duchain/duchainlock.h" #include "../duchain/duchain.h" #include "../duchain/persistentsymboltable.h" #include "../duchain/codemodel.h" #include #include #include using namespace KDevelop; using namespace ClassModelNodes; ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// /// Contains a static list of classes within the namespace. class ClassModelNodes::StaticNamespaceFolderNode : public Node { public: StaticNamespaceFolderNode(const KDevelop::QualifiedIdentifier& a_identifier, NodesModelInterface* a_model); /// Returns the qualified identifier for this node const KDevelop::QualifiedIdentifier& qualifiedIdentifier() const { return m_identifier; } public: // Node overrides bool getIcon(QIcon& a_resultIcon) override; int getScore() const override { return 101; } private: /// The namespace identifier. KDevelop::QualifiedIdentifier m_identifier; }; StaticNamespaceFolderNode::StaticNamespaceFolderNode(const KDevelop::QualifiedIdentifier& a_identifier, NodesModelInterface* a_model) : Node(a_identifier.last().toString(), a_model) , m_identifier(a_identifier) { } bool StaticNamespaceFolderNode::getIcon(QIcon& a_resultIcon) { - static QIcon folderIcon = QIcon::fromTheme("namespace"); + static QIcon folderIcon = QIcon::fromTheme(QStringLiteral("namespace")); a_resultIcon = folderIcon; return true; } ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// DocumentClassesFolder::OpenedFileClassItem::OpenedFileClassItem(const KDevelop::IndexedString& a_file, const KDevelop::IndexedQualifiedIdentifier& a_classIdentifier, ClassModelNodes::ClassNode* a_nodeItem) : file(a_file) , classIdentifier(a_classIdentifier) , nodeItem(a_nodeItem) { } DocumentClassesFolder::DocumentClassesFolder(const QString& a_displayName, NodesModelInterface* a_model) : DynamicFolderNode(a_displayName, a_model) , m_updateTimer( new QTimer(this) ) { connect( m_updateTimer, &QTimer::timeout, this, &DocumentClassesFolder::updateChangedFiles); } void DocumentClassesFolder::updateChangedFiles() { bool hadChanges = false; // re-parse changed documents. foreach( const IndexedString& file, m_updatedFiles ) { // Make sure it's one of the monitored files. if ( m_openFiles.contains(file) ) hadChanges |= updateDocument(file); } // Processed all files. m_updatedFiles.clear(); // Sort if had changes. if ( hadChanges ) recursiveSort(); } void DocumentClassesFolder::nodeCleared() { // Clear cached namespaces list (node was cleared). m_namespaces.clear(); // Clear open files and classes list m_openFiles.clear(); m_openFilesClasses.clear(); // Stop the update timer. m_updateTimer->stop(); } void DocumentClassesFolder::populateNode() { // Start updates timer - this is the required delay. m_updateTimer->start(2000); } QSet< KDevelop::IndexedString > DocumentClassesFolder::getAllOpenDocuments() { return m_openFiles; } ClassNode* DocumentClassesFolder::findClassNode(const IndexedQualifiedIdentifier& a_id) { // Make sure that the classes node is populated, otherwise // the lookup will not work. performPopulateNode(); ClassIdentifierIterator iter = m_openFilesClasses.get().find(a_id); if ( iter == m_openFilesClasses.get().end() ) return 0; // If the node is invisible - make it visible by going over the identifiers list. if ( iter->nodeItem == 0 ) { QualifiedIdentifier qualifiedIdentifier = a_id.identifier(); // Ignore zero length identifiers. if ( qualifiedIdentifier.count() == 0 ) return 0; ClassNode* closestNode = 0; int closestNodeIdLen = qualifiedIdentifier.count(); // First find the closest visible class node by reverse iteration over the id list. while ( (closestNodeIdLen > 0) && (closestNode == 0) ) { // Omit one from the end. --closestNodeIdLen; // Find the closest class. closestNode = findClassNode(qualifiedIdentifier.mid(0, closestNodeIdLen)); } if ( closestNode != 0 ) { // Start iterating forward from this node by exposing each class. // By the end of this loop, closestNode should hold the actual node. while ( closestNode && (closestNodeIdLen < qualifiedIdentifier.count()) ) { // Try the next Id. ++closestNodeIdLen; closestNode = closestNode->findSubClass(qualifiedIdentifier.mid(0, closestNodeIdLen)); } } return closestNode; } return iter->nodeItem; } void DocumentClassesFolder::closeDocument(const IndexedString& a_file) { // Get list of nodes associated with this file and remove them. std::pair< FileIterator, FileIterator > range = m_openFilesClasses.get().equal_range( a_file ); if ( range.first != m_openFilesClasses.get().end() ) { BOOST_FOREACH( const OpenedFileClassItem& item, range ) { if ( item.nodeItem ) removeClassNode(item.nodeItem); } // Clear the lists m_openFilesClasses.get().erase(range.first, range.second); } // Clear the file from the list of monitored documents. m_openFiles.remove(a_file); } bool DocumentClassesFolder::updateDocument(const KDevelop::IndexedString& a_file) { uint codeModelItemCount = 0; const CodeModelItem* codeModelItems; CodeModel::self().items(a_file, codeModelItemCount, codeModelItems); // List of declared namespaces in this file. QSet< QualifiedIdentifier > declaredNamespaces; // List of removed classes - it initially contains all the known classes, we'll eliminate them // one by one later on when we encounter them in the document. QMap< IndexedQualifiedIdentifier, FileIterator > removedClasses; { std::pair< FileIterator, FileIterator > range = m_openFilesClasses.get().equal_range( a_file ); for ( FileIterator iter = range.first; iter != range.second; ++iter ) { removedClasses.insert(iter->classIdentifier, iter); } } bool documentChanged = false; for(uint codeModelItemIndex = 0; codeModelItemIndex < codeModelItemCount; ++codeModelItemIndex) { const CodeModelItem& item = codeModelItems[codeModelItemIndex]; // Don't insert unknown or forward declarations into the class browser if ( (item.kind & CodeModelItem::Unknown) || (item.kind & CodeModelItem::ForwardDeclaration) ) continue; KDevelop::QualifiedIdentifier id = item.id.identifier(); // Don't add empty identifiers. if ( id.count() == 0 ) continue; // If it's a namespace, create it in the list. if ( item.kind & CodeModelItem::Namespace ) { // This should create the namespace folder and add it to the cache. getNamespaceFolder(id); // Add to the locally created namespaces. declaredNamespaces.insert(id); } else if ( item.kind & CodeModelItem::Class ) { // Ignore empty unnamed classes. if ( id.last().toString().isEmpty() ) continue; // See if it matches our filter? if ( isClassFiltered(id) ) continue; // Is this a new class or an existing class? if ( removedClasses.contains(id) ) { // It already exist - remove it from the known classes and continue. removedClasses.remove(id); continue; } // Where should we put this class? Node* parentNode = 0; // Check if it's namespaced and add it to the proper namespace. if ( id.count() > 1 ) { QualifiedIdentifier parentIdentifier(id.left(-1)); // Look up the namespace in the cache. // If we fail to find it we assume that the parent context is a class // and in that case, when the parent class gets expanded, it will show it. NamespacesMap::iterator iter = m_namespaces.find(parentIdentifier); if ( iter != m_namespaces.end() ) { // Add to the namespace node. parentNode = iter.value(); } else { // Reaching here means we didn't encounter any namespace declaration in the document // But a class might still be declared under a namespace. // So we'll perform a more through search to see if it's under a namespace. DUChainReadLocker readLock(DUChain::lock()); uint declsCount = 0; const IndexedDeclaration* decls; PersistentSymbolTable::self().declarations(parentIdentifier, declsCount, decls); for ( uint i = 0; i < declsCount; ++i ) { // Look for the first valid declaration. if ( decls->declaration() ) { // See if it should be namespaced. if ( decls->declaration()->kind() == Declaration::Namespace ) { // This should create the namespace folder and add it to the cache. parentNode = getNamespaceFolder(parentIdentifier); // Add to the locally created namespaces. declaredNamespaces.insert(parentIdentifier); } break; } } } } else { // Add to the main root. parentNode = this; } ClassNode* newNode = 0; if ( parentNode != 0 ) { // Create the new node and add it. IndexedDeclaration decl; uint count = 0; const IndexedDeclaration* declarations; DUChainReadLocker lock; PersistentSymbolTable::self().declarations(item.id, count, declarations); for ( uint i = 0; i < count; ++i ) { if (declarations[i].indexedTopContext().url() == a_file) { decl = declarations[i]; break; } } if (decl.isValid()) { newNode = new ClassNode(decl.declaration(), m_model); parentNode->addNode( newNode ); } } // Insert it to the map - newNode can be 0 - meaning the class is hidden. m_openFilesClasses.insert( OpenedFileClassItem( a_file, id, newNode ) ); documentChanged = true; } } // Remove empty namespaces from the list. // We need this because when a file gets unloaded, we unload the declared classes in it // and if a namespace has no class in it, it'll forever exist and no one will remove it // from the children list. foreach( const QualifiedIdentifier& id, declaredNamespaces ) removeEmptyNamespace(id); // Clear erased classes. - foreach( const FileIterator& item, removedClasses ) + foreach( const FileIterator item, removedClasses ) { if ( item->nodeItem ) removeClassNode(item->nodeItem); m_openFilesClasses.get().erase(item); documentChanged = true; } return documentChanged; } void DocumentClassesFolder::parseDocument(const IndexedString& a_file) { // Add the document to the list of open files - this means we monitor it. if ( !m_openFiles.contains(a_file) ) m_openFiles.insert(a_file); updateDocument(a_file); } void DocumentClassesFolder::removeClassNode(ClassModelNodes::ClassNode* a_node) { // Get the parent namespace identifier. QualifiedIdentifier parentNamespaceIdentifier; if ( auto namespaceParent = dynamic_cast(a_node->getParent()) ) { parentNamespaceIdentifier = namespaceParent->qualifiedIdentifier(); } // Remove the node. a_node->removeSelf(); // Remove empty namespace removeEmptyNamespace(parentNamespaceIdentifier); } void DocumentClassesFolder::removeEmptyNamespace(const QualifiedIdentifier& a_identifier) { // Stop condition. if ( a_identifier.count() == 0 ) return; // Look it up in the cache. NamespacesMap::iterator iter = m_namespaces.find(a_identifier); if ( iter != m_namespaces.end() ) { if ( !(*iter)->hasChildren() ) { // Remove this node and try to remove the parent node. QualifiedIdentifier parentIdentifier = (*iter)->qualifiedIdentifier().left(-1); (*iter)->removeSelf(); m_namespaces.remove(a_identifier); removeEmptyNamespace(parentIdentifier); } } } StaticNamespaceFolderNode* DocumentClassesFolder::getNamespaceFolder(const KDevelop::QualifiedIdentifier& a_identifier) { // Stop condition. if ( a_identifier.count() == 0 ) return 0; // Look it up in the cache. NamespacesMap::iterator iter = m_namespaces.find(a_identifier); if ( iter == m_namespaces.end() ) { // It's not in the cache - create folders up to it. Node* parentNode = getNamespaceFolder(a_identifier.left(-1)); if ( parentNode == 0 ) parentNode = this; // Create the new node. StaticNamespaceFolderNode* newNode = new StaticNamespaceFolderNode(a_identifier, m_model); parentNode->addNode( newNode ); // Add it to the cache. m_namespaces.insert( a_identifier, newNode ); // Return the result. return newNode; } else return *iter; } // kate: space-indent on; indent-width 2; tab-width 4; replace-tabs on; auto-insert-doxygen on diff --git a/language/classmodel/projectfolder.cpp b/language/classmodel/projectfolder.cpp index 613529a6f..34c6657e8 100644 --- a/language/classmodel/projectfolder.cpp +++ b/language/classmodel/projectfolder.cpp @@ -1,97 +1,97 @@ /* 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 "projectfolder.h" #include "../../interfaces/iproject.h" #include "../../serialization/indexedstring.h" #include using namespace KDevelop; using namespace ClassModelNodes; ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// ProjectFolder::ProjectFolder( NodesModelInterface* a_model, IProject* project ) : DocumentClassesFolder( i18n("Classes in project %1", project->name()), a_model ) , m_project(project) { } ProjectFolder::ProjectFolder( NodesModelInterface* a_model ) - : DocumentClassesFolder( "", a_model ) + : DocumentClassesFolder( QLatin1String(""), a_model ) , m_project( 0 ) { } void ProjectFolder::populateNode() { foreach( const IndexedString &file, m_project->fileSet() ) { parseDocument(file); } recursiveSort(); } ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// FilteredProjectFolder::FilteredProjectFolder(NodesModelInterface* a_model, IProject* project) : ProjectFolder(a_model, project) { } void FilteredProjectFolder::updateFilterString(QString a_newFilterString) { m_filterString = a_newFilterString; if ( isPopulated() ) { #if 1 // Choose speed over correctness. // Close the node and re-open it should be quicker than reload each document // and remove indevidual nodes (at the cost of loosing the current selection). performPopulateNode(true); #else bool hadChanges = false; // Reload the documents. foreach( const IndexedString& file, getAllOpenDocuments() ) hadChanges |= updateDocument(file); // Sort if we've updated documents. if ( hadChanges ) recursiveSort(); else { // If nothing changed, the title changed so mark the node as updated. m_model->nodesLayoutAboutToBeChanged(this); m_model->nodesLayoutChanged(this); } #endif } else { // Displayed name changed only... m_model->nodesLayoutAboutToBeChanged(this); m_model->nodesLayoutChanged(this); } } bool FilteredProjectFolder::isClassFiltered(const KDevelop::QualifiedIdentifier& a_id) { return !a_id.last().toString().contains(m_filterString, Qt::CaseInsensitive); } // kate: space-indent on; indent-width 2; tab-width 4; replace-tabs on; auto-insert-doxygen on diff --git a/language/codecompletion/codecompletion.cpp b/language/codecompletion/codecompletion.cpp index c90da5c60..5de2ddc10 100644 --- a/language/codecompletion/codecompletion.cpp +++ b/language/codecompletion/codecompletion.cpp @@ -1,133 +1,133 @@ /* * KDevelop Generic Code Completion Support * * Copyright 2006 Hamish Rodda * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "codecompletion.h" #include #include #include #include #include #include #include "../duchain/duchain.h" #include "../duchain/topducontext.h" #include "util/debug.h" #include "codecompletionmodel.h" #include using namespace KTextEditor; using namespace KDevelop; CodeCompletion::CodeCompletion(QObject *parent, KTextEditor::CodeCompletionModel* aModel, const QString& language) : QObject(parent), m_model(aModel), m_language(language) { KDevelop::CodeCompletionModel* kdevModel = dynamic_cast(aModel); if(kdevModel) kdevModel->initialize(); connect(KDevelop::ICore::self()->documentController(), &IDocumentController::textDocumentCreated, this, &CodeCompletion::textDocumentCreated); connect( ICore::self()->documentController(), &IDocumentController::documentUrlChanged, this, &CodeCompletion::documentUrlChanged ); aModel->setParent(this); // prevent deadlock QMetaObject::invokeMethod(this, "checkDocuments", Qt::QueuedConnection); } CodeCompletion::~CodeCompletion() { } void CodeCompletion::checkDocuments() { foreach( KDevelop::IDocument* doc, KDevelop::ICore::self()->documentController()->openDocuments() ) { if (doc->textDocument()) { checkDocument(doc->textDocument()); } } } void CodeCompletion::viewCreated(KTextEditor::Document * document, KTextEditor::View * view) { Q_UNUSED(document); if (CodeCompletionInterface* cc = dynamic_cast(view)) { cc->registerCompletionModel(m_model); qCDebug(LANGUAGE) << "Registered completion model"; emit registeredToView(view); } } void CodeCompletion::documentUrlChanged(KDevelop::IDocument* document) { // The URL has changed (might have a different language now), so we re-register the document Document* textDocument = document->textDocument(); if(textDocument) { checkDocument(textDocument); } } void CodeCompletion::textDocumentCreated(KDevelop::IDocument* document) { Q_ASSERT(document->textDocument()); checkDocument(document->textDocument()); } void CodeCompletion::unregisterDocument(Document* textDocument) { foreach (KTextEditor::View* view, textDocument->views()) { if (CodeCompletionInterface* cc = dynamic_cast(view)) { cc->unregisterCompletionModel(m_model); emit unregisteredFromView(view); } } disconnect(textDocument, &Document::viewCreated, this, &CodeCompletion::viewCreated); } void CodeCompletion::checkDocument(Document* textDocument) { unregisterDocument(textDocument); auto langs = ICore::self()->languageController()->languagesForUrl( textDocument->url() ); bool found = false; - foreach(const auto& lang, langs) { + foreach(const auto lang, langs) { if(m_language==lang->name()) { found=true; break; } } if(!found && !m_language.isEmpty()) return; foreach (KTextEditor::View* view, textDocument->views()) viewCreated(textDocument, view); connect(textDocument, &Document::viewCreated, this, &CodeCompletion::viewCreated); } diff --git a/language/codecompletion/codecompletionhelper.cpp b/language/codecompletion/codecompletionhelper.cpp index f43b25e97..c72ba9bc6 100644 --- a/language/codecompletion/codecompletionhelper.cpp +++ b/language/codecompletion/codecompletionhelper.cpp @@ -1,104 +1,104 @@ /* 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 "codecompletionhelper.h" #include "../duchain/duchain.h" #include "../duchain/declaration.h" #include "../duchain/duchainlock.h" #include "../duchain/types/functiontype.h" #include "../duchain/types/integraltype.h" #include #include namespace KDevelop { void insertFunctionParenText(KTextEditor::View* view, const KTextEditor::Cursor& pos, DeclarationPointer declaration, bool jumpForbidden) { bool spaceBeforeParen = false; ///@todo Take this from some astyle config or something bool spaceBetweenParens = false; bool spaceBetweenEmptyParens = false; KDevelop::DUChainReadLocker lock(KDevelop::DUChain::lock()); bool haveArguments = false; if(!declaration) return; TypePtr< FunctionType > funcType = declaration->type(); if( declaration->kind() == Declaration::Type || (funcType && funcType->indexedArgumentsSize()) ) haveArguments = true; if( declaration->kind() == Declaration::Instance && !declaration->isFunctionDeclaration()) haveArguments = true; //probably a constructor initializer //Need to have a paren behind QString suffix = view->document()->text( KTextEditor::Range( pos, pos + KTextEditor::Cursor(1, 0) ) ); if( suffix.trimmed().startsWith('(') ) { //Move the cursor behind the opening paren if( view ) view->setCursorPosition( pos + KTextEditor::Cursor( 0, suffix.indexOf('(')+1 ) ); }else{ //We need to insert an opening paren QString openingParen; if( spaceBeforeParen ) - openingParen = " ("; + openingParen = QStringLiteral(" ("); else openingParen = '('; if( spaceBetweenParens && (haveArguments || spaceBetweenEmptyParens) ) openingParen += ' '; QString closingParen; if( spaceBetweenParens && (haveArguments) ) { - closingParen = " )"; + closingParen = QStringLiteral(" )"); } else closingParen = ')'; KTextEditor::Cursor jumpPos = pos + KTextEditor::Cursor( 0, openingParen.length() ); // when function returns void, also add a semicolon if (funcType) { if (IntegralType::Ptr type = funcType->returnType().cast()) { if (type->dataType() == IntegralType::TypeVoid) { const QChar nextChar = view->document()->characterAt(pos); if (nextChar != ';' && nextChar != ')' && nextChar != ',') { closingParen += ';'; } } } } //If no arguments, move the cursor behind the closing paren (or semicolon) if( !haveArguments ) jumpPos += KTextEditor::Cursor( 0, closingParen.length() ); lock.unlock(); view->document()->insertText( pos, openingParen + closingParen ); if(!jumpForbidden) { if( view ) view->setCursorPosition( jumpPos ); } } } } diff --git a/language/codecompletion/codecompletionmodel.cpp b/language/codecompletion/codecompletionmodel.cpp index aa9c242c0..e89cb806f 100644 --- a/language/codecompletion/codecompletionmodel.cpp +++ b/language/codecompletion/codecompletionmodel.cpp @@ -1,452 +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) : QThread(model), m_model(model), m_worker(m_model->createCompletionWorker()) { Q_ASSERT(m_worker->parent() == 0); // 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(0) { 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/codecompletion/normaldeclarationcompletionitem.cpp b/language/codecompletion/normaldeclarationcompletionitem.cpp index 09f53426a..9d3143073 100644 --- a/language/codecompletion/normaldeclarationcompletionitem.cpp +++ b/language/codecompletion/normaldeclarationcompletionitem.cpp @@ -1,227 +1,227 @@ /* * KDevelop Generic Code Completion Support * * Copyright 2007-2008 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "normaldeclarationcompletionitem.h" #include "codecompletionmodel.h" #include "../duchain/duchainlock.h" #include "../duchain/duchain.h" #include "../duchain/classfunctiondeclaration.h" #include "../duchain/types/functiontype.h" #include "../duchain/types/enumeratortype.h" #include "../duchain/duchainutils.h" #include "../duchain/navigation/abstractdeclarationnavigationcontext.h" #include "util/debug.h" #include #include namespace KDevelop { const int NormalDeclarationCompletionItem::normalBestMatchesCount = 5; //If this is true, the return-values of argument-hints will be just written as "..." if they are too long const bool NormalDeclarationCompletionItem::shortenArgumentHintReturnValues = true; const int NormalDeclarationCompletionItem::maximumArgumentHintReturnValueLength = 30; const int NormalDeclarationCompletionItem::desiredTypeLength = 20; NormalDeclarationCompletionItem::NormalDeclarationCompletionItem(KDevelop::DeclarationPointer decl, QExplicitlySharedDataPointer context, int inheritanceDepth) : m_completionContext(context), m_declaration(decl), m_inheritanceDepth(inheritanceDepth) { } KDevelop::DeclarationPointer NormalDeclarationCompletionItem::declaration() const { return m_declaration; } QExplicitlySharedDataPointer< KDevelop::CodeCompletionContext > NormalDeclarationCompletionItem::completionContext() const { return m_completionContext; } int NormalDeclarationCompletionItem::inheritanceDepth() const { return m_inheritanceDepth; } int NormalDeclarationCompletionItem::argumentHintDepth() const { if( m_completionContext ) return m_completionContext->depth(); else return 0; } QString NormalDeclarationCompletionItem::declarationName() const { QString ret = m_declaration->identifier().toString(); if (ret.isEmpty()) - return ""; + return QStringLiteral(""); else return ret; } void NormalDeclarationCompletionItem::execute(KTextEditor::View* view, const KTextEditor::Range& word) { if( m_completionContext && m_completionContext->depth() != 0 ) return; //Do not replace any text when it is an argument-hint KTextEditor::Document* document = view->document(); QString newText; { KDevelop::DUChainReadLocker lock(KDevelop::DUChain::lock()); if(m_declaration) { newText = declarationName(); } else { qCDebug(LANGUAGE) << "Declaration disappeared"; return; } } document->replaceText(word, newText); KTextEditor::Range newRange = word; newRange.setEnd(KTextEditor::Cursor(newRange.end().line(), newRange.start().column() + newText.length())); executed(view, newRange); } QWidget* NormalDeclarationCompletionItem::createExpandingWidget(const KDevelop::CodeCompletionModel* model) const { Q_UNUSED(model); return 0; } bool NormalDeclarationCompletionItem::createsExpandingWidget() const { return false; } QString NormalDeclarationCompletionItem::shortenedTypeString(KDevelop::DeclarationPointer decl, int desiredTypeLength) const { Q_UNUSED(desiredTypeLength); return decl->abstractType()->toString(); } void NormalDeclarationCompletionItem::executed(KTextEditor::View* view, const KTextEditor::Range& word) { Q_UNUSED(view); Q_UNUSED(word); } QVariant NormalDeclarationCompletionItem::data(const QModelIndex& index, int role, const KDevelop::CodeCompletionModel* model) const { DUChainReadLocker lock(DUChain::lock(), 500); if(!lock.locked()) { qCDebug(LANGUAGE) << "Failed to lock the du-chain in time"; return QVariant(); } if(!m_declaration) return QVariant(); switch (role) { case Qt::DisplayRole: if (index.column() == CodeCompletionModel::Name) { return declarationName(); } else if(index.column() == CodeCompletionModel::Postfix) { if (FunctionType::Ptr functionType = m_declaration->type()) { // Retrieve const/volatile string return functionType->AbstractType::toString(); } } else if(index.column() == CodeCompletionModel::Prefix) { if(m_declaration->kind() == Declaration::Namespace) return QStringLiteral("namespace"); if (m_declaration->abstractType()) { if(EnumeratorType::Ptr enumerator = m_declaration->type()) { if(m_declaration->context()->owner() && m_declaration->context()->owner()->abstractType()) { if(!m_declaration->context()->owner()->identifier().isEmpty()) return shortenedTypeString(DeclarationPointer(m_declaration->context()->owner()), desiredTypeLength); else return "enum"; } } if (FunctionType::Ptr functionType = m_declaration->type()) { ClassFunctionDeclaration* funDecl = dynamic_cast(m_declaration.data()); if (functionType->returnType()) { QString ret = shortenedTypeString(m_declaration, desiredTypeLength); if(shortenArgumentHintReturnValues && argumentHintDepth() && ret.length() > maximumArgumentHintReturnValueLength) return QStringLiteral("..."); else return ret; }else if(argumentHintDepth()) { return QString();//Don't show useless prefixes in the argument-hints }else if(funDecl && funDecl->isConstructor() ) return ""; else if(funDecl && funDecl->isDestructor() ) return ""; else return ""; } else { return shortenedTypeString(m_declaration, desiredTypeLength); } } else { return ""; } } else if (index.column() == CodeCompletionModel::Arguments) { if (m_declaration->isFunctionDeclaration()) { auto functionType = declaration()->type(); return functionType ? functionType->partToString(FunctionType::SignatureArguments) : QVariant(); } } break; case CodeCompletionModel::BestMatchesCount: return QVariant(normalBestMatchesCount); break; case CodeCompletionModel::IsExpandable: return QVariant(createsExpandingWidget()); case CodeCompletionModel::ExpandingWidget: { QWidget* nav = createExpandingWidget(model); Q_ASSERT(nav); model->addNavigationWidget(this, nav); QVariant v; v.setValue(nav); return v; } case CodeCompletionModel::ScopeIndex: return static_cast(reinterpret_cast(m_declaration->context())); case CodeCompletionModel::CompletionRole: return (int)completionProperties(); case CodeCompletionModel::ItemSelected: { NavigationContextPointer ctx(new AbstractDeclarationNavigationContext(DeclarationPointer(m_declaration), TopDUContextPointer())); return ctx->html(true); } case Qt::DecorationRole: { if( index.column() == CodeCompletionModel::Icon ) { CodeCompletionModel::CompletionProperties p = completionProperties(); lock.unlock(); return DUChainUtils::iconForProperties(p); } break; } } return QVariant(); } } diff --git a/language/codegen/applychangeswidget.cpp b/language/codegen/applychangeswidget.cpp index 6fa12f9dd..904c851f1 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) : 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 auto templateName = QDir::tempPath() + QLatin1Char('/') + url.fileName().split('.').last(); + 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 57f1b96b0..9cc22ccd3 100644 --- a/language/codegen/basicrefactoring.cpp +++ b/language/codegen/basicrefactoring.cpp @@ -1,356 +1,356 @@ /* 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()), 0); action->setData(QVariant::fromValue(IndexedDeclaration(declaration))); - action->setIcon(QIcon::fromTheme("edit-rename")); + 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); 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); } DocumentChangeSet::ChangeResult BasicRefactoring::applyChangesToDeclarations(const QString &oldName, const QString &newName, DocumentChangeSet &changes, const QList &declarations) { - foreach (const IndexedDeclaration &decl, 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); } KDevelop::IndexedDeclaration BasicRefactoring::declarationUnderCursor(bool allowUse) { KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView(); Q_ASSERT(view); KTextEditor::Document* doc = view->document(); DUChainReadLocker lock; if (allowUse) return DUChainUtils::itemUnderCursor(doc->url(), KTextEditor::Cursor(view->cursorPosition())); 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(); 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()) { + foreach (const KDevelop::IndexedTopDUContext collected, collector->allUsingContexts()) { QSet hadIndices; - foreach (const IndexedDeclaration& decl, collector->declarations()) { + foreach (const IndexedDeclaration decl, collector->declarations()) { uint usedDeclarationIndex = collected.data()->indexForUsedDeclaration(decl.data(), false); if (hadIndices.contains(usedDeclarationIndex)) continue; hadIndices.insert(usedDeclarationIndex); DocumentChangeSet::ChangeResult result = applyChanges(originalName, replacementName, changes, collected.data(), usedDeclarationIndex); if (!result) { KMessageBox::error(0, i18n("Applying changes failed: %1", result.m_failureReason)); return {}; } } } DocumentChangeSet::ChangeResult result = applyChangesToDeclarations(originalName, replacementName, changes, collector->declarations()); if (!result) { KMessageBox::error(0, 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(0, i18n("Applying changes failed: %1", result.m_failureReason)); } return {}; } //END: BasicRefactoring diff --git a/language/codegen/basicrefactoring.h b/language/codegen/basicrefactoring.h index 9017da5c3..8eec1faea 100644 --- a/language/codegen/basicrefactoring.h +++ b/language/codegen/basicrefactoring.h @@ -1,160 +1,162 @@ /* This file is part of KDevelop * * Copyright 2014 Miquel Sabaté * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef BASICREFACTORING_H_ #define BASICREFACTORING_H_ #include #include #include #include #include class CppLanguageSupport; namespace KDevelop { class ContextMenuExtension; class IndexedDeclaration; class Context; class Declaration; class DUContext; /** * A widget that show the uses that it has collected for * the given declaration. */ class KDEVPLATFORMLANGUAGE_EXPORT BasicRefactoringCollector : public UsesWidget::UsesWidgetCollector { + Q_OBJECT + public: explicit BasicRefactoringCollector(const IndexedDeclaration &decl); QVector allUsingContexts() const; protected: /// Process the uses for the given TopDUContext. void processUses(KDevelop::ReferencedTopDUContext topContext) override; private: QVector m_allUsingContexts; }; /// The base class for Refactoring classes from Language plugins. class KDEVPLATFORMLANGUAGE_EXPORT BasicRefactoring : public QObject { Q_OBJECT public: explicit BasicRefactoring(QObject *parent = NULL); /// Update the context menu with the "Rename" action. virtual void fillContextMenu(KDevelop::ContextMenuExtension &extension, KDevelop::Context *context); struct NameAndCollector { QString newName; QSharedPointer collector; }; /** * @return Suggestion for new filename based on the current file's name @p current and new identifer @p newName */ virtual QString newFileName(const QUrl& current, const QString& newName); /** * Add the change(s) related to renaming @p file to @p newName to @p changes and return the result. * * @param current The URL for the file you want to rename. * @param newName The new name of the file *without* the file extension. * @param changes The change set to add the rename changes to. */ virtual KDevelop::DocumentChangeSet::ChangeResult addRenameFileChanges(const QUrl& current, const QString& newName, KDevelop::DocumentChangeSet* changes); virtual bool shouldRenameUses(Declaration* declaration) const; /** * @return true if the declaration's file should be renamed if the declaration * was renamed. */ virtual bool shouldRenameFile(KDevelop::Declaration* declaration); public slots: void executeRenameAction(); protected: /** * Apply the changes to the uses that can be found inside the given * context and its children. * NOTE: the DUChain must be locked. */ virtual DocumentChangeSet::ChangeResult applyChanges(const QString &oldName, const QString &newName, DocumentChangeSet &changes, DUContext *context, int usedDeclarationIndex); /** * Apply the changes to the given declarations. * NOTE: the DUChain must be locked. */ virtual DocumentChangeSet::ChangeResult applyChangesToDeclarations(const QString &oldName, const QString &newName, DocumentChangeSet &changes, const QList &declarations); /** * Get the declaration under the current position of the cursor. * * @param allowUse Set to false if the declarations to be returned * cannot come from uses. */ virtual IndexedDeclaration declarationUnderCursor(bool allowUse = true); /** * Start the renaming of a declaration. * This function retrieves the new name for declaration @p decl and if approved renames all instances of it. */ virtual void startInteractiveRename(const KDevelop::IndexedDeclaration &decl); /** * Asks user to input a new name for @p declaration * @return new name or empty string if user changed his mind or new name contains inappropriate symbols (e.g. spaces, points, braces e.t.c) and the collector used for collecting information about @p declaration. * NOTE: unlock the DUChain before calling this. */ virtual BasicRefactoring::NameAndCollector newNameForDeclaration(const KDevelop::DeclarationPointer& declaration); /** * Renames all declarations collected by @p collector from @p oldName to @p newName * @param apply - if changes should be applied immediately * @return all changes if @p apply is false and empty set otherwise. */ DocumentChangeSet renameCollectedDeclarations(KDevelop::BasicRefactoringCollector* collector, const QString& replacementName, const QString& originalName, bool apply = true); /** * @returns true if we can show the interactive rename widget for the * given declaration. The default implementation just returns true. */ virtual bool acceptForContextMenu(const Declaration *decl); }; } // End of namespace KDevelop #endif /* BASICREFACTORING_H_ */ diff --git a/language/codegen/codedescription.cpp b/language/codegen/codedescription.cpp index 68a5f394f..68fd29aee 100644 --- a/language/codegen/codedescription.cpp +++ b/language/codegen/codedescription.cpp @@ -1,173 +1,173 @@ /* This file is part of KDevelop Copyright 2012 Miha Čančula This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "codedescription.h" #include "util/debug.h" #include #include #include #include #include #include #include #include using namespace KDevelop; /** * The access policy as a string, or an empty string * if the policy is set to default * * The DUChain must be locked when calling this function **/ QString accessPolicyName(const DeclarationPointer& declaration) { DUChainPointer member = declaration.dynamicCast(); if (member) { switch (member->accessPolicy()) { case Declaration::Private: - return "private"; + return QStringLiteral("private"); case Declaration::Protected: - return "protected"; + return QStringLiteral("protected"); case Declaration::Public: - return "public"; + return QStringLiteral("public"); default: break; } } return QString(); } VariableDescription::VariableDescription() { } VariableDescription::VariableDescription(const QString& type, const QString& name) : name(name) , type(type) { } VariableDescription::VariableDescription(const DeclarationPointer& declaration) { DUChainReadLocker lock; if (declaration) { name = declaration->identifier().toString(); type = declaration->abstractType()->toString(); } access = accessPolicyName(declaration); } FunctionDescription::FunctionDescription() { } FunctionDescription::FunctionDescription(const QString& name, const VariableDescriptionList& arguments, const VariableDescriptionList& returnArguments) : name(name) , arguments(arguments) , returnArguments(returnArguments) { } FunctionDescription::FunctionDescription(const DeclarationPointer& declaration) { DUChainReadLocker lock; if (declaration) { name = declaration->identifier().toString(); DUContext* context = declaration->internalContext(); DUChainPointer function = declaration.dynamicCast(); if (function) { context = DUChainUtils::getArgumentContext(declaration.data()); } DUChainPointer method = declaration.dynamicCast(); if (method) { isConstructor = method->isConstructor(); isDestructor = method->isDestructor(); isVirtual = method->isVirtual(); isStatic = method->isStatic(); isSlot = method->isSlot(); isSignal = method->isSignal(); } int i = 0; foreach (Declaration* arg, context->localDeclarations()) { VariableDescription var = VariableDescription(DeclarationPointer(arg)); if (function) { var.value = function->defaultParameterForArgument(i).str(); qCDebug(LANGUAGE) << var.name << var.value; } arguments << var; ++i; } FunctionType::Ptr functionType = declaration->abstractType().cast(); if (functionType) { isConst = (functionType->modifiers() & AbstractType::ConstModifier); } if (functionType && functionType->returnType()) { returnArguments << VariableDescription(functionType->returnType()->toString(), QString()); } access = accessPolicyName(declaration); } } QString FunctionDescription::returnType() const { if (returnArguments.isEmpty()) { return QString(); } return returnArguments.first().type; } ClassDescription::ClassDescription() { } ClassDescription::ClassDescription(const QString& name) : name(name) { } diff --git a/language/codegen/codedescriptionmetatypes.h b/language/codegen/codedescriptionmetatypes.h index ff4bfe447..404d70b63 100644 --- a/language/codegen/codedescriptionmetatypes.h +++ b/language/codegen/codedescriptionmetatypes.h @@ -1,70 +1,70 @@ /* This file is part of KDevelop Copyright 2012 Miha Čančula This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_CODEDESCRIPTIONMETATYPES_H #define KDEVPLATFORM_CODEDESCRIPTIONMETATYPES_H #include "codedescription.h" #include #define GRANTLEE_LOOKUP_PROPERTY(name) \ -if (property == #name) return QVariant::fromValue(object.name); +if (property == QStringLiteral(#name)) return QVariant::fromValue(object.name); #define GRANTLEE_LOOKUP_LIST_PROPERTY(name) \ -if (property == #name) return QVariant::fromValue(KDevelop::CodeDescription::toVariantList(object.name)); +if (property == QStringLiteral(#name)) return QVariant::fromValue(KDevelop::CodeDescription::toVariantList(object.name)); GRANTLEE_BEGIN_LOOKUP(KDevelop::VariableDescription) GRANTLEE_LOOKUP_PROPERTY(name) GRANTLEE_LOOKUP_PROPERTY(type) GRANTLEE_LOOKUP_PROPERTY(access) GRANTLEE_LOOKUP_PROPERTY(value) GRANTLEE_END_LOOKUP GRANTLEE_BEGIN_LOOKUP(KDevelop::FunctionDescription) GRANTLEE_LOOKUP_PROPERTY(name) GRANTLEE_LOOKUP_PROPERTY(access) GRANTLEE_LOOKUP_LIST_PROPERTY(arguments) GRANTLEE_LOOKUP_LIST_PROPERTY(returnArguments) GRANTLEE_LOOKUP_PROPERTY(isConstructor) GRANTLEE_LOOKUP_PROPERTY(isDestructor) GRANTLEE_LOOKUP_PROPERTY(isVirtual) GRANTLEE_LOOKUP_PROPERTY(isStatic) GRANTLEE_LOOKUP_PROPERTY(isConst) GRANTLEE_LOOKUP_PROPERTY(isSignal) GRANTLEE_LOOKUP_PROPERTY(isSlot) - if (property == "returnType") + if (property == QLatin1String("returnType")) { return object.returnType(); } GRANTLEE_END_LOOKUP GRANTLEE_BEGIN_LOOKUP(KDevelop::InheritanceDescription) GRANTLEE_LOOKUP_PROPERTY(inheritanceMode) GRANTLEE_LOOKUP_PROPERTY(baseType) GRANTLEE_END_LOOKUP GRANTLEE_BEGIN_LOOKUP(KDevelop::ClassDescription) GRANTLEE_LOOKUP_PROPERTY(name) GRANTLEE_LOOKUP_LIST_PROPERTY(baseClasses) GRANTLEE_LOOKUP_LIST_PROPERTY(members) GRANTLEE_LOOKUP_LIST_PROPERTY(methods) GRANTLEE_END_LOOKUP #endif // KDEVPLATFORM_CODEDESCRIPTIONMETATYPES_H diff --git a/language/codegen/coderepresentation.cpp b/language/codegen/coderepresentation.cpp index 965271791..fe4b25d13 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) { 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) { 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) { 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) { 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 QString::fromLatin1("/kdev-artificial/") + url.adjusted(QUrl::NormalizePathSegments).path(); + 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/documentchangeset.cpp b/language/codegen/documentchangeset.cpp index 1c38a1c08..3288c155a 100644 --- a/language/codegen/documentchangeset.cpp +++ b/language/codegen/documentchangeset.cpp @@ -1,582 +1,582 @@ /* 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 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("\n"); + 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; } DocumentChangeSet::ChangeResult DocumentChangeSetPrivate::addChange(const DocumentChangePointer& change) { changes[change->m_document].append(change); return true; } 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(); } // 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); 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; } //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; } DocumentChangeSet::ChangeResult DocumentChangeSetPrivate::generateNewText(const IndexedString & file, ChangesList& sortedChanges, const CodeRepresentation * repr, QString & output) { ISourceFormatter* formatter = 0; 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("\n"); + 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("\n").mid(change.m_range.end().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("\n"); + 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("\n"); + output = textLines.join(QStringLiteral("\n")); return true; } //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; } 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/sourcefiletemplate.cpp b/language/codegen/sourcefiletemplate.cpp index 1707d5571..d24be0e13 100644 --- a/language/codegen/sourcefiletemplate.cpp +++ b/language/codegen/sourcefiletemplate.cpp @@ -1,319 +1,319 @@ /* * This file is part of KDevelop * Copyright 2012 Miha Čančula * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "sourcefiletemplate.h" #include "templaterenderer.h" #include "util/debug.h" #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; typedef SourceFileTemplate::ConfigOption ConfigOption; class KDevelop::SourceFileTemplatePrivate { public: KArchive* archive; QString descriptionFileName; QStringList searchLocations; ConfigOption readEntry(const QDomElement& element, TemplateRenderer* renderer); }; ConfigOption SourceFileTemplatePrivate::readEntry(const QDomElement& element, TemplateRenderer* renderer) { ConfigOption entry; - entry.name = element.attribute("name"); - entry.type = element.attribute("type", "String"); + entry.name = element.attribute(QStringLiteral("name")); + entry.type = element.attribute(QStringLiteral("type"), QStringLiteral("String")); for (QDomElement e = element.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) { QString tag = e.tagName(); - if (tag == "label") + if (tag == QLatin1String("label")) { entry.label = e.text(); } - else if (tag == "tooltip") + else if (tag == QLatin1String("tooltip")) { entry.label = e.text(); } - else if (tag == "whatsthis") + else if (tag == QLatin1String("whatsthis")) { entry.label = e.text(); } - else if ( tag == "min" ) + else if ( tag == QLatin1String("min") ) { entry.minValue = e.text(); } - else if ( tag == "max" ) + else if ( tag == QLatin1String("max") ) { entry.maxValue = e.text(); } - else if ( tag == "default" ) + else if ( tag == QLatin1String("default") ) { entry.value = renderer->render(e.text(), entry.name); } } qCDebug(LANGUAGE) << "Read entry" << entry.name << "with default value" << entry.value; return entry; } SourceFileTemplate::SourceFileTemplate (const QString& templateDescription) : d(new KDevelop::SourceFileTemplatePrivate) { d->archive = 0; setTemplateDescription(templateDescription); } SourceFileTemplate::SourceFileTemplate() : d(new KDevelop::SourceFileTemplatePrivate) { d->archive = 0; } SourceFileTemplate::SourceFileTemplate (const SourceFileTemplate& other) : d(new KDevelop::SourceFileTemplatePrivate) { d->archive = 0; *this = other; } SourceFileTemplate::~SourceFileTemplate() { delete d->archive; delete d; } SourceFileTemplate& SourceFileTemplate::operator=(const SourceFileTemplate& other) { if (other.d == d) { return *this; } delete d->archive; if (other.d->archive) { - if (other.d->archive->fileName().endsWith(".zip")) { + if (other.d->archive->fileName().endsWith(QLatin1String(".zip"))) { d->archive = new KZip(other.d->archive->fileName()); } else { d->archive = new KTar(other.d->archive->fileName()); } d->archive->open(QIODevice::ReadOnly); } else { d->archive = 0; } d->descriptionFileName = other.d->descriptionFileName; return *this; } void SourceFileTemplate::setTemplateDescription(const QString& templateDescription) { delete d->archive; d->descriptionFileName = templateDescription; QString archiveFileName; const QString templateBaseName = QFileInfo(templateDescription).baseName(); - d->searchLocations.append(QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, "/kdevfiletemplates/templates/", QStandardPaths::LocateDirectory)); + d->searchLocations.append(QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("/kdevfiletemplates/templates/"), QStandardPaths::LocateDirectory)); foreach(const QString& dir, d->searchLocations) { qDebug() << "search in:" << dir << "look for:" << templateBaseName; foreach(const auto& entry, QDir(dir).entryInfoList(QDir::Files)) { qDebug() << entry.baseName(); if (entry.baseName() == templateBaseName) { archiveFileName = entry.absoluteFilePath(); qCDebug(LANGUAGE) << "Found template archive" << archiveFileName; break; } } } if (archiveFileName.isEmpty() || !QFileInfo(archiveFileName).exists()) { qCWarning(LANGUAGE) << "Could not find a template archive for description" << templateDescription << ", archive file" << archiveFileName; d->archive = 0; } else { QFileInfo info(archiveFileName); - if (info.suffix() == "zip") { + if (info.suffix() == QLatin1String("zip")) { d->archive = new KZip(archiveFileName); } else { d->archive = new KTar(archiveFileName); } d->archive->open(QIODevice::ReadOnly); } } bool SourceFileTemplate::isValid() const { return d->archive; } QString SourceFileTemplate::name() const { KConfig templateConfig(d->descriptionFileName); KConfigGroup cg(&templateConfig, "General"); return cg.readEntry("Name"); } QString SourceFileTemplate::type() const { KConfig templateConfig(d->descriptionFileName); KConfigGroup cg(&templateConfig, "General"); return cg.readEntry("Type", QString()); } QString SourceFileTemplate::languageName() const { KConfig templateConfig(d->descriptionFileName); KConfigGroup cg(&templateConfig, "General"); return cg.readEntry("Language", QString()); } QStringList SourceFileTemplate::category() const { KConfig templateConfig(d->descriptionFileName); KConfigGroup cg(&templateConfig, "General"); return cg.readEntry("Category", QStringList()); } QStringList SourceFileTemplate::defaultBaseClasses() const { KConfig templateConfig(d->descriptionFileName); KConfigGroup cg(&templateConfig, "General"); return cg.readEntry("BaseClasses", QStringList()); } const KArchiveDirectory* SourceFileTemplate::directory() const { Q_ASSERT(isValid()); return d->archive->directory(); } QList< SourceFileTemplate::OutputFile > SourceFileTemplate::outputFiles() const { QList outputFiles; KConfig templateConfig(d->descriptionFileName); KConfigGroup group(&templateConfig, "General"); QStringList files = group.readEntry("Files", QStringList()); qCDebug(LANGUAGE) << "Files in template" << files; foreach (const QString& fileGroup, files) { KConfigGroup cg(&templateConfig, fileGroup); OutputFile f; f.identifier = cg.name(); f.label = cg.readEntry("Name"); f.fileName = cg.readEntry("File"); f.outputName = cg.readEntry("OutputFile"); outputFiles << f; } return outputFiles; } bool SourceFileTemplate::hasCustomOptions() const { Q_ASSERT(isValid()); KConfig templateConfig(d->descriptionFileName); KConfigGroup cg(&templateConfig, "General"); bool hasOptions = d->archive->directory()->entries().contains(cg.readEntry("OptionsFile", "options.kcfg")); qCDebug(LANGUAGE) << cg.readEntry("OptionsFile", "options.kcfg") << hasOptions; return hasOptions; } QHash< QString, QList > SourceFileTemplate::customOptions(TemplateRenderer* renderer) const { Q_ASSERT(isValid()); KConfig templateConfig(d->descriptionFileName); KConfigGroup cg(&templateConfig, "General"); const KArchiveEntry* entry = d->archive->directory()->entry(cg.readEntry("OptionsFile", "options.kcfg")); QHash > options; if (!entry->isFile()) { return options; } const KArchiveFile* file = static_cast(entry); /* * Copied from kconfig_compiler.kcfg */ QDomDocument doc; QString errorMsg; int errorRow; int errorCol; if ( !doc.setContent( file->data(), &errorMsg, &errorRow, &errorCol ) ) { qCDebug(LANGUAGE) << "Unable to load document."; qCDebug(LANGUAGE) << "Parse error in line " << errorRow << ", col " << errorCol << ": " << errorMsg; return options; } QDomElement cfgElement = doc.documentElement(); if ( cfgElement.isNull() ) { qCDebug(LANGUAGE) << "No document in kcfg file"; return options; } - QDomNodeList groups = cfgElement.elementsByTagName("group"); + QDomNodeList groups = cfgElement.elementsByTagName(QStringLiteral("group")); for (int i = 0; i < groups.size(); ++i) { QDomElement group = groups.at(i).toElement(); QList optionGroup; - QString groupName = group.attribute("name"); + QString groupName = group.attribute(QStringLiteral("name")); - QDomNodeList entries = group.elementsByTagName("entry"); + QDomNodeList entries = group.elementsByTagName(QStringLiteral("entry")); for (int j = 0; j < entries.size(); ++j) { QDomElement entry = entries.at(j).toElement(); optionGroup << d->readEntry(entry, renderer); } options.insert(groupName, optionGroup); } return options; } void SourceFileTemplate::addAdditionalSearchLocation(const QString& location) { if(!d->searchLocations.contains(location)) d->searchLocations.append(location); } diff --git a/language/codegen/templateclassgenerator.cpp b/language/codegen/templateclassgenerator.cpp index 85dbf367d..90b7e4b28 100644 --- a/language/codegen/templateclassgenerator.cpp +++ b/language/codegen/templateclassgenerator.cpp @@ -1,338 +1,338 @@ /* This file is part of KDevelop Copyright 2012 Miha Čančula This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "templateclassgenerator.h" #include "archivetemplateloader.h" #include "util/debug.h" #include "interfaces/icore.h" #include #include #include "language/codegen/documentchangeset.h" #include "codedescription.h" #include "templaterenderer.h" #include "sourcefiletemplate.h" #include "templateengine.h" #include #include #include #include #include #include #include #include using namespace KDevelop; /// @param base String such as 'public QObject' or 'QObject' InheritanceDescription descriptionFromString(const QString& base) { QStringList splitBase = base.split(' '); QString identifier = splitBase.takeLast(); - QString inheritanceMode = splitBase.join(" "); + QString inheritanceMode = splitBase.join(QStringLiteral(" ")); InheritanceDescription desc; desc.baseType = identifier; desc.inheritanceMode = inheritanceMode; return desc; } class KDevelop::TemplateClassGeneratorPrivate { public: SourceFileTemplate fileTemplate; QUrl baseUrl; TemplateRenderer renderer; QString name; QString identifier; QStringList namespaces; QString license; QHash fileUrls; QHash filePositions; ClassDescription description; QList directBaseClasses; QList allBaseClasses; void fetchSuperClasses(const DeclarationPointer& declaration); }; void TemplateClassGeneratorPrivate::fetchSuperClasses(const DeclarationPointer& declaration) { DUChainReadLocker lock; //Prevent duplicity if(allBaseClasses.contains(declaration)) { return; } allBaseClasses << declaration; DUContext* context = declaration->internalContext(); if (context) { foreach (const DUContext::Import& import, context->importedParentContexts()) { if (DUContext * parentContext = import.context(context->topContext())) { if (parentContext->type() == DUContext::Class) { fetchSuperClasses( DeclarationPointer(parentContext->owner()) ); } } } } } TemplateClassGenerator::TemplateClassGenerator(const QUrl& baseUrl) : d(new TemplateClassGeneratorPrivate) { Q_ASSERT(QFileInfo(baseUrl.toLocalFile()).isDir()); // assume folder d->baseUrl = baseUrl; d->renderer.setEmptyLinesPolicy(TemplateRenderer::TrimEmptyLines); } TemplateClassGenerator::~TemplateClassGenerator() { delete d; } void TemplateClassGenerator::setTemplateDescription(const SourceFileTemplate& fileTemplate) { d->fileTemplate = fileTemplate; Q_ASSERT(fileTemplate.isValid()); } DocumentChangeSet TemplateClassGenerator::generate() { return d->renderer.renderFileTemplate(d->fileTemplate, d->baseUrl, fileUrls()); } QHash TemplateClassGenerator::fileLabels() const { Q_ASSERT(d->fileTemplate.isValid()); QHash labels; foreach (const SourceFileTemplate::OutputFile& outputFile, d->fileTemplate.outputFiles()) { labels.insert(outputFile.identifier, outputFile.label); } return labels; } TemplateClassGenerator::UrlHash TemplateClassGenerator::fileUrls() const { if (d->fileUrls.isEmpty()) { foreach (const SourceFileTemplate::OutputFile& outputFile, d->fileTemplate.outputFiles()) { QString outputName = d->renderer.render(outputFile.outputName, outputFile.identifier); QUrl url = d->baseUrl.resolved(QUrl(outputName)); d->fileUrls.insert(outputFile.identifier, url); } } return d->fileUrls; } QUrl TemplateClassGenerator::baseUrl() const { return d->baseUrl; } QUrl TemplateClassGenerator::fileUrl(const QString& outputFile) const { return fileUrls().value(outputFile); } void TemplateClassGenerator::setFileUrl(const QString& outputFile, const QUrl& url) { d->fileUrls.insert(outputFile, url); d->renderer.addVariable("output_file_" + outputFile.toLower(), QDir(d->baseUrl.path()).relativeFilePath(url.path())); d->renderer.addVariable("output_file_" + outputFile.toLower() + "_absolute", url.toLocalFile()); } KTextEditor::Cursor TemplateClassGenerator::filePosition(const QString& outputFile) const { return d->filePositions.value(outputFile); } void TemplateClassGenerator::setFilePosition(const QString& outputFile, const KTextEditor::Cursor& position) { d->filePositions.insert(outputFile, position); } void TemplateClassGenerator::addVariables(const QVariantHash& variables) { d->renderer.addVariables(variables); } QString TemplateClassGenerator::renderString(const QString& text) const { return d->renderer.render(text); } SourceFileTemplate TemplateClassGenerator::sourceFileTemplate() const { return d->fileTemplate; } TemplateRenderer* TemplateClassGenerator::renderer() const { return &(d->renderer); } QString TemplateClassGenerator::name() const { return d->name; } void TemplateClassGenerator::setName(const QString& newName) { d->name = newName; - d->renderer.addVariable("name", newName); + d->renderer.addVariable(QStringLiteral("name"), newName); } QString TemplateClassGenerator::identifier() const { return name(); } void TemplateClassGenerator::setIdentifier(const QString& identifier) { - d->renderer.addVariable("identifier", identifier);; + d->renderer.addVariable(QStringLiteral("identifier"), identifier);; QStringList separators; - separators << "::" << "." << ":" << "\\" << "/"; + separators << QStringLiteral("::") << QStringLiteral(".") << QStringLiteral(":") << QStringLiteral("\\") << QStringLiteral("/"); QStringList ns; foreach (const QString& separator, separators) { ns = identifier.split(separator); if (ns.size() > 1) { break; } } setName(ns.takeLast()); setNamespaces(ns); } QStringList TemplateClassGenerator::namespaces() const { return d->namespaces; } void TemplateClassGenerator::setNamespaces(const QStringList& namespaces) const { d->namespaces = namespaces; - d->renderer.addVariable("namespaces", namespaces); + d->renderer.addVariable(QStringLiteral("namespaces"), namespaces); } /// Specify license for this class void TemplateClassGenerator::setLicense(const QString& license) { qCDebug(LANGUAGE) << "New Class: " << d->name << "Set license: " << d->license; d->license = license; - d->renderer.addVariable("license", license); + d->renderer.addVariable(QStringLiteral("license"), license); } /// Get the license specified for this classes QString TemplateClassGenerator::license() const { return d->license; } void TemplateClassGenerator::setDescription(const ClassDescription& description) { d->description = description; QVariantHash variables; - variables["description"] = QVariant::fromValue(description); - variables["members"] = CodeDescription::toVariantList(description.members); - variables["functions"] = CodeDescription::toVariantList(description.methods); - variables["base_classes"] = CodeDescription::toVariantList(description.baseClasses); + variables[QStringLiteral("description")] = QVariant::fromValue(description); + variables[QStringLiteral("members")] = CodeDescription::toVariantList(description.members); + variables[QStringLiteral("functions")] = CodeDescription::toVariantList(description.methods); + variables[QStringLiteral("base_classes")] = CodeDescription::toVariantList(description.baseClasses); d->renderer.addVariables(variables); } ClassDescription TemplateClassGenerator::description() const { return d->description; } void TemplateClassGenerator::addBaseClass(const QString& base) { const InheritanceDescription desc = descriptionFromString(base); ClassDescription cd = description(); cd.baseClasses << desc; setDescription(cd); DUChainReadLocker lock; PersistentSymbolTable::Declarations decl = PersistentSymbolTable::self().getDeclarations(IndexedQualifiedIdentifier(QualifiedIdentifier(desc.baseType))); //Search for all super classes for(PersistentSymbolTable::Declarations::Iterator it = decl.iterator(); it; ++it) { DeclarationPointer declaration = DeclarationPointer(it->declaration()); if(declaration->isForwardDeclaration()) { continue; } // Check if it's a class/struct/etc if(declaration->type()) { d->fetchSuperClasses(declaration); d->directBaseClasses << declaration; break; } } } void TemplateClassGenerator::setBaseClasses(const QList& bases) { // clear ClassDescription cd = description(); cd.baseClasses.clear(); setDescription(cd); d->directBaseClasses.clear(); d->allBaseClasses.clear(); // add all bases foreach (const QString& base, bases) { addBaseClass(base); } } QList< DeclarationPointer > TemplateClassGenerator::directBaseClasses() const { return d->directBaseClasses; } QList< DeclarationPointer > TemplateClassGenerator::allBaseClasses() const { return d->allBaseClasses; } diff --git a/language/codegen/templateengine.cpp b/language/codegen/templateengine.cpp index 1a7adc0e0..2db564aba 100644 --- a/language/codegen/templateengine.cpp +++ b/language/codegen/templateengine.cpp @@ -1,67 +1,67 @@ /* * 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. */ #include "templateengine.h" #include "templateengine_p.h" #include "codedescription.h" #include "codedescriptionmetatypes.h" #include "archivetemplateloader.h" #include #include #include using namespace KDevelop; using namespace Grantlee; TemplateEngine* TemplateEngine::self() { static TemplateEngine* engine = new TemplateEngine; return engine; } TemplateEngine::TemplateEngine() : d(new TemplateEnginePrivate) { d->engine.setSmartTrimEnabled(true); - addTemplateDirectories(QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, "kdevcodegen/templates", QStandardPaths::LocateDirectory)); + addTemplateDirectories(QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("kdevcodegen/templates"), QStandardPaths::LocateDirectory)); Grantlee::registerMetaType(); Grantlee::registerMetaType(); Grantlee::registerMetaType(); Grantlee::registerMetaType(); d->engine.addTemplateLoader(QSharedPointer(ArchiveTemplateLoader::self())); } TemplateEngine::~TemplateEngine() { } void TemplateEngine::addTemplateDirectories(const QStringList& directories) { FileSystemTemplateLoader* loader = new FileSystemTemplateLoader; loader->setTemplateDirs(directories); d->engine.addTemplateLoader(QSharedPointer(loader)); } diff --git a/language/codegen/templaterenderer.cpp b/language/codegen/templaterenderer.cpp index 8a6e7ac61..62473b158 100644 --- a/language/codegen/templaterenderer.cpp +++ b/language/codegen/templaterenderer.cpp @@ -1,310 +1,310 @@ /* * This file is part of KDevelop * Copyright 2012 Miha Čančula * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "templaterenderer.h" #include "documentchangeset.h" #include "sourcefiletemplate.h" #include "templateengine.h" #include "templateengine_p.h" #include "archivetemplateloader.h" #include "util/debug.h" #include #include #include #include #include #include #include using namespace Grantlee; class NoEscapeStream : public OutputStream { public: NoEscapeStream(); explicit NoEscapeStream (QTextStream* stream); QString escape (const QString& input) const override; QSharedPointer< OutputStream > clone (QTextStream* stream) const override; }; NoEscapeStream::NoEscapeStream() : OutputStream() { } NoEscapeStream::NoEscapeStream(QTextStream* stream) : OutputStream (stream) { } QString NoEscapeStream::escape(const QString& input) const { return input; } QSharedPointer NoEscapeStream::clone(QTextStream* stream) const { QSharedPointer clonedStream = QSharedPointer( new NoEscapeStream( stream ) ); return clonedStream; } using namespace KDevelop; namespace KDevelop { class TemplateRendererPrivate { public: Engine* engine; Grantlee::Context context; TemplateRenderer::EmptyLinesPolicy emptyLinesPolicy; QString errorString; }; } TemplateRenderer::TemplateRenderer() : d(new TemplateRendererPrivate) { d->engine = &TemplateEngine::self()->d->engine; d->emptyLinesPolicy = KeepEmptyLines; } TemplateRenderer::~TemplateRenderer() { delete d; } void TemplateRenderer::addVariables(const QVariantHash& variables) { QVariantHash::const_iterator it = variables.constBegin(); QVariantHash::const_iterator end = variables.constEnd(); for (; it != end; ++it) { d->context.insert(it.key(), it.value()); } } void TemplateRenderer::addVariable(const QString& name, const QVariant& value) { d->context.insert(name, value); } QVariantHash TemplateRenderer::variables() const { return d->context.stackHash(0); } QString TemplateRenderer::render(const QString& content, const QString& name) const { Template t = d->engine->newTemplate(content, name); QString output; QTextStream textStream(&output); NoEscapeStream stream(&textStream); t->render(&stream, &d->context); if (t->error() != Grantlee::NoError) { d->errorString = t->errorString(); } else { d->errorString.clear(); } if (d->emptyLinesPolicy == TrimEmptyLines && output.contains('\n')) { QStringList lines = output.split('\n', QString::KeepEmptyParts); QMutableStringListIterator it(lines); // Remove empty lines from the start of the document while (it.hasNext()) { if (it.next().trimmed().isEmpty()) { it.remove(); } else { break; } } // Remove single empty lines it.toFront(); bool prePreviousEmpty = false; bool previousEmpty = false; while (it.hasNext()) { bool currentEmpty = it.peekNext().trimmed().isEmpty(); if (!prePreviousEmpty && previousEmpty && !currentEmpty) { it.remove(); } prePreviousEmpty = previousEmpty; previousEmpty = currentEmpty; it.next(); } // Compress multiple empty lines it.toFront(); previousEmpty = false; while (it.hasNext()) { bool currentEmpty = it.next().trimmed().isEmpty(); if (currentEmpty && previousEmpty) { it.remove(); } previousEmpty = currentEmpty; } // Remove empty lines from the end it.toBack(); while (it.hasPrevious()) { if (it.previous().trimmed().isEmpty()) { it.remove(); } else { break; } } // Add a newline to the end of file it.toBack(); it.insert(QString()); - output = lines.join("\n"); + output = lines.join(QStringLiteral("\n")); } else if (d->emptyLinesPolicy == RemoveEmptyLines) { QStringList lines = output.split('\n', QString::SkipEmptyParts); QMutableStringListIterator it(lines); while (it.hasNext()) { if (it.next().trimmed().isEmpty()) { it.remove(); } } it.toBack(); if (lines.size() > 1) { it.insert(QString()); } - output = lines.join("\n"); + output = lines.join(QStringLiteral("\n")); } return output; } QString TemplateRenderer::renderFile(const QUrl& url, const QString& name) const { QFile file(url.toLocalFile()); file.open(QIODevice::ReadOnly); QString content(file.readAll()); qCDebug(LANGUAGE) << content; return render(content, name); } QStringList TemplateRenderer::render(const QStringList& contents) const { qCDebug(LANGUAGE) << d->context.stackHash(0); QStringList ret; foreach (const QString& content, contents) { ret << render(content); } return ret; } void TemplateRenderer::setEmptyLinesPolicy(TemplateRenderer::EmptyLinesPolicy policy) { d->emptyLinesPolicy = policy; } TemplateRenderer::EmptyLinesPolicy TemplateRenderer::emptyLinesPolicy() const { return d->emptyLinesPolicy; } DocumentChangeSet TemplateRenderer::renderFileTemplate(const SourceFileTemplate& fileTemplate, const QUrl& baseUrl, const QHash& fileUrls) { DocumentChangeSet changes; const QDir baseDir(baseUrl.path()); QRegExp nonAlphaNumeric("\\W"); for (QHash::const_iterator it = fileUrls.constBegin(); it != fileUrls.constEnd(); ++it) { QString cleanName = it.key().toLower(); - cleanName.replace(nonAlphaNumeric, "_"); + cleanName.replace(nonAlphaNumeric, QLatin1Char('_')); const QString path = it.value().toLocalFile(); addVariable("output_file_" + cleanName, baseDir.relativeFilePath(path)); addVariable("output_file_" + cleanName + "_absolute", path); } const KArchiveDirectory* directory = fileTemplate.directory(); ArchiveTemplateLocation location(directory); foreach (const SourceFileTemplate::OutputFile& outputFile, fileTemplate.outputFiles()) { const KArchiveEntry* entry = directory->entry(outputFile.fileName); if (!entry) { qCWarning(LANGUAGE) << "Entry" << outputFile.fileName << "is mentioned in group" << outputFile.identifier << "but is not present in the archive"; continue; } const KArchiveFile* file = dynamic_cast(entry); if (!file) { qCWarning(LANGUAGE) << "Entry" << entry->name() << "is not a file"; continue; } QUrl url = fileUrls[outputFile.identifier]; IndexedString document(url); KTextEditor::Range range(KTextEditor::Cursor(0, 0), 0); DocumentChange change(document, range, QString(), render(file->data(), outputFile.identifier)); changes.addChange(change); qCDebug(LANGUAGE) << "Added change for file" << document.str(); } return changes; } QString TemplateRenderer::errorString() const { return d->errorString; } diff --git a/language/codegen/templatesmodel.cpp b/language/codegen/templatesmodel.cpp index 125daa95d..2f36db3e8 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); 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("/"))) { + if (!templateItems.contains(currentPath.join(QLatin1Char('/')))) { QStandardItem *item = new QStandardItem(entry); item->setEditable(false); parent->appendRow(item); - templateItems[currentPath.join("/")] = item; + templateItems[currentPath.join(QLatin1Char('/'))] = item; parent = item; } else { - parent = templateItems[currentPath.join("/")]; + 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(".zip") || file.endsWith(".tar.bz2")) { + 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("."); + dir.mkpath(QStringLiteral(".")); foreach (const QString &archName, templateArchives) { qCDebug(LANGUAGE) << "processing template" << archName; QScopedPointer templateArchive; - if (QFileInfo(archName).completeSuffix() == "zip") + 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(".kdevtemplate")) + 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(".desktop")) + 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 { qCDebug(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("."); + 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() == "application/x-desktop") + 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 += ".tar.bz2"; - KTar archive(destination, "application/x-bzip"); + destination += QLatin1String(".tar.bz2"); + KTar archive(destination, QStringLiteral("application/x-bzip")); #endif //Q_WS_WIN archive.open(QIODevice::WriteOnly); QDir dir(info.absoluteDir()); QDir::Filters filter = QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot; foreach (const QFileInfo& entry, dir.entryInfoList(filter)) { if (entry.isFile()) { archive.addLocalFile(entry.absoluteFilePath(), entry.fileName()); } else if (entry.isDir()) { archive.addLocalDirectory(entry.absoluteFilePath(), entry.fileName()); } } archive.close(); } else { qCDebug(LANGUAGE) << "Copying" << fileName << "to" << saveLocation; QFile::copy(fileName, saveLocation + info.fileName()); } refresh(); return destination; } diff --git a/language/codegen/tests/test_documentchangeset.cpp b/language/codegen/tests/test_documentchangeset.cpp index baca70161..9f8877239 100644 --- a/language/codegen/tests/test_documentchangeset.cpp +++ b/language/codegen/tests/test_documentchangeset.cpp @@ -1,74 +1,74 @@ /* * This file is part of KDevelop * Copyright 2013 Milian Wolff * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU 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_documentchangeset.h" #include #include #include #include #include QTEST_GUILESS_MAIN(TestDocumentchangeset); using namespace KDevelop; void TestDocumentchangeset::initTestCase() { AutoTestShell::init(); TestCore::initialize(Core::NoUi); } void TestDocumentchangeset::cleanupTestCase() { TestCore::shutdown(); } void TestDocumentchangeset::testReplaceSameLine() { - TestFile file("abc def ghi", "cpp"); + TestFile file(QStringLiteral("abc def ghi"), QStringLiteral("cpp")); qDebug() << file.fileContents(); DocumentChangeSet changes; changes.addChange( DocumentChange( file.url(), KTextEditor::Range(0, 0, 0, 3), - "abc", "foobar" + QStringLiteral("abc"), QStringLiteral("foobar") )); changes.addChange( DocumentChange( file.url(), KTextEditor::Range(0, 4, 0, 7), - "def", "foobar" + QStringLiteral("def"), QStringLiteral("foobar") )); changes.addChange( DocumentChange( file.url(), KTextEditor::Range(0, 8, 0, 11), - "ghi", "foobar" + QStringLiteral("ghi"), QStringLiteral("foobar") )); DocumentChangeSet::ChangeResult result = changes.applyAllChanges(); qDebug() << result.m_failureReason << result.m_success; QVERIFY(result); } diff --git a/language/codegen/tests/test_templateclassgenerator.cpp b/language/codegen/tests/test_templateclassgenerator.cpp index d8136b2ad..96250f742 100644 --- a/language/codegen/tests/test_templateclassgenerator.cpp +++ b/language/codegen/tests/test_templateclassgenerator.cpp @@ -1,274 +1,278 @@ /* * 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 "test_templateclassgenerator.h" #include "codegen_tests_config.h" #include "language/codegen/templateclassgenerator.h" #include "language/codegen/documentchangeset.h" #include "language/codegen/sourcefiletemplate.h" #include "language/codegen/templaterenderer.h" #include "language/codegen/templatesmodel.h" #include "tests/autotestshell.h" #include "tests/testcore.h" +/* +* CHECK_TEMPLATE_VARIABLE expects second parameter as type with 'to' instead of 'Q' in begin +* For example, QString -> toString +*/ #define CHECK_TEMPLATE_VARIABLE(name, type, val) \ -QVERIFY(variables.contains(#name)); \ -QCOMPARE(variables.value(#name).value(), val) +QVERIFY(variables.contains(QStringLiteral(#name))); \ +QCOMPARE(variables.value(QStringLiteral(#name)).type(), val) #define COMPARE_FILES(one, two) \ QCOMPARE(QString(one.readAll()), QString(two.readAll())) \ using namespace KDevelop; void TestTemplateClassGenerator::initTestCase() { AutoTestShell::init(); TestCore::initialize(Core::NoUi); // Use a temporary directory for the template work tempDir.setAutoRemove(true); baseUrl = QUrl::fromLocalFile(tempDir.path() + '/'); // Needed for extracting description out of template archives - TemplatesModel model("kdevcodegentest"); + TemplatesModel model(QStringLiteral("kdevcodegentest")); model.refresh(); - description.members << VariableDescription("QString", "name") - << VariableDescription("int", "number") - << VariableDescription("SomeCustomType", "data"); + description.members << VariableDescription(QStringLiteral("QString"), QStringLiteral("name")) + << VariableDescription(QStringLiteral("int"), QStringLiteral("number")) + << VariableDescription(QStringLiteral("SomeCustomType"), QStringLiteral("data")); FunctionDescription function; - function.name = "doSomething"; + function.name = QStringLiteral("doSomething"); function.isVirtual = true; - function.arguments << VariableDescription("double", "howMuch") - << VariableDescription("bool", "doSomethingElse"); + function.arguments << VariableDescription(QStringLiteral("double"), QStringLiteral("howMuch")) + << VariableDescription(QStringLiteral("bool"), QStringLiteral("doSomethingElse")); VariableDescriptionList args; VariableDescriptionList returnArgs; - returnArgs << VariableDescription("int", "someOtherNumber"); - FunctionDescription otherFunction("getSomeOtherNumber", args, returnArgs); + returnArgs << VariableDescription(QStringLiteral("int"), QStringLiteral("someOtherNumber")); + FunctionDescription otherFunction(QStringLiteral("getSomeOtherNumber"), args, returnArgs); description.methods << function << otherFunction; } void TestTemplateClassGenerator::cleanupTestCase() { TestCore::shutdown(); } void TestTemplateClassGenerator::fileLabelsCpp() { - TemplateClassGenerator* generator = loadTemplate("test_cpp"); + TemplateClassGenerator* generator = loadTemplate(QStringLiteral("test_cpp")); QHash labels = generator->fileLabels(); QCOMPARE(labels.size(), 2); /* * File labels can be translated, so we don't check their equality here. * But they have to be present and non-empty */ - QVERIFY(labels.contains("Header")); - QVERIFY(!labels["Header"].isEmpty()); - QVERIFY(labels.contains("Implementation")); - QVERIFY(!labels["Implementation"].isEmpty()); + QVERIFY(labels.contains(QStringLiteral("Header"))); + QVERIFY(!labels[QStringLiteral("Header")].isEmpty()); + QVERIFY(labels.contains(QStringLiteral("Implementation"))); + QVERIFY(!labels[QStringLiteral("Implementation")].isEmpty()); } void TestTemplateClassGenerator::fileLabelsYaml() { - TemplateClassGenerator* generator = loadTemplate("test_yaml"); + TemplateClassGenerator* generator = loadTemplate(QStringLiteral("test_yaml")); QHash labels = generator->fileLabels(); QCOMPARE(labels.size(), 1); - QVERIFY(labels.contains("Description")); - QVERIFY(!labels["Description"].isEmpty()); + QVERIFY(labels.contains(QStringLiteral("Description"))); + QVERIFY(!labels[QStringLiteral("Description")].isEmpty()); } void TestTemplateClassGenerator::defaultFileUrlsCpp() { - TemplateClassGenerator* generator = loadTemplate("test_cpp"); + TemplateClassGenerator* generator = loadTemplate(QStringLiteral("test_cpp")); QHash files = generator->fileUrls(); QCOMPARE(files.size(), 2); - QVERIFY(files.contains("Header")); - QCOMPARE(files["Header"], baseUrl.resolved(QUrl("ClassName.h"))); + QVERIFY(files.contains(QStringLiteral("Header"))); + QCOMPARE(files[QStringLiteral("Header")], baseUrl.resolved(QUrl(QStringLiteral("ClassName.h")))); - QVERIFY(files.contains("Implementation")); - QCOMPARE(files["Implementation"], baseUrl.resolved(QUrl("ClassName.cpp"))); + QVERIFY(files.contains(QStringLiteral("Implementation"))); + QCOMPARE(files[QStringLiteral("Implementation")], baseUrl.resolved(QUrl(QStringLiteral("ClassName.cpp")))); } void TestTemplateClassGenerator::defaultFileUrlsYaml() { - TemplateClassGenerator* generator = loadTemplate("test_yaml"); + TemplateClassGenerator* generator = loadTemplate(QStringLiteral("test_yaml")); QHash files = generator->fileUrls(); QCOMPARE(files.size(), 1); - QVERIFY(files.contains("Description")); - QCOMPARE(files["Description"], baseUrl.resolved(QUrl("ClassName.yaml"))); + QVERIFY(files.contains(QStringLiteral("Description"))); + QCOMPARE(files[QStringLiteral("Description")], baseUrl.resolved(QUrl(QStringLiteral("ClassName.yaml")))); } void TestTemplateClassGenerator::customOptions() { - TemplateClassGenerator* generator = loadTemplate("test_yaml"); + TemplateClassGenerator* generator = loadTemplate(QStringLiteral("test_yaml")); QCOMPARE(generator->sourceFileTemplate().hasCustomOptions(), false); } void TestTemplateClassGenerator::templateVariablesCpp() { - TemplateClassGenerator* generator = loadTemplate("test_cpp"); + TemplateClassGenerator* generator = loadTemplate(QStringLiteral("test_cpp")); setLowercaseFileNames(generator); QVariantHash variables = generator->renderer()->variables(); - CHECK_TEMPLATE_VARIABLE(name, QString, QStringLiteral("ClassName")); + CHECK_TEMPLATE_VARIABLE(name, toString, QStringLiteral("ClassName")); - CHECK_TEMPLATE_VARIABLE(output_file_header, QString, QStringLiteral("classname.h")); - CHECK_TEMPLATE_VARIABLE(output_file_header_absolute, QString, baseUrl.resolved(QUrl("classname.h")).toLocalFile()); + CHECK_TEMPLATE_VARIABLE(output_file_header, toString, QStringLiteral("classname.h")); + CHECK_TEMPLATE_VARIABLE(output_file_header_absolute, toString, baseUrl.resolved(QUrl(QStringLiteral("classname.h"))).toLocalFile()); } void TestTemplateClassGenerator::templateVariablesYaml() { - TemplateClassGenerator* generator = loadTemplate("test_yaml"); + TemplateClassGenerator* generator = loadTemplate(QStringLiteral("test_yaml")); setLowercaseFileNames(generator); QVariantHash variables = generator->renderer()->variables(); - CHECK_TEMPLATE_VARIABLE(name, QString, QStringLiteral("ClassName")); + CHECK_TEMPLATE_VARIABLE(name, toString, QStringLiteral("ClassName")); - CHECK_TEMPLATE_VARIABLE(output_file_description, QString, QStringLiteral("classname.yaml")); - CHECK_TEMPLATE_VARIABLE(output_file_description_absolute, QString, baseUrl.resolved(QUrl("classname.yaml")).toLocalFile()); + CHECK_TEMPLATE_VARIABLE(output_file_description, toString, QStringLiteral("classname.yaml")); + CHECK_TEMPLATE_VARIABLE(output_file_description_absolute, toString, baseUrl.resolved(QUrl(QStringLiteral("classname.yaml"))).toLocalFile()); } void TestTemplateClassGenerator::codeDescription() { - TemplateClassGenerator* generator = loadTemplate("test_yaml"); + TemplateClassGenerator* generator = loadTemplate(QStringLiteral("test_yaml")); QVariantHash variables = generator->renderer()->variables(); qDebug() << variables; - QVERIFY(variables.contains("base_classes")); - QVariantList inheritance = variables["base_classes"].toList(); + QVERIFY(variables.contains(QStringLiteral("base_classes"))); + QVariantList inheritance = variables[QStringLiteral("base_classes")].toList(); QCOMPARE(inheritance.size(), 1); QCOMPARE(inheritance.first().value().baseType, QStringLiteral("QObject")); QCOMPARE(inheritance.first().value().inheritanceMode, QStringLiteral("public")); - QVERIFY(variables.contains("members")); - QVariantList members = variables["members"].toList(); + QVERIFY(variables.contains(QStringLiteral("members"))); + QVariantList members = variables[QStringLiteral("members")].toList(); QCOMPARE(members.size(), 3); QCOMPARE(members.first().value().type, QStringLiteral("QString")); QCOMPARE(members.first().value().name, QStringLiteral("name")); - QVERIFY(variables.contains("functions")); - QVariantList methods = variables["functions"].toList(); + QVERIFY(variables.contains(QStringLiteral("functions"))); + QVariantList methods = variables[QStringLiteral("functions")].toList(); QCOMPARE(methods.size(), 2); QCOMPARE(methods.first().value().name, QStringLiteral("doSomething")); QCOMPARE(methods.first().value().arguments.size(), 2); QCOMPARE(methods.first().value().returnArguments.size(), 0); QCOMPARE(methods.last().value().name, QStringLiteral("getSomeOtherNumber")); QCOMPARE(methods.last().value().arguments.size(), 0); QCOMPARE(methods.last().value().returnArguments.size(), 1); } void TestTemplateClassGenerator::generate() { - TemplateClassGenerator* generator = loadTemplate("test_cpp"); + TemplateClassGenerator* generator = loadTemplate(QStringLiteral("test_cpp")); DocumentChangeSet changes = generator->generate(); DocumentChangeSet::ChangeResult result = changes.applyAllChanges(); QVERIFY(result.m_success); QDir dir(baseUrl.toLocalFile()); QFileInfoList entries = dir.entryInfoList(QDir::Files | QDir::NoDotAndDotDot); QCOMPARE(entries.size(), 2); } void TestTemplateClassGenerator::cppOutput() { - TemplateClassGenerator* generator = loadTemplate("test_cpp"); + TemplateClassGenerator* generator = loadTemplate(QStringLiteral("test_cpp")); setLowercaseFileNames(generator); DocumentChangeSet changes = generator->generate(); changes.setFormatPolicy(DocumentChangeSet::NoAutoFormat); changes.applyAllChanges(); - QFile header(baseUrl.resolved(QUrl("classname.h")).toLocalFile()); + QFile header(baseUrl.resolved(QUrl(QStringLiteral("classname.h"))).toLocalFile()); QVERIFY(header.open(QIODevice::ReadOnly)); - QFile testHeader(CODEGEN_TESTS_EXPECTED_DIR "/classname.h"); + QFile testHeader(QStringLiteral(CODEGEN_TESTS_EXPECTED_DIR "/classname.h")); testHeader.open(QIODevice::ReadOnly); COMPARE_FILES(header, testHeader); - QFile implementation(baseUrl.resolved(QUrl("classname.cpp")).toLocalFile()); + QFile implementation(baseUrl.resolved(QUrl(QStringLiteral("classname.cpp"))).toLocalFile()); QVERIFY(implementation.open(QIODevice::ReadOnly)); - QFile testImplementation(CODEGEN_TESTS_EXPECTED_DIR "/classname.cpp"); + QFile testImplementation(QStringLiteral(CODEGEN_TESTS_EXPECTED_DIR "/classname.cpp")); testImplementation.open(QIODevice::ReadOnly); COMPARE_FILES(implementation, testImplementation); } void TestTemplateClassGenerator::yamlOutput() { - TemplateClassGenerator* generator = loadTemplate("test_yaml"); + TemplateClassGenerator* generator = loadTemplate(QStringLiteral("test_yaml")); setLowercaseFileNames(generator); generator->generate().applyAllChanges(); - QFile yaml(baseUrl.resolved(QUrl("classname.yaml")).toLocalFile()); + QFile yaml(baseUrl.resolved(QUrl(QStringLiteral("classname.yaml"))).toLocalFile()); QVERIFY(yaml.open(QIODevice::ReadOnly)); - QFile testYaml(CODEGEN_TESTS_EXPECTED_DIR "/classname.yaml"); + QFile testYaml(QStringLiteral(CODEGEN_TESTS_EXPECTED_DIR "/classname.yaml")); testYaml.open(QIODevice::ReadOnly); COMPARE_FILES(yaml, testYaml); } TemplateClassGenerator* TestTemplateClassGenerator::loadTemplate (const QString& name) { QDir dir(baseUrl.toLocalFile()); foreach (const QString& fileName, dir.entryList(QDir::Files | QDir::NoDotAndDotDot)) { dir.remove(fileName); } TemplateClassGenerator* generator = new TemplateClassGenerator(baseUrl); - QString tplDescription = QString(CODEGEN_DATA_DIR) + "/kdevcodegentest/templates/" + name + "/" + name + ".desktop"; + QString tplDescription = QStringLiteral(CODEGEN_DATA_DIR) + "/kdevcodegentest/templates/" + name + "/" + name + ".desktop"; Q_ASSERT(!tplDescription.isEmpty()); SourceFileTemplate tpl; - tpl.addAdditionalSearchLocation(QString(CODEGEN_TESTS_DATA_DIR) + "/kdevcodegentest/templates/"); + tpl.addAdditionalSearchLocation(QStringLiteral(CODEGEN_TESTS_DATA_DIR) + "/kdevcodegentest/templates/"); tpl.setTemplateDescription(tplDescription); Q_ASSERT(tpl.isValid()); generator->setTemplateDescription(tpl); generator->setDescription(description); - generator->setIdentifier("ClassName"); - generator->addBaseClass("public QObject"); - generator->setLicense("This is just a test.\nYou may do with it as you please."); + generator->setIdentifier(QStringLiteral("ClassName")); + generator->addBaseClass(QStringLiteral("public QObject")); + generator->setLicense(QStringLiteral("This is just a test.\nYou may do with it as you please.")); return generator; } void TestTemplateClassGenerator::setLowercaseFileNames(TemplateClassGenerator* generator) { QHash urls = generator->fileUrls(); QHash::const_iterator it = urls.constBegin(); for (; it != urls.constEnd(); ++it) { QString fileName = it.value().fileName().toLower(); QUrl base = it.value().resolved(QUrl(QStringLiteral("./%1").arg(fileName))); generator->setFileUrl(it.key(), base); } } QTEST_GUILESS_MAIN(TestTemplateClassGenerator) diff --git a/language/codegen/tests/test_templaterenderer.cpp b/language/codegen/tests/test_templaterenderer.cpp index 86e62889e..c22283e86 100644 --- a/language/codegen/tests/test_templaterenderer.cpp +++ b/language/codegen/tests/test_templaterenderer.cpp @@ -1,102 +1,102 @@ /* * 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 "test_templaterenderer.h" #include "language/codegen/templaterenderer.h" #include "language/codegen/codedescription.h" #include "tests/autotestshell.h" #include "tests/testcore.h" using namespace KDevelop; void TestTemplateRenderer::initTestCase() { AutoTestShell::init(); TestCore::initialize(Core::NoUi); renderer = new KDevelop::TemplateRenderer(); QVariantHash variables; - variables["name"] = "Tester"; - variables["age"] = 23; + variables[QStringLiteral("name")] = "Tester"; + variables[QStringLiteral("age")] = 23; renderer->addVariables(variables); } void TestTemplateRenderer::cleanupTestCase() { delete renderer; TestCore::shutdown(); } void TestTemplateRenderer::simpleVariables_data() { QTest::addColumn("name"); QTest::addColumn("content"); QTest::addColumn("result"); QTest::newRow("string") << "string" << "Hello, {{ name }}!" << "Hello, Tester!"; QTest::newRow("int") << "int" << "I am {{ age }} years old" << "I am 23 years old"; QTest::newRow("filter") << "filter" << "HELLO, {{ name|upper }}!" << "HELLO, TESTER!"; QTest::newRow("int+string") << "int+string" << "Mr. {{ name }} is {{ age }} years old" << "Mr. Tester is 23 years old"; } void TestTemplateRenderer::simpleVariables() { QFETCH(QString, name); QFETCH(QString, content); QFETCH(QString, result); QCOMPARE(renderer->render(content, name), result); } void TestTemplateRenderer::includeTemplates() { - renderer->addVariable("namespaces", QStringList() << "Entity" << "Human" << "ComputerPerson"); - QString result = renderer->render("HELLO {% include 'include_guard_cpp.txt' %}", QString()); - QString expected = "HELLO ENTITY_HUMAN_COMPUTERPERSON_TESTER_H"; + renderer->addVariable(QStringLiteral("namespaces"), QStringList() << QStringLiteral("Entity") << QStringLiteral("Human") << QStringLiteral("ComputerPerson")); + QString result = renderer->render(QStringLiteral("HELLO {% include 'include_guard_cpp.txt' %}"), QString()); + QString expected = QStringLiteral("HELLO ENTITY_HUMAN_COMPUTERPERSON_TESTER_H"); QCOMPARE(result, expected); } void TestTemplateRenderer::kdevFilters() { - renderer->addVariable("activity", QStringLiteral("testing")); - QString result = renderer->render("{% load kdev_filters %}I am {{ activity }} software. {{ activity|upper_first }} is a very rewarding task. ", QString()); - QString expected = "I am testing software. Testing is a very rewarding task. "; + renderer->addVariable(QStringLiteral("activity"), QStringLiteral("testing")); + QString result = renderer->render(QStringLiteral("{% load kdev_filters %}I am {{ activity }} software. {{ activity|upper_first }} is a very rewarding task. "), QString()); + QString expected = QStringLiteral("I am testing software. Testing is a very rewarding task. "); QCOMPARE(result, expected); } void TestTemplateRenderer::kdevFiltersWithLookup() { VariableDescription description; - description.type = "int"; - description.name = "number"; + description.type = QStringLiteral("int"); + description.name = QStringLiteral("number"); - renderer->addVariable("var", QVariant::fromValue(description)); - QString result = renderer->render("{% load kdev_filters %}void set{{ var.name|upper_first }}({{ var.type }} {{ var.name }});", QString()); - QString expected = "void setNumber(int number);"; + renderer->addVariable(QStringLiteral("var"), QVariant::fromValue(description)); + QString result = renderer->render(QStringLiteral("{% load kdev_filters %}void set{{ var.name|upper_first }}({{ var.type }} {{ var.name }});"), QString()); + QString expected = QStringLiteral("void setNumber(int number);"); QCOMPARE(result, expected); } QTEST_MAIN(TestTemplateRenderer) diff --git a/language/codegen/tests/test_templatesmodel.cpp b/language/codegen/tests/test_templatesmodel.cpp index 64a01e924..1911e5d4a 100644 --- a/language/codegen/tests/test_templatesmodel.cpp +++ b/language/codegen/tests/test_templatesmodel.cpp @@ -1,87 +1,87 @@ /* * 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 "test_templatesmodel.h" #include "codegen_tests_config.h" #include #include #include using namespace KDevelop; void TestTemplatesModel::initTestCase() { AutoTestShell::init(); TestCore::initialize(Core::NoUi); - model = new TemplatesModel("kdevcodegentest", this); - model->addDataPath(QString(CODEGEN_TESTS_DATA_DIR) + "/"); + model = new TemplatesModel(QStringLiteral("kdevcodegentest"), this); + model->addDataPath(QStringLiteral(CODEGEN_TESTS_DATA_DIR) + "/"); model->refresh(); } void TestTemplatesModel::cleanupTestCase() { delete model; TestCore::shutdown(); } void TestTemplatesModel::descriptionExtraction() { QCOMPARE(model->rowCount(), 1); QModelIndex testingCategoryIndex = model->index(0, 0); QCOMPARE(model->rowCount(testingCategoryIndex), 2); for (int i = 0; i < 2; ++i) { QModelIndex languageCategoryIndex = model->index(i, 0, testingCategoryIndex); QCOMPARE(model->rowCount(languageCategoryIndex), 1); QModelIndex templateIndex = model->index(0, 0, languageCategoryIndex); QCOMPARE(model->rowCount(templateIndex), 0); } } void TestTemplatesModel::descriptionParsing() { - QList items = model->findItems("Testing YAML template", Qt::MatchRecursive); + QList items = model->findItems(QStringLiteral("Testing YAML template"), Qt::MatchRecursive); QCOMPARE(items.size(), 1); QStandardItem* item = items.first(); QCOMPARE(item->data(TemplatesModel::CommentRole).toString(), QStringLiteral("Describes a class using YAML syntax")); QVERIFY(item->data(TemplatesModel::IconNameRole).toString().isEmpty()); QString descriptionFile = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/kdevcodegentest/template_descriptions/test_yaml.desktop"; QVERIFY(QFile::exists(descriptionFile)); QCOMPARE(item->data(TemplatesModel::DescriptionFileRole).toString(), descriptionFile); } void TestTemplatesModel::templateIndexes() { - QModelIndexList indexes = model->templateIndexes("test_yaml.tar.bz2"); + QModelIndexList indexes = model->templateIndexes(QStringLiteral("test_yaml.tar.bz2")); QCOMPARE(indexes.size(), 3); QCOMPARE(model->data(indexes[0]).toString(), QStringLiteral("Testing")); QCOMPARE(model->data(indexes[1]).toString(), QStringLiteral("YAML")); QCOMPARE(model->data(indexes[2]).toString(), QStringLiteral("Testing YAML template")); } QTEST_GUILESS_MAIN(TestTemplatesModel) diff --git a/language/codegen/utilities.h b/language/codegen/utilities.h index 73033e784..0b6da79e0 100644 --- a/language/codegen/utilities.h +++ b/language/codegen/utilities.h @@ -1,73 +1,74 @@ /* Copyright 2009 Ramón Zarazúa This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #ifndef KDEVPLATFORM_CODEGEN_UTILITIES_H #define KDEVPLATFORM_CODEGEN_UTILITIES_H namespace KDevelop { class IndexedString; class DUContext; class Declaration; namespace CodeGenUtils { /*! * A validator object that verifies if a string would be an acceptable identifier * If inserted into the given context, including if it conflicts with any other identifier */ class KDEVPLATFORMLANGUAGE_EXPORT IdentifierValidator : public QValidator { + Q_OBJECT public: explicit IdentifierValidator( DUContext * context); ~IdentifierValidator(); virtual State validate(QString & input, int &) const override; private: DUContext * m_context; }; /** * @brief Search for the file that contains the implementation of a specified type * * Search for the file that contains the implementation of @p targetClass. For languages that * allow implementation of a type through multiple files, the file with the most implementations of * class methods will be chosen, if a tie is found, then the file with the most uses will be chosen. * Else the file that contains the declaration is chosen. * * @note If called with a Forward declaration, the real declaration will be searched for. * * @return The file that matched best */ KDEVPLATFORMLANGUAGE_EXPORT IndexedString fetchImplementationFileForClass(const Declaration & targetClass); } } #endif //KDEVPLATFORM_CODEGEN_UTILITIES_H diff --git a/language/duchain/classdeclaration.cpp b/language/duchain/classdeclaration.cpp index ea572385b..fca7ef335 100644 --- a/language/duchain/classdeclaration.cpp +++ b/language/duchain/classdeclaration.cpp @@ -1,198 +1,198 @@ /* This file is part of KDevelop Copyright 2007 David Nolden Copyright 2009 Lior Mualem This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "classdeclaration.h" #include "identifier.h" #include #include #include #include "types/structuretype.h" #include "util/debug.h" namespace KDevelop { DEFINE_LIST_MEMBER_HASH(ClassDeclarationData, baseClasses, BaseClassInstance) ClassDeclaration::ClassDeclaration(const KDevelop::RangeInRevision& range, DUContext* context) : ClassMemberDeclaration(*new ClassDeclarationData, range) { d_func_dynamic()->setClassId(this); setContext(context); } ClassDeclaration::ClassDeclaration( ClassDeclarationData& data, const KDevelop::RangeInRevision& range, DUContext* context ) : ClassMemberDeclaration( data, range ) { setContext(context); } ClassDeclaration::ClassDeclaration(ClassDeclarationData& data) : ClassMemberDeclaration(data) { } REGISTER_DUCHAIN_ITEM(ClassDeclaration); void ClassDeclaration::clearBaseClasses() { d_func_dynamic()->baseClassesList().clear(); } uint ClassDeclaration::baseClassesSize() const { return d_func()->baseClassesSize(); } const BaseClassInstance* ClassDeclaration::baseClasses() const { return d_func()->baseClasses(); } void ClassDeclaration::addBaseClass(const BaseClassInstance& klass) { d_func_dynamic()->baseClassesList().append(klass); } void ClassDeclaration::replaceBaseClass(uint n, const BaseClassInstance& klass) { Q_ASSERT(n <= d_func()->baseClassesSize()); d_func_dynamic()->baseClassesList()[n] = klass; } ClassDeclaration::~ClassDeclaration() { } ClassDeclaration::ClassDeclaration(const ClassDeclaration& rhs) : ClassMemberDeclaration(*new ClassDeclarationData(*rhs.d_func())) { d_func_dynamic()->setClassId(this); } Declaration* ClassDeclaration::clonePrivate() const { return new ClassDeclaration(*this); } namespace { bool isPublicBaseClassInternal( const ClassDeclaration* self, ClassDeclaration* base, const KDevelop::TopDUContext* topContext, int* baseConversionLevels, int depth, QSet* checked ) { if(checked) { if(checked->contains(self)) return false; checked->insert(self); } else if(depth > 3) { //Too much depth, to prevent endless recursion, we control the recursion using the 'checked' set QSet checkedSet; return isPublicBaseClassInternal(self, base, topContext, baseConversionLevels, depth, &checkedSet); } if( baseConversionLevels ) *baseConversionLevels = 0; if( self->indexedType() == base->indexedType() ) return true; FOREACH_FUNCTION(const BaseClassInstance& b, self->baseClasses) { if( baseConversionLevels ) ++ (*baseConversionLevels); //qCDebug(LANGUAGE) << "public base of" << c->toString() << "is" << b.baseClass->toString(); if( b.access != KDevelop::Declaration::Private ) { int nextBaseConversion = 0; if( StructureType::Ptr c = b.baseClass.type() ) { ClassDeclaration* decl = dynamic_cast(c->declaration(topContext)); if( decl && isPublicBaseClassInternal( decl, base, topContext, &nextBaseConversion, depth+1, checked ) ) { if ( baseConversionLevels ) *baseConversionLevels += nextBaseConversion; return true; } } } if( baseConversionLevels ) -- (*baseConversionLevels); } return false; } } bool ClassDeclaration::isPublicBaseClass( ClassDeclaration* base, const KDevelop::TopDUContext* topContext, int* baseConversionLevels ) const { return isPublicBaseClassInternal( this, base, topContext, baseConversionLevels, 0, 0 ); } QString ClassDeclaration::toString() const { QString ret; switch ( classModifier() ) { case ClassDeclarationData::None: //nothing break; case ClassDeclarationData::Abstract: - ret += "abstract "; + ret += QLatin1String("abstract "); break; case ClassDeclarationData::Final: - ret += "final "; + ret += QLatin1String("final "); break; } switch ( classType() ) { case ClassDeclarationData::Class: - ret += "class "; + ret += QLatin1String("class "); break; case ClassDeclarationData::Interface: - ret += "interface "; + ret += QLatin1String("interface "); break; case ClassDeclarationData::Trait: - ret += "trait "; + ret += QLatin1String("trait "); break; case ClassDeclarationData::Union: - ret += "union "; + ret += QLatin1String("union "); break; case ClassDeclarationData::Struct: - ret += "struct "; + ret += QLatin1String("struct "); break; } return ret + identifier().toString(); } ClassDeclarationData::ClassType ClassDeclaration::classType() const { return d_func()->m_classType; } void ClassDeclaration::setClassType(ClassDeclarationData::ClassType type) { d_func_dynamic()->m_classType = type; } ClassDeclarationData::ClassModifier ClassDeclaration::classModifier() const { return d_func()->m_classModifier; } void ClassDeclaration::setClassModifier(ClassDeclarationData::ClassModifier modifier) { d_func_dynamic()->m_classModifier = modifier; } } diff --git a/language/duchain/declaration.cpp b/language/duchain/declaration.cpp index 0d0726f19..3113c7824 100644 --- a/language/duchain/declaration.cpp +++ b/language/duchain/declaration.cpp @@ -1,776 +1,776 @@ /* 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 "declaration.h" #include "declarationdata.h" #include #include #include "topducontext.h" #include "topducontextdynamicdata.h" #include "use.h" #include "forwarddeclaration.h" #include "duchain.h" #include "duchainlock.h" #include "ducontextdata.h" #include "declarationid.h" #include "uses.h" #include #include "duchainregister.h" #include "persistentsymboltable.h" #include "types/identifiedtype.h" #include "types/structuretype.h" #include "functiondefinition.h" #include "codemodel.h" #include "specializationstore.h" #include "types/typeutils.h" #include "types/typealiastype.h" #include "classdeclaration.h" #include "serialization/stringrepository.h" #include "ducontextdynamicdata.h" namespace KDevelop { REGISTER_DUCHAIN_ITEM(Declaration); DeclarationData::DeclarationData() : m_comment(0) , m_isDefinition(false) , m_inSymbolTable(false) , m_isTypeAlias(false) , m_anonymousInContext(false) , m_isDeprecated(false) , m_alwaysForceDirect(false) , m_isAutoDeclaration(false) , m_isExplicitlyDeleted(false) { m_kind = Declaration::Instance; } DeclarationData::DeclarationData( const DeclarationData& rhs ) : DUChainBaseData(rhs), m_internalContext(rhs.m_internalContext), m_type(rhs.m_type), m_identifier(rhs.m_identifier), m_declaration(rhs.m_declaration), m_comment(rhs.m_comment), m_kind(rhs.m_kind), m_isDefinition(rhs.m_isDefinition), m_inSymbolTable(rhs.m_inSymbolTable), m_isTypeAlias(rhs.m_isTypeAlias), m_anonymousInContext(rhs.m_anonymousInContext), m_isDeprecated(rhs.m_isDeprecated), m_alwaysForceDirect(rhs.m_alwaysForceDirect), m_isAutoDeclaration(rhs.m_isAutoDeclaration), m_isExplicitlyDeleted(rhs.m_isExplicitlyDeleted) { } ///@todo Use reference counting static Repositories::StringRepository& commentRepository() { static Repositories::StringRepository commentRepositoryObject(QStringLiteral("Comment Repository")); return commentRepositoryObject; } void initDeclarationRepositories() { commentRepository(); } Declaration::Kind Declaration::kind() const { DUCHAIN_D(Declaration); return d->m_kind; } void Declaration::setKind(Kind kind) { DUCHAIN_D_DYNAMIC(Declaration); d->m_kind = kind; updateCodeModel(); } bool Declaration::inDUChain() const { DUCHAIN_D(Declaration); if( d->m_anonymousInContext ) return false; if( !context() ) return false; TopDUContext* top = topContext(); return top && top->inDUChain(); } Declaration::Declaration( const RangeInRevision& range, DUContext* context ) : DUChainBase(*new DeclarationData, range) { d_func_dynamic()->setClassId(this); m_topContext = 0; m_context = 0; m_indexInTopContext = 0; if(context) setContext(context); } uint Declaration::ownIndex() const { ENSURE_CAN_READ return m_indexInTopContext; } Declaration::Declaration(const Declaration& rhs) : DUChainBase(*new DeclarationData( *rhs.d_func() )) { m_topContext = 0; m_context = 0; m_indexInTopContext = 0; } Declaration::Declaration( DeclarationData & dd ) : DUChainBase(dd) { m_topContext = 0; m_context = 0; m_indexInTopContext = 0; } Declaration::Declaration( DeclarationData & dd, const RangeInRevision& range ) : DUChainBase(dd, range) { m_topContext = 0; m_context = 0; m_indexInTopContext = 0; } bool Declaration::persistentlyDestroying() const { TopDUContext* topContext = this->topContext(); return !topContext->deleting() || !topContext->isOnDisk(); } Declaration::~Declaration() { uint oldOwnIndex = m_indexInTopContext; TopDUContext* topContext = this->topContext(); //Only perform the actions when the top-context isn't being deleted, or when it hasn't been stored to disk if(persistentlyDestroying()) { DUCHAIN_D_DYNAMIC(Declaration); // Inserted by the builder after construction has finished. if( d->m_internalContext.context() ) d->m_internalContext.context()->setOwner(0); setInSymbolTable(false); } // If the parent-context already has dynamic data, like for example any temporary context, // always delete the declaration, to not create crashes within more complex code like C++ template stuff. if (context() && !d_func()->m_anonymousInContext) { if(!topContext->deleting() || !topContext->isOnDisk() || context()->d_func()->isDynamic()) context()->m_dynamicData->removeDeclaration(this); } clearOwnIndex(); if(!topContext->deleting() || !topContext->isOnDisk()) { setContext(0); setAbstractType(AbstractType::Ptr()); } Q_ASSERT(d_func()->isDynamic() == (!topContext->deleting() || !topContext->isOnDisk() || topContext->m_dynamicData->isTemporaryDeclarationIndex(oldOwnIndex))); Q_UNUSED(oldOwnIndex); } QByteArray Declaration::comment() const { DUCHAIN_D(Declaration); if(!d->m_comment) return 0; else return Repositories::arrayFromItem(commentRepository().itemFromIndex(d->m_comment)); } void Declaration::setComment(const QByteArray& str) { DUCHAIN_D_DYNAMIC(Declaration); if(str.isEmpty()) d->m_comment = 0; else d->m_comment = commentRepository().index(Repositories::StringRepositoryItemRequest(str, IndexedString::hashString(str, str.length()), str.length())); } void Declaration::setComment(const QString& str) { setComment(str.toUtf8()); } Identifier Declaration::identifier( ) const { //ENSURE_CAN_READ Commented out for performance reasons return d_func()->m_identifier.identifier(); } const IndexedIdentifier& Declaration::indexedIdentifier( ) const { //ENSURE_CAN_READ Commented out for performance reasons return d_func()->m_identifier; } void Declaration::rebuildDynamicData(DUContext* parent, uint ownIndex) { DUChainBase::rebuildDynamicData(parent, ownIndex); m_context = parent; m_topContext = parent->topContext(); m_indexInTopContext = ownIndex; } void Declaration::setIdentifier(const Identifier& identifier) { ENSURE_CAN_WRITE DUCHAIN_D_DYNAMIC(Declaration); bool wasInSymbolTable = d->m_inSymbolTable; setInSymbolTable(false); d->m_identifier = identifier; setInSymbolTable(wasInSymbolTable); } IndexedType Declaration::indexedType() const { return d_func()->m_type; } AbstractType::Ptr Declaration::abstractType( ) const { //ENSURE_CAN_READ Commented out for performance reasons return d_func()->m_type.abstractType(); } void Declaration::setAbstractType(AbstractType::Ptr type) { ENSURE_CAN_WRITE DUCHAIN_D_DYNAMIC(Declaration); d->m_type = type->indexed(); updateCodeModel(); } Declaration* Declaration::specialize(const IndexedInstantiationInformation& /*specialization*/, const TopDUContext* topContext, int /*upDistance*/) { if(!topContext) return 0; return this; } QualifiedIdentifier Declaration::qualifiedIdentifier() const { ENSURE_CAN_READ QualifiedIdentifier ret; DUContext* ctx = m_context; if(ctx) ret = ctx->scopeIdentifier(true); ret.push(d_func()->m_identifier); return ret; } DUContext * Declaration::context() const { //ENSURE_CAN_READ Commented out for performance reasons return m_context; } bool Declaration::isAnonymous() const { return d_func()->m_anonymousInContext; } void Declaration::setContext(DUContext* context, bool anonymous) { Q_ASSERT(!context || context->topContext()); DUCHAIN_D_DYNAMIC(Declaration); if (context == m_context && anonymous == d->m_anonymousInContext) { // skip costly operations below when the same context is set // this happens often when updating a TopDUContext from the cache return; } setInSymbolTable(false); //We don't need to clear, because it's not allowed to move from one top-context into another // clearOwnIndex(); if (m_context && context) { Q_ASSERT(m_context->topContext() == context->topContext()); } if (m_context) { if( !d->m_anonymousInContext ) { m_context->m_dynamicData->removeDeclaration(this); } } if(context) m_topContext = context->topContext(); else m_topContext = 0; d->m_anonymousInContext = anonymous; m_context = context; if (context) { if(!m_indexInTopContext) allocateOwnIndex(); if(!d->m_anonymousInContext) { context->m_dynamicData->addDeclaration(this); } if(context->inSymbolTable() && !anonymous) setInSymbolTable(true); } } void Declaration::clearOwnIndex() { if(!m_indexInTopContext) return; if(!context() || (!d_func()->m_anonymousInContext && !context()->isAnonymous())) { ENSURE_CAN_WRITE } if(m_indexInTopContext) { Q_ASSERT(m_topContext); m_topContext->m_dynamicData->clearDeclarationIndex(this); } m_indexInTopContext = 0; } void Declaration::allocateOwnIndex() { ///@todo Fix multithreading stuff with template instantiation, preferably using some internal mutexes // if(context() && (!context()->isAnonymous() && !d_func()->m_anonymousInContext)) { // ENSURE_CAN_WRITE // } Q_ASSERT(m_topContext); m_indexInTopContext = m_topContext->m_dynamicData->allocateDeclarationIndex(this, d_func()->m_anonymousInContext || !context() || context()->isAnonymous()); Q_ASSERT(m_indexInTopContext); if(!m_topContext->m_dynamicData->getDeclarationForIndex(m_indexInTopContext)) qFatal("Could not re-retrieve declaration\nindex: %d", m_indexInTopContext); } const Declaration* Declaration::logicalDeclaration(const TopDUContext* topContext) const { ENSURE_CAN_READ if(isForwardDeclaration()) { const auto dec = static_cast(this); Declaration* ret = dec->resolve(topContext); if(ret) return ret; } return this; } Declaration* Declaration::logicalDeclaration(const TopDUContext* topContext) { ENSURE_CAN_READ if(isForwardDeclaration()) { const auto dec = static_cast(this); Declaration* ret = dec->resolve(topContext); if(ret) return ret; } return this; } DUContext * Declaration::logicalInternalContext(const TopDUContext* topContext) const { ENSURE_CAN_READ if(!isDefinition()) { Declaration* def = FunctionDefinition::definition(this); if( def ) return def->internalContext(); } if( d_func()->m_isTypeAlias ) { ///If this is a type-alias, return the internal context of the actual type. TypeAliasType::Ptr t = type(); if(t) { AbstractType::Ptr target = t->type(); IdentifiedType* idType = dynamic_cast(target.data()); if( idType ) { Declaration* decl = idType->declaration(topContext); if(decl && decl != this) { return decl->logicalInternalContext( topContext ); } } } } return internalContext(); } DUContext * Declaration::internalContext() const { // ENSURE_CAN_READ return d_func()->m_internalContext.context(); } void Declaration::setInternalContext(DUContext* context) { if(this->context()) { ENSURE_CAN_WRITE } DUCHAIN_D_DYNAMIC(Declaration); if( context == d->m_internalContext.context() ) return; if(!m_topContext) { //Take the top-context from the other side. We need to allocate an index, so we can safely call setOwner(..) m_topContext = context->topContext(); allocateOwnIndex(); } DUContext* oldInternalContext = d->m_internalContext.context(); d->m_internalContext = context; //Q_ASSERT( !oldInternalContext || oldInternalContext->owner() == this ); if( oldInternalContext && oldInternalContext->owner() == this ) oldInternalContext->setOwner(0); if( context ) context->setOwner(this); } bool Declaration::operator ==(const Declaration & other) const { ENSURE_CAN_READ return this == &other; } QString Declaration::toString() const { - return QStringLiteral("%3 %4").arg(abstractType() ? abstractType()->toString() : QStringLiteral("")).arg(identifier().toString()); + return QStringLiteral("%3 %4").arg(abstractType() ? abstractType()->toString() : QStringLiteral(""), identifier().toString()); } bool Declaration::isDefinition() const { ENSURE_CAN_READ DUCHAIN_D(Declaration); return d->m_isDefinition; } void Declaration::setDeclarationIsDefinition(bool dd) { ENSURE_CAN_WRITE DUCHAIN_D_DYNAMIC(Declaration); d->m_isDefinition = dd; // if (d->m_isDefinition && definition()) { // setDefinition(0); // } } bool Declaration::isAutoDeclaration() const { return d_func()->m_isAutoDeclaration; } void Declaration::setAutoDeclaration(bool _auto) { d_func_dynamic()->m_isAutoDeclaration = _auto; } bool Declaration::isDeprecated() const { return d_func()->m_isDeprecated; } void Declaration::setDeprecated(bool deprecated) { d_func_dynamic()->m_isDeprecated = deprecated; } bool Declaration::alwaysForceDirect() const { return d_func()->m_alwaysForceDirect; } void Declaration::setAlwaysForceDirect(bool direct) { d_func_dynamic()->m_alwaysForceDirect = direct; } bool Declaration::isExplicitlyDeleted() const { return d_func()->m_isExplicitlyDeleted; } void Declaration::setExplicitlyDeleted(bool deleted) { d_func_dynamic()->m_isExplicitlyDeleted = deleted; } ///@todo see whether it would be useful to create an own TypeAliasDeclaration sub-class for this bool Declaration::isTypeAlias() const { DUCHAIN_D(Declaration); return d->m_isTypeAlias; } void Declaration::setIsTypeAlias(bool isTypeAlias) { DUCHAIN_D_DYNAMIC(Declaration); d->m_isTypeAlias = isTypeAlias; } IndexedInstantiationInformation Declaration::specialization() const { return IndexedInstantiationInformation(); } void Declaration::activateSpecialization() { if(specialization().index()) { DeclarationId baseId(id()); baseId.setSpecialization(IndexedInstantiationInformation()); SpecializationStore::self().set(baseId, specialization()); } } DeclarationId Declaration::id(bool forceDirect) const { ENSURE_CAN_READ if(inSymbolTable() && !forceDirect && !alwaysForceDirect()) return DeclarationId(qualifiedIdentifier(), additionalIdentity(), specialization()); else return DeclarationId(IndexedDeclaration(const_cast(this)), specialization()); } bool Declaration::inSymbolTable() const { DUCHAIN_D(Declaration); return d->m_inSymbolTable; } CodeModelItem::Kind kindForDeclaration(Declaration* decl) { CodeModelItem::Kind kind = CodeModelItem::Unknown; if(decl->kind() == Declaration::Namespace) return CodeModelItem::Namespace; if(decl->isFunctionDeclaration()) { kind = CodeModelItem::Function; } if(decl->kind() == Declaration::Type && (decl->type() || dynamic_cast(decl))) kind = CodeModelItem::Class; if(kind == CodeModelItem::Unknown && decl->kind() == Declaration::Instance) kind = CodeModelItem::Variable; if(decl->isForwardDeclaration()) kind = (CodeModelItem::Kind)(kind | CodeModelItem::ForwardDeclaration); if ( decl->context() && decl->context()->type() == DUContext::Class ) kind = (CodeModelItem::Kind)(kind | CodeModelItem::ClassMember); return kind; } void Declaration::updateCodeModel() { DUCHAIN_D(Declaration); if(!d->m_identifier.isEmpty() && d->m_inSymbolTable) { QualifiedIdentifier id(qualifiedIdentifier()); CodeModel::self().updateItem(url(), id, kindForDeclaration(this)); } } void Declaration::setInSymbolTable(bool inSymbolTable) { DUCHAIN_D_DYNAMIC(Declaration); if(!d->m_identifier.isEmpty()) { if(!d->m_inSymbolTable && inSymbolTable) { QualifiedIdentifier id(qualifiedIdentifier()); PersistentSymbolTable::self().addDeclaration(id, this); CodeModel::self().addItem(url(), id, kindForDeclaration(this)); } else if(d->m_inSymbolTable && !inSymbolTable) { QualifiedIdentifier id(qualifiedIdentifier()); PersistentSymbolTable::self().removeDeclaration(id, this); CodeModel::self().removeItem(url(), id); } } d->m_inSymbolTable = inSymbolTable; } TopDUContext * Declaration::topContext() const { return m_topContext; } Declaration* Declaration::clonePrivate() const { return new Declaration(*this); } Declaration* Declaration::clone() const { Declaration* ret = clonePrivate(); ret->d_func_dynamic()->m_inSymbolTable = false; return ret; } bool Declaration::isForwardDeclaration() const { return false; } bool Declaration::isFunctionDeclaration() const { return false; } uint Declaration::additionalIdentity() const { return 0; } bool Declaration::equalQualifiedIdentifier(const Declaration* rhs) const { ENSURE_CAN_READ DUCHAIN_D(Declaration); if(d->m_identifier != rhs->d_func()->m_identifier) return false; return m_context->equalScopeIdentifier(m_context); } QMap > Declaration::uses() const { ENSURE_CAN_READ QMap > tempUses; //First, search for uses within the own context { QMap& ranges(tempUses[topContext()->url()]); - foreach(const RangeInRevision& range, allUses(topContext(), const_cast(this))) + foreach(const RangeInRevision range, allUses(topContext(), const_cast(this))) ranges[range] = true; } KDevVarLengthArray useContexts = DUChain::uses()->uses(id()); - for (const IndexedTopDUContext& indexedContext : useContexts) { + foreach (const IndexedTopDUContext indexedContext, useContexts) { TopDUContext* context = indexedContext.data(); if(context) { QMap& ranges(tempUses[context->url()]); - foreach(const RangeInRevision& range, allUses(context, const_cast(this))) + foreach(const RangeInRevision range, allUses(context, const_cast(this))) ranges[range] = true; } } QMap > ret; for(QMap >::const_iterator it = tempUses.constBegin(); it != tempUses.constEnd(); ++it) { if(!(*it).isEmpty()) { QList& list(ret[it.key()]); for(QMap::const_iterator it2 = (*it).constBegin(); it2 != (*it).constEnd(); ++it2) list << it2.key(); } } return ret; } bool hasDeclarationUse(DUContext* context, int declIdx) { bool ret=false; int usescount=context->usesCount(); const Use* uses=context->uses(); for(int i=0; !ret && ichildContexts()) { ret = ret || hasDeclarationUse(child, declIdx); if(ret) break; } return ret; } bool Declaration::hasUses() const { ENSURE_CAN_READ int idx = topContext()->indexForUsedDeclaration(const_cast(this), false); bool ret = idx != std::numeric_limits::max() && (idx>=0 || hasDeclarationUse(topContext(), idx)); //hasLocalUses ret = ret || DUChain::uses()->hasUses(id()); //hasUsesInOtherContexts return ret; } QMap > Declaration::usesCurrentRevision() const { ENSURE_CAN_READ QMap > tempUses; //First, search for uses within the own context { QMap& ranges(tempUses[topContext()->url()]); - foreach(const RangeInRevision& range, allUses(topContext(), const_cast(this))) + foreach(const RangeInRevision range, allUses(topContext(), const_cast(this))) { ranges[topContext()->transformFromLocalRevision(range)] = true; } } KDevVarLengthArray useContexts = DUChain::uses()->uses(id()); - for (const IndexedTopDUContext& indexedContext : useContexts) { + foreach (const IndexedTopDUContext indexedContext, useContexts) { TopDUContext* context = indexedContext.data(); if(context) { QMap& ranges(tempUses[context->url()]); - foreach(const RangeInRevision& range, allUses(context, const_cast(this))) + foreach(const RangeInRevision range, allUses(context, const_cast(this))) ranges[context->transformFromLocalRevision(range)] = true; } } QMap > ret; for(QMap >::const_iterator it = tempUses.constBegin(); it != tempUses.constEnd(); ++it) { if(!(*it).isEmpty()) { QList& list(ret[it.key()]); for(QMap::const_iterator it2 = (*it).constBegin(); it2 != (*it).constEnd(); ++it2) list << it2.key(); } } return ret; } } diff --git a/language/duchain/declarationid.cpp b/language/duchain/declarationid.cpp index 206c4451f..ffd4c9f23 100644 --- a/language/duchain/declarationid.cpp +++ b/language/duchain/declarationid.cpp @@ -1,245 +1,245 @@ /* 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 "declarationid.h" #include "ducontext.h" #include "topducontext.h" #include "duchain.h" #include "declaration.h" #include "persistentsymboltable.h" #include "instantiationinformation.h" #include "../editor/cursorinrevision.h" #include namespace KDevelop { DeclarationId::DeclarationId(const IndexedQualifiedIdentifier& id, uint additionalId, const IndexedInstantiationInformation& specialization) : m_indirectData{id, additionalId} , m_isDirect(false) , m_specialization(specialization) { } DeclarationId::DeclarationId(const IndexedDeclaration& decl, const IndexedInstantiationInformation& specialization) : m_directData(decl) , m_isDirect(true) , m_specialization(specialization) { } DeclarationId::DeclarationId(const DeclarationId& rhs) : m_isDirect(rhs.m_isDirect) , m_specialization(rhs.m_specialization) { if (!m_isDirect) { // IndexedQualifiedIdentifier doesn't like zero-initialization... new (&m_indirectData.identifier) IndexedQualifiedIdentifier(rhs.m_indirectData.identifier); m_indirectData.additionalIdentity = rhs.m_indirectData.additionalIdentity; } else { m_directData = rhs.m_directData; } } DeclarationId::~DeclarationId() { if (!m_isDirect) { m_indirectData.~Indirect(); } } DeclarationId& DeclarationId::operator=(const DeclarationId& rhs) { if (&rhs == this) return *this; m_isDirect = rhs.m_isDirect; m_specialization = rhs.m_specialization; if (!m_isDirect) { m_indirectData = rhs.m_indirectData; } else { m_directData = rhs.m_directData; } return *this; } bool DeclarationId::isDirect() const { return m_isDirect; } void DeclarationId::setSpecialization(const IndexedInstantiationInformation& spec) { m_specialization = spec; } IndexedInstantiationInformation DeclarationId::specialization() const { return m_specialization; } KDevVarLengthArray DeclarationId::getDeclarations(const TopDUContext* top) const { KDevVarLengthArray ret; if(m_isDirect == false) { //Find the declaration by its qualified identifier and additionalIdentity QualifiedIdentifier id(m_indirectData.identifier); if(top) { //Do filtering PersistentSymbolTable::FilteredDeclarationIterator filter = PersistentSymbolTable::self().getFilteredDeclarations(id, top->recursiveImportIndices()); for(; filter; ++filter) { Declaration* decl = filter->data(); if(decl && m_indirectData.additionalIdentity == decl->additionalIdentity()) { //Hit ret.append(decl); } } }else{ //Just accept anything PersistentSymbolTable::Declarations decls = PersistentSymbolTable::self().getDeclarations(id); PersistentSymbolTable::Declarations::Iterator decl = decls.iterator(); for(; decl; ++decl) { const IndexedDeclaration& iDecl(*decl); ///@todo think this over once we don't pull in all imported top-context any more //Don't trigger loading of top-contexts from here, it will create a lot of problems if((!DUChain::self()->isInMemory(iDecl.topContextIndex()))) continue; if(!top) { Declaration* decl = iDecl.data(); if(decl && m_indirectData.additionalIdentity == decl->additionalIdentity()) { //Hit ret.append(decl); } } } } }else{ Declaration* decl = m_directData.declaration(); if(decl) ret.append(decl); } if(!ret.isEmpty() && m_specialization.index()) { KDevVarLengthArray newRet; - for (Declaration* decl : ret) { + foreach (Declaration* decl, ret) { Declaration* specialized = decl->specialize(m_specialization, top ? top : decl->topContext()); if(specialized) newRet.append(specialized); } return newRet; } return ret; } Declaration* DeclarationId::getDeclaration(const TopDUContext* top, bool instantiateIfRequired) const { Declaration* ret = 0; if(m_isDirect == false) { //Find the declaration by its qualified identifier and additionalIdentity QualifiedIdentifier id(m_indirectData.identifier); if(top) { //Do filtering PersistentSymbolTable::FilteredDeclarationIterator filter = PersistentSymbolTable::self().getFilteredDeclarations(id, top->recursiveImportIndices()); for(; filter; ++filter) { Declaration* decl = filter->data(); if(decl && m_indirectData.additionalIdentity == decl->additionalIdentity()) { //Hit ret = decl; if(!ret->isForwardDeclaration()) break; } } }else{ //Just accept anything PersistentSymbolTable::Declarations decls = PersistentSymbolTable::self().getDeclarations(id); PersistentSymbolTable::Declarations::Iterator decl = decls.iterator(); for(; decl; ++decl) { const IndexedDeclaration& iDecl(*decl); ///@todo think this over once we don't pull in all imported top-context any more //Don't trigger loading of top-contexts from here, it will create a lot of problems if((!DUChain::self()->isInMemory(iDecl.topContextIndex()))) continue; if(!top) { Declaration* decl = iDecl.data(); if(decl && m_indirectData.additionalIdentity == decl->additionalIdentity()) { //Hit ret = decl; if(!ret->isForwardDeclaration()) break; } } } } }else{ //Find the declaration by m_topContext and m_declaration ret = m_directData.declaration(); } if(ret) { if(m_specialization.isValid()) { const TopDUContext* topContextForSpecialization = top; if(!instantiateIfRequired) topContextForSpecialization = 0; //If we don't want to instantiate new declarations, set the top-context to zero, so specialize(..) will only look-up else if(!topContextForSpecialization) topContextForSpecialization = ret->topContext(); return ret->specialize(m_specialization, topContextForSpecialization); }else{ return ret; } }else return 0; } QualifiedIdentifier DeclarationId::qualifiedIdentifier() const { if(!m_isDirect) { QualifiedIdentifier baseIdentifier = m_indirectData.identifier.identifier(); if(!m_specialization.index()) return baseIdentifier; return m_specialization.information().applyToIdentifier(baseIdentifier); } else { Declaration* decl = getDeclaration(0); if(decl) return decl->qualifiedIdentifier(); return QualifiedIdentifier(i18n("(unknown direct declaration)")); } return QualifiedIdentifier(i18n("(missing)")) + m_indirectData.identifier.identifier(); } } diff --git a/language/duchain/definitions.cpp b/language/duchain/definitions.cpp index 6029d344f..91211fb55 100644 --- a/language/duchain/definitions.cpp +++ b/language/duchain/definitions.cpp @@ -1,230 +1,230 @@ /* This file is part of KDevelop Copyright 2008 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 "appendedlist.h" #include "definitions.h" #include "declaration.h" #include "declarationid.h" #include "duchainpointer.h" #include #include "serialization/itemrepository.h" #include #include namespace KDevelop { DEFINE_LIST_MEMBER_HASH(DefinitionsItem, definitions, IndexedDeclaration) class DefinitionsItem { public: DefinitionsItem() { initializeAppendedLists(); } DefinitionsItem(const DefinitionsItem& rhs, bool dynamic = true) : declaration(rhs.declaration) { initializeAppendedLists(dynamic); copyListsFrom(rhs); } ~DefinitionsItem() { 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 declaration.hash(); } unsigned int itemSize() const { return dynamicSize(); } uint classSize() const { return sizeof(DefinitionsItem); } DeclarationId declaration; START_APPENDED_LISTS(DefinitionsItem); APPENDED_LIST_FIRST(DefinitionsItem, IndexedDeclaration, definitions); END_APPENDED_LISTS(DefinitionsItem, definitions); }; class DefinitionsRequestItem { public: DefinitionsRequestItem(const DefinitionsItem& 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(DefinitionsItem* item) const { new (item) DefinitionsItem(m_item, false); } static void destroy(DefinitionsItem* item, KDevelop::AbstractItemRepository&) { item->~DefinitionsItem(); } static bool persistent(const DefinitionsItem*) { return true; } bool equals(const DefinitionsItem* item) const { return m_item.declaration == item->declaration; } const DefinitionsItem& m_item; }; class DefinitionsVisitor { public: DefinitionsVisitor(Definitions* _definitions, const QTextStream& _out) : definitions(_definitions) , out(_out) { } bool operator()(const DefinitionsItem* item) { QDebug qout(out.device()); auto id = item->declaration; auto allDefinitions = definitions->definitions(id); qout << "Definitions for" << id.qualifiedIdentifier() << endl; - for (const IndexedDeclaration& decl : allDefinitions) { + foreach (const IndexedDeclaration decl, allDefinitions) { if(decl.data()) { qout << " " << decl.data()->qualifiedIdentifier() << "in" << decl.data()->url().byteArray() << "at" << decl.data()->rangeInCurrentRevision() << endl; } } return true; } private: const Definitions* definitions; const QTextStream& out; }; class DefinitionsPrivate { public: - DefinitionsPrivate() : m_definitions("Definition Map") { + DefinitionsPrivate() : m_definitions(QStringLiteral("Definition Map")) { } //Maps declaration-ids to definitions ItemRepository m_definitions; }; Definitions::Definitions() : d(new DefinitionsPrivate()) { } Definitions::~Definitions() { delete d; } void Definitions::addDefinition(const DeclarationId& id, const IndexedDeclaration& definition) { DefinitionsItem item; item.declaration = id; item.definitionsList().append(definition); DefinitionsRequestItem request(item); uint index = d->m_definitions.findIndex(item); if(index) { //Check whether the item is already in the mapped list, else copy the list into the new created item const DefinitionsItem* oldItem = d->m_definitions.itemFromIndex(index); for(unsigned int a = 0; a < oldItem->definitionsSize(); ++a) { if(oldItem->definitions()[a] == definition) return; //Already there item.definitionsList().append(oldItem->definitions()[a]); } d->m_definitions.deleteItem(index); } //This inserts the changed item d->m_definitions.index(request); } void Definitions::removeDefinition(const DeclarationId& id, const IndexedDeclaration& definition) { DefinitionsItem item; item.declaration = id; DefinitionsRequestItem request(item); uint index = d->m_definitions.findIndex(item); if(index) { //Check whether the item is already in the mapped list, else copy the list into the new created item const DefinitionsItem* oldItem = d->m_definitions.itemFromIndex(index); for(unsigned int a = 0; a < oldItem->definitionsSize(); ++a) if(!(oldItem->definitions()[a] == definition)) item.definitionsList().append(oldItem->definitions()[a]); d->m_definitions.deleteItem(index); Q_ASSERT(d->m_definitions.findIndex(item) == 0); //This inserts the changed item if(item.definitionsSize() != 0) d->m_definitions.index(request); } } KDevVarLengthArray Definitions::definitions(const DeclarationId& id) const { KDevVarLengthArray ret; DefinitionsItem item; item.declaration = id; DefinitionsRequestItem request(item); uint index = d->m_definitions.findIndex(item); if(index) { const DefinitionsItem* repositoryItem = d->m_definitions.itemFromIndex(index); FOREACH_FUNCTION(const IndexedDeclaration& decl, repositoryItem->definitions) ret.append(decl); } return ret; } void Definitions::dump(const QTextStream& out) { QMutexLocker lock(d->m_definitions.mutex()); DefinitionsVisitor v(this, out); d->m_definitions.visitAllItems(v); } } diff --git a/language/duchain/duchain.cpp b/language/duchain/duchain.cpp index d0f1e4ef4..112f67d5a 100644 --- a/language/duchain/duchain.cpp +++ b/language/duchain/duchain.cpp @@ -1,1702 +1,1702 @@ /* 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 "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" #ifdef 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(0), m_index(topContextIndex) { } EnvironmentInformationRequest(const ParsingEnvironmentFile* file) : m_file(file), m_index(file->indexedTopContext().index()) { } enum { AverageSize = 32 //This should be the approximate average size of an Item }; unsigned int hash() const { return m_index; } uint itemSize() const { return sizeof(EnvironmentInformationItem) + DUChainItemSystem::self().dynamicSize(*m_file->d_func()); } void createItem(EnvironmentInformationItem* item) const { new (item) EnvironmentInformationItem(m_index, DUChainItemSystem::self().dynamicSize(*m_file->d_func())); Q_ASSERT(m_file->d_func()->m_dynamic); DUChainBaseData* data = reinterpret_cast(reinterpret_cast(item) + sizeof(EnvironmentInformationItem)); DUChainItemSystem::self().copy(*m_file->d_func(), *data, true); Q_ASSERT(data->m_range == m_file->d_func()->m_range); Q_ASSERT(data->classId == m_file->d_func()->classId); Q_ASSERT(data->m_dynamic == false); } static void destroy(EnvironmentInformationItem* item, KDevelop::AbstractItemRepository&) { item->~EnvironmentInformationItem(); //We don't need to call the destructor, because that's done in DUChainBase::makeDynamic() //We just need to make sure that every environment-file is dynamic when it's deleted // DUChainItemSystem::self().callDestructor((DUChainBaseData*)(((char*)item) + sizeof(EnvironmentInformationItem))); } static bool persistent(const EnvironmentInformationItem* ) { //Cleanup done separately return true; } bool equals(const EnvironmentInformationItem* item) const { return m_index == item->m_topContext; } const ParsingEnvironmentFile* m_file; uint m_index; }; ///A list of environment-information/top-contexts mapped to a file-name class EnvironmentInformationListItem { public: EnvironmentInformationListItem() { initializeAppendedLists(true); } EnvironmentInformationListItem(const EnvironmentInformationListItem& rhs, bool dynamic = true) { initializeAppendedLists(dynamic); m_file = rhs.m_file; copyListsFrom(rhs); } ~EnvironmentInformationListItem() { freeAppendedLists(); } unsigned int hash() const { //We only compare the declaration. This allows us implementing a map, although the item-repository //originally represents a set. return m_file.hash(); } unsigned short int itemSize() const { return dynamicSize(); } IndexedString m_file; uint classSize() const { return sizeof(*this); } START_APPENDED_LISTS(EnvironmentInformationListItem); ///Contains the index of each contained environment-item APPENDED_LIST_FIRST(EnvironmentInformationListItem, uint, items); END_APPENDED_LISTS(EnvironmentInformationListItem, items); }; class EnvironmentInformationListRequest { public: ///This constructor should only be used for lookup EnvironmentInformationListRequest(const IndexedString& file) : m_file(file), m_item(0) { } ///This is used to actually construct the information in the repository EnvironmentInformationListRequest(const IndexedString& file, const EnvironmentInformationListItem& item) : m_file(file), m_item(&item) { } enum { AverageSize = 160 //This should be the approximate average size of an Item }; unsigned int hash() const { return m_file.hash(); } uint itemSize() const { return m_item->itemSize(); } void createItem(EnvironmentInformationListItem* item) const { Q_ASSERT(m_item->m_file == m_file); new (item) EnvironmentInformationListItem(*m_item, false); } static void destroy(EnvironmentInformationListItem* item, KDevelop::AbstractItemRepository&) { item->~EnvironmentInformationListItem(); } static bool persistent(const EnvironmentInformationListItem*) { //Cleanup is done separately return true; } bool equals(const EnvironmentInformationListItem* item) const { return m_file == item->m_file; } IndexedString m_file; const EnvironmentInformationListItem* m_item; }; class DUChainPrivate; static DUChainPrivate* duChainPrivateSelf = 0; class DUChainPrivate { class CleanupThread : public QThread { public: CleanupThread(DUChainPrivate* data) : m_stopRunning(false), m_data(data) { } void stopThread() { { QMutexLocker lock(&m_waitMutex); m_stopRunning = true; m_wait.wakeAll(); //Wakes the thread up, so it notices it should exit } wait(); } private: void run() 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); 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(0), 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.values()) removeDocumentChainFromMemory(top); m_indexEnvironmentInformations.clear(); m_fileEnvironmentInformations.clear(); Q_ASSERT(m_fileEnvironmentInformations.isEmpty()); Q_ASSERT(m_chainsByUrl.isEmpty()); } ///DUChain must be write-locked ///Also removes from the environment-manager if the top-context is not on disk void removeDocumentChainFromMemory(TopDUContext* context) { QMutexLocker l(&m_chainsMutex); { QMutexLocker l(&m_referenceCountsMutex); if(m_referenceCounts.contains(context)) { //This happens during shutdown, since everything is unloaded 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] = 0; } ///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. - for (uint topContextIndex : topContextIndices) { + 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(ParsingEnvironmentFilePointer file, m_fileEnvironmentInformations.values(url)) + 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 0; } ///Makes sure that the chain with the given index is loaded ///@warning m_chainsMutex must NOT be locked when this is called void loadChain(uint index, QSet& loaded) { QMutexLocker l(&m_chainsMutex); if(!hasChainForIndex(index)) { if(m_loading.contains(index)) { //It's probably being loaded by another thread. So wait until the load is ready while(m_loading.contains(index)) { l.unlock(); 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; } ///@param retries When this is nonzero, then doMoreCleanup will do the specified amount of cycles ///doing the cleanup without permanently locking the du-chain. During these steps the consistency ///of the disk-storage is not guaranteed, but only few changes will be done during these steps, ///so the final step where the duchain is permanently locked is much faster. void doMoreCleanup(int retries = 0, bool needLockRepository = true) { if(m_cleanupDisabled) return; //This mutex makes sure that there's never 2 threads at he same time trying to clean up QMutexLocker lockCleanupMutex(&cleanupMutex()); if(m_destroyed || m_cleanupDisabled) return; Q_ASSERT(!instance->lock()->currentThreadHasReadLock() && !instance->lock()->currentThreadHasWriteLock()); DUChainWriteLocker writeLock(instance->lock()); PersistentSymbolTable::self().clearCache(); //This is used to stop all parsing before starting to do the cleanup. This way less happens during the //soft cleanups, and we have a good chance that during the "hard" cleanup only few data has to be written. QList lockedParseMutexes; QList locked; if(needLockRepository) { if (ICore* core = ICore::self()) if (ILanguageController* lc = core->languageController()) lockedParseMutexes = lc->loadedLanguages(); writeLock.unlock(); //Here we wait for all parsing-threads to stop their processing - foreach(const auto& language, lockedParseMutexes) { + foreach(const auto language, lockedParseMutexes) { language->parseLock()->lockForWrite(); locked << language->parseLock(); } writeLock.lock(); globalItemRepositoryRegistry().lockForWriting(); qCDebug(LANGUAGE) << "starting cleanup"; } QTime startTime = QTime::currentTime(); storeAllInformation(!retries, writeLock); //Puts environment-information into a repository //We don't need to increase the reference-count, since the cleanup-mutex is locked QSet workOnContexts; { QMutexLocker l(&m_chainsMutex); foreach(TopDUContext* top, m_chainsByUrl.values()) { workOnContexts << top; Q_ASSERT(hasChainForIndex(top->ownIndex())); } } foreach(TopDUContext* context, workOnContexts) { context->m_dynamicData->store(); if(retries) { //Eventually give other threads a chance to access the duchain writeLock.unlock(); //Sleep to give the other threads a realistic chance to get a read-lock in between 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 foreach(TopDUContext* context, m_referenceCounts.keys()) { if(context == unload || context->imports(unload, CursorInRevision())) { workOnContexts.remove(unload); hasReference = true; } } } if(!hasReference) ++hadUnloadable; //We have found a context that is not referenced else continue; //This context is referenced bool isImportedByLoaded = !unload->loadedImporters().isEmpty(); //If we unload a context that is imported by other contexts, we create a bad loaded state if(isImportedByLoaded && !unloadAllUnreferenced) continue; unloadedNames.insert(unload->url()); //Since we've released the write-lock in between, we've got to call store() again to be sure that none of the data is dynamic //If nothing has changed, it is only a low-cost call. unload->m_dynamicData->store(); Q_ASSERT(!unload->d_func()->m_dynamic); removeDocumentChainFromMemory(unload); workOnContexts.remove(unload); unloadedOne = true; if(!unloadAllUnreferenced) { //Eventually give other threads a chance to access the duchain writeLock.unlock(); //Sleep to give the other threads a realistic chance to get a read-lock in between 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, false); writeLock.lock(); } if(needLockRepository) { 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(); #ifdef 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 0; } ///Loads/gets the environment-information for the given top-context index, or returns zero if none exists ///@warning m_chainsMutex should NOT be locked when this is called, because it triggers I/O ///@warning no other mutexes should be locked, as that may lead to a dedalock ParsingEnvironmentFile* loadInformation(uint topContextIndex) { ParsingEnvironmentFile* alreadyLoaded = findInformation(topContextIndex); if(alreadyLoaded) return alreadyLoaded; //Step two: Check if it is on disk, and if is, load it uint dataIndex = m_environmentInfo.findIndex(EnvironmentInformationRequest(topContextIndex)); if(!dataIndex) { //No environment-information stored for this top-context return 0; } const EnvironmentInformationItem& item(*m_environmentInfo.itemFromIndex(dataIndex)); QMutexLocker lock(&m_chainsMutex); //Due to multi-threading, we must do this check after locking the mutex, so we can be sure we don't create the same item twice at the same time alreadyLoaded = findInformation(topContextIndex); if(alreadyLoaded) return alreadyLoaded; ///FIXME: ugly, and remove const_cast ParsingEnvironmentFile* ret = dynamic_cast(DUChainItemSystem::self().create( const_cast(reinterpret_cast(reinterpret_cast(&item) + sizeof(EnvironmentInformationItem))) )); if(ret) { Q_ASSERT(ret->d_func()->classId); Q_ASSERT(ret->indexedTopContext().index() == topContextIndex); ParsingEnvironmentFilePointer retPtr(ret); m_fileEnvironmentInformations.insert(ret->url(), retPtr); Q_ASSERT(!m_indexEnvironmentInformations.contains(ret->indexedTopContext().index())); m_indexEnvironmentInformations.insert(ret->indexedTopContext().index(), retPtr); } return ret; } struct CleanupListVisitor { QList checkContexts; bool operator()(const EnvironmentInformationItem* item) { checkContexts << item->m_topContext; return true; } }; ///Will check a selection of all top-contexts for up-to-date ness, and remove them if out of date void cleanupTopContexts() { DUChainWriteLocker lock( DUChain::lock() ); 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, 0); DUChain::chainsByIndex[chain->ownIndex()] = chain; } { Q_ASSERT(DUChain::chainsByIndex[chain->ownIndex()]); } Q_ASSERT(sdDUChainPrivate->hasChainForIndex(chain->ownIndex())); sdDUChainPrivate->m_chainsByUrl.insert(chain->url(), chain); Q_ASSERT(sdDUChainPrivate->hasChainForIndex(chain->ownIndex())); chain->setInDuChain(true); l.unlock(); addToEnvironmentManager(chain); // 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 0; } TopDUContext* DUChain::chainForDocument(const KDevelop::IndexedString& document, bool proxyContext) const { ENSURE_CHAIN_READ_LOCKED; if(sdDUChainPrivate->m_destroyed) return 0; QList list = sdDUChainPrivate->getEnvironmentInformation(document); foreach(const ParsingEnvironmentFilePointer &file, list) if(isInMemory(file->indexedTopContext().index()) && file->isProxyContext() == proxyContext) { return file->topContext(); } foreach(const ParsingEnvironmentFilePointer &file, list) if(proxyContext == file->isProxyContext()) { return file->topContext(); } //Allow selecting a top-context even if there is no ParsingEnvironmentFile QList< TopDUContext* > ret = chainsForDocument(document); foreach(TopDUContext* ctx, ret) { if(!ctx->parsingEnvironmentFile() || (ctx->parsingEnvironmentFile()->isProxyContext() == proxyContext)) return ctx; } return 0; } QList DUChain::chainsForDocument(const 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 0; ParsingEnvironmentFilePointer envFile = environmentFileForDocument(document, environment, proxyContext); if(envFile) { return envFile->topContext(); }else{ return 0; } } QList DUChain::documents() const { QMutexLocker l(&sdDUChainPrivate->m_chainsMutex); QList ret; foreach(TopDUContext* top, sdDUChainPrivate->m_chainsByUrl.values()) { ret << top->url().toUrl(); } return ret; } QList DUChain::indexedDocuments() const { QMutexLocker l(&sdDUChainPrivate->m_chainsMutex); QList ret; foreach(TopDUContext* top, sdDUChainPrivate->m_chainsByUrl.values()) { ret << top->url(); } return ret; } void DUChain::documentActivated(KDevelop::IDocument* doc) { if(sdDUChainPrivate->m_destroyed) return; //Check whether the document has an attached environment-manager, and whether that one thinks the document needs to be updated. //If yes, update it. DUChainReadLocker lock( DUChain::lock() ); QMutexLocker l(&sdDUChainPrivate->m_chainsMutex); TopDUContext* ctx = DUChainUtils::standardContextForUrl(doc->url(), true); if(ctx && ctx->parsingEnvironmentFile()) if(ctx->parsingEnvironmentFile()->needsUpdate()) ICore::self()->languageController()->backgroundParser()->addDocument(IndexedString(doc->url())); } void DUChain::documentClosed(IDocument* document) { if(sdDUChainPrivate->m_destroyed) return; IndexedString url(document->url()); foreach(const ReferencedTopDUContext &top, sdDUChainPrivate->m_openDocumentContexts) if(top->url() == url) sdDUChainPrivate->m_openDocumentContexts.remove(top); } void DUChain::documentLoadedPrepare(KDevelop::IDocument* doc) { if(sdDUChainPrivate->m_destroyed) return; const IndexedString url(doc->url()); DUChainWriteLocker lock( DUChain::lock() ); QMutexLocker l(&sdDUChainPrivate->m_chainsMutex); TopDUContext* standardContext = DUChainUtils::standardContextForUrl(doc->url()); QList chains = chainsForDocument(url); 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) { + 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); if(!sdDUChainPrivate->m_referenceCounts.contains(top)) sdDUChainPrivate->m_referenceCounts.insert(top, 1); else ++sdDUChainPrivate->m_referenceCounts[top]; } bool DUChain::deleted() { return m_deleted; } void DUChain::refCountDown(TopDUContext* top) { QMutexLocker l(&sdDUChainPrivate->m_referenceCountsMutex); if(!sdDUChainPrivate->m_referenceCounts.contains(top)) { //qCWarning(LANGUAGE) << "tried to decrease reference-count for" << top->url().str() << "but this top-context is not referenced"; return; } --sdDUChainPrivate->m_referenceCounts[top]; if(!sdDUChainPrivate->m_referenceCounts[top]) sdDUChainPrivate->m_referenceCounts.remove(top); } void DUChain::emitDeclarationSelected(const DeclarationPointer& decl) { emit declarationSelected(decl); } KDevelop::ReferencedTopDUContext DUChain::waitForUpdate(const KDevelop::IndexedString& document, KDevelop::TopDUContext::Features minFeatures, bool proxyContext) { Q_ASSERT(!lock()->currentThreadHasReadLock() && !lock()->currentThreadHasWriteLock()); WaitForUpdate waiter; updateContextForUrl(document, minFeatures, &waiter); // waiter.m_waitMutex.lock(); // waiter.m_dataMutex.unlock(); while(!waiter.m_ready) { // we might have been shut down in the meanwhile if (!ICore::self()) { return 0; } QMetaObject::invokeMethod(ICore::self()->languageController()->backgroundParser(), "parseDocuments"); QApplication::processEvents(); 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 463c1f6dd..f7b639647 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 "Global"; - case DUContext::Namespace: return "Namespace"; - case DUContext::Class: return "Class"; - case DUContext::Function: return "Function"; - case DUContext::Template: return "Template"; - case DUContext::Enum: return "Enum"; - case DUContext::Helper: return "Helper"; - case DUContext::Other: return "Other"; + 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) {} 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) + 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/duchainutils.cpp b/language/duchain/duchainutils.cpp index 7096e05e4..fc6223b4f 100644 --- a/language/duchain/duchainutils.cpp +++ b/language/duchain/duchainutils.cpp @@ -1,634 +1,634 @@ /* * DUChain Utilities * * Copyright 2007 Hamish Rodda * Copyright 2007-2008 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "duchainutils.h" #include #include #include "../interfaces/ilanguagesupport.h" #include "util/debug.h" #include "declaration.h" #include "classfunctiondeclaration.h" #include "ducontext.h" #include "duchain.h" #include "use.h" #include "duchainlock.h" #include "classmemberdeclaration.h" #include "functiondefinition.h" #include "specializationstore.h" #include "persistentsymboltable.h" #include "classdeclaration.h" #include "parsingenvironment.h" #include using namespace KDevelop; using namespace KTextEditor; CodeCompletionModel::CompletionProperties DUChainUtils::completionProperties(const Declaration* dec) { CodeCompletionModel::CompletionProperties p; if(dec->context()->type() == DUContext::Class) { if (const ClassMemberDeclaration* member = dynamic_cast(dec)) { switch (member->accessPolicy()) { case Declaration::Public: p |= CodeCompletionModel::Public; break; case Declaration::Protected: p |= CodeCompletionModel::Protected; break; case Declaration::Private: p |= CodeCompletionModel::Private; break; default: break; } if (member->isStatic()) p |= CodeCompletionModel::Static; if (member->isAuto()) {}//TODO if (member->isFriend()) p |= CodeCompletionModel::Friend; if (member->isRegister()) {}//TODO if (member->isExtern()) {}//TODO if (member->isMutable()) {}//TODO } } if (const AbstractFunctionDeclaration* function = dynamic_cast(dec)) { p |= CodeCompletionModel::Function; if (function->isVirtual()) p |= CodeCompletionModel::Virtual; if (function->isInline()) p |= CodeCompletionModel::Inline; if (function->isExplicit()) {}//TODO } if( dec->isTypeAlias() ) p |= CodeCompletionModel::TypeAlias; if (dec->abstractType()) { switch (dec->abstractType()->whichType()) { case AbstractType::TypeIntegral: p |= CodeCompletionModel::Variable; break; case AbstractType::TypePointer: p |= CodeCompletionModel::Variable; break; case AbstractType::TypeReference: p |= CodeCompletionModel::Variable; break; case AbstractType::TypeFunction: p |= CodeCompletionModel::Function; break; case AbstractType::TypeStructure: p |= CodeCompletionModel::Class; break; case AbstractType::TypeArray: p |= CodeCompletionModel::Variable; break; case AbstractType::TypeEnumeration: p |= CodeCompletionModel::Enum; break; case AbstractType::TypeEnumerator: p |= CodeCompletionModel::Variable; break; case AbstractType::TypeAbstract: case AbstractType::TypeDelayed: case AbstractType::TypeUnsure: case AbstractType::TypeAlias: // TODO break; } if( dec->abstractType()->modifiers() & AbstractType::ConstModifier ) p |= CodeCompletionModel::Const; if( dec->kind() == Declaration::Instance && !dec->isFunctionDeclaration() ) p |= CodeCompletionModel::Variable; } if (dec->context()) { if( dec->context()->type() == DUContext::Global ) p |= CodeCompletionModel::GlobalScope; else if( dec->context()->type() == DUContext::Namespace ) p |= CodeCompletionModel::NamespaceScope; else if( dec->context()->type() != DUContext::Class && dec->context()->type() != DUContext::Enum ) p |= CodeCompletionModel::LocalScope; } return p; } /**We have to construct the item from the pixmap, else the icon will be marked as "load on demand", * and for some reason will be loaded every time it's used(this function returns a QIcon marked "load on demand" * each time this is called). And the loading is very slow. Seems like a bug somewhere, it cannot be ment to be that slow. */ #define RETURN_CACHED_ICON(name) {static QIcon icon(QIcon( \ QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kdevelop/pics/" name ".png"))\ ).pixmap(QSize(16, 16)));\ return icon;} QIcon DUChainUtils::iconForProperties(KTextEditor::CodeCompletionModel::CompletionProperties p) { if( (p & CodeCompletionModel::Variable) ) if( (p & CodeCompletionModel::Protected) ) RETURN_CACHED_ICON("CVprotected_var") else if( p & CodeCompletionModel::Private ) RETURN_CACHED_ICON("CVprivate_var") else RETURN_CACHED_ICON("CVpublic_var") else if( (p & CodeCompletionModel::Union) && (p & CodeCompletionModel::Protected) ) RETURN_CACHED_ICON("protected_union") else if( p & CodeCompletionModel::Enum ) if( p & CodeCompletionModel::Protected ) RETURN_CACHED_ICON("protected_enum") else if( p & CodeCompletionModel::Private ) RETURN_CACHED_ICON("private_enum") else RETURN_CACHED_ICON("enum") else if( p & CodeCompletionModel::Struct ) if( p & CodeCompletionModel::Private ) RETURN_CACHED_ICON("private_struct") else RETURN_CACHED_ICON("struct") else if( p & CodeCompletionModel::Slot ) if( p & CodeCompletionModel::Protected ) RETURN_CACHED_ICON("CVprotected_slot") else if( p & CodeCompletionModel::Private ) RETURN_CACHED_ICON("CVprivate_slot") else if(p & CodeCompletionModel::Public ) RETURN_CACHED_ICON("CVpublic_slot") else RETURN_CACHED_ICON("slot") else if( p & CodeCompletionModel::Signal ) if( p & CodeCompletionModel::Protected ) RETURN_CACHED_ICON("CVprotected_signal") else RETURN_CACHED_ICON("signal") else if( p & CodeCompletionModel::Class ) if( (p & CodeCompletionModel::Class) && (p & CodeCompletionModel::Protected) ) RETURN_CACHED_ICON("protected_class") else if( (p & CodeCompletionModel::Class) && (p & CodeCompletionModel::Private) ) RETURN_CACHED_ICON("private_class") else RETURN_CACHED_ICON("code-class") else if( p & CodeCompletionModel::Union ) if( p & CodeCompletionModel::Private ) RETURN_CACHED_ICON("private_union") else RETURN_CACHED_ICON("union") else if( p & CodeCompletionModel::TypeAlias ) if ((p & CodeCompletionModel::Const) /*|| (p & CodeCompletionModel::Volatile)*/) RETURN_CACHED_ICON("CVtypedef") else RETURN_CACHED_ICON("typedef") else if( p & CodeCompletionModel::Function ) { if( p & CodeCompletionModel::Protected ) RETURN_CACHED_ICON("protected_function") else if( p & CodeCompletionModel::Private ) RETURN_CACHED_ICON("private_function") else RETURN_CACHED_ICON("code-function") } if( p & CodeCompletionModel::Protected ) RETURN_CACHED_ICON("protected_field") else if( p & CodeCompletionModel::Private ) RETURN_CACHED_ICON("private_field") else RETURN_CACHED_ICON("field") return QIcon(); } QIcon DUChainUtils::iconForDeclaration(const Declaration* dec) { return iconForProperties(completionProperties(dec)); } TopDUContext* DUChainUtils::contentContextFromProxyContext(TopDUContext* top) { if(!top) return 0; if(top->parsingEnvironmentFile() && top->parsingEnvironmentFile()->isProxyContext()) { if(!top->importedParentContexts().isEmpty()) { - DUContext* ctx = top->importedParentContexts()[0].context(0); + DUContext* ctx = top->importedParentContexts().at(0).context(0); if(!ctx) return 0; TopDUContext* ret = ctx->topContext(); if(!ret) return 0; if(ret->url() != top->url()) qCDebug(LANGUAGE) << "url-mismatch between content and proxy:" << top->url().toUrl() << ret->url().toUrl(); if(ret->url() == top->url() && !ret->parsingEnvironmentFile()->isProxyContext()) return ret; } else { qCDebug(LANGUAGE) << "Proxy-context imports no content-context"; } } else return top; return 0; } TopDUContext* DUChainUtils::standardContextForUrl(const QUrl& url, bool preferProxyContext) { KDevelop::TopDUContext* chosen = 0; auto languages = ICore::self()->languageController()->languagesForUrl(url); - foreach(const auto& language, languages) + foreach(const auto language, languages) { if(!chosen) { chosen = language->standardContext(url, preferProxyContext); } } if(!chosen) chosen = DUChain::self()->chainForDocument(IndexedString(url), preferProxyContext); if(!chosen && preferProxyContext) return standardContextForUrl(url, false); // Fall back to a normal context return chosen; } Declaration* declarationUnderCursor(const CursorInRevision& c, DUContext* ctx) { foreach( Declaration* decl, ctx->localDeclarations() ) if( decl->range().contains(c, RangeInRevision::IncludeBackEdge) ) return decl; //Search all collapsed sub-contexts. In C++, those can contain declarations that have ranges out of the context foreach( DUContext* subCtx, ctx->childContexts() ) { //This is a little hacky, but we need it in case of foreach macros and similar stuff if(subCtx->range().isEmpty() || subCtx->range().start.line == c.line || subCtx->range().end.line == c.line) { Declaration* decl = declarationUnderCursor(c, subCtx); if(decl) return decl; } } return 0; } Declaration* DUChainUtils::itemUnderCursor(const QUrl& url, const KTextEditor::Cursor& _c) { KDevelop::TopDUContext* chosen = standardContextForUrl(url); if( chosen ) { CursorInRevision c = chosen->transformToLocalRevision(_c); DUContext* ctx = chosen->findContextAt(c); while( ctx ) { //Try finding a declaration under the cursor Declaration* decl = declarationUnderCursor(c, ctx); if(decl) return decl; //Try finding a use under the cursor for(int a = 0; a < ctx->usesCount(); ++a) if( ctx->uses()[a].m_range.contains(c, RangeInRevision::IncludeBackEdge) ) return ctx->topContext()->usedDeclarationForIndex(ctx->uses()[a].m_declarationIndex); ctx = ctx->parentContext(); //It may happen, for example in the case of function-declarations, that the use is one context higher. } } return 0; } KTextEditor::Range DUChainUtils::itemRangeUnderCursor(const QUrl& url, const KTextEditor::Cursor& cursor) { KDevelop::TopDUContext* chosen = standardContextForUrl(url); if( chosen ) { CursorInRevision c = chosen->transformToLocalRevision(cursor); DUContext* ctx = chosen->findContextAt(c); if (ctx) { Declaration* decl = declarationUnderCursor(c, ctx); // Some declarations need to be searched in parent context if (!decl && ctx->parentContext()) { decl = declarationUnderCursor(c, ctx->parentContext()); } if (decl && decl->range().contains(c, RangeInRevision::IncludeBackEdge) ) { return decl->rangeInCurrentRevision(); } for(int a = 0; a < ctx->usesCount(); ++a) { if( ctx->uses()[a].m_range.contains(c, RangeInRevision::IncludeBackEdge) ) { return ctx->transformFromLocalRevision(ctx->uses()[a].m_range); } } } } return KTextEditor::Range(); } Declaration* DUChainUtils::declarationForDefinition(Declaration* definition, TopDUContext* topContext) { if(!definition) return 0; if(!topContext) topContext = definition->topContext(); if(dynamic_cast(definition)) { Declaration* ret = static_cast(definition)->declaration(); if(ret) return ret; } return definition; } Declaration* DUChainUtils::declarationInLine(const KTextEditor::Cursor& _cursor, DUContext* ctx) { if(!ctx) return 0; CursorInRevision cursor = ctx->transformToLocalRevision(_cursor); foreach(Declaration* decl, ctx->localDeclarations()) { if(decl->range().start.line == cursor.line) return decl; DUContext* funCtx = getFunctionContext(decl); if(funCtx && funCtx->range().contains(cursor)) return decl; } foreach(DUContext* child, ctx->childContexts()){ Declaration* decl = declarationInLine(_cursor, child); if(decl) return decl; } return 0; } DUChainUtils::DUChainItemFilter::~DUChainItemFilter() { } void DUChainUtils::collectItems( DUContext* context, DUChainItemFilter& filter ) { QVector children = context->childContexts(); QVector localDeclarations = context->localDeclarations(); QVector::const_iterator childIt = children.constBegin(); QVector::const_iterator declIt = localDeclarations.constBegin(); while(childIt != children.constEnd() || declIt != localDeclarations.constEnd()) { DUContext* child = 0; if(childIt != children.constEnd()) child = *childIt; Declaration* decl = 0; if(declIt != localDeclarations.constEnd()) decl = *declIt; if(decl) { if(child && child->range().start.line >= decl->range().start.line) child = 0; } if(child) { if(decl && decl->range().start >= child->range().start) decl = 0; } if(decl) { if( filter.accept(decl) ) { //Action is done in the filter } ++declIt; continue; } if(child) { if( filter.accept(child) ) collectItems(child, filter); ++childIt; continue; } } } KDevelop::DUContext* DUChainUtils::getArgumentContext(KDevelop::Declaration* decl) { DUContext* internal = decl->internalContext(); if( !internal ) return 0; if( internal->type() == DUContext::Function ) return internal; foreach( const DUContext::Import &ctx, internal->importedParentContexts() ) { if( ctx.context(decl->topContext()) ) if( ctx.context(decl->topContext())->type() == DUContext::Function ) return ctx.context(decl->topContext()); } return 0; } QList DUChainUtils::collectAllVersions(Declaration* decl) { QList ret; ret << IndexedDeclaration(decl); if(decl->inSymbolTable()) { uint count; const IndexedDeclaration* allDeclarations; PersistentSymbolTable::self().declarations(decl->qualifiedIdentifier(), count, allDeclarations); for(uint a = 0; a < count; ++a) if(!(allDeclarations[a] == IndexedDeclaration(decl))) ret << allDeclarations[a]; } return ret; } static QList getInheritersInternal(const Declaration* decl, uint& maxAllowedSteps, bool collectVersions) { QList ret; if(!dynamic_cast(decl)) return ret; if(maxAllowedSteps == 0) return ret; if(decl->internalContext() && decl->internalContext()->type() == DUContext::Class) - for (const IndexedDUContext& importer : decl->internalContext()->indexedImporters()) { + foreach (const IndexedDUContext importer, decl->internalContext()->indexedImporters()) { DUContext* imp = importer.data(); if(!imp) continue; if(imp->type() == DUContext::Class && imp->owner()) ret << imp->owner(); --maxAllowedSteps; if(maxAllowedSteps == 0) return ret; } if(maxAllowedSteps == 0) return ret; if(collectVersions && decl->inSymbolTable()) { uint count; const IndexedDeclaration* allDeclarations; PersistentSymbolTable::self().declarations(decl->qualifiedIdentifier(), count, allDeclarations); for(uint a = 0; a < count; ++a) { ++maxAllowedSteps; if(allDeclarations[a].data() && allDeclarations[a].data() != decl) { ret += getInheritersInternal(allDeclarations[a].data(), maxAllowedSteps, false); } if(maxAllowedSteps == 0) return ret; } } return ret; } QList DUChainUtils::getInheriters(const Declaration* decl, uint& maxAllowedSteps, bool collectVersions) { auto inheriters = getInheritersInternal(decl, maxAllowedSteps, collectVersions); // remove duplicates std::sort(inheriters.begin(), inheriters.end()); inheriters.erase(std::unique(inheriters.begin(), inheriters.end()), inheriters.end()); return inheriters; } QList DUChainUtils::getOverriders(const Declaration* currentClass, const Declaration* overriddenDeclaration, uint& maxAllowedSteps) { QList ret; if(maxAllowedSteps == 0) return ret; if(currentClass != overriddenDeclaration->context()->owner() && currentClass->internalContext()) ret += currentClass->internalContext()->findLocalDeclarations(overriddenDeclaration->identifier(), CursorInRevision::invalid(), currentClass->topContext(), overriddenDeclaration->abstractType()); foreach(Declaration* inheriter, getInheriters(currentClass, maxAllowedSteps)) ret += getOverriders(inheriter, overriddenDeclaration, maxAllowedSteps); return ret; } static bool hasUse(DUContext* context, int usedDeclarationIndex) { if(usedDeclarationIndex == std::numeric_limits::max()) return false; for(int a = 0; a < context->usesCount(); ++a) if(context->uses()[a].m_declarationIndex == usedDeclarationIndex) return true; foreach(DUContext* child, context->childContexts()) if(hasUse(child, usedDeclarationIndex)) return true; return false; } bool DUChainUtils::contextHasUse(DUContext* context, Declaration* declaration) { return hasUse(context, context->topContext()->indexForUsedDeclaration(declaration, false)); } static uint countUses(DUContext* context, int usedDeclarationIndex) { if(usedDeclarationIndex == std::numeric_limits::max()) return 0; uint ret = 0; for(int a = 0; a < context->usesCount(); ++a) if(context->uses()[a].m_declarationIndex == usedDeclarationIndex) ++ret; foreach(DUContext* child, context->childContexts()) ret += countUses(child, usedDeclarationIndex); return ret; } uint DUChainUtils::contextCountUses(DUContext* context, Declaration* declaration) { return countUses(context, context->topContext()->indexForUsedDeclaration(declaration, false)); } Declaration* DUChainUtils::getOverridden(const Declaration* decl) { const ClassFunctionDeclaration* classFunDecl = dynamic_cast(decl); if(!classFunDecl || !classFunDecl->isVirtual()) return 0; QList decls; foreach(const DUContext::Import &import, decl->context()->importedParentContexts()) { DUContext* ctx = import.context(decl->topContext()); if(ctx) decls += ctx->findDeclarations(QualifiedIdentifier(decl->identifier()), CursorInRevision::invalid(), decl->abstractType(), decl->topContext(), DUContext::DontSearchInParent); } foreach(Declaration* found, decls) { const ClassFunctionDeclaration* foundClassFunDecl = dynamic_cast(found); if(foundClassFunDecl && foundClassFunDecl->isVirtual()) return found; } return 0; } DUContext* DUChainUtils::getFunctionContext(Declaration* decl) { DUContext* functionContext = decl->internalContext(); if(functionContext && functionContext->type() != DUContext::Function) { foreach(const DUContext::Import& import, functionContext->importedParentContexts()) { DUContext* ctx = import.context(decl->topContext()); if(ctx && ctx->type() == DUContext::Function) functionContext = ctx; } } if(functionContext && functionContext->type() == DUContext::Function) return functionContext; return 0; } diff --git a/language/duchain/ducontext.cpp b/language/duchain/ducontext.cpp index 0b8fca27a..4c9bcc8db 100644 --- a/language/duchain/ducontext.cpp +++ b/language/duchain/ducontext.cpp @@ -1,1707 +1,1707 @@ /* This is part of KDevelop Copyright 2006 Hamish Rodda Copyright 2007-2009 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "ducontext.h" #include #include #include #include #include #include "ducontextdata.h" #include "declaration.h" #include "duchain.h" #include "duchainlock.h" #include "use.h" #include "identifier.h" #include "topducontext.h" #include "persistentsymboltable.h" #include "aliasdeclaration.h" #include "namespacealiasdeclaration.h" #include "abstractfunctiondeclaration.h" #include "duchainregister.h" #include "topducontextdynamicdata.h" #include "importers.h" #include "uses.h" #include "navigation/abstractdeclarationnavigationcontext.h" #include "navigation/abstractnavigationwidget.h" #include "ducontextdynamicdata.h" #include "util/debug.h" // maximum depth for DUContext::findDeclarationsInternal searches const uint maxParentDepth = 20; using namespace KTextEditor; #ifndef NDEBUG #define ENSURE_CAN_WRITE_(x) {if(x->inDUChain()) { ENSURE_CHAIN_WRITE_LOCKED }} #define ENSURE_CAN_READ_(x) {if(x->inDUChain()) { ENSURE_CHAIN_READ_LOCKED }} #else #define ENSURE_CAN_WRITE_(x) #define ENSURE_CAN_READ_(x) #endif QDebug operator<<(QDebug dbg, const KDevelop::DUContext::Import& import) { QDebugStateSaver saver(dbg); dbg.nospace() << "Import(" << import.indexedContext().data() << ')'; return dbg; } namespace KDevelop { DEFINE_LIST_MEMBER_HASH(DUContextData, m_childContexts, LocalIndexedDUContext) DEFINE_LIST_MEMBER_HASH(DUContextData, m_importers, IndexedDUContext) DEFINE_LIST_MEMBER_HASH(DUContextData, m_importedContexts, DUContext::Import) DEFINE_LIST_MEMBER_HASH(DUContextData, m_localDeclarations, LocalIndexedDeclaration) DEFINE_LIST_MEMBER_HASH(DUContextData, m_uses, Use) REGISTER_DUCHAIN_ITEM(DUContext); DUChainVisitor::~DUChainVisitor() { } /** * We leak here, to prevent a possible crash during destruction, as the destructor * of Identifier is not safe to be called after the duchain has been destroyed */ const Identifier& globalImportIdentifier() { static const Identifier globalImportIdentifierObject(*new Identifier(QStringLiteral("{...import...}"))); return globalImportIdentifierObject; } const Identifier& globalAliasIdentifier() { static const Identifier globalAliasIdentifierObject(*new Identifier(QStringLiteral("{...alias...}"))); return globalAliasIdentifierObject; } const IndexedIdentifier& globalIndexedImportIdentifier() { static const IndexedIdentifier id(globalImportIdentifier()); return id; } const IndexedIdentifier& globalIndexedAliasIdentifier() { static const IndexedIdentifier id(globalAliasIdentifier()); return id; } void DUContext::rebuildDynamicData(DUContext* parent, uint ownIndex) { Q_ASSERT(!parent || ownIndex); m_dynamicData->m_topContext = parent ? parent->topContext() : static_cast(this); m_dynamicData->m_indexInTopContext = ownIndex; m_dynamicData->m_parentContext = DUContextPointer(parent); m_dynamicData->m_context = this; m_dynamicData->m_childContexts.clear(); m_dynamicData->m_childContexts.reserve(d_func()->m_childContextsSize()); FOREACH_FUNCTION(const LocalIndexedDUContext& ctx, d_func()->m_childContexts) { m_dynamicData->m_childContexts << ctx.data(m_dynamicData->m_topContext); } m_dynamicData->m_localDeclarations.clear(); m_dynamicData->m_localDeclarations.reserve(d_func()->m_localDeclarationsSize()); FOREACH_FUNCTION(const LocalIndexedDeclaration& idx, d_func()->m_localDeclarations) { auto declaration = idx.data(m_dynamicData->m_topContext); if (!declaration) { qCWarning(LANGUAGE) << "child declaration number" << idx.localIndex() << "of" << d_func_dynamic()->m_localDeclarationsSize() << "is invalid"; continue; } m_dynamicData->m_localDeclarations << declaration; } DUChainBase::rebuildDynamicData(parent, ownIndex); } DUContextData::DUContextData() : m_inSymbolTable(false) , m_anonymousInParent(false) , m_propagateDeclarations(false) { initializeAppendedLists(); } DUContextData::~DUContextData() { freeAppendedLists(); } DUContextData::DUContextData(const DUContextData& rhs) : DUChainBaseData(rhs) , m_inSymbolTable(rhs.m_inSymbolTable) , m_anonymousInParent(rhs.m_anonymousInParent) , m_propagateDeclarations(rhs.m_propagateDeclarations) { initializeAppendedLists(); copyListsFrom(rhs); m_scopeIdentifier = rhs.m_scopeIdentifier; m_contextType = rhs.m_contextType; m_owner = rhs.m_owner; } DUContextDynamicData::DUContextDynamicData(DUContext* d) : m_topContext(0) , m_indexInTopContext(0) , m_context(d) , m_rangesChanged(true) { } void DUContextDynamicData::scopeIdentifier(bool includeClasses, QualifiedIdentifier& target) const { if (m_parentContext) m_parentContext->m_dynamicData->scopeIdentifier(includeClasses, target); if (includeClasses || d_func()->m_contextType != DUContext::Class) target += d_func()->m_scopeIdentifier; } bool DUContextDynamicData::imports(const DUContext* context, const TopDUContext* source, QSet* recursionGuard) const { if( this == context->m_dynamicData ) return true; if (recursionGuard->contains(this)) { return false; } recursionGuard->insert(this); FOREACH_FUNCTION( const DUContext::Import& ctx, d_func()->m_importedContexts ) { DUContext* import = ctx.context(source); if(import == context || (import && import->m_dynamicData->imports(context, source, recursionGuard))) return true; } return false; } inline bool isContextTemporary(uint index) { return index > (0xffffffff/2); } void DUContextDynamicData::addDeclaration( Declaration * newDeclaration ) { // The definition may not have its identifier set when it's assigned... // allow dupes here, TODO catch the error elsewhere //If this context is temporary, added declarations should be as well, and viceversa Q_ASSERT(isContextTemporary(m_indexInTopContext) == isContextTemporary(newDeclaration->ownIndex())); CursorInRevision start = newDeclaration->range().start; bool inserted = false; ///@todo Do binary search to find the position for (int i = m_localDeclarations.size() - 1; i >= 0; --i) { Declaration* child = m_localDeclarations[i]; Q_ASSERT(d_func()->m_localDeclarations()[i].data(m_topContext) == child); if(child == newDeclaration) return; //TODO: All declarations in a macro will have the same empty range, and just get appended //that may not be Good Enough in complex cases. if (start >= child->range().start) { m_localDeclarations.insert(i + 1, newDeclaration); d_func_dynamic()->m_localDeclarationsList().insert(i+1, newDeclaration); Q_ASSERT(d_func()->m_localDeclarations()[i+1].data(m_topContext) == newDeclaration); inserted = true; break; } } if (!inserted) { // We haven't found any child that is before this one, so prepend it m_localDeclarations.insert(0, newDeclaration); d_func_dynamic()->m_localDeclarationsList().insert(0, newDeclaration); Q_ASSERT(d_func()->m_localDeclarations()[0].data(m_topContext) == newDeclaration); } } bool DUContextDynamicData::removeDeclaration(Declaration* declaration) { const int idx = m_localDeclarations.indexOf(declaration); if (idx != -1) { Q_ASSERT(d_func()->m_localDeclarations()[idx].data(m_topContext) == declaration); m_localDeclarations.remove(idx); d_func_dynamic()->m_localDeclarationsList().remove(idx); return true; } else { Q_ASSERT(d_func_dynamic()->m_localDeclarationsList().indexOf(LocalIndexedDeclaration(declaration)) == -1); return false; } } void DUContextDynamicData::addChildContext( DUContext * context ) { // Internal, don't need to assert a lock Q_ASSERT(!context->m_dynamicData->m_parentContext || context->m_dynamicData->m_parentContext.data()->m_dynamicData == this ); LocalIndexedDUContext indexed(context->m_dynamicData->m_indexInTopContext); //If this context is temporary, added declarations should be as well, and viceversa Q_ASSERT(isContextTemporary(m_indexInTopContext) == isContextTemporary(indexed.localIndex())); bool inserted = false; int childCount = m_childContexts.size(); for (int i = childCount-1; i >= 0; --i) {///@todo Do binary search to find the position DUContext* child = m_childContexts[i]; Q_ASSERT(d_func_dynamic()->m_childContexts()[i] == LocalIndexedDUContext(child)); if (context == child) return; if (context->range().start >= child->range().start) { m_childContexts.insert(i+1, context); d_func_dynamic()->m_childContextsList().insert(i+1, indexed); context->m_dynamicData->m_parentContext = m_context; inserted = true; break; } } if( !inserted ) { m_childContexts.insert(0, context); d_func_dynamic()->m_childContextsList().insert(0, indexed); context->m_dynamicData->m_parentContext = m_context; } } bool DUContextDynamicData::removeChildContext( DUContext* context ) { // ENSURE_CAN_WRITE const int idx = m_childContexts.indexOf(context); if (idx != -1) { m_childContexts.remove(idx); Q_ASSERT(d_func()->m_childContexts()[idx] == LocalIndexedDUContext(context)); d_func_dynamic()->m_childContextsList().remove(idx); return true; } else { Q_ASSERT(d_func_dynamic()->m_childContextsList().indexOf(LocalIndexedDUContext(context)) == -1); return false; } } void DUContextDynamicData::addImportedChildContext( DUContext * context ) { // ENSURE_CAN_WRITE DUContext::Import import(m_context, context); if(import.isDirect()) { //Direct importers are registered directly within the data if(d_func_dynamic()->m_importersList().contains(IndexedDUContext(context))) { qCDebug(LANGUAGE) << m_context->scopeIdentifier(true).toString() << "importer added multiple times:" << context->scopeIdentifier(true).toString(); return; } d_func_dynamic()->m_importersList().append(context); }else{ //Indirect importers are registered separately Importers::self().addImporter(import.indirectDeclarationId(), IndexedDUContext(context)); } } //Can also be called with a context that is not in the list void DUContextDynamicData::removeImportedChildContext( DUContext * context ) { // ENSURE_CAN_WRITE DUContext::Import import(m_context, context); if(import.isDirect()) { d_func_dynamic()->m_importersList().removeOne(IndexedDUContext(context)); }else{ //Indirect importers are registered separately Importers::self().removeImporter(import.indirectDeclarationId(), IndexedDUContext(context)); } } int DUContext::depth() const { { if (!parentContext()) return 0; return parentContext()->depth() + 1; } } DUContext::DUContext(DUContextData& data) : DUChainBase(data) , m_dynamicData(new DUContextDynamicData(this)) { } DUContext::DUContext(const RangeInRevision& range, DUContext* parent, bool anonymous) : DUChainBase(*new DUContextData(), range) , m_dynamicData(new DUContextDynamicData(this)) { d_func_dynamic()->setClassId(this); if(parent) m_dynamicData->m_topContext = parent->topContext(); else m_dynamicData->m_topContext = static_cast(this); d_func_dynamic()->setClassId(this); DUCHAIN_D_DYNAMIC(DUContext); d->m_contextType = Other; m_dynamicData->m_parentContext = 0; d->m_anonymousInParent = anonymous; d->m_inSymbolTable = false; if (parent) { m_dynamicData->m_indexInTopContext = parent->topContext()->m_dynamicData->allocateContextIndex(this, parent->isAnonymous() || anonymous); Q_ASSERT(m_dynamicData->m_indexInTopContext); if( !anonymous ) parent->m_dynamicData->addChildContext(this); else m_dynamicData->m_parentContext = parent; } if(parent && !anonymous && parent->inSymbolTable()) setInSymbolTable(true); } bool DUContext::isAnonymous() const { return d_func()->m_anonymousInParent || (m_dynamicData->m_parentContext && m_dynamicData->m_parentContext->isAnonymous()); } DUContext::DUContext( DUContextData& dd, const RangeInRevision& range, DUContext * parent, bool anonymous ) : DUChainBase(dd, range) , m_dynamicData(new DUContextDynamicData(this)) { if(parent) m_dynamicData->m_topContext = parent->topContext(); else m_dynamicData->m_topContext = static_cast(this); DUCHAIN_D_DYNAMIC(DUContext); d->m_contextType = Other; m_dynamicData->m_parentContext = 0; d->m_inSymbolTable = false; d->m_anonymousInParent = anonymous; if (parent) { m_dynamicData->m_indexInTopContext = parent->topContext()->m_dynamicData->allocateContextIndex(this, parent->isAnonymous() || anonymous); if( !anonymous ) parent->m_dynamicData->addChildContext(this); else m_dynamicData->m_parentContext = parent; } } DUContext::DUContext(DUContext& useDataFrom) : DUChainBase(useDataFrom) , m_dynamicData(useDataFrom.m_dynamicData) { } DUContext::~DUContext( ) { TopDUContext* top = topContext(); if(!top->deleting() || !top->isOnDisk()) { DUCHAIN_D_DYNAMIC(DUContext); if(d->m_owner.declaration()) d->m_owner.declaration()->setInternalContext(0); while( d->m_importersSize() != 0 ) { if(d->m_importers()[0].data()) d->m_importers()[0].data()->removeImportedParentContext(this); else { qCDebug(LANGUAGE) << "importer disappeared"; d->m_importersList().removeOne(d->m_importers()[0]); } } clearImportedParentContexts(); } deleteChildContextsRecursively(); if(!topContext()->deleting() || !topContext()->isOnDisk()) deleteUses(); deleteLocalDeclarations(); //If the top-context is being delete, we don't need to spend time rebuilding the inner structure. //That's expensive, especially when the data is not dynamic. if(!top->deleting() || !top->isOnDisk()) { if (m_dynamicData->m_parentContext) m_dynamicData->m_parentContext->m_dynamicData->removeChildContext(this); } top->m_dynamicData->clearContextIndex(this); Q_ASSERT(d_func()->isDynamic() == (!top->deleting() || !top->isOnDisk() || top->m_dynamicData->isTemporaryContextIndex(m_dynamicData->m_indexInTopContext))); delete m_dynamicData; } QVector< DUContext * > DUContext::childContexts( ) const { ENSURE_CAN_READ return m_dynamicData->m_childContexts; } Declaration* DUContext::owner() const { ENSURE_CAN_READ return d_func()->m_owner.declaration(); } void DUContext::setOwner(Declaration* owner) { ENSURE_CAN_WRITE DUCHAIN_D_DYNAMIC(DUContext); if( owner == d->m_owner.declaration() ) return; Declaration* oldOwner = d->m_owner.declaration(); d->m_owner = owner; //Q_ASSERT(!oldOwner || oldOwner->internalContext() == this); if( oldOwner && oldOwner->internalContext() == this ) oldOwner->setInternalContext(0); //The context set as internal context should always be the last opened context if( owner ) owner->setInternalContext(this); } DUContext* DUContext::parentContext( ) const { //ENSURE_CAN_READ Commented out for performance reasons return m_dynamicData->m_parentContext.data(); } void DUContext::setPropagateDeclarations(bool propagate) { ENSURE_CAN_WRITE DUCHAIN_D_DYNAMIC(DUContext); if(propagate == d->m_propagateDeclarations) return; d->m_propagateDeclarations = propagate; } bool DUContext::isPropagateDeclarations() const { return d_func()->m_propagateDeclarations; } QList DUContext::findLocalDeclarations( const IndexedIdentifier& identifier, const CursorInRevision& position, const TopDUContext* topContext, const AbstractType::Ptr& dataType, SearchFlags flags ) const { ENSURE_CAN_READ DeclarationList ret; findLocalDeclarationsInternal(identifier, position.isValid() ? position : range().end, dataType, ret, topContext ? topContext : this->topContext(), flags); return ret; } QList DUContext::findLocalDeclarations( const Identifier& identifier, const CursorInRevision& position, const TopDUContext* topContext, const AbstractType::Ptr& dataType, SearchFlags flags ) const { return findLocalDeclarations(IndexedIdentifier(identifier), position, topContext, dataType, flags); } namespace { bool contextIsChildOrEqual(const DUContext* childContext, const DUContext* context) { if(childContext == context) return true; if(childContext->parentContext()) return contextIsChildOrEqual(childContext->parentContext(), context); else return false; } struct Checker { Checker(DUContext::SearchFlags flags, const AbstractType::Ptr& dataType, const CursorInRevision & position, DUContext::ContextType ownType) : m_flags(flags) , m_dataType(dataType) , m_position(position) , m_ownType(ownType) { } Declaration* check(Declaration* declaration) const { ///@todo This is C++-specific if (m_ownType != DUContext::Class && m_ownType != DUContext::Template && m_position.isValid() && m_position <= declaration->range().start) { return nullptr; } if (declaration->kind() == Declaration::Alias && !(m_flags & DUContext::DontResolveAliases)) { //Apply alias declarations AliasDeclaration* alias = static_cast(declaration); if (alias->aliasedDeclaration().isValid()) { declaration = alias->aliasedDeclaration().declaration(); } else { qCDebug(LANGUAGE) << "lost aliased declaration"; } } if (declaration->kind() == Declaration::NamespaceAlias && !(m_flags & DUContext::NoFiltering)) { return nullptr; } if ((m_flags & DUContext::OnlyFunctions) && !declaration->isFunctionDeclaration()) { return nullptr; } if (m_dataType && m_dataType->indexed() != declaration->indexedType()) { return nullptr; } return declaration; } DUContext::SearchFlags m_flags; const AbstractType::Ptr m_dataType; const CursorInRevision m_position; DUContext::ContextType m_ownType; }; } void DUContext::findLocalDeclarationsInternal(const Identifier& identifier, const CursorInRevision& position, const AbstractType::Ptr& dataType, DeclarationList& ret, const TopDUContext* source, SearchFlags flags) const { return findLocalDeclarationsInternal(IndexedIdentifier(identifier), position, dataType, ret, source, flags); } void DUContext::findLocalDeclarationsInternal( const IndexedIdentifier& identifier, const CursorInRevision & position, const AbstractType::Ptr& dataType, DeclarationList& ret, const TopDUContext* /*source*/, SearchFlags flags ) const { Checker checker(flags, dataType, position, type()); DUCHAIN_D(DUContext); if (d->m_inSymbolTable && !d->m_scopeIdentifier.isEmpty() && !identifier.isEmpty()) { //This context is in the symbol table, use the symbol-table to speed up the search QualifiedIdentifier id(scopeIdentifier(true) + identifier); TopDUContext* top = topContext(); uint count; const IndexedDeclaration* declarations; PersistentSymbolTable::self().declarations(id, count, declarations); for (uint a = 0; a < count; ++a) { ///@todo Eventually do efficient iteration-free filtering if (declarations[a].topContextIndex() == top->ownIndex()) { Declaration* decl = declarations[a].declaration(); if (decl && contextIsChildOrEqual(decl->context(), this)) { Declaration* checked = checker.check(decl); if (checked) { ret.append(checked); } } } } } else { //Iterate through all declarations DUContextDynamicData::VisibleDeclarationIterator it(m_dynamicData); while (it) { Declaration* declaration = *it; if (declaration && declaration->indexedIdentifier() == identifier) { Declaration* checked = checker.check(declaration); if (checked) ret.append(checked); } ++it; } } } bool DUContext::foundEnough( const DeclarationList& ret, SearchFlags flags ) const { if( !ret.isEmpty() && !(flags & DUContext::NoFiltering)) return true; else return false; } bool DUContext::findDeclarationsInternal( const SearchItem::PtrList & baseIdentifiers, const CursorInRevision & position, const AbstractType::Ptr& dataType, DeclarationList& ret, const TopDUContext* source, SearchFlags flags, uint depth ) const { if (depth > maxParentDepth) { qCDebug(LANGUAGE) << "maximum depth reached in" << scopeIdentifier(true); return false; } DUCHAIN_D(DUContext); if (d->m_contextType != Namespace) { // If we're in a namespace, delay all the searching into the top-context, because only that has the overview to pick the correct declarations. for (int a = 0; a < baseIdentifiers.size(); ++a) { if (!baseIdentifiers[a]->isExplicitlyGlobal && baseIdentifiers[a]->next.isEmpty()) { // It makes no sense searching locally for qualified identifiers findLocalDeclarationsInternal(baseIdentifiers[a]->identifier, position, dataType, ret, source, flags); } } if (foundEnough(ret, flags)) { return true; } } ///Step 1: Apply namespace-aliases and -imports SearchItem::PtrList aliasedIdentifiers; //Because of namespace-imports and aliases, this identifier may need to be searched under multiple names applyAliases(baseIdentifiers, aliasedIdentifiers, position, false, type() != DUContext::Namespace && type() != DUContext::Global); if (d->m_importedContextsSize() != 0) { ///Step 2: Give identifiers that are not marked as explicitly-global to imported contexts(explicitly global ones are treatead in TopDUContext) SearchItem::PtrList nonGlobalIdentifiers; foreach (const SearchItem::Ptr& identifier, aliasedIdentifiers) { if (!identifier->isExplicitlyGlobal) { nonGlobalIdentifiers << identifier; } } if (!nonGlobalIdentifiers.isEmpty()) { const auto& url = this->url(); for(int import = d->m_importedContextsSize()-1; import >= 0; --import ) { if (position.isValid() && d->m_importedContexts()[import].position.isValid() && position < d->m_importedContexts()[import].position) { continue; } DUContext* context = d->m_importedContexts()[import].context(source); if (!context) { continue; } else if (context == this) { qCDebug(LANGUAGE) << "resolved self as import:" << scopeIdentifier(true); continue; } if (!context->findDeclarationsInternal(nonGlobalIdentifiers, url == context->url() ? position : context->range().end, dataType, ret, source, flags | InImportedParentContext, depth+1)) { return false; } } } } if (foundEnough(ret, flags)) { return true; } ///Step 3: Continue search in parent-context if (!(flags & DontSearchInParent) && shouldSearchInParent(flags) && m_dynamicData->m_parentContext) { applyUpwardsAliases(aliasedIdentifiers, source); return m_dynamicData->m_parentContext->findDeclarationsInternal(aliasedIdentifiers, url() == m_dynamicData->m_parentContext->url() ? position : m_dynamicData->m_parentContext->range().end, dataType, ret, source, flags, depth); } return true; } QList< QualifiedIdentifier > DUContext::fullyApplyAliases(const QualifiedIdentifier& id, const TopDUContext* source) const { ENSURE_CAN_READ if(!source) source = topContext(); SearchItem::PtrList identifiers; identifiers << SearchItem::Ptr(new SearchItem(id)); const DUContext* current = this; while(current) { SearchItem::PtrList aliasedIdentifiers; current->applyAliases(identifiers, aliasedIdentifiers, CursorInRevision::invalid(), true, false); current->applyUpwardsAliases(identifiers, source); current = current->parentContext(); } QList ret; - for (const SearchItem::Ptr& item : identifiers) + foreach (const SearchItem::Ptr& item, identifiers) ret += item->toList(); return ret; } QList DUContext::findDeclarations( const QualifiedIdentifier & identifier, const CursorInRevision & position, const AbstractType::Ptr& dataType, const TopDUContext* topContext, SearchFlags flags) const { ENSURE_CAN_READ DeclarationList ret; SearchItem::PtrList identifiers; // optimize: we don't want to allocate the top node always // so create it on stack but ref it so its not deleted by the smart pointer SearchItem item(identifier); item.ref.ref(); identifiers << SearchItem::Ptr(&item); findDeclarationsInternal(identifiers, position.isValid() ? position : range().end, dataType, ret, topContext ? topContext : this->topContext(), flags, 0); return ret; } bool DUContext::imports(const DUContext* origin, const CursorInRevision& /*position*/ ) const { ENSURE_CAN_READ QSet recursionGuard; recursionGuard.reserve(8); return m_dynamicData->imports(origin, topContext(), &recursionGuard); } bool DUContext::addIndirectImport(const DUContext::Import& import) { ENSURE_CAN_WRITE DUCHAIN_D_DYNAMIC(DUContext); for(unsigned int a = 0; a < d->m_importedContextsSize(); ++a) { if(d->m_importedContexts()[a] == import) { d->m_importedContextsList()[a].position = import.position; return true; } } ///Do not sort the imported contexts by their own line-number, it makes no sense. ///Contexts added first, aka template-contexts, should stay in first place, so they are searched first. d->m_importedContextsList().append(import); return false; } void DUContext::addImportedParentContext( DUContext * context, const CursorInRevision& position, bool anonymous, bool /*temporary*/ ) { ENSURE_CAN_WRITE if(context == this) { qCDebug(LANGUAGE) << "Tried to import self"; return; } if(!context) { qCDebug(LANGUAGE) << "Tried to import invalid context"; return; } Import import(context, this, position); if(addIndirectImport(import)) return; if( !anonymous ) { ENSURE_CAN_WRITE_(context) context->m_dynamicData->addImportedChildContext(this); } } void DUContext::removeImportedParentContext( DUContext * context ) { ENSURE_CAN_WRITE DUCHAIN_D_DYNAMIC(DUContext); Import import(context, this, CursorInRevision::invalid()); for(unsigned int a = 0; a < d->m_importedContextsSize(); ++a) { if(d->m_importedContexts()[a] == import) { d->m_importedContextsList().remove(a); break; } } if( !context ) return; context->m_dynamicData->removeImportedChildContext(this); } KDevVarLengthArray DUContext::indexedImporters() const { KDevVarLengthArray ret; if(owner()) ret = Importers::self().importers(owner()->id()); //Add indirect importers to the list FOREACH_FUNCTION(const IndexedDUContext& ctx, d_func()->m_importers) ret.append(ctx); return ret; } QVector DUContext::importers() const { ENSURE_CAN_READ QVector ret; FOREACH_FUNCTION(const IndexedDUContext& ctx, d_func()->m_importers) ret << ctx.context(); if(owner()) { //Add indirect importers to the list KDevVarLengthArray indirect = Importers::self().importers(owner()->id()); - for (const IndexedDUContext& ctx : indirect) { + foreach (const IndexedDUContext ctx, indirect) { ret << ctx.context(); } } return ret; } DUContext * DUContext::findContext( const CursorInRevision& position, DUContext* parent) const { ENSURE_CAN_READ if (!parent) parent = const_cast(this); foreach (DUContext* context, parent->m_dynamicData->m_childContexts) { if (context->range().contains(position)) { DUContext* ret = findContext(position, context); if (!ret) { ret = context; } return ret; } } return 0; } bool DUContext::parentContextOf(DUContext* context) const { if (this == context) return true; foreach (DUContext* child, m_dynamicData->m_childContexts) { if (child->parentContextOf(context)) { return true; } } return false; } QList< QPair > DUContext::allDeclarations(const CursorInRevision& position, const TopDUContext* topContext, bool searchInParents) const { ENSURE_CAN_READ QList< QPair > ret; QHash hadContexts; // Iterate back up the chain mergeDeclarationsInternal(ret, position, hadContexts, topContext ? topContext : this->topContext(), searchInParents); return ret; } QVector DUContext::localDeclarations(const TopDUContext* source) const { ENSURE_CAN_READ // TODO: remove this parameter once we kill old-cpp Q_UNUSED(source); return m_dynamicData->m_localDeclarations; } void DUContext::mergeDeclarationsInternal(QList< QPair >& definitions, const CursorInRevision& position, QHash& hadContexts, const TopDUContext* source, bool searchInParents, int currentDepth) const { ENSURE_CAN_READ if((currentDepth > 300 && currentDepth < 1000) || currentDepth > 1300) { qCDebug(LANGUAGE) << "too much depth"; return; } DUCHAIN_D(DUContext); if(hadContexts.contains(this) && !searchInParents) return; if(!hadContexts.contains(this)) { hadContexts[this] = true; if( (type() == DUContext::Namespace || type() == DUContext::Global) && currentDepth < 1000 ) currentDepth += 1000; { DUContextDynamicData::VisibleDeclarationIterator it(m_dynamicData); while(it) { Declaration* decl = *it; if ( decl && (!position.isValid() || decl->range().start <= position) ) definitions << qMakePair(decl, currentDepth); ++it; } } for(int a = d->m_importedContextsSize()-1; a >= 0; --a) { const Import* import(&d->m_importedContexts()[a]); DUContext* context = import->context(source); while( !context && a > 0 ) { --a; import = &d->m_importedContexts()[a]; context = import->context(source); } if( !context ) break; if(context == this) { qCDebug(LANGUAGE) << "resolved self as import:" << scopeIdentifier(true); continue; } if( position.isValid() && import->position.isValid() && position < import->position ) continue; context->mergeDeclarationsInternal(definitions, CursorInRevision::invalid(), hadContexts, source, searchInParents && context->shouldSearchInParent(InImportedParentContext) && context->parentContext()->type() == DUContext::Helper, currentDepth+1); } } ///Only respect the position if the parent-context is not a class(@todo this is language-dependent) if (parentContext() && searchInParents ) parentContext()->mergeDeclarationsInternal(definitions, parentContext()->type() == DUContext::Class ? parentContext()->range().end : position, hadContexts, source, searchInParents, currentDepth+1); } void DUContext::deleteLocalDeclarations() { ENSURE_CAN_WRITE // It may happen that the deletion of one declaration triggers the deletion of another one // Therefore we copy the list of indexed declarations and work on those. Indexed declarations // will return zero for already deleted declarations. KDevVarLengthArray indexedLocal; if (d_func()->m_localDeclarations()) { indexedLocal.append(d_func()->m_localDeclarations(), d_func()->m_localDeclarationsSize()); } foreach (const LocalIndexedDeclaration& indexed, m_dynamicData->m_localDeclarations) { delete indexed.data(topContext()); } m_dynamicData->m_localDeclarations.clear(); } void DUContext::deleteChildContextsRecursively() { ENSURE_CAN_WRITE // note: don't use qDeleteAll here because child ctx deletion changes m_dynamicData->m_childContexts // also note: foreach iterates on a copy, so this is safe foreach (DUContext* ctx, m_dynamicData->m_childContexts) { delete ctx; } m_dynamicData->m_childContexts.clear(); } QVector DUContext::clearLocalDeclarations( ) { auto copy = m_dynamicData->m_localDeclarations; foreach (Declaration* dec, copy) { dec->setContext(0); } return copy; } QualifiedIdentifier DUContext::scopeIdentifier(bool includeClasses) const { ENSURE_CAN_READ QualifiedIdentifier ret; m_dynamicData->scopeIdentifier(includeClasses, ret); return ret; } bool DUContext::equalScopeIdentifier(const DUContext* rhs) const { ENSURE_CAN_READ const DUContext* left = this; const DUContext* right = rhs; while(left || right) { if(!left || !right) return false; if(!(left->d_func()->m_scopeIdentifier == right->d_func()->m_scopeIdentifier)) return false; left = left->parentContext(); right = right->parentContext(); } return true; } void DUContext::setLocalScopeIdentifier(const QualifiedIdentifier & identifier) { ENSURE_CAN_WRITE bool wasInSymbolTable = inSymbolTable(); setInSymbolTable(false); d_func_dynamic()->m_scopeIdentifier = identifier; setInSymbolTable(wasInSymbolTable); } QualifiedIdentifier DUContext::localScopeIdentifier() const { //ENSURE_CAN_READ Commented out for performance reasons return d_func()->m_scopeIdentifier; } IndexedQualifiedIdentifier DUContext::indexedLocalScopeIdentifier() const { return d_func()->m_scopeIdentifier; } DUContext::ContextType DUContext::type() const { //ENSURE_CAN_READ This is disabled, because type() is called very often while searching, and it costs us performance return d_func()->m_contextType; } void DUContext::setType(ContextType type) { ENSURE_CAN_WRITE d_func_dynamic()->m_contextType = type; } QList DUContext::findDeclarations(const Identifier& identifier, const CursorInRevision& position, const TopDUContext* topContext, SearchFlags flags) const { return findDeclarations(IndexedIdentifier(identifier), position, topContext, flags); } QList DUContext::findDeclarations(const IndexedIdentifier& identifier, const CursorInRevision& position, const TopDUContext* topContext, SearchFlags flags) const { ENSURE_CAN_READ DeclarationList ret; SearchItem::PtrList identifiers; identifiers << SearchItem::Ptr(new SearchItem(false, identifier, SearchItem::PtrList())); findDeclarationsInternal(identifiers, position.isValid() ? position : range().end, AbstractType::Ptr(), ret, topContext ? topContext : this->topContext(), flags, 0); return ret; } void DUContext::deleteUse(int index) { ENSURE_CAN_WRITE DUCHAIN_D_DYNAMIC(DUContext); d->m_usesList().remove(index); } void DUContext::deleteUses() { ENSURE_CAN_WRITE DUCHAIN_D_DYNAMIC(DUContext); d->m_usesList().clear(); } void DUContext::deleteUsesRecursively() { deleteUses(); foreach (DUContext* childContext, m_dynamicData->m_childContexts) { childContext->deleteUsesRecursively(); } } bool DUContext::inDUChain() const { if( d_func()->m_anonymousInParent || !m_dynamicData->m_parentContext) return false; TopDUContext* top = topContext(); return top && top->inDUChain(); } DUContext* DUContext::specialize(const IndexedInstantiationInformation& /*specialization*/, const TopDUContext* topContext, int /*upDistance*/) { if(!topContext) return 0; return this; } CursorInRevision DUContext::importPosition(const DUContext* target) const { ENSURE_CAN_READ DUCHAIN_D(DUContext); Import import(const_cast(target), this, CursorInRevision::invalid()); for(unsigned int a = 0; a < d->m_importedContextsSize(); ++a) if(d->m_importedContexts()[a] == import) return d->m_importedContexts()[a].position; return CursorInRevision::invalid(); } QVector DUContext::importedParentContexts() const { ENSURE_CAN_READ QVector ret; ret.reserve(d_func()->m_importedContextsSize()); FOREACH_FUNCTION(const DUContext::Import& import, d_func()->m_importedContexts) ret << import; return ret; } void DUContext::applyAliases(const SearchItem::PtrList& baseIdentifiers, SearchItem::PtrList& identifiers, const CursorInRevision& position, bool canBeNamespace, bool onlyImports) const { DeclarationList imports; findLocalDeclarationsInternal(globalIndexedImportIdentifier(), position, AbstractType::Ptr(), imports, topContext(), DUContext::NoFiltering); if(imports.isEmpty() && onlyImports) { identifiers = baseIdentifiers; return; } for ( const SearchItem::Ptr& identifier : baseIdentifiers ) { bool addUnmodified = true; if( !identifier->isExplicitlyGlobal ) { if( !imports.isEmpty() ) { //We have namespace-imports. - for ( Declaration* importDecl : imports ) + foreach ( Declaration* importDecl, imports ) { //Search for the identifier with the import-identifier prepended if(dynamic_cast(importDecl)) { NamespaceAliasDeclaration* alias = static_cast(importDecl); identifiers.append( SearchItem::Ptr( new SearchItem( alias->importIdentifier(), identifier ) ) ) ; }else{ qCDebug(LANGUAGE) << "Declaration with namespace alias identifier has the wrong type" << importDecl->url().str() << importDecl->range().castToSimpleRange(); } } } if( !identifier->isEmpty() && (identifier->hasNext() || canBeNamespace) ) { DeclarationList aliases; findLocalDeclarationsInternal(identifier->identifier, position, AbstractType::Ptr(), imports, 0, DUContext::NoFiltering); if(!aliases.isEmpty()) { //The first part of the identifier has been found as a namespace-alias. //In c++, we only need the first alias. However, just to be correct, follow them all for now. - for ( Declaration* aliasDecl : aliases ) + foreach ( Declaration* aliasDecl, aliases ) { if(!dynamic_cast(aliasDecl)) continue; addUnmodified = false; //The un-modified identifier can be ignored, because it will be replaced with the resolved alias NamespaceAliasDeclaration* alias = static_cast(aliasDecl); //Create an identifier where namespace-alias part is replaced with the alias target identifiers.append( SearchItem::Ptr( new SearchItem( alias->importIdentifier(), identifier->next ) ) ) ; } } } } if( addUnmodified ) identifiers.append(identifier); } } void DUContext::applyUpwardsAliases(SearchItem::PtrList& identifiers, const TopDUContext* /*source*/) const { if(type() == Namespace) { if(d_func()->m_scopeIdentifier.isEmpty()) return; //Make sure we search for the items in all namespaces of the same name, by duplicating each one with the namespace-identifier prepended. //We do this by prepending items to the current identifiers that equal the local scope identifier. SearchItem::Ptr newItem( new SearchItem(d_func()->m_scopeIdentifier.identifier()) ); //This will exclude explictly global identifiers newItem->addToEachNode( identifiers ); if(!newItem->next.isEmpty()) { //Prepend the full scope before newItem DUContext* parent = m_dynamicData->m_parentContext.data(); while(parent) { newItem = SearchItem::Ptr( new SearchItem(parent->d_func()->m_scopeIdentifier, newItem) ); parent = parent->m_dynamicData->m_parentContext.data(); } newItem->isExplicitlyGlobal = true; identifiers.insert(0, newItem); } } } bool DUContext::shouldSearchInParent(SearchFlags flags) const { return (parentContext() && parentContext()->type() == DUContext::Helper && (flags & InImportedParentContext)) || !(flags & InImportedParentContext); } const Use* DUContext::uses() const { ENSURE_CAN_READ return d_func()->m_uses(); } bool DUContext::declarationHasUses(Declaration* decl) { return DUChain::uses()->hasUses(decl->id()); } int DUContext::usesCount() const { return d_func()->m_usesSize(); } bool usesRangeLessThan(const Use& left, const Use& right) { return left.m_range.start < right.m_range.start; } int DUContext::createUse(int declarationIndex, const RangeInRevision& range, int insertBefore) { DUCHAIN_D_DYNAMIC(DUContext); ENSURE_CAN_WRITE Use use(range, declarationIndex); if(insertBefore == -1) { //Find position where to insert const unsigned int size = d->m_usesSize(); const Use* uses = d->m_uses(); const Use* lowerBound = std::lower_bound(uses, uses + size, use, usesRangeLessThan); insertBefore = lowerBound - uses; // comment out to test this: /* unsigned int a = 0; for(; a < size && range.start > uses[a].m_range.start; ++a) { } Q_ASSERT(a == insertBefore); */ } d->m_usesList().insert(insertBefore, use); return insertBefore; } void DUContext::changeUseRange(int useIndex, const RangeInRevision& range) { ENSURE_CAN_WRITE d_func_dynamic()->m_usesList()[useIndex].m_range = range; } void DUContext::setUseDeclaration(int useNumber, int declarationIndex) { ENSURE_CAN_WRITE d_func_dynamic()->m_usesList()[useNumber].m_declarationIndex = declarationIndex; } DUContext * DUContext::findContextAt(const CursorInRevision & position, bool includeRightBorder) const { ENSURE_CAN_READ // qCDebug(LANGUAGE) << "searchign" << position << "in:" << scopeIdentifier(true).toString() << range() << includeRightBorder; if (!range().contains(position) && (!includeRightBorder || range().end != position)) { // qCDebug(LANGUAGE) << "mismatch"; return 0; } const auto childContexts = m_dynamicData->m_childContexts; for(int a = childContexts.size() - 1; a >= 0; --a) { if (DUContext* specific = childContexts[a]->findContextAt(position, includeRightBorder)) { return specific; } } return const_cast(this); } Declaration * DUContext::findDeclarationAt(const CursorInRevision & position) const { ENSURE_CAN_READ if (!range().contains(position)) return 0; foreach (Declaration* child, m_dynamicData->m_localDeclarations) { if (child->range().contains(position)) { return child; } } return 0; } DUContext* DUContext::findContextIncluding(const RangeInRevision& range) const { ENSURE_CAN_READ if (!this->range().contains(range)) return 0; foreach (DUContext* child, m_dynamicData->m_childContexts) { if (DUContext* specific = child->findContextIncluding(range)) { return specific; } } return const_cast(this); } int DUContext::findUseAt(const CursorInRevision & position) const { ENSURE_CAN_READ if (!range().contains(position)) return -1; for(unsigned int a = 0; a < d_func()->m_usesSize(); ++a) if (d_func()->m_uses()[a].m_range.contains(position)) return a; return -1; } bool DUContext::inSymbolTable() const { return d_func()->m_inSymbolTable; } void DUContext::setInSymbolTable(bool inSymbolTable) { d_func_dynamic()->m_inSymbolTable = inSymbolTable; } void DUContext::clearImportedParentContexts() { ENSURE_CAN_WRITE DUCHAIN_D_DYNAMIC(DUContext); while( d->m_importedContextsSize() != 0 ) { DUContext* ctx = d->m_importedContexts()[0].context(0, false); if(ctx) ctx->m_dynamicData->removeImportedChildContext(this); d->m_importedContextsList().removeOne(d->m_importedContexts()[0]); } } void DUContext::cleanIfNotEncountered(const QSet& encountered) { ENSURE_CAN_WRITE // It may happen that the deletion of one declaration triggers the deletion of another one // Therefore we copy the list of indexed declarations and work on those. Indexed declarations // will return zero for already deleted declarations. KDevVarLengthArray indexedLocal; if (d_func()->m_localDeclarations()) { indexedLocal.append(d_func()->m_localDeclarations(), d_func()->m_localDeclarationsSize()); } foreach (const LocalIndexedDeclaration& indexed, m_dynamicData->m_localDeclarations) { auto dec = indexed.data(topContext()); if (dec && !encountered.contains(dec) && (!dec->isAutoDeclaration() || !dec->hasUses())) { delete dec; } } foreach (DUContext* childContext, m_dynamicData->m_childContexts) { if (!encountered.contains(childContext)) { delete childContext; } } } TopDUContext* DUContext::topContext() const { return m_dynamicData->m_topContext; } QWidget* DUContext::createNavigationWidget(Declaration* decl, TopDUContext* topContext, const QString& htmlPrefix, const QString& htmlSuffix) const { if (decl) { AbstractNavigationWidget* widget = new AbstractNavigationWidget; AbstractDeclarationNavigationContext* context = new AbstractDeclarationNavigationContext(DeclarationPointer(decl), TopDUContextPointer(topContext)); context->setPrefixSuffix(htmlPrefix, htmlSuffix); widget->setContext(NavigationContextPointer(context)); return widget; } else { return 0; } } QList allUses(DUContext* context, int declarationIndex, bool noEmptyUses) { QList ret; for(int a = 0; a < context->usesCount(); ++a) if(context->uses()[a].m_declarationIndex == declarationIndex) if(!noEmptyUses || !context->uses()[a].m_range.isEmpty()) ret << context->uses()[a].m_range; foreach(DUContext* child, context->childContexts()) ret += allUses(child, declarationIndex, noEmptyUses); return ret; } DUContext::SearchItem::SearchItem(const QualifiedIdentifier& id, const Ptr& nextItem, int start) : isExplicitlyGlobal(start == 0 ? id.explicitlyGlobal() : false) { if(!id.isEmpty()) { if(id.count() > start) identifier = id.indexedAt(start); if(id.count() > start+1) addNext(Ptr( new SearchItem(id, nextItem, start+1) )); else if(nextItem) next.append(nextItem); }else if(nextItem) { ///If there is no prefix, just copy nextItem isExplicitlyGlobal = nextItem->isExplicitlyGlobal; identifier = nextItem->identifier; next = nextItem->next; } } DUContext::SearchItem::SearchItem(const QualifiedIdentifier& id, const PtrList& nextItems, int start) : isExplicitlyGlobal(start == 0 ? id.explicitlyGlobal() : false) { if(id.count() > start) identifier = id.indexedAt(start); if(id.count() > start+1) addNext(Ptr( new SearchItem(id, nextItems, start+1) )); else next = nextItems; } DUContext::SearchItem::SearchItem(bool explicitlyGlobal, const IndexedIdentifier& id, const PtrList& nextItems) : isExplicitlyGlobal(explicitlyGlobal) , identifier(id) , next(nextItems) { } DUContext::SearchItem::SearchItem(bool explicitlyGlobal, const IndexedIdentifier& id, const Ptr& nextItem) : isExplicitlyGlobal(explicitlyGlobal) , identifier(id) { next.append(nextItem); } bool DUContext::SearchItem::match(const QualifiedIdentifier& id, int offset) const { if(id.isEmpty()) { if(identifier.isEmpty() && next.isEmpty()) return true; else return false; } if(id.at(offset) != identifier) //The identifier is different return false; if(offset == id.count()-1) { if(next.isEmpty()) return true; //match else return false; //id is too short } for(int a = 0; a < next.size(); ++a) if(next[a]->match(id, offset+1)) return true; return false; } bool DUContext::SearchItem::isEmpty() const { return identifier.isEmpty(); } bool DUContext::SearchItem::hasNext() const { return !next.isEmpty(); } QList DUContext::SearchItem::toList(const QualifiedIdentifier& prefix) const { QList ret; QualifiedIdentifier id = prefix; if(id.isEmpty()) id.setExplicitlyGlobal(isExplicitlyGlobal); if(!identifier.isEmpty()) id.push(identifier); if(next.isEmpty()) { ret << id; } else { for(int a = 0; a < next.size(); ++a) ret += next[a]->toList(id); } return ret; } void DUContext::SearchItem::addNext(const SearchItem::Ptr& other) { next.append(other); } void DUContext::SearchItem::addToEachNode(const SearchItem::Ptr& other) { if(other->isExplicitlyGlobal) return; next.append(other); for(int a = 0; a < next.size()-1; ++a) next[a]->addToEachNode(other); } void DUContext::SearchItem::addToEachNode(const SearchItem::PtrList& other) { int added = 0; for (const SearchItem::Ptr& o : other) { if(!o->isExplicitlyGlobal) { next.append(o); ++added; } } for(int a = 0; a < next.size()-added; ++a) next[a]->addToEachNode(other); } DUContext::Import::Import(DUContext* _context, const DUContext* importer, const CursorInRevision& _position) : position(_position) { if(_context && _context->owner() && (_context->owner()->specialization().index() || (importer && importer->topContext() != _context->topContext()))) { m_declaration = _context->owner()->id(); }else{ m_context = _context; } } DUContext::Import::Import(const DeclarationId& id, const CursorInRevision& _position) : position(_position) { m_declaration = id; } DUContext* DUContext::Import::context(const TopDUContext* topContext, bool instantiateIfRequired) const { if(m_declaration.isValid()) { Declaration* decl = m_declaration.getDeclaration(topContext, instantiateIfRequired); //This first case rests on the assumption that no context will ever import a function's expression context //More accurately, that no specialized or cross-topContext imports will, but if the former assumption fails the latter will too if (AbstractFunctionDeclaration *functionDecl = dynamic_cast(decl)) { if (functionDecl->internalFunctionContext()) { return functionDecl->internalFunctionContext(); } else { qCWarning(LANGUAGE) << "Import of function declaration without internal function context encountered!"; } } if(decl) return decl->logicalInternalContext(topContext); else return 0; }else{ return m_context.data(); } } bool DUContext::Import::isDirect() const { return m_context.isValid(); } void DUContext::visit(DUChainVisitor& visitor) { ENSURE_CAN_READ visitor.visit(this); foreach (Declaration* decl, m_dynamicData->m_localDeclarations) { visitor.visit(decl); } foreach (DUContext* childContext, m_dynamicData->m_childContexts) { childContext->visit(visitor); } } static bool sortByRange(const DUChainBase* lhs, const DUChainBase* rhs) { return lhs->range() < rhs->range(); } void DUContext::resortLocalDeclarations() { ENSURE_CAN_WRITE std::sort(m_dynamicData->m_localDeclarations.begin(), m_dynamicData->m_localDeclarations.end(), sortByRange); auto top = topContext(); auto& declarations = d_func_dynamic()->m_localDeclarationsList(); std::sort(declarations.begin(), declarations.end(), [top] (const LocalIndexedDeclaration& lhs, const LocalIndexedDeclaration& rhs) { return lhs.data(top)->range() < rhs.data(top)->range(); }); } void DUContext::resortChildContexts() { ENSURE_CAN_WRITE std::sort(m_dynamicData->m_childContexts.begin(), m_dynamicData->m_childContexts.end(), sortByRange); auto top = topContext(); auto& contexts = d_func_dynamic()->m_childContextsList(); std::sort(contexts.begin(), contexts.end(), [top] (const LocalIndexedDUContext& lhs, const LocalIndexedDUContext& rhs) { return lhs.data(top)->range() < rhs.data(top)->range(); }); } } diff --git a/language/duchain/dumpdotgraph.cpp b/language/duchain/dumpdotgraph.cpp index c3492b4c1..597950da8 100644 --- a/language/duchain/dumpdotgraph.cpp +++ b/language/duchain/dumpdotgraph.cpp @@ -1,201 +1,201 @@ /*************************************************************************** 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 "dumpdotgraph.h" #include "ducontext.h" #include "topducontext.h" #include "declaration.h" #include "duchainpointer.h" #include "parsingenvironment.h" #include "identifier.h" #include "functiondefinition.h" namespace KDevelop { QString shortLabel(KDevelop::DUContext* context) { return QStringLiteral("q%1").arg((quint64)context); } QString shortLabel(KDevelop::Declaration* declaration) { return QStringLiteral("q%1").arg((quint64)declaration); } QString rangeToString( const KTextEditor::Range& r ) { return QStringLiteral("%1:%2->%3:%4").arg(r.start().line()).arg(r.start().column()).arg(r.end().line()).arg(r.end().column()); } class DumpDotGraphPrivate { public: QString dotGraphInternal(KDevelop::DUContext* contex, bool isMaster, bool shortened); void addDeclaration(QTextStream& stream, Declaration* decl); QMap m_hadVersions; QMap m_hadObjects; TopDUContext* m_topContext; }; QString DumpDotGraph::dotGraph(KDevelop::DUContext* context, bool shortened ) { d->m_hadObjects.clear(); d->m_hadVersions.clear(); d->m_topContext = context->topContext(); ///@todo maybe get this as a parameter return d->dotGraphInternal(context, true, shortened); } DumpDotGraph::DumpDotGraph() : d(new DumpDotGraphPrivate()) { } DumpDotGraph::~DumpDotGraph() { delete d; } void DumpDotGraphPrivate::addDeclaration(QTextStream& stream, Declaration* dec) { if( m_hadObjects.contains(dec) ) return; m_hadObjects[dec] = true; Declaration* declarationForDefinition = 0; if(dynamic_cast(dec)) declarationForDefinition = static_cast(dec)->declaration(m_topContext); if(!declarationForDefinition) { //Declaration stream << shortLabel(dec) << "[shape=box, label=\"" << dec->toString() << " [" << dec->qualifiedIdentifier().toString() << "]" << " " << rangeToString(dec->range().castToSimpleRange()) << "\"];\n"; stream << shortLabel(dec->context()) << " -> " << shortLabel(dec) << "[color=green];\n"; if( dec->internalContext() ) stream << shortLabel(dec) << " -> " << shortLabel(dec->internalContext()) << "[label=\"internal\", color=blue];\n"; }else{ //Definition stream << shortLabel(dec) << "[shape=regular,color=yellow,label=\"" << declarationForDefinition->toString() << " "<< rangeToString(dec->range().castToSimpleRange()) << "\"];\n"; stream << shortLabel(dec->context()) << " -> " << shortLabel(dec) << ";\n"; stream << shortLabel(dec) << " -> " << shortLabel(declarationForDefinition) << "[label=\"defines\",color=green];\n"; addDeclaration(stream, declarationForDefinition); if( dec->internalContext() ) stream << shortLabel(dec) << " -> " << shortLabel(dec->internalContext()) << "[label=\"internal\", color=blue];\n"; } } QString DumpDotGraphPrivate::dotGraphInternal(KDevelop::DUContext* context, bool isMaster, bool shortened) { if( m_hadObjects.contains(context) ) return QString(); m_hadObjects[context] = true; QTextStream stream; QString ret; stream.setString(&ret, QIODevice::WriteOnly); if( isMaster ) stream << "Digraph chain {\n"; - QString shape = "parallelogram"; + QString shape = QStringLiteral("parallelogram"); //QString shape = "box"; - QString label = "unknown"; + QString label = QStringLiteral("unknown"); if( dynamic_cast(context) ) { TopDUContext* topCtx = static_cast(context); if( topCtx->parsingEnvironmentFile() ) { QUrl url = topCtx->parsingEnvironmentFile()->url().toUrl(); const QString fileName = url.fileName(); QString file = url.toDisplayString(QUrl::PreferLocalFile); if(topCtx->parsingEnvironmentFile() && topCtx->parsingEnvironmentFile()->isProxyContext()) - url.setPath(url.path() + QString::fromLatin1("/_[proxy]_")); + url.setPath(url.path() + QLatin1String("/_[proxy]_")); //Find the context this one is derived from. If there is one, connect it with a line, and shorten the url. if( m_hadVersions.contains(url) ) { stream << shortLabel(context) << " -> " << m_hadVersions[url] << "[color=blue,label=\"version\"];\n"; file = fileName; } else { m_hadVersions[url] = shortLabel(context); } label = file; if( topCtx->importers().count() != 0 ) label += QStringLiteral(" imported by %1").arg(topCtx->importers().count()); } else { - label = "unknown file"; + label = QStringLiteral("unknown file"); } if(topCtx->parsingEnvironmentFile() && topCtx->parsingEnvironmentFile()->isProxyContext()) label = "Proxy-context " + label; }else{ label = /*"context " + */context->localScopeIdentifier().toString(); label += ' ' + rangeToString(context->range().castToSimpleRange()); } //label = QStringLiteral("%1 ").arg((size_t)context) + label; if( isMaster && !dynamic_cast(context) ) { //Also draw contexts that import this one foreach( DUContext* ctx, context->importers() ) stream << dotGraphInternal(ctx, false, true); } foreach (const DUContext::Import &parent, context->importedParentContexts()) { if( parent.context(m_topContext) ) { stream << dotGraphInternal(parent.context(m_topContext), false, true); - QString label = "imports"; + QString label = QStringLiteral("imports"); if( (!dynamic_cast(parent.context(m_topContext)) || !dynamic_cast(context)) && !(parent.context(m_topContext)->url() == context->url()) ) { label += " from " + parent.context(m_topContext)->url().toUrl().fileName() + " to " + context->url().toUrl().fileName(); } stream << shortLabel(context) << " -> " << shortLabel(parent.context(m_topContext)) << "[style=dotted,label=\"" << label << "\"];\n"; } } if( !context->childContexts().isEmpty() ) label += QStringLiteral(", %1 C.").arg(context->childContexts().count()); if( !shortened ) { foreach (DUContext* child, context->childContexts()) { stream << dotGraphInternal(child, false, false); stream << shortLabel(context) << " -> " << shortLabel(child) << "[style=dotted,color=green];\n"; } } if( !context->localDeclarations().isEmpty() ) label += QStringLiteral(", %1 D.").arg(context->localDeclarations().count()); if(!shortened ) { foreach (Declaration* dec, context->localDeclarations()) addDeclaration(stream, dec); } if( context->owner() ) { addDeclaration(stream, context->owner()); } stream << shortLabel(context) << "[shape=" << shape << ",label=\"" << label << "\"" << (isMaster ? QStringLiteral("color=red") : QStringLiteral("color=blue")) << "];\n"; if( isMaster ) stream << "}\n"; return ret; } } diff --git a/language/duchain/functiondefinition.cpp b/language/duchain/functiondefinition.cpp index d62f0aa1e..038a44ec8 100644 --- a/language/duchain/functiondefinition.cpp +++ b/language/duchain/functiondefinition.cpp @@ -1,101 +1,101 @@ /* 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 "functiondefinition.h" #include "duchainregister.h" #include "definitions.h" namespace KDevelop { REGISTER_DUCHAIN_ITEM(FunctionDefinition); FunctionDefinition::FunctionDefinition(FunctionDefinitionData& data) : FunctionDeclaration(data) { } FunctionDefinition::FunctionDefinition(const RangeInRevision& range, DUContext* context) : FunctionDeclaration(*new FunctionDefinitionData, range) { d_func_dynamic()->setClassId(this); setDeclarationIsDefinition(true); if( context ) setContext( context ); } FunctionDefinition::FunctionDefinition(const FunctionDefinition& rhs) : FunctionDeclaration(*new FunctionDefinitionData(*rhs.d_func())) { } FunctionDefinition::~FunctionDefinition() { if(!topContext()->isOnDisk()) DUChain::definitions()->removeDefinition(d_func()->m_declaration, this); } Declaration* FunctionDefinition::declaration(const TopDUContext* topContext) const { ENSURE_CAN_READ KDevVarLengthArray declarations = d_func()->m_declaration.getDeclarations(topContext ? topContext : this->topContext()); - for (Declaration* decl : declarations) { + foreach (Declaration* decl, declarations) { if(!dynamic_cast(decl)) return decl; } return 0; } bool FunctionDefinition::hasDeclaration() const { return d_func()->m_declaration.isValid(); } void FunctionDefinition::setDeclaration(Declaration* declaration) { ENSURE_CAN_WRITE if(declaration) { DUChain::definitions()->addDefinition(declaration->id(), this); d_func_dynamic()->m_declaration = declaration->id(); }else{ if(d_func()->m_declaration.isValid()) { DUChain::definitions()->removeDefinition(d_func()->m_declaration, this); d_func_dynamic()->m_declaration = DeclarationId(); } } } FunctionDefinition* FunctionDefinition::definition(const Declaration* decl) { ENSURE_CHAIN_READ_LOCKED if (!decl) { return nullptr; } KDevVarLengthArray allDefinitions = DUChain::definitions()->definitions(decl->id()); - for (const IndexedDeclaration& decl : allDefinitions) { + foreach (const IndexedDeclaration decl, allDefinitions) { if(decl.data()) ///@todo Find better ways of deciding which definition to use return dynamic_cast(decl.data()); } return 0; } Declaration* FunctionDefinition::clonePrivate() const { return new FunctionDefinition(*new FunctionDefinitionData(*d_func())); } } diff --git a/language/duchain/identifier.cpp b/language/duchain/identifier.cpp index 2e40eb4ee..37cf77250 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) : 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) : 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("Qualified Identifier Repository", 1, [] () -> AbstractRepositoryManager* { return &identifierRepository(); }); + 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 = 0; 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 = 0; } 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() const { QString ret = identifier().str(); if (templateIdentifiersCount()) { ret.append("< "); for (uint i = 0; i < templateIdentifiersCount(); ++i) { ret.append(templateIdentifier(i).toString()); 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() 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(); }else{ FOREACH_FUNCTION_STATIC(const IndexedIdentifier& index, dd->identifiers) ret << index.identifier().toString(); } return ret; } QString QualifiedIdentifier::toString(bool ignoreExplicitlyGlobal) const { const QString doubleColon = QStringLiteral("::"); QString ret; if( !ignoreExplicitlyGlobal && 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(); } }else{ FOREACH_FUNCTION_STATIC(const IndexedIdentifier& index, dd->identifiers) { if( !first ) ret += doubleColon; else first = false; ret += index.identifier().toString(); } } 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(bool ignoreExplicitlyGlobal) const { QString ret; if(isConstant()) - ret += "const "; + ret += QLatin1String("const "); if(isVolatile()) - ret += "volatile "; + ret += QLatin1String("volatile "); ret += m_identifier.identifier().toString(ignoreExplicitlyGlobal); for(int a = 0; a < pointerDepth(); ++a) { ret += '*'; if( isConstPointer(a) ) - ret += "const"; + ret += QLatin1String("const"); } if(isRValue()) - ret += "&&"; + 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/importers.cpp b/language/duchain/importers.cpp index 3c84eff15..d33a49fb3 100644 --- a/language/duchain/importers.cpp +++ b/language/duchain/importers.cpp @@ -1,197 +1,197 @@ /* 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 "importers.h" #include #include #include "declarationid.h" #include "duchainpointer.h" #include "serialization/itemrepository.h" #include "topducontext.h" namespace KDevelop { DEFINE_LIST_MEMBER_HASH(ImportersItem, importers, IndexedDUContext) class ImportersItem { public: ImportersItem() { initializeAppendedLists(); } ImportersItem(const ImportersItem& rhs, bool dynamic = true) : declaration(rhs.declaration) { initializeAppendedLists(dynamic); copyListsFrom(rhs); } ~ImportersItem() { 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 declaration.hash(); } unsigned int itemSize() const { return dynamicSize(); } uint classSize() const { return sizeof(ImportersItem); } DeclarationId declaration; START_APPENDED_LISTS(ImportersItem); APPENDED_LIST_FIRST(ImportersItem, IndexedDUContext, importers); END_APPENDED_LISTS(ImportersItem, importers); }; class ImportersRequestItem { public: ImportersRequestItem(const ImportersItem& 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(ImportersItem* item) const { new (item) ImportersItem(m_item, false); } static void destroy(ImportersItem* item, KDevelop::AbstractItemRepository&) { item->~ImportersItem(); } static bool persistent(const ImportersItem* /*item*/) { return true; } bool equals(const ImportersItem* item) const { return m_item.declaration == item->declaration; } const ImportersItem& m_item; }; class ImportersPrivate { public: - ImportersPrivate() : m_importers("Importer Map") { + ImportersPrivate() : m_importers(QStringLiteral("Importer Map")) { } //Maps declaration-ids to Importers ItemRepository m_importers; }; Importers::Importers() : d(new ImportersPrivate()) { } Importers::~Importers() { delete d; } void Importers::addImporter(const DeclarationId& id, const IndexedDUContext& use) { ImportersItem item; item.declaration = id; item.importersList().append(use); ImportersRequestItem request(item); uint index = d->m_importers.findIndex(item); if(index) { //Check whether the item is already in the mapped list, else copy the list into the new created item const ImportersItem* oldItem = d->m_importers.itemFromIndex(index); for(unsigned int a = 0; a < oldItem->importersSize(); ++a) { if(oldItem->importers()[a] == use) return; //Already there item.importersList().append(oldItem->importers()[a]); } d->m_importers.deleteItem(index); } //This inserts the changed item d->m_importers.index(request); } void Importers::removeImporter(const DeclarationId& id, const IndexedDUContext& use) { ImportersItem item; item.declaration = id; ImportersRequestItem request(item); uint index = d->m_importers.findIndex(item); if(index) { //Check whether the item is already in the mapped list, else copy the list into the new created item const ImportersItem* oldItem = d->m_importers.itemFromIndex(index); for(unsigned int a = 0; a < oldItem->importersSize(); ++a) if(!(oldItem->importers()[a] == use)) item.importersList().append(oldItem->importers()[a]); d->m_importers.deleteItem(index); Q_ASSERT(d->m_importers.findIndex(item) == 0); //This inserts the changed item if(item.importersSize() != 0) d->m_importers.index(request); } } KDevVarLengthArray Importers::importers(const DeclarationId& id) const { KDevVarLengthArray ret; ImportersItem item; item.declaration = id; ImportersRequestItem request(item); uint index = d->m_importers.findIndex(item); if(index) { const ImportersItem* repositoryItem = d->m_importers.itemFromIndex(index); FOREACH_FUNCTION(const IndexedDUContext& decl, repositoryItem->importers) ret.append(decl); } return ret; } Importers& Importers::self() { static Importers globalImporters; return globalImporters; } } diff --git a/language/duchain/instantiationinformation.cpp b/language/duchain/instantiationinformation.cpp index 1ce3d0b83..0cc7ab41b 100644 --- a/language/duchain/instantiationinformation.cpp +++ b/language/duchain/instantiationinformation.cpp @@ -1,191 +1,191 @@ /* This file is part of KDevelop Copyright 2007-2009 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "instantiationinformation.h" #include "identifier.h" #include "serialization/itemrepository.h" #include "types/typeutils.h" #include #include "types/typealiastype.h" #include "types/typerepository.h" namespace KDevelop { DEFINE_LIST_MEMBER_HASH(InstantiationInformation, templateParameters, IndexedType) QualifiedIdentifier InstantiationInformation::applyToIdentifier(const QualifiedIdentifier& id) const { QualifiedIdentifier ret; if(id.count() > 1) { ret = id; ret.pop(); if(previousInstantiationInformation.index()) ret = previousInstantiationInformation.information().applyToIdentifier(ret); } Identifier lastId(id.last()); KDevVarLengthArray oldTemplateIdentifiers; for(uint a = 0; a < lastId.templateIdentifiersCount(); ++a) oldTemplateIdentifiers.append(lastId.templateIdentifier(a)); lastId.clearTemplateIdentifiers(); for(uint a = 0; a < templateParametersSize(); ++a) { if(templateParameters()[a].abstractType()) { lastId.appendTemplateIdentifier(IndexedTypeIdentifier(templateParameters()[a].abstractType()->toString(), true)); }else{ lastId.appendTemplateIdentifier((uint) oldTemplateIdentifiers.size() > a ? oldTemplateIdentifiers[a] : IndexedTypeIdentifier()); } } for(int a = templateParametersSize(); a < oldTemplateIdentifiers.size(); ++a) lastId.appendTemplateIdentifier(oldTemplateIdentifiers[a]); ret.push(lastId); return ret; } void InstantiationInformation::addTemplateParameter(KDevelop::AbstractType::Ptr type) { templateParametersList().append(type->indexed()); } QString InstantiationInformation::toString(bool local) const { QString ret; if(previousInstantiationInformation.index() && !local) ret = previousInstantiationInformation.information().toString() + "::"; ret += '<'; for(uint a = 0; a < templateParametersSize(); ++a) { if(a) - ret += ", "; + ret += QLatin1String(", "); if(templateParameters()[a].abstractType()) ret += templateParameters()[a].abstractType()->toString(); } ret += '>'; return ret; } InstantiationInformation::InstantiationInformation() : m_refCount(0) { initializeAppendedLists(); } InstantiationInformation::InstantiationInformation(const InstantiationInformation& rhs, bool dynamic) : previousInstantiationInformation(rhs.previousInstantiationInformation), m_refCount(0) { initializeAppendedLists(dynamic); copyListsFrom(rhs); } InstantiationInformation::~InstantiationInformation() { freeAppendedLists(); } InstantiationInformation& InstantiationInformation::operator=(const InstantiationInformation& rhs) { previousInstantiationInformation = rhs.previousInstantiationInformation; copyListsFrom(rhs); return *this; } bool InstantiationInformation::operator==(const InstantiationInformation& rhs) const { if(!(previousInstantiationInformation == rhs.previousInstantiationInformation)) return false; return listsEqual(rhs); } uint InstantiationInformation::hash() const { KDevHash kdevhash; FOREACH_FUNCTION(const IndexedType& param, templateParameters) { kdevhash << param.hash(); } return kdevhash << previousInstantiationInformation.index(); } AbstractRepositoryManager* returnTypeRepository() { return typeRepositoryManager(); } static KDevelop::RepositoryManager > >& instantiationInformationRepository() { - static KDevelop::RepositoryManager > > instantiationInformationRepositoryObject("Instantiation Information Repository", 1, &returnTypeRepository); + static KDevelop::RepositoryManager > > instantiationInformationRepositoryObject(QStringLiteral("Instantiation Information Repository"), 1, &returnTypeRepository); return instantiationInformationRepositoryObject; } uint standardInstantiationInformationIndex() { static uint idx = instantiationInformationRepository()->index( InstantiationInformation() ); return idx; } void initInstantiationInformationRepository() { standardInstantiationInformationIndex(); } IndexedInstantiationInformation::IndexedInstantiationInformation() : m_index(0) { } IndexedInstantiationInformation::IndexedInstantiationInformation(uint index) : m_index(index) { if(m_index == standardInstantiationInformationIndex()) m_index = 0; if(m_index && shouldDoDUChainReferenceCounting(this)) { QMutexLocker lock(instantiationInformationRepository()->mutex()); increase(instantiationInformationRepository()->dynamicItemFromIndexSimple(m_index)->m_refCount, m_index); } } IndexedInstantiationInformation::IndexedInstantiationInformation(const IndexedInstantiationInformation& rhs) : m_index(rhs.m_index) { if(m_index && shouldDoDUChainReferenceCounting(this)) { QMutexLocker lock(instantiationInformationRepository()->mutex()); increase(instantiationInformationRepository()->dynamicItemFromIndexSimple(m_index)->m_refCount, m_index); } } IndexedInstantiationInformation& IndexedInstantiationInformation::operator=(const IndexedInstantiationInformation& rhs) { if(m_index && shouldDoDUChainReferenceCounting(this)) { QMutexLocker lock(instantiationInformationRepository()->mutex()); decrease(instantiationInformationRepository()->dynamicItemFromIndexSimple(m_index)->m_refCount, m_index); } m_index = rhs.m_index; if(m_index && shouldDoDUChainReferenceCounting(this)) { QMutexLocker lock(instantiationInformationRepository()->mutex()); increase(instantiationInformationRepository()->dynamicItemFromIndexSimple(m_index)->m_refCount, m_index); } return *this; } IndexedInstantiationInformation::~IndexedInstantiationInformation() { if(m_index && shouldDoDUChainReferenceCounting(this)) { QMutexLocker lock(instantiationInformationRepository()->mutex()); decrease(instantiationInformationRepository()->dynamicItemFromIndexSimple(m_index)->m_refCount, m_index); } } bool IndexedInstantiationInformation::isValid() const { return m_index; } const InstantiationInformation& IndexedInstantiationInformation::information() const { return *instantiationInformationRepository()->itemFromIndex(m_index ? m_index : standardInstantiationInformationIndex()); } IndexedInstantiationInformation InstantiationInformation::indexed() const { return IndexedInstantiationInformation(instantiationInformationRepository()->index(*this)); } } diff --git a/language/duchain/navigation/abstractdeclarationnavigationcontext.cpp b/language/duchain/navigation/abstractdeclarationnavigationcontext.cpp index c2983d8c7..f1bae7273 100644 --- a/language/duchain/navigation/abstractdeclarationnavigationcontext.cpp +++ b/language/duchain/navigation/abstractdeclarationnavigationcontext.cpp @@ -1,754 +1,754 @@ /* Copyright 2007 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "abstractdeclarationnavigationcontext.h" #include #include #include "../functiondeclaration.h" #include "../functiondefinition.h" #include "../classfunctiondeclaration.h" #include "../namespacealiasdeclaration.h" #include "../forwarddeclaration.h" #include "../types/enumeratortype.h" #include "../types/enumerationtype.h" #include "../types/functiontype.h" #include "../duchainutils.h" #include "../types/pointertype.h" #include "../types/referencetype.h" #include "../types/typeutils.h" #include "../types/typesystem.h" #include "../persistentsymboltable.h" #include "util/debug.h" #include #include #include #include #include namespace KDevelop { AbstractDeclarationNavigationContext::AbstractDeclarationNavigationContext( DeclarationPointer decl, TopDUContextPointer topContext, AbstractNavigationContext* previousContext) : AbstractNavigationContext((topContext ? topContext : TopDUContextPointer(decl ? decl->topContext() : 0)), previousContext), m_declaration(decl), m_fullBackwardSearch(false) { //Jump from definition to declaration if possible FunctionDefinition* definition = dynamic_cast(m_declaration.data()); if(definition && definition->declaration()) m_declaration = DeclarationPointer(definition->declaration()); } QString AbstractDeclarationNavigationContext::name() const { if(m_declaration.data()) return prettyQualifiedIdentifier(m_declaration).toString(); else return declarationName(m_declaration); } QString AbstractDeclarationNavigationContext::html(bool shorten) { clear(); m_shorten = shorten; modifyHtml() += "

" + fontSizePrefix(shorten); addExternalHtml(m_prefix); if(!m_declaration.data()) { modifyHtml() += i18n("
lost declaration
"); return currentHtml(); } if( m_previousContext ) { QString link = createLink( m_previousContext->name(), m_previousContext->name(), NavigationAction(m_previousContext) ); modifyHtml() += navigationHighlight(i18n("Back to %1
", link)); } QExplicitlySharedDataPointer doc; if( !shorten ) { doc = ICore::self()->documentationController()->documentationForDeclaration(m_declaration.data()); const AbstractFunctionDeclaration* function = dynamic_cast(m_declaration.data()); if( function ) { htmlFunction(); } else if( m_declaration->isTypeAlias() || m_declaration->type() || m_declaration->kind() == Declaration::Instance ) { if( m_declaration->isTypeAlias() ) - modifyHtml() += importantHighlight("typedef "); + modifyHtml() += importantHighlight(QStringLiteral("typedef ")); if(m_declaration->type()) modifyHtml() += i18n("enumerator "); AbstractType::Ptr useType = m_declaration->abstractType(); if(m_declaration->isTypeAlias()) { //Do not show the own name as type of typedefs if(useType.cast()) useType = useType.cast()->type(); } eventuallyMakeTypeLinks( useType ); modifyHtml() += ' ' + identifierHighlight(declarationName(m_declaration).toHtmlEscaped(), m_declaration); if(auto integralType = m_declaration->type()) { const QString plainValue = integralType->valueAsString(); if (!plainValue.isEmpty()) { modifyHtml() += QStringLiteral(" = ") + plainValue; } } - modifyHtml() += "
"; + modifyHtml() += QStringLiteral("
"); }else{ if( m_declaration->kind() == Declaration::Type && m_declaration->abstractType().cast() ) { htmlClass(); } if ( m_declaration->kind() == Declaration::Namespace ) { modifyHtml() += i18n("namespace %1 ", identifierHighlight(m_declaration->qualifiedIdentifier().toString().toHtmlEscaped(), m_declaration)); } else if ( m_declaration->kind() == Declaration::NamespaceAlias ) { modifyHtml() += identifierHighlight(declarationName(m_declaration).toHtmlEscaped(), m_declaration); } if(m_declaration->type()) { EnumerationType::Ptr enumeration = m_declaration->type(); modifyHtml() += i18n("enumeration %1 ", identifierHighlight(m_declaration->identifier().toString().toHtmlEscaped(), m_declaration)); } if(m_declaration->isForwardDeclaration()) { ForwardDeclaration* forwardDec = static_cast(m_declaration.data()); Declaration* resolved = forwardDec->resolve(m_topContext.data()); if(resolved) { modifyHtml() += i18n("(resolved forward-declaration: "); makeLink(resolved->identifier().toString(), DeclarationPointer(resolved), NavigationAction::NavigateDeclaration ); modifyHtml() += i18n(") "); }else{ modifyHtml() += i18n("(unresolved forward-declaration) "); QualifiedIdentifier id = forwardDec->qualifiedIdentifier(); uint count; const IndexedDeclaration* decls; PersistentSymbolTable::self().declarations(id, count, decls); for(uint a = 0; a < count; ++a) { if(decls[a].isValid() && !decls[a].data()->isForwardDeclaration()) { - modifyHtml() += "
"; + modifyHtml() += QStringLiteral("
"); makeLink(i18n("possible resolution from"), DeclarationPointer(decls[a].data()), NavigationAction::NavigateDeclaration); modifyHtml() += ' ' + decls[a].data()->url().str(); } } } } - modifyHtml() += "
"; + modifyHtml() += QStringLiteral("
"); } }else{ AbstractType::Ptr showType = m_declaration->abstractType(); if(showType && showType.cast()) { showType = showType.cast()->returnType(); if(showType) modifyHtml() += labelHighlight(i18n("Returns: ")); }else if(showType) { modifyHtml() += labelHighlight(i18n("Type: ")); } if(showType) { eventuallyMakeTypeLinks(showType); - modifyHtml() += " "; + modifyHtml() += QStringLiteral(" "); } } QualifiedIdentifier identifier = m_declaration->qualifiedIdentifier(); if( identifier.count() > 1 ) { if( m_declaration->context() && m_declaration->context()->owner() ) { Declaration* decl = m_declaration->context()->owner(); FunctionDefinition* definition = dynamic_cast(decl); if(definition && definition->declaration()) decl = definition->declaration(); if(decl->abstractType().cast()) modifyHtml() += labelHighlight(i18n("Enum: ")); else modifyHtml() += labelHighlight(i18n("Container: ")); makeLink( declarationName(DeclarationPointer(decl)), DeclarationPointer(decl), NavigationAction::NavigateDeclaration ); - modifyHtml() += " "; + modifyHtml() += QStringLiteral(" "); } else { QualifiedIdentifier parent = identifier; parent.pop(); modifyHtml() += labelHighlight(i18n("Scope: %1 ", typeHighlight(parent.toString().toHtmlEscaped()))); } } if( shorten && !m_declaration->comment().isEmpty() ) { QString comment = QString::fromUtf8(m_declaration->comment()); if( comment.length() > 60 ) { comment.truncate(60); - comment += "..."; + comment += QLatin1String("..."); } - comment.replace('\n', " "); - comment.replace("
", " "); - comment.replace("
", " "); + comment.replace('\n', QLatin1String(" ")); + comment.replace(QLatin1String("
"), QLatin1String(" ")); + comment.replace(QLatin1String("
"), QLatin1String(" ")); modifyHtml() += commentHighlight(comment.toHtmlEscaped()) + " "; } QString access = stringFromAccess(m_declaration); if( !access.isEmpty() ) modifyHtml() += labelHighlight(i18n("Access: %1 ", propertyHighlight(access.toHtmlEscaped()))); ///@todo Enumerations QString detailsHtml; QStringList details = declarationDetails(m_declaration); if( !details.isEmpty() ) { bool first = true; foreach( const QString &str, details ) { if( !first ) - detailsHtml += ", "; + detailsHtml += QLatin1String(", "); first = false; detailsHtml += propertyHighlight(str); } } QString kind = declarationKind(m_declaration); if( !kind.isEmpty() ) { if( !detailsHtml.isEmpty() ) modifyHtml() += labelHighlight(i18n("Kind: %1 %2 ", importantHighlight(kind.toHtmlEscaped()), detailsHtml)); else modifyHtml() += labelHighlight(i18n("Kind: %1 ", importantHighlight(kind.toHtmlEscaped()))); } if (m_declaration->isDeprecated()) { modifyHtml() += labelHighlight(i18n("Status: %1 ", propertyHighlight(i18n("Deprecated")))); } - modifyHtml() += "
"; + modifyHtml() += QStringLiteral("
"); if(!shorten) htmlAdditionalNavigation(); if( !shorten ) { if(dynamic_cast(m_declaration.data())) modifyHtml() += labelHighlight(i18n( "Def.: " )); else modifyHtml() += labelHighlight(i18n( "Decl.: " )); makeLink( QStringLiteral("%1 :%2").arg( m_declaration->url().toUrl().fileName() ).arg( m_declaration->rangeInCurrentRevision().start().line()+1 ), m_declaration, NavigationAction::JumpToSource ); - modifyHtml() += " "; + modifyHtml() += QLatin1Char(' '); //modifyHtml() += "
"; if(!dynamic_cast(m_declaration.data())) { if( FunctionDefinition* definition = FunctionDefinition::definition(m_declaration.data()) ) { modifyHtml() += labelHighlight(i18n( " Def.: " )); makeLink( QStringLiteral("%1 :%2").arg( definition->url().toUrl().fileName() ).arg( definition->rangeInCurrentRevision().start().line()+1 ), DeclarationPointer(definition), NavigationAction::JumpToSource ); } } if( FunctionDefinition* definition = dynamic_cast(m_declaration.data()) ) { if(definition->declaration()) { modifyHtml() += labelHighlight(i18n( " Decl.: " )); makeLink( QStringLiteral("%1 :%2").arg( definition->declaration()->url().toUrl().fileName() ).arg( definition->declaration()->rangeInCurrentRevision().start().line()+1 ), DeclarationPointer(definition->declaration()), NavigationAction::JumpToSource ); } } - modifyHtml() += " "; //The action name _must_ stay "show_uses", since that is also used from outside - makeLink(i18n("Show uses"), "show_uses", NavigationAction(m_declaration, NavigationAction::NavigateUses)); + modifyHtml() += QLatin1Char(' '); //The action name _must_ stay "show_uses", since that is also used from outside + makeLink(i18n("Show uses"), QStringLiteral("show_uses"), NavigationAction(m_declaration, NavigationAction::NavigateUses)); } QByteArray declarationComment = m_declaration->comment(); if( !shorten && (!declarationComment.isEmpty() || doc) ) { - modifyHtml() += "
"; + modifyHtml() += QStringLiteral("
"); if(doc) { QString comment = doc->description(); connect(doc.data(), &IDocumentation::descriptionChanged, this, &AbstractDeclarationNavigationContext::contentsChanged); if(!comment.isEmpty()) { modifyHtml() += "
" + commentHighlight(comment); } } QString comment = QString::fromUtf8(declarationComment); if(!comment.isEmpty()) { // if the first paragraph does not contain a tag, we assume that this is a plain-text comment if (!Qt::mightBeRichText(comment)) { // still might contain extra html tags for line breaks (this is the case for doxygen-style comments sometimes) // let's protect them from being removed completely - comment.replace(QRegExp("
"), "\n"); + comment.replace(QRegExp("
"), QLatin1Char('\n')); comment = comment.toHtmlEscaped(); - comment.replace('\n', "
"); //Replicate newlines in html + comment.replace('\n', QLatin1String("
")); //Replicate newlines in html } modifyHtml() += commentHighlight(comment); - modifyHtml() += "
"; + modifyHtml() += QStringLiteral("
"); } } if(!shorten && doc) { modifyHtml() += "
" + i18n("Show documentation for "); makeLink(prettyQualifiedName(m_declaration), m_declaration, NavigationAction::ShowDocumentation); } //modifyHtml() += "
"; addExternalHtml(m_suffix); modifyHtml() += fontSizeSuffix(shorten) + "

"; return currentHtml(); } AbstractType::Ptr AbstractDeclarationNavigationContext::typeToShow(AbstractType::Ptr type) { return type; } void AbstractDeclarationNavigationContext::htmlFunction() { const AbstractFunctionDeclaration* function = dynamic_cast(m_declaration.data()); Q_ASSERT(function); const ClassFunctionDeclaration* classFunDecl = dynamic_cast(m_declaration.data()); const FunctionType::Ptr type = m_declaration->abstractType().cast(); if( !type ) { - modifyHtml() += errorHighlight("Invalid type
"); + modifyHtml() += errorHighlight(QStringLiteral("Invalid type
")); return; } if( !classFunDecl || (!classFunDecl->isConstructor() && !classFunDecl->isDestructor()) ) { // only print return type for global functions and non-ctor/dtor methods eventuallyMakeTypeLinks( type->returnType() ); } modifyHtml() += ' ' + identifierHighlight(prettyIdentifier(m_declaration).toString().toHtmlEscaped(), m_declaration); if( type->indexedArgumentsSize() == 0 ) { - modifyHtml() += "()"; + modifyHtml() += QStringLiteral("()"); } else { - modifyHtml() += "( "; + modifyHtml() += QStringLiteral("( "); bool first = true; int firstDefaultParam = type->indexedArgumentsSize() - function->defaultParametersSize(); int currentArgNum = 0; QVector decls; if (DUContext* argumentContext = DUChainUtils::getArgumentContext(m_declaration.data())) { decls = argumentContext->localDeclarations(m_topContext.data()); } foreach(const AbstractType::Ptr& argType, type->arguments()) { if( !first ) - modifyHtml() += ", "; + modifyHtml() += QStringLiteral(", "); first = false; eventuallyMakeTypeLinks( argType ); if (currentArgNum < decls.size()) { modifyHtml() += ' ' + identifierHighlight(decls[currentArgNum]->identifier().toString().toHtmlEscaped(), m_declaration); } if( currentArgNum >= firstDefaultParam ) modifyHtml() += " = " + function->defaultParameters()[ currentArgNum - firstDefaultParam ].str().toHtmlEscaped(); ++currentArgNum; } - modifyHtml() += " )"; + modifyHtml() += QStringLiteral(" )"); } - modifyHtml() += "
"; + modifyHtml() += QStringLiteral("
"); } Identifier AbstractDeclarationNavigationContext::prettyIdentifier(DeclarationPointer decl) const { Identifier ret; QualifiedIdentifier q = prettyQualifiedIdentifier(decl); if(!q.isEmpty()) ret = q.last(); return ret; } QualifiedIdentifier AbstractDeclarationNavigationContext::prettyQualifiedIdentifier(DeclarationPointer decl) const { if(decl) return decl->qualifiedIdentifier(); else return QualifiedIdentifier(); } QString AbstractDeclarationNavigationContext::prettyQualifiedName(DeclarationPointer decl) const { const auto qid = prettyQualifiedIdentifier(decl); if (qid.isEmpty()) { return i18nc("An anonymous declaration (class, function, etc.)", ""); } return qid.toString(); } void AbstractDeclarationNavigationContext::htmlAdditionalNavigation() { ///Check if the function overrides or hides another one const ClassFunctionDeclaration* classFunDecl = dynamic_cast(m_declaration.data()); if(classFunDecl) { Declaration* overridden = DUChainUtils::getOverridden(m_declaration.data()); if(overridden) { modifyHtml() += i18n("Overrides a "); makeLink(i18n("function"), QStringLiteral("jump_to_overridden"), NavigationAction(DeclarationPointer(overridden), NavigationAction::NavigateDeclaration)); modifyHtml() += i18n(" from "); makeLink(prettyQualifiedName(DeclarationPointer(overridden->context()->owner())), QStringLiteral("jump_to_overridden_container"), NavigationAction(DeclarationPointer(overridden->context()->owner()), NavigationAction::NavigateDeclaration)); - modifyHtml() += "
"; + modifyHtml() += QStringLiteral("
"); }else{ //Check if this declarations hides other declarations QList decls; foreach(const DUContext::Import &import, m_declaration->context()->importedParentContexts()) if(import.context(m_topContext.data())) decls += import.context(m_topContext.data())->findDeclarations(QualifiedIdentifier(m_declaration->identifier()), CursorInRevision::invalid(), AbstractType::Ptr(), m_topContext.data(), DUContext::DontSearchInParent); uint num = 0; foreach(Declaration* decl, decls) { modifyHtml() += i18n("Hides a "); makeLink(i18n("function"), QStringLiteral("jump_to_hide_%1").arg(num), NavigationAction(DeclarationPointer(decl), NavigationAction::NavigateDeclaration)); modifyHtml() += i18n(" from "); makeLink(prettyQualifiedName(DeclarationPointer(decl->context()->owner())), QStringLiteral("jump_to_hide_container_%1").arg(num), NavigationAction(DeclarationPointer(decl->context()->owner()), NavigationAction::NavigateDeclaration)); - modifyHtml() += "
"; + modifyHtml() += QStringLiteral("
"); ++num; } } ///Show all places where this function is overridden if(classFunDecl->isVirtual()) { Declaration* classDecl = m_declaration->context()->owner(); if(classDecl) { uint maxAllowedSteps = m_fullBackwardSearch ? (uint)-1 : 10; QList overriders = DUChainUtils::getOverriders(classDecl, classFunDecl, maxAllowedSteps); if(!overriders.isEmpty()) { modifyHtml() += i18n("Overridden in "); bool first = true; foreach(Declaration* overrider, overriders) { if(!first) - modifyHtml() += ", "; + modifyHtml() += QStringLiteral(", "); first = false; const auto owner = DeclarationPointer(overrider->context()->owner()); const QString name = prettyQualifiedName(owner); makeLink(name, name, NavigationAction(DeclarationPointer(overrider), NavigationAction::NavigateDeclaration)); } - modifyHtml() += "
"; + modifyHtml() += QStringLiteral("
"); } if(maxAllowedSteps == 0) createFullBackwardSearchLink(overriders.isEmpty() ? i18n("Overriders possible, show all") : i18n("More overriders possible, show all")); } } } ///Show all classes that inherit this one uint maxAllowedSteps = m_fullBackwardSearch ? (uint)-1 : 10; QList inheriters = DUChainUtils::getInheriters(m_declaration.data(), maxAllowedSteps); if(!inheriters.isEmpty()) { modifyHtml() += i18n("Inherited by "); bool first = true; foreach(Declaration* importer, inheriters) { if(!first) - modifyHtml() += ", "; + modifyHtml() += QStringLiteral(", "); first = false; const QString importerName = prettyQualifiedName(DeclarationPointer(importer)); makeLink(importerName, importerName, NavigationAction(DeclarationPointer(importer), NavigationAction::NavigateDeclaration)); } - modifyHtml() += "
"; + modifyHtml() += QStringLiteral("
"); } if(maxAllowedSteps == 0) createFullBackwardSearchLink(inheriters.isEmpty() ? i18n("Inheriters possible, show all") : i18n("More inheriters possible, show all")); } void AbstractDeclarationNavigationContext::createFullBackwardSearchLink(QString string) { - makeLink(string, "m_fullBackwardSearch=true", NavigationAction("m_fullBackwardSearch=true")); - modifyHtml() += "
"; + makeLink(string, QStringLiteral("m_fullBackwardSearch=true"), NavigationAction(QStringLiteral("m_fullBackwardSearch=true"))); + modifyHtml() += QStringLiteral("
"); } NavigationContextPointer AbstractDeclarationNavigationContext::executeKeyAction( QString key ) { - if(key == "m_fullBackwardSearch=true") { + if(key == QLatin1String("m_fullBackwardSearch=true")) { m_fullBackwardSearch = true; clear(); } return NavigationContextPointer(this); } void AbstractDeclarationNavigationContext::htmlClass() { StructureType::Ptr klass = m_declaration->abstractType().cast(); Q_ASSERT(klass); ClassDeclaration* classDecl = dynamic_cast(klass->declaration(m_topContext.data())); if(classDecl) { switch ( classDecl->classType() ) { case ClassDeclarationData::Class: - modifyHtml() += "class "; + modifyHtml() += QStringLiteral("class "); break; case ClassDeclarationData::Struct: - modifyHtml() += "struct "; + modifyHtml() += QStringLiteral("struct "); break; case ClassDeclarationData::Union: - modifyHtml() += "union "; + modifyHtml() += QStringLiteral("union "); break; case ClassDeclarationData::Interface: - modifyHtml() += "interface "; + modifyHtml() += QStringLiteral("interface "); break; case ClassDeclarationData::Trait: - modifyHtml() += "trait "; + modifyHtml() += QStringLiteral("trait "); break; default: - modifyHtml() += " "; + modifyHtml() += QStringLiteral(" "); break; } eventuallyMakeTypeLinks( klass.cast() ); FOREACH_FUNCTION( const BaseClassInstance& base, classDecl->baseClasses ) { modifyHtml() += ", " + stringFromAccess(base.access) + " " + (base.virtualInheritance ? QStringLiteral("virtual") : QString()) + " "; eventuallyMakeTypeLinks(base.baseClass.abstractType()); } } else { /// @todo How can we get here? and should this really be a class? - modifyHtml() += "class "; + modifyHtml() += QStringLiteral("class "); eventuallyMakeTypeLinks( klass.cast() ); } - modifyHtml() += " "; + modifyHtml() += QLatin1Char(' '); } void AbstractDeclarationNavigationContext::htmlIdentifiedType(AbstractType::Ptr type, const IdentifiedType* idType) { Q_ASSERT(type); Q_ASSERT(idType); if( Declaration* decl = idType->declaration(m_topContext.data()) ) { //Remove the last template-identifiers, because we create those directly QualifiedIdentifier id = prettyQualifiedIdentifier(DeclarationPointer(decl)); Identifier lastId = id.last(); id.pop(); lastId.clearTemplateIdentifiers(); id.push(lastId); if(decl->context() && decl->context()->owner()) { //Also create full type-links for the context around AbstractType::Ptr contextType = decl->context()->owner()->abstractType(); IdentifiedType* contextIdType = dynamic_cast(contextType.data()); if(contextIdType && !contextIdType->equals(idType)) { //Create full type information for the context if(!id.isEmpty()) id = id.mid(id.count()-1); htmlIdentifiedType(contextType, contextIdType); modifyHtml() += QStringLiteral("::").toHtmlEscaped(); } } //We leave out the * and & reference and pointer signs, those are added to the end makeLink(id.toString() , DeclarationPointer(idType->declaration(m_topContext.data())), NavigationAction::NavigateDeclaration ); } else { qCDebug(LANGUAGE) << "could not resolve declaration:" << idType->declarationId().isDirect() << idType->qualifiedIdentifier().toString() << "in top-context" << m_topContext->url().str(); modifyHtml() += typeHighlight(type->toString().toHtmlEscaped()); } } void AbstractDeclarationNavigationContext::eventuallyMakeTypeLinks( AbstractType::Ptr type ) { type = typeToShow(type); if( !type ) { modifyHtml() += typeHighlight(QStringLiteral("").toHtmlEscaped()); return; } AbstractType::Ptr target = TypeUtils::targetTypeKeepAliases( type, m_topContext.data() ); const IdentifiedType* idType = dynamic_cast( target.data() ); qCDebug(LANGUAGE) << "making type-links for" << type->toString(); if( idType && idType->declaration(m_topContext.data()) ) { ///@todo This is C++ specific, move into subclass if(target->modifiers() & AbstractType::ConstModifier) - modifyHtml() += typeHighlight("const "); + modifyHtml() += typeHighlight(QStringLiteral("const ")); htmlIdentifiedType(target, idType); //We need to exchange the target type, else template-parameters may confuse this SimpleTypeExchanger exchangeTarget(target, AbstractType::Ptr()); AbstractType::Ptr exchanged = exchangeTarget.exchange(type); if(exchanged) { QString typeSuffixString = exchanged->toString(); QRegExp suffixExp("\\&|\\*"); int suffixPos = typeSuffixString.indexOf(suffixExp); if(suffixPos != -1) modifyHtml() += typeHighlight(typeSuffixString.mid(suffixPos)); } } else { if(idType) { qCDebug(LANGUAGE) << "identified type could not be resolved:" << idType->qualifiedIdentifier() << idType->declarationId().isValid() << idType->declarationId().isDirect(); } modifyHtml() += typeHighlight(type->toString().toHtmlEscaped()); } } DeclarationPointer AbstractDeclarationNavigationContext::declaration() const { return m_declaration; } QString AbstractDeclarationNavigationContext::identifierHighlight(const QString& identifier, const DeclarationPointer& decl) const { QString ret = nameHighlight(identifier); if (!decl) { return ret; } if (decl->isDeprecated()) { ret = QStringLiteral("") + ret + QStringLiteral(""); } return ret; } QString AbstractDeclarationNavigationContext::stringFromAccess(Declaration::AccessPolicy access) { switch(access) { case Declaration::Private: - return "private"; + return QStringLiteral("private"); case Declaration::Protected: - return "protected"; + return QStringLiteral("protected"); case Declaration::Public: - return "public"; + return QStringLiteral("public"); default: break; } - return ""; + return QString(); } QString AbstractDeclarationNavigationContext::stringFromAccess(DeclarationPointer decl) { const ClassMemberDeclaration* memberDecl = dynamic_cast(decl.data()); if( memberDecl ) { return stringFromAccess(memberDecl->accessPolicy()); } return QString(); } QString AbstractDeclarationNavigationContext::declarationName( DeclarationPointer decl ) const { if( NamespaceAliasDeclaration* alias = dynamic_cast(decl.data()) ) { if( alias->identifier().isEmpty() ) return "using namespace " + alias->importIdentifier().toString(); else return "namespace " + alias->identifier().toString() + " = " + alias->importIdentifier().toString(); } if( !decl ) return i18nc("A declaration that is unknown", "Unknown"); else return prettyIdentifier(decl).toString(); } QStringList AbstractDeclarationNavigationContext::declarationDetails(DeclarationPointer decl) { QStringList details; const AbstractFunctionDeclaration* function = dynamic_cast(decl.data()); const ClassMemberDeclaration* memberDecl = dynamic_cast(decl.data()); if( memberDecl ) { if( memberDecl->isMutable() ) - details << "mutable"; + details << QStringLiteral("mutable"); if( memberDecl->isRegister() ) - details << "register"; + details << QStringLiteral("register"); if( memberDecl->isStatic() ) - details << "static"; + details << QStringLiteral("static"); if( memberDecl->isAuto() ) - details << "auto"; + details << QStringLiteral("auto"); if( memberDecl->isExtern() ) - details << "extern"; + details << QStringLiteral("extern"); if( memberDecl->isFriend() ) - details << "friend"; + details << QStringLiteral("friend"); } if( decl->isDefinition() ) details << i18nc("tells if a declaration is defining the variable's value", "definition"); if( decl->isExplicitlyDeleted() ) - details << "deleted"; + details << QStringLiteral("deleted"); if( memberDecl && memberDecl->isForwardDeclaration() ) details << i18nc("as in c++ forward declaration", "forward"); AbstractType::Ptr t(decl->abstractType()); if( t ) { if( t->modifiers() & AbstractType::ConstModifier ) details << i18nc("a variable that won't change, const", "constant"); if( t->modifiers() & AbstractType::VolatileModifier ) - details << "volatile"; + details << QStringLiteral("volatile"); } if( function ) { if( function->isInline() ) - details << "inline"; + details << QStringLiteral("inline"); if( function->isExplicit() ) - details << "explicit"; + details << QStringLiteral("explicit"); if( function->isVirtual() ) - details << "virtual"; + details << QStringLiteral("virtual"); const ClassFunctionDeclaration* classFunDecl = dynamic_cast(decl.data()); if( classFunDecl ) { if( classFunDecl->isSignal() ) - details << "signal"; + details << QStringLiteral("signal"); if( classFunDecl->isSlot() ) - details << "slot"; + details << QStringLiteral("slot"); if( classFunDecl->isConstructor() ) - details << "constructor"; + details << QStringLiteral("constructor"); if( classFunDecl->isDestructor() ) - details << "destructor"; + details << QStringLiteral("destructor"); if( classFunDecl->isConversionFunction() ) - details << "conversion-function"; + details << QStringLiteral("conversion-function"); if( classFunDecl->isAbstract() ) - details << "abstract"; + details << QStringLiteral("abstract"); } } return details; } } diff --git a/language/duchain/navigation/abstractdeclarationnavigationcontext.h b/language/duchain/navigation/abstractdeclarationnavigationcontext.h index 2356416f4..a63e6ff37 100644 --- a/language/duchain/navigation/abstractdeclarationnavigationcontext.h +++ b/language/duchain/navigation/abstractdeclarationnavigationcontext.h @@ -1,94 +1,95 @@ /* 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_ABSTRACTDECLARATIONNAVIGATIONCONTEXT_H #define KDEVPLATFORM_ABSTRACTDECLARATIONNAVIGATIONCONTEXT_H #include "abstractnavigationcontext.h" #include "../declaration.h" #include "../duchainpointer.h" #include "../types/abstracttype.h" namespace KDevelop { class IdentifiedType; class Identifier; class QualifiedIdentifier; class KDEVPLATFORMLANGUAGE_EXPORT AbstractDeclarationNavigationContext : public AbstractNavigationContext { + Q_OBJECT public: AbstractDeclarationNavigationContext( DeclarationPointer decl, KDevelop::TopDUContextPointer topContext, AbstractNavigationContext* previousContext = 0 ); virtual QString name() const override; DeclarationPointer declaration() const; ///Execute an action. For example "show_uses" shows the uses of the declaration. ///Returns the context pointer for the new state. virtual NavigationContextPointer executeKeyAction(QString key) override; protected: virtual QString html(bool shorten = false) override; DeclarationPointer m_declaration; ///Should returns a stripped version of the declarations qualified identifier, with all implicit/redundant parts removed virtual QualifiedIdentifier prettyQualifiedIdentifier(DeclarationPointer decl) const; ///Returns a stripped version of the declarations identifier, using prettyQualifiedIdentifier Identifier prettyIdentifier(DeclarationPointer decl) const; /// @return String version of the qualified identifier of @p decl, '' on an invalid QID QString prettyQualifiedName(DeclarationPointer decl) const; /** * Return a rich-text version of the identifier @p identifier representing the declaration @p decl * * @note In case @p declaration is deprecated, the resulting string will get a special formatting */ QString identifierHighlight(const QString& identifier, const DeclarationPointer& decl) const; static QString stringFromAccess(Declaration::AccessPolicy access); static QString stringFromAccess(DeclarationPointer decl); QString declarationName( DeclarationPointer decl ) const; static QStringList declarationDetails(DeclarationPointer decl); ///This can be used for example to resolve typedefs within the type. ///All types that are visualized in the navigation-context are/should be mangled through this. ///The default-implementation returns the original type. virtual AbstractType::Ptr typeToShow(AbstractType::Ptr type); ///Print the function-signature in a way that return-type and argument can be jumped to virtual void htmlFunction(); ///Navigation for additional less important links, like what function was overloaded etc. virtual void htmlAdditionalNavigation(); virtual void htmlClass(); virtual void htmlIdentifiedType(AbstractType::Ptr type, const IdentifiedType* idType); ///Creates and registers a link for the given type that jumps to its declaration and to the template-argument declarations virtual void eventuallyMakeTypeLinks( KDevelop::AbstractType::Ptr type ); ///Creates a link that triggers a recomputation of this context with m_fullBackwardSearch set to true void createFullBackwardSearchLink(QString string); bool m_fullBackwardSearch; }; } #endif diff --git a/language/duchain/navigation/abstractincludenavigationcontext.cpp b/language/duchain/navigation/abstractincludenavigationcontext.cpp index 836d7a735..0ef646a26 100644 --- a/language/duchain/navigation/abstractincludenavigationcontext.cpp +++ b/language/duchain/navigation/abstractincludenavigationcontext.cpp @@ -1,176 +1,176 @@ /* Copyright 2007 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "abstractincludenavigationcontext.h" #include #include #include #include #include namespace KDevelop { AbstractIncludeNavigationContext::AbstractIncludeNavigationContext(const IncludeItem& item, TopDUContextPointer topContext, const ParsingEnvironmentType& type) : AbstractNavigationContext(topContext), m_type(type), m_item(item) {} TopDUContext* pickContextWithData(QList duchains, uint maxDepth, const ParsingEnvironmentType& type, bool forcePick = true) { TopDUContext* duchain = 0; foreach(TopDUContext* ctx, duchains) { if(!ctx->parsingEnvironmentFile() || ctx->parsingEnvironmentFile()->type() != type) continue; if(ctx->childContexts().count() != 0 && (duchain == 0 || ctx->childContexts().count() > duchain->childContexts().count())) { duchain = ctx; } if(ctx->localDeclarations().count() != 0 && (duchain == 0 || ctx->localDeclarations().count() > duchain->localDeclarations().count())) { duchain = ctx; } } if(!duchain && maxDepth != 0) { if(maxDepth != 0) { foreach(TopDUContext* ctx, duchains) { QList children; foreach(const DUContext::Import &import, ctx->importedParentContexts()) if(import.context(0)) children << import.context(0)->topContext(); duchain = pickContextWithData(children, maxDepth-1, type, false); if(duchain) break; } } } if(!duchain && !duchains.isEmpty() && forcePick) duchain = duchains.first(); return duchain; } QString AbstractIncludeNavigationContext::html(bool shorten) { clear(); modifyHtml() += "

" + fontSizePrefix(shorten); addExternalHtml(m_prefix); QUrl u = m_item.url(); NavigationAction action(u, KTextEditor::Cursor(0,0)); makeLink(u.toDisplayString(QUrl::PreferLocalFile), u.toString(), action); - modifyHtml() += "
"; + modifyHtml() += QStringLiteral("
"); QList duchains = DUChain::self()->chainsForDocument(u); //Pick the one duchain for this document that has the most child-contexts/declarations. //This prevents picking a context that is empty due to header-guards. TopDUContext* duchain = pickContextWithData(duchains, 2, m_type); if(duchain) { getFileInfo(duchain); if(!shorten) { modifyHtml() += labelHighlight(i18n("Declarations:")) + "
"; bool first = true; QList decs; addDeclarationsFromContext(duchain, first, decs); } }else if(duchains.isEmpty()) { modifyHtml() += i18n("not parsed yet"); } addExternalHtml(m_suffix); modifyHtml() += fontSizeSuffix(shorten) + "

"; return currentHtml(); } void AbstractIncludeNavigationContext::getFileInfo(TopDUContext* duchain) { modifyHtml() += QStringLiteral("%1: %2 %3: %4").arg(labelHighlight(i18nc("Files included into this file", "Includes"))).arg(duchain->importedParentContexts().count()).arg(labelHighlight(i18nc("Count of files this file was included into", "Included by"))).arg(duchain->importers().count()); - modifyHtml() += "
"; + modifyHtml() += QStringLiteral("
"); } QString AbstractIncludeNavigationContext::name() const { return m_item.name; } bool AbstractIncludeNavigationContext::filterDeclaration(Declaration* /*decl*/) { return true; } void AbstractIncludeNavigationContext::addDeclarationsFromContext(KDevelop::DUContext* ctx, bool& first, QList< IdentifierPair > &addedDeclarations, const QString& indent ) { //modifyHtml() += indent + ctx->localScopeIdentifier().toString() + "{
"; QVector children = ctx->childContexts(); QVector declarations = ctx->localDeclarations(); QVector::const_iterator childIterator = children.constBegin(); QVector::const_iterator declarationIterator = declarations.constBegin(); while(childIterator != children.constEnd() || declarationIterator != declarations.constEnd()) { //Show declarations/contexts in the order they appear in the file int currentDeclarationLine = -1; int currentContextLine = -1; if(declarationIterator != declarations.constEnd()) currentDeclarationLine = (*declarationIterator)->rangeInCurrentRevision().start().line(); if(childIterator != children.constEnd()) currentDeclarationLine = (*childIterator)->rangeInCurrentRevision().start().line(); if((currentDeclarationLine <= currentContextLine || currentContextLine == -1 || childIterator == children.constEnd()) && declarationIterator != declarations.constEnd() ) { IdentifierPair id = qMakePair(static_cast((*declarationIterator)->kind()), (*declarationIterator)->qualifiedIdentifier().index()); if(!addedDeclarations.contains(id) && filterDeclaration(*declarationIterator) ) { //Show the declaration if(!first) modifyHtml() += QStringLiteral(", "); else first = false; modifyHtml() += QString(indent + declarationKind(DeclarationPointer(*declarationIterator)) + " ").toHtmlEscaped(); makeLink((*declarationIterator)->qualifiedIdentifier().toString(), DeclarationPointer(*declarationIterator), NavigationAction::NavigateDeclaration); addedDeclarations << id; } ++declarationIterator; } else { //Eventually Recurse into the context if((*childIterator)->type() == DUContext::Global || (*childIterator)->type() == DUContext::Namespace /*|| (*childIterator)->type() == DUContext::Class*/) addDeclarationsFromContext(*childIterator, first, addedDeclarations, indent + ' '); ++childIterator; } } //modifyHtml() += "}
"; } } diff --git a/language/duchain/navigation/abstractincludenavigationcontext.h b/language/duchain/navigation/abstractincludenavigationcontext.h index ffd84e083..b5effb943 100644 --- a/language/duchain/navigation/abstractincludenavigationcontext.h +++ b/language/duchain/navigation/abstractincludenavigationcontext.h @@ -1,79 +1,80 @@ /* 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_ABSTRACTINCLUDENAVIGATIONCONTEXT_H #define KDEVPLATFORM_ABSTRACTINCLUDENAVIGATIONCONTEXT_H #include "abstractnavigationcontext.h" #include "../../util/includeitem.h" #include "../../duchain/parsingenvironment.h" #include namespace KDevelop { /** * Abstract navigation context for file includes. * * Example usage: * \code * namespace LANG { * class IncludeNavigationContext : public AbstractIncludeNavigationContext * { * public: * IncludeNavigationContext(const IncludeItem& item, TopDuContextPointer topContext) * : AbstractIncludeNavigationContext(item, topContext, KDevelop::LANGParsingEnvironment) {} * protected: * virtual void getFileInfo(KDevelop::TopDUContext* duchain) * { * // write language dependent stuff via modifyHtml() * } * }; * } * \endcode */ class KDEVPLATFORMLANGUAGE_EXPORT AbstractIncludeNavigationContext : public AbstractNavigationContext { + Q_OBJECT public: AbstractIncludeNavigationContext(const IncludeItem& item, TopDUContextPointer topContext, const ParsingEnvironmentType& type); virtual QString html(bool shorten) override; virtual QString name() const override; protected: /// Overwrite this to add language dependent information for a given file. /// By default only "included by" and "includes" /// NOTE: You should always append a newline (
) if you write anything. virtual void getFileInfo(KDevelop::TopDUContext* duchain); ///Should return true if this declaration should be shown, and false if not ///The duchain is locked when this is called virtual bool filterDeclaration(Declaration* decl); private: /// Only environments with this type will be considered ParsingEnvironmentType m_type; ///@param first must initially be true typedef QPair IdentifierPair; void addDeclarationsFromContext(KDevelop::DUContext* ctx, bool& first, QList& addedDeclarations, const QString& indent = {} ); IncludeItem m_item; }; } #endif // KDEVPLATFORM_ABSTRACTINCLUDENAVIGATIONCONTEXT_H diff --git a/language/duchain/navigation/abstractnavigationcontext.cpp b/language/duchain/navigation/abstractnavigationcontext.cpp index 905ac97b0..5c5e55e30 100644 --- a/language/duchain/navigation/abstractnavigationcontext.cpp +++ b/language/duchain/navigation/abstractnavigationcontext.cpp @@ -1,488 +1,488 @@ /* Copyright 2007 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "abstractnavigationcontext.h" #include #include #include "abstractdeclarationnavigationcontext.h" #include "abstractnavigationwidget.h" #include "usesnavigationcontext.h" #include "../../../interfaces/icore.h" #include "../../../interfaces/idocumentcontroller.h" #include "../functiondeclaration.h" #include "../namespacealiasdeclaration.h" #include "../types/functiontype.h" #include "../types/structuretype.h" #include "util/debug.h" #include #include #include namespace KDevelop { void AbstractNavigationContext::setTopContext(KDevelop::TopDUContextPointer context) { m_topContext = context; } KDevelop::TopDUContextPointer AbstractNavigationContext::topContext() const { return m_topContext; } AbstractNavigationContext::AbstractNavigationContext( KDevelop::TopDUContextPointer topContext, AbstractNavigationContext* previousContext) : m_selectedLink(0), m_shorten(false), m_linkCount(-1), m_currentPositionLine(0), m_previousContext(previousContext), m_topContext(topContext) { } void AbstractNavigationContext::addExternalHtml( const QString& text ) { int lastPos = 0; int pos = 0; - QString fileMark = "KDEV_FILE_LINK{"; + QString fileMark = QStringLiteral("KDEV_FILE_LINK{"); while( pos < text.length() && (pos = text.indexOf( fileMark, pos)) != -1 ) { modifyHtml() += text.mid(lastPos, pos-lastPos); pos += fileMark.length(); if( pos != text.length() ) { int fileEnd = text.indexOf('}', pos); if( fileEnd != -1 ) { QString file = text.mid( pos, fileEnd - pos ); pos = fileEnd + 1; const QUrl url = QUrl::fromUserInput(file); makeLink( url.fileName(), file, NavigationAction( url, KTextEditor::Cursor() ) ); } } lastPos = pos; } modifyHtml() += text.mid(lastPos, text.length()-lastPos); } void AbstractNavigationContext::makeLink( const QString& name, DeclarationPointer declaration, NavigationAction::Type actionType ) { NavigationAction action( declaration, actionType ); QString targetId = QString::number((quint64)declaration.data() * actionType); makeLink(name, targetId, action); } QString AbstractNavigationContext::createLink(const QString& name, QString targetId, const NavigationAction& action) { if(m_shorten) { //Do not create links in shortened mode, it's only for viewing return typeHighlight(name.toHtmlEscaped()); } m_links[ targetId ] = action; m_intLinks[ m_linkCount ] = action; m_linkLines[ m_linkCount ] = m_currentLine; if(m_currentPositionLine == m_currentLine) { m_currentPositionLine = -1; m_selectedLink = m_linkCount; } QString str = name.toHtmlEscaped(); if( m_linkCount == m_selectedLink ) str = "" + str + ""; QString ret = "" + str + ""; if( m_selectedLink == m_linkCount ) m_selectedLinkAction = action; ++m_linkCount; return ret; } void AbstractNavigationContext::makeLink( const QString& name, QString targetId, const NavigationAction& action) { modifyHtml() += createLink(name, targetId, action); } void AbstractNavigationContext::clear() { m_linkCount = 0; m_currentLine = 0; m_currentText.clear(); m_links.clear(); m_intLinks.clear(); m_linkLines.clear(); } NavigationContextPointer AbstractNavigationContext::executeLink (QString link) { if(!m_links.contains(link)) return NavigationContextPointer(this); return execute(m_links[link]); } NavigationContextPointer AbstractNavigationContext::executeKeyAction(QString key) { Q_UNUSED(key); return NavigationContextPointer(this); } NavigationContextPointer AbstractNavigationContext::execute(const NavigationAction& action) { if(action.targetContext) return NavigationContextPointer(action.targetContext); if(action.type == NavigationAction::ExecuteKey) return executeKeyAction(action.key); if( !action.decl && (action.type != NavigationAction::JumpToSource || action.document.isEmpty()) ) { qCDebug(LANGUAGE) << "Navigation-action has invalid declaration" << endl; return NavigationContextPointer(this); } qRegisterMetaType("KTextEditor::Cursor"); switch( action.type ) { case NavigationAction::ExecuteKey: break; case NavigationAction::None: qCDebug(LANGUAGE) << "Tried to execute an invalid action in navigation-widget" << endl; break; case NavigationAction::NavigateDeclaration: { AbstractDeclarationNavigationContext* ctx = dynamic_cast(m_previousContext); if( ctx && ctx->declaration() == action.decl ) return NavigationContextPointer( m_previousContext ); return AbstractNavigationContext::registerChild(action.decl); } break; case NavigationAction::NavigateUses: { IContextBrowser* browser = ICore::self()->pluginController()->extensionForPlugin(); if (browser) { browser->showUses(action.decl); return NavigationContextPointer(this); } // fall-through } case NavigationAction::ShowUses: return registerChild(new UsesNavigationContext(action.decl.data(), this)); case NavigationAction::JumpToSource: { QUrl doc = action.document; KTextEditor::Cursor cursor = action.cursor; { DUChainReadLocker lock(DUChain::lock()); if(action.decl) { if(doc.isEmpty()) { doc = action.decl->url().toUrl(); /* if(action.decl->internalContext()) cursor = action.decl->internalContext()->range().start() + KTextEditor::Cursor(0, 1); else*/ cursor = action.decl->rangeInCurrentRevision().start(); } action.decl->activateSpecialization(); } } //This is used to execute the slot delayed in the event-loop, so crashes are avoided QMetaObject::invokeMethod( ICore::self()->documentController(), "openDocument", Qt::QueuedConnection, Q_ARG(QUrl, doc), Q_ARG(KTextEditor::Cursor, cursor) ); break; } case NavigationAction::ShowDocumentation: { auto doc = ICore::self()->documentationController()->documentationForDeclaration(action.decl.data()); ICore::self()->documentationController()->showDocumentation(doc); } break; } return NavigationContextPointer( this ); } void AbstractNavigationContext::setPreviousContext(KDevelop::AbstractNavigationContext* previous) { m_previousContext = previous; } NavigationContextPointer AbstractNavigationContext::registerChild( AbstractNavigationContext* context ) { m_children << NavigationContextPointer(context); return m_children.last(); } NavigationContextPointer AbstractNavigationContext::registerChild(DeclarationPointer declaration) { //We create a navigation-widget here, and steal its context.. evil ;) QScopedPointer navigationWidget(declaration->context()->createNavigationWidget(declaration.data())); if (AbstractNavigationWidget* abstractNavigationWidget = dynamic_cast(navigationWidget.data()) ) { NavigationContextPointer ret = abstractNavigationWidget->context(); ret->setPreviousContext(this); m_children << ret; return ret; } else { return NavigationContextPointer(this); } } const int lineJump = 3; void AbstractNavigationContext::down() { //Make sure link-count is valid if( m_linkCount == -1 ) html(); int fromLine = m_currentPositionLine; if(m_selectedLink >= 0 && m_selectedLink < m_linkCount) { if(fromLine == -1) fromLine = m_linkLines[m_selectedLink]; for(int newSelectedLink = m_selectedLink+1; newSelectedLink < m_linkCount; ++newSelectedLink) { if(m_linkLines[newSelectedLink] > fromLine && m_linkLines[newSelectedLink] - fromLine <= lineJump) { m_selectedLink = newSelectedLink; m_currentPositionLine = -1; return; } } } if(fromLine == -1) fromLine = 0; m_currentPositionLine = fromLine + lineJump; if(m_currentPositionLine > m_currentLine) m_currentPositionLine = m_currentLine; } void AbstractNavigationContext::up() { //Make sure link-count is valid if( m_linkCount == -1 ) html(); int fromLine = m_currentPositionLine; if(m_selectedLink >= 0 && m_selectedLink < m_linkCount) { if(fromLine == -1) fromLine = m_linkLines[m_selectedLink]; for(int newSelectedLink = m_selectedLink-1; newSelectedLink >= 0; --newSelectedLink) { if(m_linkLines[newSelectedLink] < fromLine && fromLine - m_linkLines[newSelectedLink] <= lineJump) { m_selectedLink = newSelectedLink; m_currentPositionLine = -1; return; } } } if(fromLine == -1) fromLine = m_currentLine; m_currentPositionLine = fromLine - lineJump; if(m_currentPositionLine < 0) m_currentPositionLine = 0; } void AbstractNavigationContext::nextLink() { //Make sure link-count is valid if( m_linkCount == -1 ) html(); m_currentPositionLine = -1; if( m_linkCount > 0 ) m_selectedLink = (m_selectedLink+1) % m_linkCount; } void AbstractNavigationContext::previousLink() { //Make sure link-count is valid if( m_linkCount == -1 ) html(); m_currentPositionLine = -1; if( m_linkCount > 0 ) { --m_selectedLink; if( m_selectedLink < 0 ) m_selectedLink += m_linkCount; } Q_ASSERT(m_selectedLink >= 0); } void AbstractNavigationContext::setPrefixSuffix( const QString& prefix, const QString& suffix ) { m_prefix = prefix; m_suffix = suffix; } NavigationContextPointer AbstractNavigationContext::back() { if(m_previousContext) return NavigationContextPointer(m_previousContext); else return NavigationContextPointer(this); } NavigationContextPointer AbstractNavigationContext::accept() { if( m_selectedLink >= 0 && m_selectedLink < m_linkCount ) { NavigationAction action = m_intLinks[m_selectedLink]; return execute(action); } return NavigationContextPointer(this); } NavigationContextPointer AbstractNavigationContext::accept(IndexedDeclaration decl) { if(decl.data()) { NavigationAction action(DeclarationPointer(decl.data()), NavigationAction::NavigateDeclaration); return execute(action); }else{ return NavigationContextPointer(this); } } NavigationContextPointer AbstractNavigationContext::acceptLink(const QString& link) { if( !m_links.contains(link) ) { qCDebug(LANGUAGE) << "Executed unregistered link " << link << endl; return NavigationContextPointer(this); } return execute(m_links[link]); } NavigationAction AbstractNavigationContext::currentAction() const { return m_selectedLinkAction; } QString AbstractNavigationContext::declarationKind(DeclarationPointer decl) { const AbstractFunctionDeclaration* function = dynamic_cast(decl.data()); QString kind; if( decl->isTypeAlias() ) kind = i18n("Typedef"); else if( decl->kind() == Declaration::Type ) { if( decl->type() ) kind = i18n("Class"); } else if( decl->kind() == Declaration::Instance ) { kind = i18n("Variable"); } else if ( decl->kind() == Declaration::Namespace ) { kind = i18n("Namespace"); } if( NamespaceAliasDeclaration* alias = dynamic_cast(decl.data()) ) { if( alias->identifier().isEmpty() ) kind = i18n("Namespace import"); else kind = i18n("Namespace alias"); } if(function) kind = i18n("Function"); if( decl->isForwardDeclaration() ) kind = i18n("Forward Declaration"); return kind; } QString AbstractNavigationContext::html(bool shorten) { m_shorten = shorten; return QString(); } bool AbstractNavigationContext::alreadyComputed() const { return !m_currentText.isEmpty(); } bool AbstractNavigationContext::isWidgetMaximized() const { return true; } QWidget* AbstractNavigationContext::widget() const { return 0; } ///Splits the string by the given regular expression, but keeps the split-matches at the end of each line static QStringList splitAndKeep(QString str, QRegExp regExp) { QStringList ret; int place = regExp.indexIn(str); while(place != -1) { ret << str.left(place + regExp.matchedLength()); str = str.mid(place + regExp.matchedLength()); place = regExp.indexIn(str); } ret << str; return ret; } void AbstractNavigationContext::addHtml(QString html) { QRegExp newLineRegExp("
|
"); foreach(const QString& line, splitAndKeep(html, newLineRegExp)) { m_currentText += line; if(line.indexOf(newLineRegExp) != -1) { ++m_currentLine; if(m_currentLine == m_currentPositionLine) { m_currentText += QStringLiteral(" <-> "); // ><-> is <-> } } } } QString AbstractNavigationContext::currentHtml() const { return m_currentText; } QString AbstractNavigationContext::fontSizePrefix(bool /*shorten*/) const { return QString(); } QString AbstractNavigationContext::fontSizeSuffix(bool /*shorten*/) const { return QString(); } QString Colorizer::operator() ( const QString& str ) const { QString ret = "" + str + ""; if( m_formatting & Fixed ) ret = ""+ret+""; if ( m_formatting & Bold ) ret = ""+ret+""; if ( m_formatting & Italic ) ret = ""+ret+""; return ret; } -const Colorizer AbstractNavigationContext::typeHighlight("006000"); -const Colorizer AbstractNavigationContext::errorHighlight("990000"); -const Colorizer AbstractNavigationContext::labelHighlight("000000"); -const Colorizer AbstractNavigationContext::codeHighlight("005000"); -const Colorizer AbstractNavigationContext::propertyHighlight("009900"); -const Colorizer AbstractNavigationContext::navigationHighlight("000099"); -const Colorizer AbstractNavigationContext::importantHighlight("000000", Colorizer::Bold | Colorizer::Italic); -const Colorizer AbstractNavigationContext::commentHighlight("303030"); -const Colorizer AbstractNavigationContext::nameHighlight("000000", Colorizer::Bold); +const Colorizer AbstractNavigationContext::typeHighlight(QStringLiteral("006000")); +const Colorizer AbstractNavigationContext::errorHighlight(QStringLiteral("990000")); +const Colorizer AbstractNavigationContext::labelHighlight(QStringLiteral("000000")); +const Colorizer AbstractNavigationContext::codeHighlight(QStringLiteral("005000")); +const Colorizer AbstractNavigationContext::propertyHighlight(QStringLiteral("009900")); +const Colorizer AbstractNavigationContext::navigationHighlight(QStringLiteral("000099")); +const Colorizer AbstractNavigationContext::importantHighlight(QStringLiteral("000000"), Colorizer::Bold | Colorizer::Italic); +const Colorizer AbstractNavigationContext::commentHighlight(QStringLiteral("303030")); +const Colorizer AbstractNavigationContext::nameHighlight(QStringLiteral("000000"), Colorizer::Bold); } diff --git a/language/duchain/navigation/abstractnavigationwidget.cpp b/language/duchain/navigation/abstractnavigationwidget.cpp index b32ff5b1e..3bbb7c8cf 100644 --- a/language/duchain/navigation/abstractnavigationwidget.cpp +++ b/language/duchain/navigation/abstractnavigationwidget.cpp @@ -1,291 +1,291 @@ /* Copyright 2007 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "abstractnavigationwidget.h" #include #include #include #include #include #include #include "../declaration.h" #include "../ducontext.h" #include "../duchain.h" #include "../duchainlock.h" #include "util/debug.h" namespace KDevelop { AbstractNavigationWidget::AbstractNavigationWidget() : m_browser(0), m_currentWidget(0) { setPalette( QApplication::palette() ); setFocusPolicy(Qt::NoFocus); resize(100, 100); } const int maxNavigationWidgetWidth = 580; QSize AbstractNavigationWidget::sizeHint() const { if(m_browser) { updateIdealSize(); QSize ret = QSize(qMin(m_idealTextSize.width(), maxNavigationWidgetWidth), qMin(m_idealTextSize.height(), 300)); if(m_idealTextSize.height()>=300) { //make space for the scrollbar in case it's not fitting ret.rwidth() += 17; //m_browser->verticalScrollBar()->width() returns 100, for some reason } if(m_currentWidget) { ret.setHeight( ret.height() + m_currentWidget->sizeHint().height() ); if(m_currentWidget->sizeHint().width() > ret.width()) ret.setWidth(m_currentWidget->sizeHint().width()); if(ret.width() < 500) //When we embed a widget, give it some space, even if it doesn't have a large size-hint ret.setWidth(500); } return ret; } else return QWidget::sizeHint(); } void AbstractNavigationWidget::initBrowser(int height) { Q_UNUSED(height); m_browser = new QTextBrowser; // since we can embed arbitrary HTML we have to make sure it stays readable by forcing a black-white palette QPalette p; p.setColor(QPalette::AlternateBase, Qt::white); p.setColor(QPalette::Base, Qt::white); p.setColor(QPalette::Text, Qt::black); m_browser->setPalette( p ); m_browser->setOpenLinks(false); m_browser->setOpenExternalLinks(false); QVBoxLayout* layout = new QVBoxLayout; layout->addWidget(m_browser); layout->setMargin(0); setLayout(layout); connect( m_browser.data(), &QTextBrowser::anchorClicked, this, &AbstractNavigationWidget::anchorClicked ); foreach(QWidget* w, findChildren()) w->setContextMenuPolicy(Qt::NoContextMenu); } AbstractNavigationWidget::~AbstractNavigationWidget() { if(m_currentWidget) layout()->removeWidget(m_currentWidget); } void AbstractNavigationWidget::setContext(NavigationContextPointer context, int initBrows) { if(m_browser == 0) initBrowser(initBrows); if(!context) { qCDebug(LANGUAGE) << "no new context created"; return; } if(context == m_context && (!context || context->alreadyComputed())) return; if (!m_startContext) m_startContext = m_context; bool wasInitial = (m_context == m_startContext); m_context = context; update(); emit contextChanged(wasInitial, m_context == m_startContext); emit sizeHintChanged(); } void AbstractNavigationWidget::updateIdealSize() const { if(m_context && !m_idealTextSize.isValid()) { QTextDocument doc; doc.setHtml(m_currentText); if(doc.idealWidth() > maxNavigationWidgetWidth) { doc.setPageSize( QSize(maxNavigationWidgetWidth, 30) ); m_idealTextSize.setWidth(maxNavigationWidgetWidth); }else{ m_idealTextSize.setWidth(doc.idealWidth()); } m_idealTextSize.setHeight(doc.size().height()); } } void AbstractNavigationWidget::update() { setUpdatesEnabled(false); Q_ASSERT( m_context ); QString html = m_context->html(); if(!html.isEmpty()) { int scrollPos = m_browser->verticalScrollBar()->value(); m_browser->setHtml( html ); m_currentText = html; m_idealTextSize = QSize(); QSize hint = sizeHint(); if(hint.height() >= m_idealTextSize.height()) { m_browser->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); }else{ m_browser->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); } m_browser->verticalScrollBar()->setValue(scrollPos); - m_browser->scrollToAnchor("currentPosition"); + m_browser->scrollToAnchor(QStringLiteral("currentPosition")); m_browser->show(); }else{ m_browser->hide(); } if(m_currentWidget) { layout()->removeWidget(m_currentWidget); m_currentWidget->setParent(0); } m_currentWidget = m_context->widget(); m_browser->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); m_browser->setMaximumHeight(10000); if(m_currentWidget) { if (m_currentWidget->metaObject() ->indexOfSignal(QMetaObject::normalizedSignature("navigateDeclaration(KDevelop::IndexedDeclaration)")) != -1) { connect(m_currentWidget, SIGNAL(navigateDeclaration(KDevelop::IndexedDeclaration)), this, SLOT(navigateDeclaration(KDevelop::IndexedDeclaration))); } layout()->addWidget(m_currentWidget); if(m_context->isWidgetMaximized()) { //Leave unused room to the widget m_browser->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); m_browser->setMaximumHeight(m_idealTextSize.height()); } } setUpdatesEnabled(true); } NavigationContextPointer AbstractNavigationWidget::context() { return m_context; } void AbstractNavigationWidget::navigateDeclaration(KDevelop::IndexedDeclaration decl) { DUChainReadLocker lock( DUChain::lock() ); setContext(m_context->accept(decl)); } void AbstractNavigationWidget::anchorClicked(const QUrl& url) { DUChainReadLocker lock( DUChain::lock() ); //We may get deleted while the call to acceptLink, so make sure we don't crash in that case QPointer thisPtr(this); NavigationContextPointer oldContext = m_context; NavigationContextPointer nextContext = m_context->acceptLink(url.toString()); if(thisPtr) setContext( nextContext ); } void AbstractNavigationWidget::next() { DUChainReadLocker lock( DUChain::lock() ); Q_ASSERT( m_context ); m_context->nextLink(); update(); } void AbstractNavigationWidget::previous() { DUChainReadLocker lock( DUChain::lock() ); Q_ASSERT( m_context ); m_context->previousLink(); update(); } void AbstractNavigationWidget::accept() { DUChainReadLocker lock( DUChain::lock() ); Q_ASSERT( m_context ); QPointer thisPtr(this); NavigationContextPointer oldContext = m_context; NavigationContextPointer nextContext = m_context->accept(); if(thisPtr) setContext( nextContext ); } void AbstractNavigationWidget::back() { DUChainReadLocker lock( DUChain::lock() ); QPointer thisPtr(this); NavigationContextPointer oldContext = m_context; NavigationContextPointer nextContext = m_context->back(); if(thisPtr) setContext( nextContext ); } void AbstractNavigationWidget::up() { DUChainReadLocker lock( DUChain::lock() ); m_context->up(); update(); } void AbstractNavigationWidget::down() { DUChainReadLocker lock( DUChain::lock() ); m_context->down(); update(); } void AbstractNavigationWidget::embeddedWidgetAccept() { accept(); } void AbstractNavigationWidget::embeddedWidgetDown() { down(); } void AbstractNavigationWidget::embeddedWidgetRight() { next(); } void AbstractNavigationWidget::embeddedWidgetLeft() { previous(); } void AbstractNavigationWidget::embeddedWidgetUp() { up(); } void AbstractNavigationWidget::wheelEvent(QWheelEvent* event ) { QWidget::wheelEvent(event); event->accept(); return; } } diff --git a/language/duchain/navigation/problemnavigationcontext.cpp b/language/duchain/navigation/problemnavigationcontext.cpp index fa5798745..1cc00d607 100644 --- a/language/duchain/navigation/problemnavigationcontext.cpp +++ b/language/duchain/navigation/problemnavigationcontext.cpp @@ -1,119 +1,119 @@ /* Copyright 2009 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "problemnavigationcontext.h" #include #include #include #include #include #include #include #include #include using namespace KDevelop; ProblemNavigationContext::ProblemNavigationContext(ProblemPointer problem): m_problem(problem) { m_widget = 0; QExplicitlySharedDataPointer< IAssistant > solution = problem->solutionAssistant(); if(solution && !solution->actions().isEmpty()) { m_widget = new QWidget; QHBoxLayout* layout = new QHBoxLayout(m_widget); RichTextPushButton* button = new RichTextPushButton; // button->setPopupMode(QToolButton::InstantPopup); if(!solution->title().isEmpty()) button->setHtml(i18n("Solve: %1", solution->title())); else button->setHtml(i18n("Solve")); QMenu* menu = new QMenu; menu->setFocusPolicy(Qt::NoFocus); foreach(IAssistantAction::Ptr action, solution->actions()) { menu->addAction(action->toKAction()); } button->setMenu(menu); layout->addWidget(button); layout->setAlignment(button, Qt::AlignLeft); m_widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); } } ProblemNavigationContext::~ProblemNavigationContext() { delete m_widget; } QWidget* ProblemNavigationContext::widget() const { return m_widget; } bool ProblemNavigationContext::isWidgetMaximized() const { return false; } QString ProblemNavigationContext::name() const { return i18n("Problem"); } QString ProblemNavigationContext::html(bool shorten) { clear(); m_shorten = shorten; - modifyHtml() += "

"; + modifyHtml() += QStringLiteral("

"); modifyHtml() += i18n("Problem in %1:
", m_problem->sourceString()); modifyHtml() += m_problem->description().toHtmlEscaped(); - modifyHtml() += "
"; + modifyHtml() += QStringLiteral("
"); modifyHtml() += "" + m_problem->explanation().toHtmlEscaped() + ""; const QVector diagnostics = m_problem->diagnostics(); if (!diagnostics.isEmpty()) { - modifyHtml() += "
"; + modifyHtml() += QStringLiteral("
"); DUChainReadLocker lock; for (auto diagnostic : diagnostics) { const DocumentRange range = diagnostic->finalLocation(); Declaration* declaration = DUChainUtils::itemUnderCursor(range.document.toUrl(), range.start()); modifyHtml() += labelHighlight(QStringLiteral("%1: ").arg(diagnostic->severityString())); modifyHtml() += diagnostic->description(); if (declaration) { - modifyHtml() += "
"; + modifyHtml() += QStringLiteral("
"); makeLink(declaration->toString(), KDevelop::DeclarationPointer(declaration), NavigationAction::NavigateDeclaration); modifyHtml() += i18n(" in "); makeLink(QStringLiteral("%1 :%2").arg(declaration->url().toUrl().fileName()).arg(declaration->rangeInCurrentRevision().start().line()+1), KDevelop::DeclarationPointer(declaration), NavigationAction::NavigateDeclaration); } - modifyHtml() += "
"; + modifyHtml() += QStringLiteral("
"); } } - modifyHtml() += "

"; + modifyHtml() += QStringLiteral("

"); return currentHtml(); } diff --git a/language/duchain/navigation/problemnavigationcontext.h b/language/duchain/navigation/problemnavigationcontext.h index ffd71aff8..70728fe62 100644 --- a/language/duchain/navigation/problemnavigationcontext.h +++ b/language/duchain/navigation/problemnavigationcontext.h @@ -1,45 +1,46 @@ /* 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_PROBLEMNAVIGATIONCONTEXT_H #define KDEVPLATFORM_PROBLEMNAVIGATIONCONTEXT_H #include #include #include #include namespace KDevelop { class KDEVPLATFORMLANGUAGE_EXPORT ProblemNavigationContext : public AbstractNavigationContext { + Q_OBJECT public: explicit ProblemNavigationContext(KDevelop::ProblemPointer problem); ~ProblemNavigationContext(); virtual QString name() const override; virtual QString html(bool shorten = false) override; virtual QWidget* widget() const override; virtual bool isWidgetMaximized() const override; private: QPointer m_widget; ProblemPointer m_problem; }; } #endif // KDEVPLATFORM_PROBLEMNAVIGATIONCONTEXT_H diff --git a/language/duchain/navigation/usescollector.cpp b/language/duchain/navigation/usescollector.cpp index 120da9d69..935273422 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(ParsingEnvironmentFilePointer importer, current->importers()) + 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) { } 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(), 0, 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(), 0, 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) { + 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) { + 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(ParsingEnvironmentFilePointer version, allVersions) + 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(0) && import.context(0)->topContext()->parsingEnvironmentFile() && !import.context(0)->topContext()->parsingEnvironmentFile()->isProxyContext()) { if((import.context(0)->topContext()->features() & TopDUContext::AllDeclarationsContextsAndUses)) { ReferencedTopDUContext newTop(import.context(0)->topContext()); topContext = newTop; break; } } } if(topContext->parsingEnvironmentFile() && topContext->parsingEnvironmentFile()->isProxyContext()) { qCDebug(LANGUAGE) << "got bad proxy-context for" << url.str(); topContext = 0; } } } 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(0) && imported.context(0)->topContext()) imports << KDevelop::ReferencedTopDUContext(imported.context(0)->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/usesnavigationcontext.cpp b/language/duchain/navigation/usesnavigationcontext.cpp index fd0a70abe..cfe4ced7c 100644 --- a/language/duchain/navigation/usesnavigationcontext.cpp +++ b/language/duchain/navigation/usesnavigationcontext.cpp @@ -1,62 +1,62 @@ /* 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 "usesnavigationcontext.h" #include "useswidget.h" #include #include #include #include using namespace KDevelop; UsesNavigationContext::UsesNavigationContext( KDevelop::IndexedDeclaration declaration, AbstractNavigationContext* previousContext ) : AbstractNavigationContext(TopDUContextPointer(), previousContext), m_declaration(declaration) { m_widget = new UsesWidget(m_declaration); } UsesNavigationContext::~UsesNavigationContext() { delete m_widget; } QString UsesNavigationContext::name() const { - return "Uses"; + return QStringLiteral("Uses"); } QString UsesNavigationContext::html(bool shorten) { clear(); modifyHtml() += "

" + fontSizePrefix(shorten); if( m_previousContext ) { modifyHtml() += navigationHighlight(i18n("Uses of ")); makeLink( m_previousContext->name(), m_previousContext->name(), NavigationAction(m_previousContext) ); }else{ KDevelop::DUChainReadLocker lock(DUChain::lock()); if(Declaration* decl = m_declaration.data()) { makeLink( i18n("Uses of %1", decl->toString()), DeclarationPointer(decl), NavigationAction::NavigateDeclaration); } } modifyHtml() += fontSizeSuffix(shorten) + "

"; return currentHtml(); } QWidget* UsesNavigationContext::widget() const { return m_widget; } diff --git a/language/duchain/navigation/usesnavigationcontext.h b/language/duchain/navigation/usesnavigationcontext.h index bab42c09e..2863068da 100644 --- a/language/duchain/navigation/usesnavigationcontext.h +++ b/language/duchain/navigation/usesnavigationcontext.h @@ -1,42 +1,43 @@ /* 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_USESNAVIGATIONCONTEXT_H #define KDEVPLATFORM_USESNAVIGATIONCONTEXT_H #include "abstractnavigationwidget.h" #include namespace KDevelop { class UsesWidget; class KDEVPLATFORMLANGUAGE_EXPORT UsesNavigationContext : public AbstractNavigationContext { + Q_OBJECT public: explicit UsesNavigationContext( KDevelop::IndexedDeclaration declaration, AbstractNavigationContext* previousContext = 0 ); ~UsesNavigationContext(); virtual QString name() const override; virtual QWidget* widget() const override; virtual QString html(bool shorten) override; private: KDevelop::IndexedDeclaration m_declaration; UsesWidget* m_widget; }; } #endif diff --git a/language/duchain/navigation/useswidget.cpp b/language/duchain/navigation/useswidget.cpp index 2cb96c291..ad02101aa 100644 --- a/language/duchain/navigation/useswidget.cpp +++ b/language/duchain/navigation/useswidget.cpp @@ -1,657 +1,655 @@ /* Copyright 2008 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "useswidget.h" #include "util/debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; const int tooltipContextSize = 2; //How many lines around the use are shown in the tooltip ///The returned text is fully escaped ///@param cutOff The total count of characters that should be cut of, all in all on both sides together. ///@param range The range that is highlighted, and that will be preserved during cutting, given that there is enough room beside it. QString highlightAndEscapeUseText(QString line, int cutOff, KTextEditor::Range range) { int leftCutRoom = range.start().column(); int rightCutRoom = line.length() - range.end().column(); if(range.start().column() < 0 || range.end().column() > line.length() || cutOff > leftCutRoom + rightCutRoom) return QString(); //Not enough room for cutting off on sides int leftCut = 0; int rightCut = 0; if(leftCutRoom < rightCutRoom) { if(leftCutRoom * 2 >= cutOff) { //Enough room on both sides. Just cut. leftCut = cutOff / 2; rightCut = cutOff - leftCut; }else{ //Not enough room in left side, but enough room all together leftCut = leftCutRoom; rightCut = cutOff - leftCut; } }else{ if(rightCutRoom * 2 >= cutOff) { //Enough room on both sides. Just cut. rightCut = cutOff / 2; leftCut = cutOff - rightCut; }else{ //Not enough room in right side, but enough room all together rightCut = rightCutRoom; leftCut = cutOff - rightCut; } } Q_ASSERT(leftCut + rightCut <= cutOff); line = line.left(line.length() - rightCut); line = line.mid(leftCut); range += KTextEditor::Range(0, -leftCut, 0, -leftCut); Q_ASSERT(range.start().column() >= 0 && range.end().column() <= line.length()); //TODO: share code with context browser // mixing (255, 255, 0, 100) with white yields this: const QColor background(251, 250, 150); const QColor foreground(0, 0, 0); return "" + line.left(range.start().column()).toHtmlEscaped() + "" + line.mid(range.start().column(), range.end().column() - range.start().column()).toHtmlEscaped() + "" + line.mid(range.end().column(), line.length() - range.end().column()).toHtmlEscaped() + ""; } OneUseWidget::OneUseWidget(IndexedDeclaration declaration, IndexedString document, KTextEditor::Range range, const CodeRepresentation& code) : m_range(new PersistentMovingRange(range, document)), m_declaration(declaration), m_document(document) { //Make the sizing of this widget independent of the content, because we will adapt the content to the size setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); m_sourceLine = code.line(m_range->range().start().line()); m_layout = new QHBoxLayout(this); m_layout->setContentsMargins(0, 0, 0, 0); setLayout(m_layout); m_label = new QLabel(this); m_icon = new QLabel(this); - m_icon->setPixmap(QIcon::fromTheme("code-function").pixmap(16)); + m_icon->setPixmap(QIcon::fromTheme(QStringLiteral("code-function")).pixmap(16)); connect(m_label, &QLabel::linkActivated, this, &OneUseWidget::jumpTo); DUChainReadLocker lock(DUChain::lock()); QString text = "" + i18nc("refers to a line in source code", "Line %1:", range.start().line()) + QStringLiteral(""); if(!m_sourceLine.isEmpty() && m_sourceLine.length() > m_range->range().end().column()) { text += "  " + highlightAndEscapeUseText(m_sourceLine, 0, m_range->range()); //Useful tooltip: int start = m_range->range().start().line() - tooltipContextSize; int end = m_range->range().end().line() + tooltipContextSize + 1; QString toolTipText; for(int a = start; a < end; ++a) { QString lineText = code.line(a).toHtmlEscaped(); if (m_range->range().start().line() <= a && m_range->range().end().line() >= a) { lineText = QStringLiteral("") + lineText + QStringLiteral(""); } if(!lineText.trimmed().isEmpty()) { toolTipText += lineText + "
"; } } - if ( toolTipText.endsWith("
") ) { + if ( toolTipText.endsWith(QLatin1String("
")) ) { toolTipText.remove(toolTipText.length() - 4, 4); } setToolTip(QStringLiteral("
") + toolTipText + QStringLiteral("
")); } m_label->setText(text); m_layout->addWidget(m_icon); m_layout->addWidget(m_label); m_layout->setAlignment(Qt::AlignLeft); } void OneUseWidget::jumpTo() { //This is used to execute the slot delayed in the event-loop, so crashes are avoided ICore::self()->documentController()->openDocument(m_document.toUrl(), m_range->range().start()); } OneUseWidget::~OneUseWidget() { } void OneUseWidget::resizeEvent ( QResizeEvent * event ) { ///Adapt the content QSize size = event->size(); KTextEditor::Range range = m_range->range(); int cutOff = 0; int maxCutOff = m_sourceLine.length() - (range.end().column() - range.start().column()); //Reset so we also get more context while up-sizing m_label->setText(QStringLiteral("") + i18nc("Refers to a line in source code", "Line %1", range.start().line()+1) + QStringLiteral(" ") + highlightAndEscapeUseText(m_sourceLine, cutOff, range)); while(sizeHint().width() > size.width() && cutOff < maxCutOff) { //We've got to save space m_label->setText(QStringLiteral("") + i18nc("Refers to a line in source code", "Line %1", range.start().line()+1) + QStringLiteral(" ") + highlightAndEscapeUseText(m_sourceLine, cutOff, range)); cutOff += 5; } event->accept(); QWidget::resizeEvent(event); } void NavigatableWidgetList::setShowHeader(bool show) { if(show && !m_headerLayout->parent()) m_layout->insertLayout(0, m_headerLayout); else m_headerLayout->setParent(0); } NavigatableWidgetList::~NavigatableWidgetList() { delete m_headerLayout; } NavigatableWidgetList::NavigatableWidgetList(bool allowScrolling, uint maxHeight, bool vertical) : m_allowScrolling(allowScrolling) { m_layout = new QVBoxLayout; m_layout->setMargin(0); m_layout->setSizeConstraint(QLayout::SetMinAndMaxSize); m_layout->setSpacing(0); setBackgroundRole(QPalette::Base); m_useArrows = false; if(vertical) m_itemLayout = new QVBoxLayout; else m_itemLayout = new QHBoxLayout; m_itemLayout->setContentsMargins(0, 0, 0, 0); m_itemLayout->setMargin(0); m_itemLayout->setSpacing(0); // m_layout->setSizeConstraint(QLayout::SetMinAndMaxSize); // setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Maximum); setWidgetResizable(true); m_headerLayout = new QHBoxLayout; m_headerLayout->setMargin(0); m_headerLayout->setSpacing(0); if(m_useArrows) { m_previousButton = new QToolButton(); - m_previousButton->setIcon(QIcon::fromTheme("go-previous")); + m_previousButton->setIcon(QIcon::fromTheme(QStringLiteral("go-previous"))); m_nextButton = new QToolButton(); - m_nextButton->setIcon(QIcon::fromTheme("go-next")); + m_nextButton->setIcon(QIcon::fromTheme(QStringLiteral("go-next"))); m_headerLayout->addWidget(m_previousButton); m_headerLayout->addWidget(m_nextButton); } //hide these buttons for now, they're senseless m_layout->addLayout(m_headerLayout); QHBoxLayout* spaceLayout = new QHBoxLayout; spaceLayout->addSpacing(10); spaceLayout->addLayout(m_itemLayout); m_layout->addLayout(spaceLayout); if(maxHeight) setMaximumHeight(maxHeight); if(m_allowScrolling) { QWidget* contentsWidget = new QWidget; contentsWidget->setLayout(m_layout); setWidget(contentsWidget); }else{ setLayout(m_layout); } } void NavigatableWidgetList::deleteItems() { foreach(QWidget* item, items()) delete item; } void NavigatableWidgetList::addItem(QWidget* widget, int pos) { if(pos == -1) m_itemLayout->addWidget(widget); else m_itemLayout->insertWidget(pos, widget); } QList NavigatableWidgetList::items() const { QList ret; for(int a = 0; a < m_itemLayout->count(); ++a) { QWidgetItem* widgetItem = dynamic_cast(m_itemLayout->itemAt(a)); if(widgetItem) { ret << widgetItem->widget(); } } return ret; } bool NavigatableWidgetList::hasItems() const { return (bool)m_itemLayout->count(); } void NavigatableWidgetList::addHeaderItem(QWidget* widget, Qt::Alignment alignment) { if(m_useArrows) { Q_ASSERT(m_headerLayout->count() >= 2); //At least the 2 back/next buttons m_headerLayout->insertWidget(m_headerLayout->count()-1, widget, alignment); }else{ //We need to do this so the header doesn't get stretched widget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); m_headerLayout->insertWidget(m_headerLayout->count(), widget, alignment); // widget->setMaximumHeight(20); } } ///Returns whether the uses in the child should be a new uses-group bool isNewGroup(DUContext* parent, DUContext* child) { if(parent->type() == DUContext::Other && child->type() == DUContext::Other) return false; else return true; } uint countUses(int usedDeclarationIndex, DUContext* context) { uint ret = 0; for(int useIndex = 0; useIndex < context->usesCount(); ++useIndex) if(context->uses()[useIndex].m_declarationIndex == usedDeclarationIndex) ++ret; foreach(DUContext* child, context->childContexts()) if(!isNewGroup(context, child)) ret += countUses(usedDeclarationIndex, child); return ret; } QList createUseWidgets(const CodeRepresentation& code, int usedDeclarationIndex, IndexedDeclaration decl, DUContext* context) { QList ret; VERIFY_FOREGROUND_LOCKED for(int useIndex = 0; useIndex < context->usesCount(); ++useIndex) if(context->uses()[useIndex].m_declarationIndex == usedDeclarationIndex) ret << new OneUseWidget(decl, context->url(), context->transformFromLocalRevision(context->uses()[useIndex].m_range), code); foreach(DUContext* child, context->childContexts()) if(!isNewGroup(context, child)) ret += createUseWidgets(code, usedDeclarationIndex, decl, child); return ret; } ContextUsesWidget::ContextUsesWidget(const CodeRepresentation& code, QList usedDeclarations, IndexedDUContext context) : m_context(context) { setFrameShape(NoFrame); DUChainReadLocker lock(DUChain::lock()); QString headerText = i18n("Unknown context"); setUpdatesEnabled(false); if(context.data()) { DUContext* ctx = context.data(); if(ctx->scopeIdentifier(true).isEmpty()) headerText = i18n("Global"); else { headerText = ctx->scopeIdentifier(true).toString(); if(ctx->type() == DUContext::Function || (ctx->owner() && ctx->owner()->isFunctionDeclaration())) - headerText += "(...)"; + headerText += QLatin1String("(...)"); } QSet hadIndices; - foreach(const IndexedDeclaration &usedDeclaration, usedDeclarations) { + foreach(const IndexedDeclaration usedDeclaration, usedDeclarations) { int usedDeclarationIndex = ctx->topContext()->indexForUsedDeclaration(usedDeclaration.data(), false); if(hadIndices.contains(usedDeclarationIndex)) continue; hadIndices.insert(usedDeclarationIndex); if(usedDeclarationIndex != std::numeric_limits::max()) { foreach(OneUseWidget* widget, createUseWidgets(code, usedDeclarationIndex, usedDeclaration, ctx)) addItem(widget); } } } QLabel* headerLabel = new QLabel(i18nc("%1: source file", "In %1", "" + headerText.toHtmlEscaped() + ": ")); addHeaderItem(headerLabel); setUpdatesEnabled(true); connect(headerLabel, &QLabel::linkActivated, this, &ContextUsesWidget::linkWasActivated); } void ContextUsesWidget::linkWasActivated(QString link) { - if ( link == "navigateToFunction" ) { + if ( link == QLatin1String("navigateToFunction") ) { DUChainReadLocker lock(DUChain::lock()); DUContext* context = m_context.context(); if(context) { CursorInRevision contextStart = context->range().start; KTextEditor::Cursor cursor(contextStart.line, contextStart.column); QUrl url = context->url().toUrl(); lock.unlock(); ForegroundLock fgLock; ICore::self()->documentController()->openDocument(url, cursor); } } } DeclarationWidget::DeclarationWidget(const CodeRepresentation& code, const IndexedDeclaration& decl) { setFrameShape(NoFrame); DUChainReadLocker lock(DUChain::lock()); setUpdatesEnabled(false); if (Declaration* dec = decl.data()) { QLabel* headerLabel = new QLabel(dec->isDefinition() ? i18n("Definition") : i18n("Declaration")); addHeaderItem(headerLabel); addItem(new OneUseWidget(decl, dec->url(), dec->rangeInCurrentRevision(), code)); } setUpdatesEnabled(true); } TopContextUsesWidget::TopContextUsesWidget(IndexedDeclaration declaration, QList allDeclarations, IndexedTopDUContext topContext) : m_topContext(topContext) , m_declaration(declaration) , m_allDeclarations(allDeclarations) , m_usesCount(0) { m_itemLayout->setContentsMargins(10, 0, 0, 5); setFrameShape(NoFrame); setUpdatesEnabled(false); DUChainReadLocker lock(DUChain::lock()); QHBoxLayout * labelLayout = new QHBoxLayout; labelLayout->setContentsMargins(0, -1, 0, 0); // let's keep the spacing *above* the line QWidget* headerWidget = new QWidget; headerWidget->setLayout(labelLayout); headerWidget->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); QLabel* label = new QLabel(this); m_icon = new QLabel(this); m_toggleButton = new QLabel(this); - m_icon->setPixmap(QIcon::fromTheme("code-class").pixmap(16)); + m_icon->setPixmap(QIcon::fromTheme(QStringLiteral("code-class")).pixmap(16)); labelLayout->addWidget(m_icon); labelLayout->addWidget(label); labelLayout->addWidget(m_toggleButton); labelLayout->setAlignment(Qt::AlignLeft); if(topContext.isLoaded()) m_usesCount = DUChainUtils::contextCountUses(topContext.data(), declaration.data()); QString labelText = i18ncp("%1: number of uses, %2: filename with uses", "%2: 1 use", "%2: %1 uses", m_usesCount, ICore::self()->projectController()->prettyFileName(topContext.url().toUrl())); label->setText(labelText); m_toggleButton->setText("   [" + i18nc("Refers to closing a UI element", "Collapse") + "]"); connect(m_toggleButton, &QLabel::linkActivated, this, &TopContextUsesWidget::labelClicked); addHeaderItem(headerWidget); setUpdatesEnabled(true); } int TopContextUsesWidget::usesCount() const { return m_usesCount; } QList buildContextUses(const CodeRepresentation& code, QList declarations, DUContext* context) { QList ret; if(!context->parentContext() || isNewGroup(context->parentContext(), context)) { ContextUsesWidget* created = new ContextUsesWidget(code, declarations, context); if(created->hasItems()) ret << created; else delete created; } foreach(DUContext* child, context->childContexts()) ret += buildContextUses(code, declarations, child); return ret; } void TopContextUsesWidget::setExpanded(bool expanded) { if(!expanded) { m_toggleButton->setText("   [" + i18nc("Refers to opening a UI element", "Expand") + "]"); deleteItems(); }else{ m_toggleButton->setText("   [" + i18nc("Refers to closing a UI element", "Collapse") + "]"); if(hasItems()) return; DUChainReadLocker lock(DUChain::lock()); TopDUContext* topContext = m_topContext.data(); if(topContext && m_declaration.data()) { CodeRepresentation::Ptr code = createCodeRepresentation(topContext->url()); setUpdatesEnabled(false); IndexedTopDUContext localTopContext(topContext); foreach(const IndexedDeclaration &decl, m_allDeclarations) { if(decl.indexedTopContext() == localTopContext) { addItem(new DeclarationWidget(*code, decl)); } } foreach(ContextUsesWidget* usesWidget, buildContextUses(*code, m_allDeclarations, topContext)) { addItem(usesWidget); } setUpdatesEnabled(true); } } } void TopContextUsesWidget::labelClicked() { if(hasItems()) { setExpanded(false); }else{ setExpanded(true); } } UsesWidget::~UsesWidget() { if (m_collector) { m_collector->setWidget(0); } } UsesWidget::UsesWidget(const IndexedDeclaration& declaration, QSharedPointer customCollector) : NavigatableWidgetList(true) { DUChainReadLocker lock(DUChain::lock()); setUpdatesEnabled(false); m_headerLine = new QLabel; redrawHeaderLine(); connect(m_headerLine, &QLabel::linkActivated, this, &UsesWidget::headerLinkActivated); m_layout->insertWidget(0, m_headerLine, 0, Qt::AlignTop); m_layout->setAlignment(Qt::AlignTop); m_itemLayout->setAlignment(Qt::AlignTop); m_progressBar = new QProgressBar; addHeaderItem(m_progressBar); if (!customCollector) { - m_collector = QSharedPointer(new UsesWidgetCollector(declaration)); + m_collector = QSharedPointer(new UsesWidget::UsesWidgetCollector(declaration)); } else { m_collector = customCollector; } m_collector->setProcessDeclarations(true); m_collector->setWidget(this); m_collector->startCollecting(); setUpdatesEnabled(true); } void UsesWidget::redrawHeaderLine() { m_headerLine->setText(headerLineText()); } const QString UsesWidget::headerLineText() const { return i18np("1 use found", "%1 uses found", countAllUses()) + " • " "[" + i18n("Expand all") + "] • " "[" + i18n("Collapse all") + "]"; } unsigned int UsesWidget::countAllUses() const { unsigned int totalUses = 0; foreach ( QWidget* w, items() ) { if ( TopContextUsesWidget* useWidget = dynamic_cast(w) ) { totalUses += useWidget->usesCount(); } } return totalUses; } void UsesWidget::setAllExpanded(bool expanded) { foreach ( QWidget* w, items() ) { if ( TopContextUsesWidget* useWidget = dynamic_cast(w) ) { useWidget->setExpanded(expanded); } } } void UsesWidget::headerLinkActivated(QString linkName) { - if(linkName == "expandAll") { + if(linkName == QLatin1String("expandAll")) { setAllExpanded(true); } - else if(linkName == "collapseAll") { + else if(linkName == QLatin1String("collapseAll")) { setAllExpanded(false); } } UsesWidget::UsesWidgetCollector::UsesWidgetCollector(IndexedDeclaration decl) : UsesCollector(decl), m_widget(0) { } void UsesWidget::UsesWidgetCollector::setWidget(UsesWidget* widget ) { m_widget = widget; } void UsesWidget::UsesWidgetCollector::maximumProgress(uint max) { if (!m_widget) { return; } if(m_widget->m_progressBar) { m_widget->m_progressBar->setMaximum(max); m_widget->m_progressBar->setMinimum(0); m_widget->m_progressBar->setValue(0); }else{ qCWarning(LANGUAGE) << "maximumProgress called twice"; } } void UsesWidget::UsesWidgetCollector::progress(uint processed, uint total) { if (!m_widget) { return; } m_widget->redrawHeaderLine(); if(m_widget->m_progressBar) { m_widget->m_progressBar->setValue(processed); if(processed == total) { m_widget->setUpdatesEnabled(false); delete m_widget->m_progressBar; m_widget->m_progressBar = 0; m_widget->setShowHeader(false); m_widget->setUpdatesEnabled(true); } }else{ qCWarning(LANGUAGE) << "progress() called too often"; } } void UsesWidget::UsesWidgetCollector::processUses( KDevelop::ReferencedTopDUContext topContext ) { if (!m_widget) { return; } DUChainReadLocker lock; qCDebug(LANGUAGE) << "processing" << topContext->url().str(); TopContextUsesWidget* widget = new TopContextUsesWidget(declaration(), declarations(), topContext.data()); // move to back if it's just the declaration/definition bool toBack = widget->usesCount() == 0; // move to front the item belonging to the current open document IDocument* doc = ICore::self()->documentController()->activeDocument(); bool toFront = doc && (doc->url() == topContext->url().toUrl()); widget->setExpanded(true); m_widget->addItem(widget, toFront ? 0 : toBack ? widget->items().size() : -1); m_widget->redrawHeaderLine(); } QSize KDevelop::UsesWidget::sizeHint() const { QSize ret = QWidget::sizeHint(); if(ret.height() < 300) ret.setHeight(300); return ret; } - - diff --git a/language/duchain/parsingenvironment.cpp b/language/duchain/parsingenvironment.cpp index 42527754e..781b0ff62 100644 --- a/language/duchain/parsingenvironment.cpp +++ b/language/duchain/parsingenvironment.cpp @@ -1,388 +1,388 @@ /* Copyright 2007 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "parsingenvironment.h" #include "topducontext.h" #include "duchainregister.h" #include "topducontextdynamicdata.h" #include "duchain.h" #include "duchainlock.h" #include "topducontextdata.h" #include "util/debug.h" #include #define ENSURE_READ_LOCKED if(indexedTopContext().isValid()) { ENSURE_CHAIN_READ_LOCKED } #define ENSURE_WRITE_LOCKED if(indexedTopContext().isValid()) { ENSURE_CHAIN_READ_LOCKED } namespace KDevelop { StaticParsingEnvironmentData* ParsingEnvironmentFile::m_staticData = 0; #if 0 ///Wrapper class around objects that are managed through the DUChain, and may contain arbitrary objects ///that support duchain-like store (IndexedString, StorableSet, and the likes). The object must not contain pointers ///or other non-persistent data. /// ///The object is stored during the normal duchain storage/cleanup cycles. template struct PersistentDUChainObject { ///@param fileName File-name that will be used to store the data of the object in the duchain directory PersistentDUChainObject(QString fileName) { object = (T*) new char[sizeof(T)]; if(!DUChain::self()->addPersistentObject(object, fileName, sizeof(T))) { //The constructor is called only if the object did not exist yet new (object) T(); } } ~PersistentDUChainObject() { DUChain::self()->unregisterPersistentObject(object); delete[] object; } T* object; }; #endif REGISTER_DUCHAIN_ITEM(ParsingEnvironmentFile); TopDUContext::Features ParsingEnvironmentFile::features() const { ENSURE_READ_LOCKED return d_func()->m_features; } ParsingEnvironment::ParsingEnvironment() { } ParsingEnvironment::~ParsingEnvironment() { } IndexedString ParsingEnvironmentFile::url() const { ENSURE_READ_LOCKED return d_func()->m_url; } bool ParsingEnvironmentFile::needsUpdate(const ParsingEnvironment* /*environment*/) const { ENSURE_READ_LOCKED return d_func()->m_allModificationRevisions.needsUpdate(); } bool ParsingEnvironmentFile::matchEnvironment(const ParsingEnvironment* /*environment*/) const { ENSURE_READ_LOCKED return true; } void ParsingEnvironmentFile::setTopContext(KDevelop::IndexedTopDUContext context) { if(d_func()->m_topContext == context) return; ENSURE_WRITE_LOCKED d_func_dynamic()->m_topContext = context; //Enforce an update of the 'features satisfied' caches TopDUContext::Features oldFeatures = features(); setFeatures(TopDUContext::Empty); setFeatures(oldFeatures); } KDevelop::IndexedTopDUContext ParsingEnvironmentFile::indexedTopContext() const { return d_func()->m_topContext; } const ModificationRevisionSet& ParsingEnvironmentFile::allModificationRevisions() const { ENSURE_READ_LOCKED return d_func()->m_allModificationRevisions; } void ParsingEnvironmentFile::addModificationRevisions(const ModificationRevisionSet& revisions) { ENSURE_WRITE_LOCKED d_func_dynamic()->m_allModificationRevisions += revisions; } ParsingEnvironmentFile::ParsingEnvironmentFile(ParsingEnvironmentFileData& data, const IndexedString& url) : DUChainBase(data) { d_func_dynamic()->m_url = url; d_func_dynamic()->m_modificationTime = ModificationRevision::revisionForFile(url); addModificationRevision(url, d_func_dynamic()->m_modificationTime); Q_ASSERT(d_func()->m_allModificationRevisions.index()); } ParsingEnvironmentFile::ParsingEnvironmentFile(const IndexedString& url) : DUChainBase(*new ParsingEnvironmentFileData()) { d_func_dynamic()->setClassId(this); d_func_dynamic()->m_url = url; d_func_dynamic()->m_modificationTime = ModificationRevision::revisionForFile(url); addModificationRevision(url, d_func_dynamic()->m_modificationTime); Q_ASSERT(d_func()->m_allModificationRevisions.index()); } TopDUContext* ParsingEnvironmentFile::topContext() const { ENSURE_READ_LOCKED return indexedTopContext().data(); } ParsingEnvironmentFile::~ParsingEnvironmentFile() { } ParsingEnvironmentFile::ParsingEnvironmentFile(ParsingEnvironmentFileData& data) : DUChainBase(data) { //If this triggers, the item has most probably not been initialized with the correct constructor that takes an IndexedString. Q_ASSERT(d_func()->m_allModificationRevisions.index()); } int ParsingEnvironment::type() const { return StandardParsingEnvironment; } int ParsingEnvironmentFile::type() const { ENSURE_READ_LOCKED return StandardParsingEnvironment; } bool ParsingEnvironmentFile::isProxyContext() const { ENSURE_READ_LOCKED return d_func()->m_isProxyContext; } void ParsingEnvironmentFile::setIsProxyContext(bool is) { ENSURE_WRITE_LOCKED d_func_dynamic()->m_isProxyContext = is; } QList< QExplicitlySharedDataPointer > ParsingEnvironmentFile::imports() const { ENSURE_READ_LOCKED QList imp; IndexedTopDUContext top = indexedTopContext(); if(top.isLoaded()) { TopDUContext* topCtx = top.data(); FOREACH_FUNCTION(const DUContext::Import& import, topCtx->d_func()->m_importedContexts) imp << import.indexedContext(); }else{ imp = TopDUContextDynamicData::loadImports(top.index()); } QList< QExplicitlySharedDataPointer > ret; - foreach(const IndexedDUContext &ctx, imp) { + foreach(const IndexedDUContext ctx, imp) { QExplicitlySharedDataPointer item = DUChain::self()->environmentFileForDocument(ctx.topContextIndex()); if(item) { ret << item; }else{ qCDebug(LANGUAGE) << url().str() << indexedTopContext().index() << ": invalid import" << ctx.topContextIndex(); } } return ret; } QList< QExplicitlySharedDataPointer > ParsingEnvironmentFile::importers() const { ENSURE_READ_LOCKED QList imp; IndexedTopDUContext top = indexedTopContext(); if(top.isLoaded()) { TopDUContext* topCtx = top.data(); FOREACH_FUNCTION(const IndexedDUContext& ctx, topCtx->d_func()->m_importers) imp << ctx; }else{ imp = TopDUContextDynamicData::loadImporters(top.index()); } QList< QExplicitlySharedDataPointer > ret; - foreach(const IndexedDUContext &ctx, imp) { + foreach(const IndexedDUContext ctx, imp) { QExplicitlySharedDataPointer f = DUChain::self()->environmentFileForDocument(ctx.topContextIndex()); if(f) ret << f; else qCDebug(LANGUAGE) << url().str() << indexedTopContext().index() << ": invalid importer context" << ctx.topContextIndex(); } return ret; } QMutex featureSatisfactionMutex; inline bool satisfied(TopDUContext::Features features, TopDUContext::Features required) { return (features & required) == required; } ///Makes sure the file has the correct features attached, and if minimumFeatures contains AllDeclarationsContextsAndUsesForRecursive, then also checks all imports. bool ParsingEnvironmentFile::featuresMatch(TopDUContext::Features minimumFeatures, QSet& checked) const { if(checked.contains(this)) return true; checked.insert(this); TopDUContext::Features localRequired = (TopDUContext::Features) (minimumFeatures | ParseJob::staticMinimumFeatures(url())); //Check other 'local' requirements localRequired = (TopDUContext::Features)(localRequired & (TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::AST)); if(!satisfied(features(), localRequired)) return false; if(ParseJob::hasStaticMinimumFeatures()) { //Do a manual recursion to check whether any of the relevant contexts has static minimum features set ///@todo Only do this if one of the imports actually has static features attached (by RecursiveImports set intersection) foreach(const ParsingEnvironmentFilePointer &import, imports()) if(!import->featuresMatch(minimumFeatures & TopDUContext::Recursive ? minimumFeatures : ((TopDUContext::Features)0), checked)) return false; }else if(minimumFeatures & TopDUContext::Recursive) { QMutexLocker lock(&featureSatisfactionMutex); TopDUContext::IndexedRecursiveImports recursiveImportIndices = d_func()->m_importsCache; if(recursiveImportIndices.isEmpty()) { //Unfortunately, we have to load the top-context TopDUContext* top = topContext(); if(top) recursiveImportIndices = top->recursiveImportIndices(); } ///@todo Do not create temporary intersected sets //Use the features-cache to efficiently check the recursive satisfaction of the features if(satisfied(minimumFeatures, TopDUContext::AST) && !((m_staticData->ASTSatisfied & recursiveImportIndices) == recursiveImportIndices)) return false; if(satisfied(minimumFeatures, TopDUContext::AllDeclarationsContextsAndUses)) return (m_staticData->allDeclarationsAndUsesSatisfied & recursiveImportIndices) == recursiveImportIndices; else if(satisfied(minimumFeatures, TopDUContext::AllDeclarationsAndContexts)) return (m_staticData->allDeclarationsSatisfied & recursiveImportIndices) == recursiveImportIndices; else if(satisfied(minimumFeatures, TopDUContext::VisibleDeclarationsAndContexts)) return (m_staticData->visibleDeclarationsSatisfied & recursiveImportIndices) == recursiveImportIndices; else if(satisfied(minimumFeatures, TopDUContext::SimplifiedVisibleDeclarationsAndContexts)) return (m_staticData->simplifiedVisibleDeclarationsSatisfied & recursiveImportIndices) == recursiveImportIndices; } return true; } void ParsingEnvironmentFile::setFeatures(TopDUContext::Features features) { if(d_func()->m_features == features) return; ENSURE_WRITE_LOCKED d_func_dynamic()->m_features = features; if(indexedTopContext().isValid()) { QMutexLocker lock(&featureSatisfactionMutex); if(!satisfied(features, TopDUContext::SimplifiedVisibleDeclarationsAndContexts)) m_staticData->simplifiedVisibleDeclarationsSatisfied.remove(indexedTopContext()); else m_staticData->simplifiedVisibleDeclarationsSatisfied.insert(indexedTopContext()); if(!satisfied(features, TopDUContext::VisibleDeclarationsAndContexts)) m_staticData->visibleDeclarationsSatisfied.remove(indexedTopContext()); else m_staticData->visibleDeclarationsSatisfied.insert(indexedTopContext()); if(!satisfied(features, TopDUContext::AllDeclarationsAndContexts)) m_staticData->allDeclarationsSatisfied.remove(indexedTopContext()); else m_staticData->allDeclarationsSatisfied.insert(indexedTopContext()); if(!satisfied(features, TopDUContext::AllDeclarationsContextsAndUses)) m_staticData->allDeclarationsAndUsesSatisfied.remove(indexedTopContext()); else m_staticData->allDeclarationsAndUsesSatisfied.insert(indexedTopContext()); if(!satisfied(features, TopDUContext::AST)) m_staticData->ASTSatisfied.remove(indexedTopContext()); else m_staticData->ASTSatisfied.insert(indexedTopContext()); } } bool ParsingEnvironmentFile::featuresSatisfied(KDevelop::TopDUContext::Features minimumFeatures) const { ENSURE_READ_LOCKED QSet checked; if(minimumFeatures & TopDUContext::ForceUpdate) return false; return featuresMatch(minimumFeatures, checked); } void ParsingEnvironmentFile::clearModificationRevisions() { ENSURE_WRITE_LOCKED d_func_dynamic()->m_allModificationRevisions.clear(); d_func_dynamic()->m_allModificationRevisions.addModificationRevision(d_func()->m_url, d_func()->m_modificationTime); } void ParsingEnvironmentFile::addModificationRevision(const IndexedString& url, const ModificationRevision& revision) { ENSURE_WRITE_LOCKED d_func_dynamic()->m_allModificationRevisions.addModificationRevision(url, revision); { //Test Q_ASSERT(d_func_dynamic()->m_allModificationRevisions.index()); bool result = d_func_dynamic()->m_allModificationRevisions.removeModificationRevision(url, revision); Q_UNUSED( result ); Q_ASSERT( result ); d_func_dynamic()->m_allModificationRevisions.addModificationRevision(url, revision); } } void ParsingEnvironmentFile::setModificationRevision( const KDevelop::ModificationRevision& rev ) { ENSURE_WRITE_LOCKED Q_ASSERT(d_func_dynamic()->m_allModificationRevisions.index()); bool result = d_func_dynamic()->m_allModificationRevisions.removeModificationRevision(d_func()->m_url, d_func()->m_modificationTime); Q_ASSERT( result ); Q_UNUSED( result ); #ifdef LEXERCACHE_DEBUG if(debugging()) { qCDebug(LANGUAGE) << id(this) << "setting modification-revision" << rev.toString(); } #endif d_func_dynamic()->m_modificationTime = rev; #ifdef LEXERCACHE_DEBUG if(debugging()) { qCDebug(LANGUAGE) << id(this) << "new modification-revision" << m_modificationTime; } #endif d_func_dynamic()->m_allModificationRevisions.addModificationRevision(d_func()->m_url, d_func()->m_modificationTime); } KDevelop::ModificationRevision ParsingEnvironmentFile::modificationRevision() const { ENSURE_READ_LOCKED return d_func()->m_modificationTime; } IndexedString ParsingEnvironmentFile::language() const { return d_func()->m_language; } void ParsingEnvironmentFile::setLanguage(IndexedString language) { d_func_dynamic()->m_language = language; } const KDevelop::TopDUContext::IndexedRecursiveImports& ParsingEnvironmentFile::importsCache() const { return d_func()->m_importsCache; } void ParsingEnvironmentFile::setImportsCache(const KDevelop::TopDUContext::IndexedRecursiveImports& importsCache) { d_func_dynamic()->m_importsCache = importsCache; } } //KDevelop diff --git a/language/duchain/persistentsymboltable.cpp b/language/duchain/persistentsymboltable.cpp index 21f39bb21..1afa5074a 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("Recursive Imports Cache", 0, false); + static Utils::BasicSetRepository recursiveImportCacheRepositoryObject(QStringLiteral("Recursive Imports Cache"), 0, 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("Persistent Declaration Table") { + 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) { } 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 = 0; } } struct DebugVisitor { 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/problem.cpp b/language/duchain/problem.cpp index 4c647af21..a645760a0 100644 --- a/language/duchain/problem.cpp +++ b/language/duchain/problem.cpp @@ -1,274 +1,274 @@ /* This file is part of KDevelop Copyright 2007 Hamish Rodda This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "problem.h" #include "duchainregister.h" #include "topducontextdynamicdata.h" #include "topducontext.h" #include "topducontextdata.h" #include "duchain.h" #include "duchainlock.h" #include #include namespace KDevelop { REGISTER_DUCHAIN_ITEM(Problem); DEFINE_LIST_MEMBER_HASH(ProblemData, diagnostics, LocalIndexedProblem) } using namespace KDevelop; LocalIndexedProblem::LocalIndexedProblem(const ProblemPointer& problem, const TopDUContext* top) : m_index(problem->m_indexInTopContext) { ENSURE_CHAIN_READ_LOCKED // ensure child problems are properly serialized before we serialize the parent problem if (static_cast(problem->m_diagnostics.size()) != problem->d_func()->diagnosticsSize()) { // see below, the diagnostic size is kept in sync by the mutable API of Problem Q_ASSERT(!problem->diagnostics().isEmpty()); // the const cast is ugly but we don't really "change" the state as observed from the outside auto& serialized = const_cast(problem.data())->d_func_dynamic()->diagnosticsList(); Q_ASSERT(serialized.isEmpty()); foreach(const ProblemPointer& child, problem->m_diagnostics) { serialized << LocalIndexedProblem(child, top); } } if (!m_index) { m_index = top->m_dynamicData->allocateProblemIndex(problem); } } ProblemPointer LocalIndexedProblem::data(const TopDUContext* top) const { if (!m_index) { return {}; } return top->m_dynamicData->getProblemForIndex(m_index); } Problem::Problem() : DUChainBase(*new ProblemData) , m_topContext(nullptr) , m_indexInTopContext(0) { d_func_dynamic()->setClassId(this); } Problem::Problem(ProblemData& data) : DUChainBase(data) , m_topContext(nullptr) , m_indexInTopContext(0) { } Problem::~Problem() { } TopDUContext* Problem::topContext() const { return m_topContext.data(); } IndexedString Problem::url() const { return d_func()->url; } DocumentRange Problem::finalLocation() const { return DocumentRange(d_func()->url, d_func()->m_range.castToSimpleRange()); } void Problem::setFinalLocation(const DocumentRange& location) { setRange(RangeInRevision::castFromSimpleRange(location)); d_func_dynamic()->url = location.document; } void Problem::clearDiagnostics() { m_diagnostics.clear(); // keep serialization in sync, see also LocalIndexedProblem ctor above d_func_dynamic()->diagnosticsList().clear(); } QVector Problem::diagnostics() const { QVector vector; foreach(ProblemPointer ptr, m_diagnostics) { vector.push_back(ptr); } return vector; } void Problem::setDiagnostics(const QVector &diagnostics) { clearDiagnostics(); - foreach(IProblem::Ptr problem, diagnostics) + foreach(const IProblem::Ptr& problem, diagnostics) { addDiagnostic(problem); } } void Problem::addDiagnostic(const IProblem::Ptr &diagnostic) { Problem *problem = dynamic_cast(diagnostic.data()); Q_ASSERT(problem != NULL); ProblemPointer ptr(problem); m_diagnostics << ptr; } QString Problem::description() const { return d_func()->description.str(); } void Problem::setDescription(const QString& description) { d_func_dynamic()->description = IndexedString(description); } QString Problem::explanation() const { return d_func()->explanation.str(); } void Problem::setExplanation(const QString& explanation) { d_func_dynamic()->explanation = IndexedString(explanation); } IProblem::Source Problem::source() const { return d_func()->source; } void Problem::setSource(IProblem::Source source) { d_func_dynamic()->source = source; } QExplicitlySharedDataPointer Problem::solutionAssistant() const { return {}; } IProblem::Severity Problem::severity() const { return d_func()->severity; } void Problem::setSeverity(Severity severity) { d_func_dynamic()->severity = severity; } QString Problem::severityString() const { switch(severity()) { case IProblem::Error: return i18n("Error"); case IProblem::Warning: return i18n("Warning"); case IProblem::Hint: return i18n("Hint"); } return QString(); } QString Problem::sourceString() const { switch (source()) { case IProblem::Disk: return i18n("Disk"); case IProblem::Preprocessor: return i18n("Preprocessor"); case IProblem::Lexer: return i18n("Lexer"); case IProblem::Parser: return i18n("Parser"); case IProblem::DUChainBuilder: return i18n("Definition-Use Chain"); case IProblem::SemanticAnalysis: return i18n("Semantic analysis"); case IProblem::ToDo: return i18n("To-do"); case IProblem::Unknown: default: return i18n("Unknown"); } } QString Problem::toString() const { return i18nc(": in :[]: (found by )", "%1: %2 in %3:[(%4,%5),(%6,%7)]: %8 (found by %9)" , severityString() , description() , url().str() , range().start.line , range().start.column , range().end.line , range().end.column , (explanation().isEmpty() ? i18n("") : explanation()) , sourceString()); } void Problem::rebuildDynamicData(DUContext* parent, uint ownIndex) { auto top = dynamic_cast(parent); Q_ASSERT(top); m_topContext = top; m_indexInTopContext = ownIndex; // deserialize child diagnostics here, as the top-context might get unloaded // but we still want to keep the child-diagnostics in-tact, as one would assume // a shared-ptr works. const auto data = d_func(); m_diagnostics.reserve(data->diagnosticsSize()); for (uint i = 0; i < data->diagnosticsSize(); ++i) { m_diagnostics << ProblemPointer(data->diagnostics()[i].data(top)); } DUChainBase::rebuildDynamicData(parent, ownIndex); } QDebug operator<<(QDebug s, const Problem& problem) { s.nospace() << problem.toString(); return s.space(); } QDebug operator<<(QDebug s, const ProblemPointer& problem) { s.nospace() << problem->toString(); return s.space(); } diff --git a/language/duchain/tests/test_duchain.cpp b/language/duchain/tests/test_duchain.cpp index a3f97213b..3e29d2c22 100644 --- a/language/duchain/tests/test_duchain.cpp +++ b/language/duchain/tests/test_duchain.cpp @@ -1,1034 +1,1034 @@ /* * This file is part of KDevelop * * Copyright 2011-2013 Milian Wolff * Copyright 2006 Hamish Rodda * Copyright 2007-2009 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "test_duchain.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // #include #include #include #include // needed for std::insert_iterator on windows #include //Extremely slow // #define TEST_NORMAL_IMPORTS QTEST_MAIN(TestDUChain) using namespace KDevelop; using namespace Utils; typedef BasicSetRepository::Index Index; struct Timer { Timer() { m_timer.start(); } qint64 elapsed() { return m_timer.nsecsElapsed(); } QElapsedTimer m_timer; }; void TestDUChain::initTestCase() { AutoTestShell::init(); TestCore::initialize(Core::NoUi); DUChain::self()->disablePersistentStorage(); CodeRepresentation::setDiskChangesForbidden(true); } void TestDUChain::cleanupTestCase() { TestCore::shutdown(); } #ifndef Q_OS_WIN void TestDUChain::testStringSets() { const unsigned int setCount = 8; const unsigned int choiceCount = 40; const unsigned int itemCount = 120; - BasicSetRepository rep("test repository"); + BasicSetRepository rep(QStringLiteral("test repository")); // qDebug() << "Start repository-layout: \n" << rep.dumpDotGraph(); qint64 repositoryTime = 0; //Time spent on repository-operations qint64 genericTime = 0; //Time spend on equivalent operations with generic sets qint64 repositoryIntersectionTime = 0; //Time spent on repository-operations qint64 genericIntersectionTime = 0; //Time spend on equivalent operations with generic sets qint64 qsetIntersectionTime = 0; //Time spend on equivalent operations with generic sets qint64 repositoryUnionTime = 0; //Time spent on repository-operations qint64 genericUnionTime = 0; //Time spend on equivalent operations with generic sets qint64 repositoryDifferenceTime = 0; //Time spent on repository-operations qint64 genericDifferenceTime = 0; //Time spend on equivalent operations with generic sets Set sets[setCount]; std::set realSets[setCount]; for(unsigned int a = 0; a < setCount; a++) { std::set chosenIndices; unsigned int thisCount = rand() % choiceCount; if(thisCount == 0) thisCount = 1; for(unsigned int b = 0; b < thisCount; b++) { Index choose = (rand() % itemCount) + 1; while(chosenIndices.find(choose) != chosenIndices.end()) { choose = (rand() % itemCount) + 1; } Timer t; chosenIndices.insert(chosenIndices.end(), choose); genericTime += t.elapsed(); } { Timer t; sets[a] = rep.createSet(chosenIndices); repositoryTime += t.elapsed(); } realSets[a] = chosenIndices; std::set tempSet = sets[a].stdSet(); if(tempSet != realSets[a]) { - QString dbg = "created set: "; + QString dbg = QStringLiteral("created set: "); for(std::set::const_iterator it = realSets[a].begin(); it != realSets[a].end(); ++it) dbg += QStringLiteral("%1 ").arg(*it); qDebug() << dbg; - dbg = "repo. set: "; + dbg = QStringLiteral("repo. set: "); for(std::set::const_iterator it = tempSet.begin(); it != tempSet.end(); ++it) dbg += QStringLiteral("%1 ").arg(*it); qDebug() << dbg; qDebug() << "DOT-Graph:\n\n" << sets[a].dumpDotGraph() << "\n\n"; QFAIL("sets are not the same!"); } } for(int cycle = 0; cycle < 100; ++cycle) { if(cycle % 10 == 0) qDebug() << "cycle" << cycle; for(unsigned int a = 0; a < setCount; a++) { for(unsigned int b = 0; b < setCount; b++) { /// ----- SUBTRACTION/DIFFERENCE std::set _realDifference; { Timer t; std::set_difference(realSets[a].begin(), realSets[a].end(), realSets[b].begin(), realSets[b].end(), std::insert_iterator >(_realDifference, _realDifference.begin())); genericDifferenceTime += t.elapsed(); } Set _difference; { Timer t; _difference = sets[a] - sets[b]; repositoryDifferenceTime += t.elapsed(); } if(_difference.stdSet() != _realDifference) { { qDebug() << "SET a:"; - QString dbg = ""; + QString dbg = QLatin1String(""); for(std::set::const_iterator it = realSets[a].begin(); it != realSets[a].end(); ++it) dbg += QStringLiteral("%1 ").arg(*it); qDebug() << dbg; qDebug() << "DOT-Graph:\n\n" << sets[a].dumpDotGraph() << "\n\n"; } { qDebug() << "SET b:"; - QString dbg = ""; + QString dbg = QLatin1String(""); for(std::set::const_iterator it = realSets[b].begin(); it != realSets[b].end(); ++it) dbg += QStringLiteral("%1 ").arg(*it); qDebug() << dbg; qDebug() << "DOT-Graph:\n\n" << sets[b].dumpDotGraph() << "\n\n"; } { std::set tempSet = _difference.stdSet(); qDebug() << "SET difference:"; - QString dbg = "real set: "; + QString dbg = QStringLiteral("real set: "); for(std::set::const_iterator it = _realDifference.begin(); it != _realDifference.end(); ++it) dbg += QStringLiteral("%1 ").arg(*it); qDebug() << dbg; - dbg = "repo. set: "; + dbg = QStringLiteral("repo. set: "); for(std::set::const_iterator it = tempSet.begin(); it != tempSet.end(); ++it) dbg += QStringLiteral("%1 ").arg(*it); qDebug() << dbg; qDebug() << "DOT-Graph:\n\n" << _difference.dumpDotGraph() << "\n\n"; } QFAIL("difference sets are not the same!"); } /// ------ UNION std::set _realUnion; { Timer t; std::set_union(realSets[a].begin(), realSets[a].end(), realSets[b].begin(), realSets[b].end(), std::insert_iterator >(_realUnion, _realUnion.begin())); genericUnionTime += t.elapsed(); } Set _union; { Timer t; _union = sets[a] + sets[b]; repositoryUnionTime += t.elapsed(); } if(_union.stdSet() != _realUnion) { { qDebug() << "SET a:"; - QString dbg = ""; + QString dbg; for(std::set::const_iterator it = realSets[a].begin(); it != realSets[a].end(); ++it) dbg += QStringLiteral("%1 ").arg(*it); qDebug() << dbg; qDebug() << "DOT-Graph:\n\n" << sets[a].dumpDotGraph() << "\n\n"; } { qDebug() << "SET b:"; - QString dbg = ""; + QString dbg; for(std::set::const_iterator it = realSets[b].begin(); it != realSets[b].end(); ++it) dbg += QStringLiteral("%1 ").arg(*it); qDebug() << dbg; qDebug() << "DOT-Graph:\n\n" << sets[b].dumpDotGraph() << "\n\n"; } { std::set tempSet = _union.stdSet(); qDebug() << "SET union:"; - QString dbg = "real set: "; + QString dbg = QStringLiteral("real set: "); for(std::set::const_iterator it = _realUnion.begin(); it != _realUnion.end(); ++it) dbg += QStringLiteral("%1 ").arg(*it); qDebug() << dbg; - dbg = "repo. set: "; + dbg = QStringLiteral("repo. set: "); for(std::set::const_iterator it = tempSet.begin(); it != tempSet.end(); ++it) dbg += QStringLiteral("%1 ").arg(*it); qDebug() << dbg; qDebug() << "DOT-Graph:\n\n" << _union.dumpDotGraph() << "\n\n"; } QFAIL("union sets are not the same"); } std::set _realIntersection; /// -------- INTERSECTION { Timer t; std::set_intersection(realSets[a].begin(), realSets[a].end(), realSets[b].begin(), realSets[b].end(), std::insert_iterator >(_realIntersection, _realIntersection.begin())); genericIntersectionTime += t.elapsed(); } //Just for fun: Test how fast QSet intersections are QSet first, second; for(std::set::const_iterator it = realSets[a].begin(); it != realSets[a].end(); ++it) { first.insert(*it); } for(std::set::const_iterator it = realSets[b].begin(); it != realSets[b].end(); ++it) { second.insert(*it); } { Timer t; QSet i = first.intersect(second); qsetIntersectionTime += t.elapsed(); } Set _intersection; { Timer t; _intersection = sets[a] & sets[b]; repositoryIntersectionTime += t.elapsed(); } if(_intersection.stdSet() != _realIntersection) { { qDebug() << "SET a:"; - QString dbg = ""; + QString dbg; for(std::set::const_iterator it = realSets[a].begin(); it != realSets[a].end(); ++it) dbg += QStringLiteral("%1 ").arg(*it); qDebug() << dbg; qDebug() << "DOT-Graph:\n\n" << sets[a].dumpDotGraph() << "\n\n"; } { qDebug() << "SET b:"; - QString dbg = ""; + QString dbg; for(std::set::const_iterator it = realSets[b].begin(); it != realSets[b].end(); ++it) dbg += QStringLiteral("%1 ").arg(*it); qDebug() << dbg; qDebug() << "DOT-Graph:\n\n" << sets[b].dumpDotGraph() << "\n\n"; } { std::set tempSet = _intersection.stdSet(); qDebug() << "SET intersection:"; - QString dbg = "real set: "; + QString dbg = QStringLiteral("real set: "); for(std::set::const_iterator it = _realIntersection.begin(); it != _realIntersection.end(); ++it) dbg += QStringLiteral("%1 ").arg(*it); qDebug() << dbg; - dbg = "repo. set: "; + dbg = QStringLiteral("repo. set: "); for(std::set::const_iterator it = tempSet.begin(); it != tempSet.end(); ++it) dbg += QStringLiteral("%1 ").arg(*it); qDebug() << dbg; qDebug() << "DOT-Graph:\n\n" << _intersection.dumpDotGraph() << "\n\n"; } QFAIL("intersection sets are not the same"); } } } qDebug() << "cycle " << cycle; qDebug() << "ns needed for set-building: repository-set: " << float(repositoryTime) << " generic-set: " << float(genericTime); qDebug() << "ns needed for intersection: repository-sets: " << float(repositoryIntersectionTime) << " generic-set: " << float(genericIntersectionTime) << " QSet: " << float(qsetIntersectionTime); qDebug() << "ns needed for union: repository-sets: " << float(repositoryUnionTime) << " generic-set: " << float(genericUnionTime); qDebug() << "ns needed for difference: repository-sets: " << float(repositoryDifferenceTime) << " generic-set: " << float(genericDifferenceTime); } } #endif void TestDUChain::testSymbolTableValid() { DUChainReadLocker lock(DUChain::lock()); PersistentSymbolTable::self().dump(QTextStream(stdout)); } void TestDUChain::testIndexedStrings() { int testCount = 600000; QHash knownIndices; int a = 0; for(a = 0; a < testCount; ++a) { QString testString; int length = rand() % 10; for(int b = 0; b < length; ++b) testString.append((char)(rand() % 6) + 'a'); QByteArray array = testString.toUtf8(); //qDebug() << "checking with" << testString; //qDebug() << "checking" << a; IndexedString indexed(array.constData(), array.size(), IndexedString::hashString(array.constData(), array.size())); QCOMPARE(indexed.str(), testString); if(knownIndices.contains(testString)) { QCOMPARE(indexed.index(), knownIndices[testString].index()); } else { knownIndices[testString] = indexed; } if(a % (testCount/10) == 0) qDebug() << a << "of" << testCount; } qDebug() << a << "successful tests"; } struct TestContext { TestContext() { static int number = 0; ++number; DUChainWriteLocker lock(DUChain::lock()); m_context = new TopDUContext(IndexedString(QStringLiteral("/test1/%1").arg(number)), RangeInRevision()); m_normalContext = new DUContext(RangeInRevision(), m_context); DUChain::self()->addDocumentChain(m_context); Q_ASSERT(IndexedDUContext(m_context).context() == m_context); } ~TestContext() { foreach(TestContext* importer, importers) importer->unImport(QList() << this); unImport(imports); DUChainWriteLocker lock(DUChain::lock()); TopDUContextPointer tp(m_context); DUChain::self()->removeDocumentChain(static_cast(m_context)); Q_ASSERT(!tp); } void verify(QList allContexts) { { DUChainReadLocker lock(DUChain::lock()); QCOMPARE(m_context->importedParentContexts().count(), imports.count()); } //Compute a closure of all children, and verify that they are imported. QSet collected; collectImports(collected); collected.remove(this); DUChainReadLocker lock(DUChain::lock()); foreach(TestContext* context, collected) { QVERIFY(m_context->imports(context->m_context, CursorInRevision::invalid())); #ifdef TEST_NORMAL_IMPORTS QVERIFY(m_normalContext->imports(context->m_normalContext)); #endif } //Verify that no other contexts are imported foreach(TestContext* context, allContexts) if(context != this) { QVERIFY(collected.contains(context) || !m_context->imports(context->m_context, CursorInRevision::invalid())); #ifdef TEST_NORMAL_IMPORTS QVERIFY(collected.contains(context) || !m_normalContext->imports(context->m_normalContext, CursorInRevision::invalid())); #endif } } void collectImports(QSet& collected) { if(collected.contains(this)) return; collected.insert(this); foreach(TestContext* context, imports) context->collectImports(collected); } void import(TestContext* ctx) { if(imports.contains(ctx) || ctx == this) return; imports << ctx; ctx->importers << this; DUChainWriteLocker lock(DUChain::lock()); m_context->addImportedParentContext(ctx->m_context); #ifdef TEST_NORMAL_IMPORTS m_normalContext->addImportedParentContext(ctx->m_normalContext); #endif } void unImport(QList ctxList) { QList list; QList normalList; foreach(TestContext* ctx, ctxList) { if(!imports.contains(ctx)) continue; list << ctx->m_context; normalList << ctx->m_normalContext; imports.removeAll(ctx); ctx->importers.removeAll(this); } DUChainWriteLocker lock(DUChain::lock()); m_context->removeImportedParentContexts(list); #ifdef TEST_NORMAL_IMPORTS foreach(DUContext* ctx, normalList) m_normalContext->removeImportedParentContext(ctx); #endif } void clearImports() { { DUChainWriteLocker lock(DUChain::lock()); m_context->clearImportedParentContexts(); m_normalContext->clearImportedParentContexts(); } foreach(TestContext* ctx, imports) { imports.removeAll(ctx); ctx->importers.removeAll(this); } } QList imports; private: TopDUContext* m_context; DUContext* m_normalContext; QList importers; }; void collectReachableNodes(QSet& reachableNodes, uint currentNode) { if(!currentNode) return; reachableNodes.insert(currentNode); const Utils::SetNodeData* node = KDevelop::RecursiveImportRepository::repository()->nodeFromIndex(currentNode); Q_ASSERT(node); collectReachableNodes(reachableNodes, node->leftNode()); collectReachableNodes(reachableNodes, node->rightNode()); } uint collectNaiveNodeCount(uint currentNode) { if(!currentNode) return 0; uint ret = 1; const Utils::SetNodeData* node = KDevelop::RecursiveImportRepository::repository()->nodeFromIndex(currentNode); Q_ASSERT(node); ret += collectNaiveNodeCount(node->leftNode()); ret += collectNaiveNodeCount(node->rightNode()); return ret; } void TestDUChain::testImportStructure() { Timer total; qDebug() << "before: " << KDevelop::RecursiveImportRepository::repository()->getDataRepository().statistics().print(); ///Maintains a naive import-structure along with a real top-context import structure, and allows comparing both. int cycles = 5; //int cycles = 100; //srand(time(NULL)); for(int t = 0; t < cycles; ++t) { QList allContexts; //Create a random structure int contextCount = 50; int verifyOnceIn = contextCount/*((contextCount*contextCount)/20)+1*/; //Verify once in every chances(not in all cases, because else the import-structure isn't built on-demand!) int clearOnceIn = contextCount; for(int a = 0; a < contextCount; a++) allContexts << new TestContext(); for(int c = 0; c < cycles; ++c) { //qDebug() << "main-cycle" << t << "sub-cycle" << c; //Add random imports and compare for(int a = 0; a < contextCount; a++) { //Import up to 5 random other contexts into each context int importCount = rand() % 5; //qDebug() << "cnt> " << importCount; for(int i = 0; i < importCount; ++i) { //int importNr = rand() % contextCount; //qDebug() << "nmr > " << importNr; //allContexts[a]->import(allContexts[importNr]); allContexts[a]->import(allContexts[rand() % contextCount]); } for(int b = 0; b < contextCount; b++) if(rand() % verifyOnceIn == 0) allContexts[b]->verify(allContexts); } //Remove random imports and compare for(int a = 0; a < contextCount; a++) { //Import up to 5 random other contexts into each context int removeCount = rand() % 3; QSet removeImports; for(int i = 0; i < removeCount; ++i) - if(allContexts[a]->imports.count()) + if(!allContexts[a]->imports.isEmpty()) removeImports.insert(allContexts[a]->imports[rand() % allContexts[a]->imports.count()]); allContexts[a]->unImport(removeImports.toList()); for(int b = 0; b < contextCount; b++) if(rand() % verifyOnceIn == 0) allContexts[b]->verify(allContexts); } for(int a = 0; a < contextCount; a++) { if(rand() % clearOnceIn == 0) { allContexts[a]->clearImports(); allContexts[a]->verify(allContexts); } } } qDebug() << "after: " << KDevelop::RecursiveImportRepository::repository()->getDataRepository().statistics().print(); for(int a = 0; a < contextCount; ++a) delete allContexts[a]; allContexts.clear(); qDebug() << "after cleanup: " << KDevelop::RecursiveImportRepository::repository()->getDataRepository().statistics().print(); } qDebug() << "total ns needed for import-structure test:" << float(total.elapsed()); } class TestWorker : public QObject { Q_OBJECT public slots: void lockForWrite() { for(int i = 0; i < 10000; ++i) { DUChainWriteLocker lock; } } void lockForRead() { for(int i = 0; i < 10000; ++i) { DUChainReadLocker lock; } } void lockForReadWrite() { for(int i = 0; i < 10000; ++i) { { DUChainReadLocker lock; } { DUChainWriteLocker lock; } } } static QSharedPointer createWorkerThread(const char* workerSlot) { QThread* thread = new QThread; TestWorker* worker = new TestWorker; connect(thread, SIGNAL(started()), worker, workerSlot); connect(thread, &QThread::finished, worker, &TestWorker::deleteLater); worker->moveToThread(thread); return QSharedPointer(thread); } }; class ThreadList : public QVector< QSharedPointer > { public: bool join(int timeout) { foreach(const QSharedPointer& thread, *this) { // quit event loop Q_ASSERT(thread->isRunning()); thread->quit(); // wait for finish if (!thread->wait(timeout)) { return false; } Q_ASSERT(thread->isFinished()); } return true; } void start() { foreach(const QSharedPointer& thread, *this) { thread->start(); } } }; void TestDUChain::testLockForWrite() { ThreadList threads; for(int i = 0; i < 10; ++i) { threads << TestWorker::createWorkerThread(SLOT(lockForWrite())); } threads.start(); QBENCHMARK { { DUChainWriteLocker lock; } { DUChainReadLocker lock; } } QVERIFY(threads.join(1000)); } void TestDUChain::testLockForRead() { ThreadList threads; for(int i = 0; i < 10; ++i) { threads << TestWorker::createWorkerThread(SLOT(lockForRead())); } threads.start(); QBENCHMARK { DUChainReadLocker lock; } QVERIFY(threads.join(1000)); } void TestDUChain::testLockForReadWrite() { ThreadList threads; for(int i = 0; i < 10; ++i) { threads << TestWorker::createWorkerThread(SLOT(lockForReadWrite())); } threads.start(); QBENCHMARK { DUChainWriteLocker lock; } QVERIFY(threads.join(1000)); } void TestDUChain::testProblemSerialization() { DUChain::self()->disablePersistentStorage(false); auto parent = ProblemPointer{new Problem}; - parent->setDescription("parent"); + parent->setDescription(QStringLiteral("parent")); auto child = ProblemPointer{new Problem}; - child->setDescription("child"); + child->setDescription(QStringLiteral("child")); parent->addDiagnostic(child); const IndexedString url("/my/test/file"); TopDUContextPointer smartTop; { // serialize DUChainWriteLocker lock; auto file = new ParsingEnvironmentFile(url); auto top = new TopDUContext(url, {}, file); top->addProblem(parent); QCOMPARE(top->problems().size(), 1); - auto p = top->problems().first(); + auto p = top->problems().at(0); QCOMPARE(p->description(), QStringLiteral("parent")); QCOMPARE(p->diagnostics().size(), 1); auto c = p->diagnostics().first(); QCOMPARE(c->description(), QStringLiteral("child")); DUChain::self()->addDocumentChain(top); QVERIFY(DUChain::self()->chainForDocument(url)); smartTop = top; } DUChain::self()->storeToDisk(); ProblemPointer parent_deserialized; IProblem::Ptr child_deserialized; { // deserialize DUChainWriteLocker lock; QVERIFY(!smartTop); auto top = DUChain::self()->chainForDocument(url); QVERIFY(top); smartTop = top; QCOMPARE(top->problems().size(), 1); - parent_deserialized = top->problems().first(); + parent_deserialized = top->problems().at(0); QCOMPARE(parent_deserialized->diagnostics().size(), 1); child_deserialized = parent_deserialized->diagnostics().first(); QCOMPARE(parent_deserialized->description(), QStringLiteral("parent")); QCOMPARE(child_deserialized->description(), QStringLiteral("child")); top->clearProblems(); QVERIFY(top->problems().isEmpty()); QCOMPARE(parent_deserialized->description(), QStringLiteral("parent")); QCOMPARE(child_deserialized->description(), QStringLiteral("child")); DUChain::self()->removeDocumentChain(top); QCOMPARE(parent_deserialized->description(), QStringLiteral("parent")); QCOMPARE(child_deserialized->description(), QStringLiteral("child")); QVERIFY(!smartTop); } DUChain::self()->disablePersistentStorage(true); QCOMPARE(parent->description(), QStringLiteral("parent")); QCOMPARE(child->description(), QStringLiteral("child")); QCOMPARE(parent_deserialized->description(), QStringLiteral("parent")); QCOMPARE(child_deserialized->description(), QStringLiteral("child")); parent->clearDiagnostics(); QVERIFY(parent->diagnostics().isEmpty()); } void TestDUChain::testIdentifiers() { - QualifiedIdentifier aj("::Area::jump"); + QualifiedIdentifier aj(QStringLiteral("::Area::jump")); QCOMPARE(aj.count(), 2); QCOMPARE(aj.explicitlyGlobal(), true); - QCOMPARE(aj.at(0), Identifier("Area")); - QCOMPARE(aj.at(1), Identifier("jump")); + QCOMPARE(aj.at(0), Identifier(QStringLiteral("Area"))); + QCOMPARE(aj.at(1), Identifier(QStringLiteral("jump"))); - QualifiedIdentifier aj2 = QualifiedIdentifier("Area::jump"); + QualifiedIdentifier aj2 = QualifiedIdentifier(QStringLiteral("Area::jump")); QCOMPARE(aj2.count(), 2); QCOMPARE(aj2.explicitlyGlobal(), false); - QCOMPARE(aj2.at(0), Identifier("Area")); - QCOMPARE(aj2.at(1), Identifier("jump")); + QCOMPARE(aj2.at(0), Identifier(QStringLiteral("Area"))); + QCOMPARE(aj2.at(1), Identifier(QStringLiteral("jump"))); QVERIFY(aj != aj2); - QVERIFY(QualifiedIdentifier("") == QualifiedIdentifier()); - QVERIFY(QualifiedIdentifier("").index() == QualifiedIdentifier().index()); + QVERIFY(QualifiedIdentifier(QLatin1String("")) == QualifiedIdentifier()); + QVERIFY(QualifiedIdentifier(QLatin1String("")).index() == QualifiedIdentifier().index()); - QualifiedIdentifier ajt("Area::jump::test"); - QualifiedIdentifier jt("jump::test"); - QualifiedIdentifier ajt2("Area::jump::tes"); + QualifiedIdentifier ajt(QStringLiteral("Area::jump::test")); + QualifiedIdentifier jt(QStringLiteral("jump::test")); + QualifiedIdentifier ajt2(QStringLiteral("Area::jump::tes")); - QualifiedIdentifier t(" Area::jump ::tes"); + QualifiedIdentifier t(QStringLiteral(" Area::jump ::tes")); QCOMPARE(t.count(), 3); QCOMPARE(t.at(0).templateIdentifiersCount(), 2u); QCOMPARE(t.at(1).templateIdentifiersCount(), 1u); QCOMPARE(t.at(2).templateIdentifiersCount(), 1u); QCOMPARE(t.at(0).identifier().str(), QStringLiteral("Area")); QCOMPARE(t.at(1).identifier().str(), QStringLiteral("jump")); QCOMPARE(t.at(2).identifier().str(), QStringLiteral("tes")); - QualifiedIdentifier op1("operator<"); - QualifiedIdentifier op2("operator<="); - QualifiedIdentifier op3("operator>"); - QualifiedIdentifier op4("operator>="); - QualifiedIdentifier op5("operator()"); - QualifiedIdentifier op6("operator( )"); + QualifiedIdentifier op1(QStringLiteral("operator<")); + QualifiedIdentifier op2(QStringLiteral("operator<=")); + QualifiedIdentifier op3(QStringLiteral("operator>")); + QualifiedIdentifier op4(QStringLiteral("operator>=")); + QualifiedIdentifier op5(QStringLiteral("operator()")); + QualifiedIdentifier op6(QStringLiteral("operator( )")); QCOMPARE(op1.count(), 1); QCOMPARE(op2.count(), 1); QCOMPARE(op3.count(), 1); QCOMPARE(op4.count(), 1); QCOMPARE(op5.count(), 1); QCOMPARE(op6.count(), 1); QCOMPARE(op4.toString(), QStringLiteral("operator>=")); QCOMPARE(op3.toString(), QStringLiteral("operator>")); QCOMPARE(op1.toString(), QStringLiteral("operator<")); QCOMPARE(op2.toString(), QStringLiteral("operator<=")); QCOMPARE(op5.toString(), QStringLiteral("operator()")); QCOMPARE(op6.toString(), QStringLiteral("operator( )")); - QCOMPARE(QualifiedIdentifier("Area::jump ::tes").index(), t.index()); - QCOMPARE(op4.index(), QualifiedIdentifier("operator>=").index()); + QCOMPARE(QualifiedIdentifier(QStringLiteral("Area::jump ::tes")).index(), t.index()); + QCOMPARE(op4.index(), QualifiedIdentifier(QStringLiteral("operator>=")).index()); - QualifiedIdentifier pushTest("foo"); + QualifiedIdentifier pushTest(QStringLiteral("foo")); QCOMPARE(pushTest.count(), 1); QCOMPARE(pushTest.toString(), QStringLiteral("foo")); - pushTest.push(Identifier("bar")); + pushTest.push(Identifier(QStringLiteral("bar"))); QCOMPARE(pushTest.count(), 2); QCOMPARE(pushTest.toString(), QStringLiteral("foo::bar")); - pushTest.push(QualifiedIdentifier("baz::asdf")); + pushTest.push(QualifiedIdentifier(QStringLiteral("baz::asdf"))); QCOMPARE(pushTest.count(), 4); QCOMPARE(pushTest.toString(), QStringLiteral("foo::bar::baz::asdf")); - QualifiedIdentifier mergeTest = pushTest.merge(QualifiedIdentifier("meh::muh")); + QualifiedIdentifier mergeTest = pushTest.merge(QualifiedIdentifier(QStringLiteral("meh::muh"))); QCOMPARE(mergeTest.count(), 6); QCOMPARE(mergeTest.toString(), QStringLiteral("meh::muh::foo::bar::baz::asdf")); - QualifiedIdentifier plusTest = QualifiedIdentifier("la::lu") + QualifiedIdentifier("ba::bu"); + QualifiedIdentifier plusTest = QualifiedIdentifier(QStringLiteral("la::lu")) + QualifiedIdentifier(QStringLiteral("ba::bu")); QCOMPARE(plusTest.count(), 4); QCOMPARE(plusTest.toString(), QStringLiteral("la::lu::ba::bu")); ///@todo create a big randomized test for the identifier repository(check that indices are the same) } #if 0 ///NOTE: the "unit tests" below are not automated, they - so far - require /// human interpretation which is not useful for a unit test! /// someone should investigate what the expected output should be /// and add proper QCOMPARE/QVERIFY checks accordingly ///FIXME: this needs to be rewritten in order to remove dependencies on formerly run unit tests void TestDUChain::testImportCache() { KDevelop::globalItemRepositoryRegistry().printAllStatistics(); KDevelop::RecursiveImportRepository::repository()->printStatistics(); //Analyze the whole existing import-cache //This is very expensive, since it involves loading all existing top-contexts uint topContextCount = DUChain::self()->newTopContextIndex(); uint analyzedCount = 0; uint totalImportCount = 0; uint naiveNodeCount = 0; QSet reachableNodes; DUChainReadLocker lock(DUChain::lock()); for(uint a = 0; a < topContextCount; ++a) { if(a % qMax(1u, topContextCount / 100) == 0) { qDebug() << "progress:" << (a * 100) / topContextCount; } TopDUContext* context = DUChain::self()->chainForIndex(a); if(context) { TopDUContext::IndexedRecursiveImports imports = context->recursiveImportIndices(); ++analyzedCount; totalImportCount += imports.set().count(); collectReachableNodes(reachableNodes, imports.setIndex()); naiveNodeCount += collectNaiveNodeCount(imports.setIndex()); } } QVERIFY(analyzedCount); qDebug() << "average total count of imports:" << totalImportCount / analyzedCount; qDebug() << "count of reachable nodes:" << reachableNodes.size(); qDebug() << "naive node-count:" << naiveNodeCount << "sharing compression factor:" << ((float)reachableNodes.size()) / ((float)naiveNodeCount); } #endif void TestDUChain::benchCodeModel() { const IndexedString file("testFile"); QVERIFY(!QTypeInfo< KDevelop::CodeModelItem >::isStatic); int i = 0; QBENCHMARK { CodeModel::self().addItem(file, QualifiedIdentifier("testQID" + QString::number(i++)), KDevelop::CodeModelItem::Class); } } void TestDUChain::benchTypeRegistry() { IntegralTypeData data; data.m_dataType = IntegralType::TypeInt; data.typeClassId = IntegralType::Identity; data.inRepository = false; data.m_modifiers = 42; data.m_dynamic = false; data.refCount = 1; IntegralTypeData to; QFETCH(int, func); QBENCHMARK { switch(func) { case 0: TypeSystem::self().dataClassSize(data); break; case 1: TypeSystem::self().dynamicSize(data); break; case 2: TypeSystem::self().create(&data); break; case 3: TypeSystem::self().isFactoryLoaded(data); break; case 4: TypeSystem::self().copy(data, to, !data.m_dynamic); break; case 5: TypeSystem::self().copy(data, to, data.m_dynamic); break; case 6: TypeSystem::self().callDestructor(&data); break; } } } void TestDUChain::benchTypeRegistry_data() { QTest::addColumn("func"); QTest::newRow("dataClassSize") << 0; QTest::newRow("dynamicSize") << 1; QTest::newRow("create") << 2; QTest::newRow("isFactoryLoaded") << 3; QTest::newRow("copy") << 4; QTest::newRow("copyNonDynamic") << 5; QTest::newRow("callDestructor") << 6; } void TestDUChain::benchDuchainReadLocker() { QBENCHMARK { DUChainReadLocker lock; } } void TestDUChain::benchDuchainWriteLocker() { QBENCHMARK { DUChainWriteLocker lock; } } void TestDUChain::benchDUChainItemFactory_copy() { DUChainItemFactory factory; DeclarationData from, to; from.classId = Declaration::Identity; QFETCH(int, constant); bool c = constant; QBENCHMARK { factory.copy(from, to, c); if (constant == 2) { c = !c; } } } void TestDUChain::benchDUChainItemFactory_copy_data() { QTest::addColumn("constant"); QTest::newRow("non-const") << 0; QTest::newRow("const") << 1; QTest::newRow("flip") << 2; } void TestDUChain::benchDeclarationQualifiedIdentifier() { QVector contexts; contexts.reserve(10); DUChainWriteLocker lock; contexts << new TopDUContext(IndexedString("/tmp/something"), {0, 0, INT_MAX, INT_MAX}); for (int i = 1; i < contexts.capacity(); ++i) { contexts << new DUContext({0, 0, INT_MAX, INT_MAX}, contexts.at(i-1)); contexts.last()->setLocalScopeIdentifier(QualifiedIdentifier(QString::number(i))); } auto dec = new Declaration({0, 0, 0, 1}, contexts.last()); dec->setIdentifier(Identifier(QStringLiteral("myDecl"))); qDebug() << "start benchmark!"; qint64 count = 0; QBENCHMARK { count += dec->qualifiedIdentifier().count(); } QVERIFY(count > 0); } #include "test_duchain.moc" #include "moc_test_duchain.cpp" diff --git a/language/duchain/tests/test_duchainshutdown.cpp b/language/duchain/tests/test_duchainshutdown.cpp index ea090e18c..7f82aaa34 100644 --- a/language/duchain/tests/test_duchainshutdown.cpp +++ b/language/duchain/tests/test_duchainshutdown.cpp @@ -1,99 +1,99 @@ /* * 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 Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "test_duchainshutdown.h" #include #include #include #include #include #include using namespace KDevelop; void TestDUChainShutdown::initTestCase() { AutoTestShell::init(); m_core = TestCore::initialize(Core::NoUi); } void TestDUChainShutdown::cleanupTestCase() { TestCore::shutdown(); } void TestDUChainShutdown::runTest() { - const QString ctxId = "foo::bar::asdf"; - const QString path = "/foo/myurl"; - const QString myLang = "fooLang"; + const QString ctxId = QStringLiteral("foo::bar::asdf"); + const QString path = QStringLiteral("/foo/myurl"); + const QString myLang = QStringLiteral("fooLang"); // step 1, store a bunch of data in the repository IndexedTopDUContext idxTop; IndexedDUContext idxCtx; { DUChainWriteLocker lock; auto file = new ParsingEnvironmentFile(IndexedString(path)); file->setLanguage(IndexedString(myLang)); ReferencedTopDUContext top(new TopDUContext(IndexedString(path), RangeInRevision(1, 2, 3, 4), file)); DUChain::self()->addDocumentChain(top); idxTop = IndexedTopDUContext(top); QVERIFY(idxTop.isValid()); QVERIFY(idxTop.isLoaded()); auto ctx = new DUContext(RangeInRevision(1, 2, 2, 3), top); ctx->setLocalScopeIdentifier(QualifiedIdentifier(ctxId)); QCOMPARE(top->childContexts().size(), 1); idxCtx = IndexedDUContext(ctx); QVERIFY(idxCtx.isValid()); } // shutdown and reinitialize - this should not crash :) m_core->setShuttingDown(true); DUChain::self()->shutdown(); m_core->setShuttingDown(false); DUChain::self()->initialize(); { DUChainReadLocker lock; // now verify that the data was properly restored QVERIFY(!idxTop.isLoaded()); QVERIFY(idxTop.isValid()); ReferencedTopDUContext top(idxTop.data()); QVERIFY(top); QVERIFY(idxTop.isLoaded()); QCOMPARE(top->childContexts().size(), 1); QCOMPARE(top->childContexts().first()->localScopeIdentifier().toString(), QStringLiteral("foo::bar::asdf")); QCOMPARE(idxCtx.data(), top->childContexts().first()); } { DUChainWriteLocker lock; DUChain::self()->removeDocumentChain(idxTop.data()); } } QTEST_MAIN(TestDUChainShutdown) diff --git a/language/duchain/tests/test_identifier.cpp b/language/duchain/tests/test_identifier.cpp index b5de64ce2..57f31f2bb 100644 --- a/language/duchain/tests/test_identifier.cpp +++ b/language/duchain/tests/test_identifier.cpp @@ -1,215 +1,215 @@ /* * This file is part of KDevelop * Copyright 2012-2013 Milian Wolff * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "test_identifier.h" #include #include #include #include #include #include QTEST_GUILESS_MAIN(TestIdentifier); using namespace KDevelop; void TestIdentifier::initTestCase() { AutoTestShell::init(); TestCore::initialize(Core::NoUi); } void TestIdentifier::cleanupTestCase() { TestCore::shutdown(); } void TestIdentifier::testIdentifier() { QFETCH(QString, stringId); const IndexedString indexedStringId(stringId); Identifier id(stringId); QCOMPARE(id.isEmpty(), stringId.isEmpty()); QCOMPARE(id, Identifier(stringId)); QVERIFY(!(id != Identifier(stringId))); QCOMPARE(id, Identifier(stringId)); QCOMPARE(id, Identifier(indexedStringId)); QCOMPARE(id.identifier(), indexedStringId); QCOMPARE(id.toString(), stringId); QVERIFY(id.nameEquals(Identifier(stringId))); QVERIFY(!id.isUnique()); if (stringId.isEmpty()) { QVERIFY(id.inRepository()); QVERIFY(Identifier(id).inRepository()); QVERIFY(Identifier(indexedStringId).inRepository()); } Identifier copy = id; QCOMPARE(copy, id); copy = copy; QCOMPARE(copy, id); copy = Identifier(); QVERIFY(copy.isEmpty()); copy = id; QCOMPARE(copy, id); IndexedIdentifier indexedId(id); QVERIFY(indexedId == id); QCOMPARE(indexedId, IndexedIdentifier(id)); QCOMPARE(indexedId.isEmpty(), stringId.isEmpty()); QCOMPARE(indexedId.identifier(), id); IndexedIdentifier indexedCopy = indexedId; QCOMPARE(indexedCopy, indexedId); indexedCopy = indexedCopy; QCOMPARE(indexedCopy, indexedId); indexedCopy = IndexedIdentifier(); QVERIFY(indexedCopy.isEmpty()); indexedCopy = indexedId; QCOMPARE(indexedCopy, indexedId); Identifier moved = std::move(id); QVERIFY(id.isEmpty()); QVERIFY(id.inRepository()); QCOMPARE(moved, copy); IndexedIdentifier movedIndexed = std::move(indexedId); QVERIFY(indexedId.isEmpty()); QCOMPARE(movedIndexed, indexedCopy); } void TestIdentifier::testIdentifier_data() { QTest::addColumn("stringId"); QTest::newRow("empty") << QString(); QTest::newRow("foo") << QStringLiteral("foo"); QTest::newRow("bar") << QStringLiteral("bar"); //TODO: test template identifiers } void TestIdentifier::testQualifiedIdentifier() { QFETCH(QString, stringId); - const QStringList list = stringId.split("::", QString::SkipEmptyParts); + const QStringList list = stringId.split(QStringLiteral("::"), QString::SkipEmptyParts); QualifiedIdentifier id(stringId); QCOMPARE(id.isEmpty(), stringId.isEmpty()); QCOMPARE(id, QualifiedIdentifier(stringId)); QVERIFY(!(id != QualifiedIdentifier(stringId))); QCOMPARE(id, QualifiedIdentifier(stringId)); if (list.size() == 1) { QCOMPARE(id, QualifiedIdentifier(Identifier(list.last()))); } else if (list.isEmpty()) { QualifiedIdentifier empty{Identifier()}; QCOMPARE(id, empty); QVERIFY(empty.isEmpty()); QVERIFY(empty.inRepository()); } QCOMPARE(id.toString(), stringId); QCOMPARE(id.toStringList(), list); QCOMPARE(id.top(), Identifier(list.isEmpty() ? QString() : list.last())); if (stringId.isEmpty()) { QVERIFY(id.inRepository()); QVERIFY(QualifiedIdentifier(id).inRepository()); } QualifiedIdentifier copy = id; QCOMPARE(copy, id); copy = copy; QCOMPARE(copy, id); copy = QualifiedIdentifier(); QVERIFY(copy.isEmpty()); copy = id; QCOMPARE(copy, id); IndexedQualifiedIdentifier indexedId(id); QVERIFY(indexedId == id); QCOMPARE(indexedId, IndexedQualifiedIdentifier(id)); QCOMPARE(indexedId.isValid(), !stringId.isEmpty()); QCOMPARE(indexedId.identifier(), id); IndexedQualifiedIdentifier indexedCopy = indexedId; QCOMPARE(indexedCopy, indexedId); indexedCopy = indexedCopy; QCOMPARE(indexedCopy, indexedId); indexedCopy = IndexedQualifiedIdentifier(); QVERIFY(!indexedCopy.isValid()); indexedCopy = indexedId; QCOMPARE(indexedCopy, indexedId); QualifiedIdentifier moved = std::move(id); QVERIFY(id.isEmpty()); QCOMPARE(moved, copy); IndexedQualifiedIdentifier movedIndexed = std::move(indexedId); QVERIFY(indexedId.isEmpty()); QCOMPARE(movedIndexed, indexedCopy); copy.clear(); QVERIFY(copy.isEmpty()); copy.push(moved); QCOMPARE(copy, moved); - copy.push(Identifier("lalala")); + copy.push(Identifier(QStringLiteral("lalala"))); QCOMPARE(copy.count(), moved.count() + 1); } void TestIdentifier::testQualifiedIdentifier_data() { QTest::addColumn("stringId"); QTest::newRow("empty") << QString(); QTest::newRow("foo") << "foo"; QTest::newRow("foo::bar") << "foo::bar"; //TODO: test template identifiers } void TestIdentifier::benchIdentifierCopyConstant() { QBENCHMARK { Identifier identifier(QStringLiteral("Asdf")); identifier.index(); Identifier copy(identifier); } } void TestIdentifier::benchIdentifierCopyDynamic() { QBENCHMARK { Identifier identifier(QStringLiteral("Asdf")); Identifier copy(identifier); } } void TestIdentifier::benchQidCopyPush() { QBENCHMARK { Identifier id(QStringLiteral("foo")); QualifiedIdentifier base(id); QualifiedIdentifier copy(base); copy.push(id); } } diff --git a/language/duchain/topducontext.cpp b/language/duchain/topducontext.cpp index f3a9a5f78..d6af4caba 100644 --- a/language/duchain/topducontext.cpp +++ b/language/duchain/topducontext.cpp @@ -1,1164 +1,1164 @@ /* This is part of KDevelop Copyright 2006 Hamish Rodda Copyright 2007-2009 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "topducontext.h" #include "topducontextutils.h" #include #include #include "persistentsymboltable.h" #include "problem.h" #include "declaration.h" #include "duchain.h" #include "duchainlock.h" #include "parsingenvironment.h" #include "duchainpointer.h" #include "declarationid.h" #include "namespacealiasdeclaration.h" #include "aliasdeclaration.h" #include "uses.h" #include "topducontextdata.h" #include "duchainregister.h" #include "topducontextdynamicdata.h" #include "util/debug.h" // #define DEBUG_SEARCH const uint maxApplyAliasesRecursion = 100; namespace KDevelop { Utils::BasicSetRepository* RecursiveImportRepository::repository() { static Utils::BasicSetRepository recursiveImportRepositoryObject(QStringLiteral("Recursive Imports"), &KDevelop::globalItemRepositoryRegistry()); return &recursiveImportRepositoryObject; } ReferencedTopDUContext::ReferencedTopDUContext(TopDUContext* context) : m_topContext(context) { if(m_topContext) DUChain::self()->refCountUp(m_topContext); } ReferencedTopDUContext::ReferencedTopDUContext(const ReferencedTopDUContext& rhs) : m_topContext(rhs.m_topContext) { if(m_topContext) DUChain::self()->refCountUp(m_topContext); } ReferencedTopDUContext::~ReferencedTopDUContext() { if(m_topContext && !DUChain::deleted()) DUChain::self()->refCountDown(m_topContext); } ReferencedTopDUContext& ReferencedTopDUContext::operator=(const ReferencedTopDUContext& rhs) { if(m_topContext == rhs.m_topContext) return *this; if(m_topContext) DUChain::self()->refCountDown(m_topContext); m_topContext = rhs.m_topContext; if(m_topContext) DUChain::self()->refCountUp(m_topContext); return *this; } DEFINE_LIST_MEMBER_HASH(TopDUContextData, m_usedDeclarationIds, DeclarationId) DEFINE_LIST_MEMBER_HASH(TopDUContextData, m_problems, LocalIndexedProblem) REGISTER_DUCHAIN_ITEM(TopDUContext); template void removeFromVector(QVector& vec, const T& t) { for(int a =0; a < vec.size(); ++a) { if(vec[a] == t) { vec.remove(a); removeFromVector(vec, t); return; } } } QMutex importStructureMutex(QMutex::Recursive); //Contains data that is not shared among top-contexts that share their duchain entries class TopDUContextLocalPrivate { public: TopDUContextLocalPrivate (TopDUContext* ctxt, uint index) : m_ctxt(ctxt), m_ownIndex(index), m_inDuChain(false) { m_indexedRecursiveImports.insert(index); } ~TopDUContextLocalPrivate() { //Either we use some other contexts data and have no users, or we own the data and have users that share it. QMutexLocker lock(&importStructureMutex); foreach(const DUContext::Import& import, m_importedContexts) if(DUChain::self()->isInMemory(import.topContextIndex()) && dynamic_cast(import.context(0))) dynamic_cast(import.context(0))->m_local->m_directImporters.remove(m_ctxt); } ///@todo Make all this work consistently together with import-caching //After loading, should rebuild the links void rebuildDynamicImportStructure() { //Currently we do not store the whole data in TopDUContextLocalPrivate, so we reconstruct it from what was stored by DUContext. Q_ASSERT(m_importedContexts.isEmpty()); FOREACH_FUNCTION(const DUContext::Import& import, m_ctxt->d_func()->m_importedContexts) { if(DUChain::self()->isInMemory(import.topContextIndex())) { Q_ASSERT(import.context(0)); TopDUContext* top = import.context(0)->topContext(); Q_ASSERT(top); addImportedContextRecursively(top, false, true); } } FOREACH_FUNCTION(const IndexedDUContext& importer, m_ctxt->d_func()->m_importers) { if(DUChain::self()->isInMemory(importer.topContextIndex())) { Q_ASSERT(importer.context()); TopDUContext* top = importer.context()->topContext(); Q_ASSERT(top); top->m_local->addImportedContextRecursively(m_ctxt, false, true); } } } //Index of this top-context within the duchain //Since the data of top-contexts can be shared among multiple, this can be used to add imports that are local to this top-context. QVector m_importedContexts; // mutable bool m_haveImportStructure : 1; TopDUContext* m_ctxt; QSet m_directImporters; ParsingEnvironmentFilePointer m_file; QExplicitlySharedDataPointer m_ast; uint m_ownIndex; bool m_inDuChain; void clearImportedContextsRecursively() { QMutexLocker lock(&importStructureMutex); // Q_ASSERT(m_recursiveImports.size() == m_indexedRecursiveImports.count()-1); QSet > rebuild; foreach(const DUContext::Import &import, m_importedContexts) { TopDUContext* top = dynamic_cast(import.context(0)); if(top) { top->m_local->m_directImporters.remove(m_ctxt); if(!m_ctxt->usingImportsCache()) { removeImportedContextRecursion(top, top, 1, rebuild); QHash > b = top->m_local->m_recursiveImports; for(RecursiveImports::const_iterator it = b.constBegin(); it != b.constEnd(); ++it) { if(m_recursiveImports.contains(it.key()) && m_recursiveImports[it.key()].second == top) removeImportedContextRecursion(top, it.key(), it->first+1, rebuild); //Remove all contexts that are imported through the context } } } } m_importedContexts.clear(); rebuildImportStructureRecursion(rebuild); Q_ASSERT(m_recursiveImports.isEmpty()); // Q_ASSERT(m_recursiveImports.size() == m_indexedRecursiveImports.count()-1); } //Adds the context to this and all contexts that import this, and manages m_recursiveImports void addImportedContextRecursively(TopDUContext* context, bool temporary, bool local) { QMutexLocker lock(&importStructureMutex); context->m_local->m_directImporters.insert(m_ctxt); if(local) m_importedContexts << DUContext::Import(context, m_ctxt); if(!m_ctxt->usingImportsCache()) { addImportedContextRecursion(context, context, 1, temporary); QHash > b = context->m_local->m_recursiveImports; for(RecursiveImports::const_iterator it = b.constBegin(); it != b.constEnd(); ++it) addImportedContextRecursion(context, it.key(), (*it).first+1, temporary); //Add contexts that were imported earlier into the given one } } //Removes the context from this and all contexts that import this, and manages m_recursiveImports void removeImportedContextRecursively(TopDUContext* context, bool local) { QMutexLocker lock(&importStructureMutex); context->m_local->m_directImporters.remove(m_ctxt); if(local) removeFromVector(m_importedContexts, DUContext::Import(context, m_ctxt)); QSet > rebuild; if(!m_ctxt->usingImportsCache()) { removeImportedContextRecursion(context, context, 1, rebuild); QHash > b = context->m_local->m_recursiveImports; for(RecursiveImports::const_iterator it = b.constBegin(); it != b.constEnd(); ++it) { if(m_recursiveImports.contains(it.key()) && m_recursiveImports[it.key()].second == context) removeImportedContextRecursion(context, it.key(), it->first+1, rebuild); //Remove all contexts that are imported through the context } } rebuildImportStructureRecursion(rebuild); } void removeImportedContextsRecursively(const QList& contexts, bool local) { QMutexLocker lock(&importStructureMutex); QSet > rebuild; foreach(TopDUContext* context, contexts) { context->m_local->m_directImporters.remove(m_ctxt); if(local) removeFromVector(m_importedContexts, DUContext::Import(context, m_ctxt)); if(!m_ctxt->usingImportsCache()) { removeImportedContextRecursion(context, context, 1, rebuild); QHash > b = context->m_local->m_recursiveImports; for(RecursiveImports::const_iterator it = b.constBegin(); it != b.constEnd(); ++it) { if(m_recursiveImports.contains(it.key()) && m_recursiveImports[it.key()].second == context) removeImportedContextRecursion(context, it.key(), it->first+1, rebuild); //Remove all contexts that are imported through the context } } } rebuildImportStructureRecursion(rebuild); } //Has an entry for every single recursively imported file, that contains the shortest path, and the next context on that path to the imported context. //This does not need to be stored to disk, because it is defined implicitly. //What makes this most complicated is the fact that loops are allowed in the import structure. typedef QHash > RecursiveImports; mutable RecursiveImports m_recursiveImports; mutable TopDUContext::IndexedRecursiveImports m_indexedRecursiveImports; private: void addImportedContextRecursion(const TopDUContext* traceNext, const TopDUContext* imported, int depth, bool temporary = false) { if(m_ctxt->usingImportsCache()) return; // if(!m_haveImportStructure) // return; if(imported == m_ctxt) return; const bool computeShortestPaths = false; ///@todo We do not compute the shortest path. Think what's right. // traceNext->m_local->needImportStructure(); // imported->m_local->needImportStructure(); RecursiveImports::iterator it = m_recursiveImports.find(imported); if(it == m_recursiveImports.end()) { //Insert new path to "imported" m_recursiveImports[imported] = qMakePair(depth, traceNext); m_indexedRecursiveImports.insert(imported->indexed()); // Q_ASSERT(m_indexedRecursiveImports.size() == m_recursiveImports.size()+1); Q_ASSERT(traceNext != m_ctxt); }else{ if(!computeShortestPaths) return; if(temporary) //For temporary imports, we don't record the best path. return; //It would be better if we would use the following code, but it creates too much cost in updateImportedContextRecursion when imports are removed again. //Check whether the new way to "imported" is shorter than the stored one if((*it).first > depth) { //Add a shorter path (*it).first = depth; Q_ASSERT(traceNext); (*it).second = traceNext; Q_ASSERT(traceNext == imported || (traceNext->m_local->m_recursiveImports.contains(imported) && traceNext->m_local->m_recursiveImports[imported].first < (*it).first)); }else{ //The imported context is already imported through a same/better path, so we can just stop processing. This saves us from endless recursion. return; } } if(temporary) return; for(QSet::const_iterator it = m_directImporters.constBegin(); it != m_directImporters.constEnd(); ++it) { TopDUContext* top = dynamic_cast(const_cast(*it)); //Avoid detaching, so use const_cast if(top) ///@todo also record this for local imports top->m_local->addImportedContextRecursion(m_ctxt, imported, depth+1); } } void removeImportedContextRecursion(const TopDUContext* traceNext, const TopDUContext* imported, int distance, QSet >& rebuild) { if(m_ctxt->usingImportsCache()) return; if(imported == m_ctxt) return; // if(!m_haveImportStructure) // return; RecursiveImports::iterator it = m_recursiveImports.find(imported); if(it == m_recursiveImports.end()) { //We don't import. Just return, this saves us from endless recursion. return; }else{ //Check whether we have imported "imported" through "traceNext". If not, return. Else find a new trace. if((*it).second == traceNext && (*it).first == distance) { //We need to remove the import through traceNext. Check whether there is another imported context that imports it. m_recursiveImports.erase(it); //In order to prevent problems, we completely remove everything, and re-add it. //Just updating these complex structures is very hard. Q_ASSERT(imported != m_ctxt); m_indexedRecursiveImports.remove(imported->indexed()); // Q_ASSERT(m_indexedRecursiveImports.size() == m_recursiveImports.size()); rebuild.insert(qMakePair(m_ctxt, imported)); //We MUST do this before finding another trace, because else we would create loops for(QSet::const_iterator childIt = m_directImporters.constBegin(); childIt != m_directImporters.constEnd(); ++childIt) { TopDUContext* top = dynamic_cast(const_cast(*childIt)); //Avoid detaching, so use const iterator if(top) top->m_local->removeImportedContextRecursion(m_ctxt, imported, distance+1, rebuild); //Don't use 'it' from here on, it may be invalid } } } } //Updates the trace to 'imported' void rebuildStructure(const TopDUContext* imported); void rebuildImportStructureRecursion(const QSet >& rebuild) { for(QSet >::const_iterator it = rebuild.constBegin(); it != rebuild.constEnd(); ++it) { //for(int a = rebuild.size()-1; a >= 0; --a) { //Find the best imported parent it->first->m_local->rebuildStructure(it->second); } } }; const TopDUContext::IndexedRecursiveImports& TopDUContext::recursiveImportIndices() const { // No lock-check for performance reasons QMutexLocker lock(&importStructureMutex); if(!d_func()->m_importsCache.isEmpty()) return d_func()->m_importsCache; return m_local->m_indexedRecursiveImports; } void TopDUContextData::updateImportCacheRecursion(uint baseIndex, IndexedTopDUContext currentContext, TopDUContext::IndexedRecursiveImports& visited) { if(visited.contains(currentContext.index())) return; Q_ASSERT(currentContext.index()); //The top-context must be in the repository when this is called if(!currentContext.data()) { qCDebug(LANGUAGE) << "importing invalid context"; return; } visited.insert(currentContext.index()); const TopDUContextData* currentData = currentContext.data()->topContext()->d_func(); if(currentData->m_importsCache.contains(baseIndex) || currentData->m_importsCache.isEmpty()) { //If we have a loop or no imports-cache is used, we have to look at each import separately. const KDevelop::DUContext::Import* imports = currentData->m_importedContexts(); uint importsSize = currentData->m_importedContextsSize(); for(uint a = 0; a < importsSize; ++a) { IndexedTopDUContext next(imports[a].topContextIndex()); if(next.isValid()) updateImportCacheRecursion(baseIndex, next, visited); } }else{ //If we don't have a loop with baseIndex, we can safely just merge with the imported importscache visited += currentData->m_importsCache; } } void TopDUContextData::updateImportCacheRecursion(IndexedTopDUContext currentContext, std::set& visited) { if(visited.find(currentContext.index()) != visited.end()) return; Q_ASSERT(currentContext.index()); //The top-context must be in the repository when this is called if(!currentContext.data()) { qCDebug(LANGUAGE) << "importing invalid context"; return; } visited.insert(currentContext.index()); const TopDUContextData* currentData = currentContext.data()->topContext()->d_func(); const KDevelop::DUContext::Import* imports = currentData->m_importedContexts(); uint importsSize = currentData->m_importedContextsSize(); for(uint a = 0; a < importsSize; ++a) { IndexedTopDUContext next(imports[a].topContextIndex()); if(next.isValid()) updateImportCacheRecursion(next, visited); } } void TopDUContext::updateImportsCache() { QMutexLocker lock(&importStructureMutex); const bool use_fully_recursive_import_cache_computation = false; if(use_fully_recursive_import_cache_computation) { std::set visited; TopDUContextData::updateImportCacheRecursion(this, visited); Q_ASSERT(visited.find(ownIndex()) != visited.end()); d_func_dynamic()->m_importsCache = IndexedRecursiveImports(visited); }else{ d_func_dynamic()->m_importsCache = IndexedRecursiveImports(); TopDUContextData::updateImportCacheRecursion(ownIndex(), this, d_func_dynamic()->m_importsCache); } Q_ASSERT(d_func_dynamic()->m_importsCache.contains(IndexedTopDUContext(this))); Q_ASSERT(usingImportsCache()); Q_ASSERT(imports(this, CursorInRevision::invalid())); if(parsingEnvironmentFile()) parsingEnvironmentFile()->setImportsCache(d_func()->m_importsCache); } bool TopDUContext::usingImportsCache() const { return !d_func()->m_importsCache.isEmpty(); } CursorInRevision TopDUContext::importPosition(const DUContext* target) const { ENSURE_CAN_READ DUCHAIN_D(DUContext); Import import(const_cast(target), const_cast(this), CursorInRevision::invalid()); for(unsigned int a = 0; a < d->m_importedContextsSize(); ++a) if(d->m_importedContexts()[a] == import) return d->m_importedContexts()[a].position; return DUContext::importPosition(target); } void TopDUContextLocalPrivate::rebuildStructure(const TopDUContext* imported) { if(m_ctxt == imported) return; for(QVector::const_iterator parentIt = m_importedContexts.constBegin(); parentIt != m_importedContexts.constEnd(); ++parentIt) { TopDUContext* top = dynamic_cast(const_cast(parentIt->context(0))); //To avoid detaching, use const iterator if(top) { // top->m_local->needImportStructure(); if(top == imported) { addImportedContextRecursion(top, imported, 1); }else{ RecursiveImports::const_iterator it2 = top->m_local->m_recursiveImports.constFind(imported); if(it2 != top->m_local->m_recursiveImports.constEnd()) { addImportedContextRecursion(top, imported, (*it2).first + 1); } } } } for(unsigned int a = 0; a < m_ctxt->d_func()->m_importedContextsSize(); ++a) { TopDUContext* top = dynamic_cast(const_cast(m_ctxt->d_func()->m_importedContexts()[a].context(0))); //To avoid detaching, use const iterator if(top) { // top->m_local->needImportStructure(); if(top == imported) { addImportedContextRecursion(top, imported, 1); }else{ RecursiveImports::const_iterator it2 = top->m_local->m_recursiveImports.constFind(imported); if(it2 != top->m_local->m_recursiveImports.constEnd()) { addImportedContextRecursion(top, imported, (*it2).first + 1); } } } } } void TopDUContext::rebuildDynamicImportStructure() { m_local->rebuildDynamicImportStructure(); } void TopDUContext::rebuildDynamicData(DUContext* parent, uint ownIndex) { Q_ASSERT(parent == 0 && ownIndex != 0); m_local->m_ownIndex = ownIndex; DUContext::rebuildDynamicData(parent, 0); } IndexedTopDUContext TopDUContext::indexed() const { return IndexedTopDUContext(m_local->m_ownIndex); } uint TopDUContext::ownIndex() const { return m_local->m_ownIndex; } TopDUContext::TopDUContext(TopDUContextData& data) : DUContext(data), m_local(new TopDUContextLocalPrivate(this, data.m_ownIndex)), m_dynamicData(new TopDUContextDynamicData(this)) { } TopDUContext::TopDUContext(const IndexedString& url, const RangeInRevision& range, ParsingEnvironmentFile* file) : DUContext(*new TopDUContextData(url), range) , m_local(new TopDUContextLocalPrivate(this, DUChain::newTopContextIndex())) , m_dynamicData(new TopDUContextDynamicData(this)) { Q_ASSERT(url.toUrl().isValid() && !url.toUrl().isRelative()); d_func_dynamic()->setClassId(this); setType(Global); DUCHAIN_D_DYNAMIC(TopDUContext); d->m_features = VisibleDeclarationsAndContexts; d->m_ownIndex = m_local->m_ownIndex; setParsingEnvironmentFile(file); setInSymbolTable(true); } QExplicitlySharedDataPointer TopDUContext::parsingEnvironmentFile() const { return m_local->m_file; } TopDUContext::~TopDUContext( ) { m_dynamicData->m_deleting = true; //Clear the AST, so that the 'feature satisfaction' cache is eventually updated clearAst(); if(!isOnDisk()) { //Clear the 'feature satisfaction' cache which is managed in ParsingEnvironmentFile setFeatures(Empty); clearUsedDeclarationIndices(); } deleteChildContextsRecursively(); deleteLocalDeclarations(); m_dynamicData->clear(); } void TopDUContext::deleteSelf() { //We've got to make sure that m_dynamicData and m_local are still valid while all the sub-contexts are destroyed TopDUContextLocalPrivate* local = m_local; TopDUContextDynamicData* dynamicData = m_dynamicData; m_dynamicData->m_deleting = true; delete this; delete local; delete dynamicData; } TopDUContext::Features TopDUContext::features() const { uint ret = d_func()->m_features; if(ast()) ret |= TopDUContext::AST; return (TopDUContext::Features)ret; } void TopDUContext::setFeatures(Features features) { features = (TopDUContext::Features)(features & (~Recursive)); //Remove the "Recursive" flag since that's only for searching features = (TopDUContext::Features)(features & (~ForceUpdateRecursive)); //Remove the update flags features = (TopDUContext::Features)(features & (~AST)); //Remove the AST flag, it's only used while updating d_func_dynamic()->m_features = features; //Replicate features to ParsingEnvironmentFile if(parsingEnvironmentFile()) parsingEnvironmentFile()->setFeatures(this->features()); } void TopDUContext::setAst(QExplicitlySharedDataPointer ast) { ENSURE_CAN_WRITE m_local->m_ast = ast; if(parsingEnvironmentFile()) parsingEnvironmentFile()->setFeatures(features()); } void TopDUContext::setParsingEnvironmentFile(ParsingEnvironmentFile* file) { if(m_local->m_file) //Clear the "feature satisfaction" cache m_local->m_file->setFeatures(Empty); //We do not enforce a duchain lock here, since this is also used while loading a top-context m_local->m_file = QExplicitlySharedDataPointer(file); //Replicate features to ParsingEnvironmentFile if(file) { file->setTopContext(IndexedTopDUContext(ownIndex())); Q_ASSERT(file->indexedTopContext().isValid()); file->setFeatures(d_func()->m_features); file->setImportsCache(d_func()->m_importsCache); } } struct TopDUContext::FindDeclarationsAcceptor { FindDeclarationsAcceptor(const TopDUContext* _top, DeclarationList& _target, const DeclarationChecker& _check, SearchFlags _flags) : top(_top), target(_target), check(_check) { flags = _flags; } bool operator() (const QualifiedIdentifier& id) { #ifdef DEBUG_SEARCH qCDebug(LANGUAGE) << "accepting" << id.toString(); #endif PersistentSymbolTable::Declarations allDecls; //This iterator efficiently filters the visible declarations out of all declarations PersistentSymbolTable::FilteredDeclarationIterator filter; //This is used if filtering is disabled PersistentSymbolTable::Declarations::Iterator unchecked; if(check.flags & DUContext::NoImportsCheck) { allDecls = PersistentSymbolTable::self().getDeclarations(id); unchecked = allDecls.iterator(); } else filter = PersistentSymbolTable::self().getFilteredDeclarations(id, top->recursiveImportIndices()); while(filter || unchecked) { IndexedDeclaration iDecl; if(filter) { iDecl = *filter; ++filter; } else { iDecl = *unchecked; ++unchecked; } Declaration* decl = iDecl.data(); if(!decl) continue; if(!check(decl)) continue; if( ! (flags & DontResolveAliases) && decl->kind() == Declaration::Alias ) { //Apply alias declarations AliasDeclaration* alias = static_cast(decl); if(alias->aliasedDeclaration().isValid()) { decl = alias->aliasedDeclaration().declaration(); } else { qCDebug(LANGUAGE) << "lost aliased declaration"; } } target.append(decl); } check.createVisibleCache = 0; return !top->foundEnough(target, flags); } const TopDUContext* top; DeclarationList& target; const DeclarationChecker& check; QFlags< KDevelop::DUContext::SearchFlag > flags; }; bool TopDUContext::findDeclarationsInternal(const SearchItem::PtrList& identifiers, const CursorInRevision& position, const AbstractType::Ptr& dataType, DeclarationList& ret, const TopDUContext* /*source*/, SearchFlags flags, uint /*depth*/) const { ENSURE_CAN_READ #ifdef DEBUG_SEARCH for (const SearchItem::Ptr& idTree : identifiers) foreach(const QualifiedIdentifier &id, idTree->toList()) qCDebug(LANGUAGE) << "searching item" << id.toString(); #endif DeclarationChecker check(this, position, dataType, flags); FindDeclarationsAcceptor storer(this, ret, check, flags); ///The actual scopes are found within applyAliases, and each complete qualified identifier is given to FindDeclarationsAcceptor. ///That stores the found declaration to the output. applyAliases(identifiers, storer, position, false); return true; } //This is used to prevent endless recursion due to "using namespace .." declarations, by storing all imports that are already being used. struct TopDUContext::ApplyAliasesBuddyInfo { ApplyAliasesBuddyInfo(uint importChainType, ApplyAliasesBuddyInfo* predecessor, IndexedQualifiedIdentifier importId) : m_importChainType(importChainType), m_predecessor(predecessor), m_importId(importId) { if(m_predecessor && m_predecessor->m_importChainType != importChainType) m_predecessor = 0; } //May also be called when this is zero. bool alreadyImporting(IndexedQualifiedIdentifier id) { ApplyAliasesBuddyInfo* current = this; while(current) { if(current->m_importId == id) return true; current = current->m_predecessor; } return false; } uint m_importChainType; ApplyAliasesBuddyInfo* m_predecessor; IndexedQualifiedIdentifier m_importId; }; ///@todo Implement a cache so at least the global import checks don't need to be done repeatedly. The cache should be thread-local, using DUChainPointer for the hashed items, and when an item was deleted, it should be discarded template bool TopDUContext::applyAliases( const QualifiedIdentifier& previous, const SearchItem::Ptr& identifier, Acceptor& accept, const CursorInRevision& position, bool canBeNamespace, ApplyAliasesBuddyInfo* buddy, uint recursionDepth ) const { if(recursionDepth > maxApplyAliasesRecursion) { QList searches = identifier->toList(); QualifiedIdentifier id; if(!searches.isEmpty()) id = searches.first(); qCDebug(LANGUAGE) << "maximum apply-aliases recursion reached while searching" << id; } bool foundAlias = false; QualifiedIdentifier id(previous); id.push(identifier->identifier); if(!id.inRepository()) return true; //If the qualified identifier is not in the identifier repository, it cannot be registered anywhere, so there's nothing we need to do if( !identifier->next.isEmpty() || canBeNamespace ) { //If it cannot be a namespace, the last part of the scope will be ignored //Search for namespace-aliases, by using globalAliasIdentifier, which is inserted into the symbol-table by NamespaceAliasDeclaration QualifiedIdentifier aliasId(id); aliasId.push(globalIndexedAliasIdentifier()); #ifdef DEBUG_SEARCH qCDebug(LANGUAGE) << "checking" << id.toString(); #endif if(aliasId.inRepository()) { //This iterator efficiently filters the visible declarations out of all declarations PersistentSymbolTable::FilteredDeclarationIterator filter = PersistentSymbolTable::self().getFilteredDeclarations(aliasId, recursiveImportIndices()); if(filter) { DeclarationChecker check(this, position, AbstractType::Ptr(), NoSearchFlags, 0); //The first part of the identifier has been found as a namespace-alias. //In c++, we only need the first alias. However, just to be correct, follow them all for now. for(; filter; ++filter) { Declaration* aliasDecl = filter->data(); if(!aliasDecl) continue; if(!check(aliasDecl)) continue; if(aliasDecl->kind() != Declaration::NamespaceAlias) continue; if(foundAlias) break; Q_ASSERT(dynamic_cast(aliasDecl)); NamespaceAliasDeclaration* alias = static_cast(aliasDecl); foundAlias = true; QualifiedIdentifier importIdentifier = alias->importIdentifier(); if(importIdentifier.isEmpty()) { qCDebug(LANGUAGE) << "found empty import"; continue; } if(buddy->alreadyImporting( importIdentifier )) continue; //This import has already been applied to this search ApplyAliasesBuddyInfo info(1, buddy, importIdentifier); if(identifier->next.isEmpty()) { //Just insert the aliased namespace identifier if(!accept(importIdentifier)) return false; }else{ //Create an identifiers where namespace-alias part is replaced with the alias target - for (const SearchItem::Ptr& item : identifier->next) + foreach (const SearchItem::Ptr& item, identifier->next) if(!applyAliases(importIdentifier, item, accept, position, canBeNamespace, &info, recursionDepth+1)) return false; } } } } } if(!foundAlias) { //If we haven't found an alias, put the current versions into the result list. Additionally we will compute the identifiers transformed through "using". if(identifier->next.isEmpty()) { if(!accept(id)) //We're at the end of a qualified identifier, accept it return false; } else { - for (const SearchItem::Ptr& next : identifier->next) + foreach (const SearchItem::Ptr& next, identifier->next) if(!applyAliases(id, next, accept, position, canBeNamespace, 0, recursionDepth+1)) return false; } } /*if( !prefix.explicitlyGlobal() || !prefix.isEmpty() ) {*/ ///@todo check iso c++ if using-directives should be respected on top-level when explicitly global ///@todo this is bad for a very big repository(the chains should be walked for the top-context instead) //Find all namespace-imports at given scope { QualifiedIdentifier importId(previous); importId.push(globalIndexedImportIdentifier()); #ifdef DEBUG_SEARCH // qCDebug(LANGUAGE) << "checking imports in" << (backPointer ? id.toString() : QStringLiteral("global")); #endif if(importId.inRepository()) { //This iterator efficiently filters the visible declarations out of all declarations PersistentSymbolTable::FilteredDeclarationIterator filter = PersistentSymbolTable::self().getFilteredDeclarations(importId, recursiveImportIndices()); if(filter) { DeclarationChecker check(this, position, AbstractType::Ptr(), NoSearchFlags, 0); for(; filter; ++filter) { Declaration* importDecl = filter->data(); if(!importDecl) continue; //We must never break or return from this loop, because else we might be creating a bad cache if(!check(importDecl)) continue; //Search for the identifier with the import-identifier prepended Q_ASSERT(dynamic_cast(importDecl)); NamespaceAliasDeclaration* alias = static_cast(importDecl); #ifdef DEBUG_SEARCH qCDebug(LANGUAGE) << "found import of" << alias->importIdentifier().toString(); #endif QualifiedIdentifier importIdentifier = alias->importIdentifier(); if(importIdentifier.isEmpty()) { qCDebug(LANGUAGE) << "found empty import"; continue; } if(buddy->alreadyImporting( importIdentifier )) continue; //This import has already been applied to this search ApplyAliasesBuddyInfo info(2, buddy, importIdentifier); if(previous != importIdentifier) if(!applyAliases(importIdentifier, identifier, accept, importDecl->topContext() == this ? importDecl->range().start : position, canBeNamespace, &info, recursionDepth+1)) return false; } } } } return true; } template void TopDUContext::applyAliases( const SearchItem::PtrList& identifiers, Acceptor& acceptor, const CursorInRevision& position, bool canBeNamespace ) const { QualifiedIdentifier emptyId; for (const SearchItem::Ptr& item : identifiers) applyAliases(emptyId, item, acceptor, position, canBeNamespace, 0, 0); } TopDUContext * TopDUContext::topContext() const { return const_cast(this); } bool TopDUContext::deleting() const { return m_dynamicData->m_deleting; } QList TopDUContext::problems() const { ENSURE_CAN_READ const auto data = d_func(); QList ret; ret.reserve(data->m_problemsSize()); for (uint i = 0; i < data->m_problemsSize(); ++i) { ret << ProblemPointer(data->m_problems()[i].data(this)); } return ret; } void TopDUContext::setProblems(const QList& problems) { ENSURE_CAN_WRITE clearProblems(); for (const auto& problem : problems) { addProblem(problem); } } void TopDUContext::addProblem(const ProblemPointer& problem) { ENSURE_CAN_WRITE Q_ASSERT(problem); auto data = d_func_dynamic(); // store for indexing LocalIndexedProblem indexedProblem(problem, this); Q_ASSERT(indexedProblem.isValid()); data->m_problemsList().append(indexedProblem); Q_ASSERT(indexedProblem.data(this)); } void TopDUContext::clearProblems() { ENSURE_CAN_WRITE d_func_dynamic()->m_problemsList().clear(); m_dynamicData->clearProblems(); } QVector TopDUContext::importers() const { ENSURE_CAN_READ return QVector::fromList( m_local->m_directImporters.toList() ); } QList TopDUContext::loadedImporters() const { ENSURE_CAN_READ return m_local->m_directImporters.toList(); } QVector TopDUContext::importedParentContexts() const { ENSURE_CAN_READ return DUContext::importedParentContexts(); } bool TopDUContext::imports(const DUContext * origin, const CursorInRevision& position) const { return importsPrivate(origin, position); } bool TopDUContext::importsPrivate(const DUContext * origin, const CursorInRevision& position) const { Q_UNUSED(position); if( const TopDUContext* top = dynamic_cast(origin) ) { QMutexLocker lock(&importStructureMutex); bool ret = recursiveImportIndices().contains(IndexedTopDUContext(const_cast(top))); if(top == this) Q_ASSERT(ret); return ret; } else { //Cannot import a non top-context return false; } } void TopDUContext::clearImportedParentContexts() { if(usingImportsCache()) { d_func_dynamic()->m_importsCache = IndexedRecursiveImports(); d_func_dynamic()->m_importsCache.insert(IndexedTopDUContext(this)); } DUContext::clearImportedParentContexts(); m_local->clearImportedContextsRecursively(); Q_ASSERT(m_local->m_recursiveImports.count() == 0); Q_ASSERT(m_local->m_indexedRecursiveImports.count() == 1); Q_ASSERT(imports(this, CursorInRevision::invalid())); } void TopDUContext::addImportedParentContext(DUContext* context, const CursorInRevision& position, bool anonymous, bool temporary) { if(context == this) return; if(!dynamic_cast(context)) { //We cannot do this, because of the extended way we treat top-context imports. qCDebug(LANGUAGE) << "tried to import a non top-context into a top-context. This is not possible."; return; } //Always make the contexts anonymous, because we care about importers in TopDUContextLocalPrivate DUContext::addImportedParentContext(context, position, anonymous, temporary); m_local->addImportedContextRecursively(static_cast(context), temporary, true); } void TopDUContext::removeImportedParentContext(DUContext* context) { DUContext::removeImportedParentContext(context); m_local->removeImportedContextRecursively(static_cast(context), true); } void TopDUContext::addImportedParentContexts(const QList >& contexts, bool temporary) { typedef QPair Pair; - foreach(const Pair &pair, contexts) + foreach(const Pair pair, contexts) addImportedParentContext(pair.first, pair.second, false, temporary); } void TopDUContext::removeImportedParentContexts(const QList& contexts) { foreach(TopDUContext* context, contexts) DUContext::removeImportedParentContext(context); m_local->removeImportedContextsRecursively(contexts, true); } /// Returns true if this object is registered in the du-chain. If it is not, all sub-objects(context, declarations, etc.) bool TopDUContext::inDUChain() const { return m_local->m_inDuChain; } /// This flag is only used by DUChain, never change it from outside. void TopDUContext::setInDuChain(bool b) { m_local->m_inDuChain = b; } bool TopDUContext::isOnDisk() const { ///@todo Change this to releasingToDisk, and only enable it while saving a top-context to disk. return m_dynamicData->isOnDisk(); } void TopDUContext::clearUsedDeclarationIndices() { ENSURE_CAN_WRITE for(unsigned int a = 0; a < d_func()->m_usedDeclarationIdsSize(); ++a) DUChain::uses()->removeUse(d_func()->m_usedDeclarationIds()[a], this); d_func_dynamic()->m_usedDeclarationIdsList().clear(); } void TopDUContext::deleteUsesRecursively() { clearUsedDeclarationIndices(); KDevelop::DUContext::deleteUsesRecursively(); } Declaration* TopDUContext::usedDeclarationForIndex(unsigned int declarationIndex) const { ENSURE_CAN_READ if(declarationIndex & (1<<31)) { //We use the highest bit to mark direct indices into the local declarations declarationIndex &= ~(1<<31); //unset the highest bit return m_dynamicData->getDeclarationForIndex(declarationIndex); }else if(declarationIndex < d_func()->m_usedDeclarationIdsSize()) return d_func()->m_usedDeclarationIds()[declarationIndex].getDeclaration(this); else return 0; } int TopDUContext::indexForUsedDeclaration(Declaration* declaration, bool create) { if(create) { ENSURE_CAN_WRITE }else{ ENSURE_CAN_READ } if(!declaration) { return std::numeric_limits::max(); } if(declaration->topContext() == this && !declaration->inSymbolTable() && !m_dynamicData->isTemporaryDeclarationIndex(declaration->ownIndex())) { uint index = declaration->ownIndex(); Q_ASSERT(!(index & (1<<31))); return (int)(index | (1<<31)); //We don't put context-local declarations into the list, that's a waste. We just use the mark them with the highest bit. } DeclarationId id(declaration->id()); int index = -1; uint size = d_func()->m_usedDeclarationIdsSize(); const DeclarationId* ids = d_func()->m_usedDeclarationIds(); ///@todo Make m_usedDeclarationIds sorted, and find the decl. using binary search for(unsigned int a = 0; a < size; ++a) if(ids[a] == id) { index = a; break; } if(index != -1) return index; if(!create) return std::numeric_limits::max(); d_func_dynamic()->m_usedDeclarationIdsList().append(id); if(declaration->topContext() != this) DUChain::uses()->addUse(id, this); return d_func()->m_usedDeclarationIdsSize()-1; } QList allUses(TopDUContext* context, Declaration* declaration, bool noEmptyRanges) { QList ret; int declarationIndex = context->indexForUsedDeclaration(declaration, false); if(declarationIndex == std::numeric_limits::max()) return ret; return allUses(context, declarationIndex, noEmptyRanges); } QExplicitlySharedDataPointer TopDUContext::ast() const { return m_local->m_ast; } void TopDUContext::clearAst() { setAst(QExplicitlySharedDataPointer(0)); } IndexedString TopDUContext::url() const { return d_func()->m_url; } } diff --git a/language/duchain/types/abstracttype.cpp b/language/duchain/types/abstracttype.cpp index c764fc6ec..4db4a53cd 100644 --- a/language/duchain/types/abstracttype.cpp +++ b/language/duchain/types/abstracttype.cpp @@ -1,150 +1,150 @@ /* This file is part of KDevelop Copyright 2006 Roberto Raggi Copyright 2006-2008 Hamish Rodda Copyright 2007-2008 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "abstracttype.h" #include "typesystemdata.h" #include "typeregister.h" #include "typesystem.h" #include "typerepository.h" #include "util/debug.h" namespace KDevelop { //REGISTER_TYPE(AbstractType); void AbstractType::makeDynamic() { if(d_ptr->m_dynamic) return; AbstractType::Ptr newType(clone()); //While cloning, all the data is cloned as well. So we use that mechanism and steal the cloned data. Q_ASSERT(newType->equals(this)); AbstractTypeData* oldData = d_ptr; d_ptr = newType->d_ptr; newType->d_ptr = oldData; Q_ASSERT(d_ptr->m_dynamic); } AbstractType::AbstractType( AbstractTypeData& dd ) : d_ptr(&dd) { } quint32 AbstractType::modifiers() const { return d_func()->m_modifiers; } void AbstractType::setModifiers(quint32 modifiers) { d_func_dynamic()->m_modifiers = modifiers; } AbstractType::AbstractType() : d_ptr(&createData()) { } AbstractType::~AbstractType() { if(!d_ptr->inRepository) { TypeSystem::self().callDestructor(d_ptr); delete[] (char*)d_ptr; } } void AbstractType::accept(TypeVisitor *v) const { if (v->preVisit (this)) this->accept0 (v); v->postVisit (this); } void AbstractType::acceptType(AbstractType::Ptr type, TypeVisitor *v) { if (! type) return; type->accept (v); } AbstractType::WhichType AbstractType::whichType() const { return TypeAbstract; } void AbstractType::exchangeTypes( TypeExchanger* /*exchanger */) { } IndexedType AbstractType::indexed() const { return IndexedType(TypeRepository::indexForType(AbstractType::Ptr(const_cast(this)))); } bool AbstractType::equals(const AbstractType* rhs) const { //qCDebug(LANGUAGE) << this << rhs << modifiers() << rhs->modifiers(); return d_func()->typeClassId == rhs->d_func()->typeClassId && modifiers() == rhs->modifiers(); } uint AbstractType::hash() const { return KDevHash() << d_func()->typeClassId << d_func()->m_modifiers; } QString AbstractType::toString() const { return toString(false); } QString AbstractType::toString(bool spaceOnLeft) const { // TODO complete if(!spaceOnLeft) { if(modifiers() & ConstModifier) { if(modifiers() & VolatileModifier) { - return "const volatile "; + return QStringLiteral("const volatile "); }else{ - return "const "; + return QStringLiteral("const "); } }else{ if(modifiers() & VolatileModifier) - return "volatile "; + return QStringLiteral("volatile "); else return QString(); } }else{ if(modifiers() & ConstModifier) { if(modifiers() & VolatileModifier) { - return " const volatile"; + return QStringLiteral(" const volatile"); }else{ - return " const"; + return QStringLiteral(" const"); } }else{ if(modifiers() & VolatileModifier) - return " volatile"; + return QStringLiteral(" volatile"); else return QString(); } } } } diff --git a/language/duchain/types/constantintegraltype.cpp b/language/duchain/types/constantintegraltype.cpp index 1b753710d..2611a69df 100644 --- a/language/duchain/types/constantintegraltype.cpp +++ b/language/duchain/types/constantintegraltype.cpp @@ -1,188 +1,188 @@ /* This file is part of KDevelop Copyright 2002-2005 Roberto Raggi Copyright 2006 Adam Treat Copyright 2006-2008 Hamish Rodda Copyright 2007-2008 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "constantintegraltype.h" #include #include "util/debug.h" #include "typesystemdata.h" #include "typeregister.h" namespace KDevelop { REGISTER_TYPE(ConstantIntegralType); ConstantIntegralType::ConstantIntegralType(const ConstantIntegralType& rhs) : IntegralType(copyData(*rhs.d_func())) { } ConstantIntegralType::ConstantIntegralType(ConstantIntegralTypeData& data) : IntegralType(data) { } ConstantIntegralType::ConstantIntegralType(uint type) : IntegralType(createData()) { setDataType(type); setModifiers(ConstModifier); } qint64 ConstantIntegralType::plainValue() const { return d_func()->m_value; } AbstractType* ConstantIntegralType::clone() const { return new ConstantIntegralType(*this); } bool ConstantIntegralType::equals(const AbstractType* _rhs) const { if( this == _rhs ) return true; if (!IntegralType::equals(_rhs)) return false; Q_ASSERT(fastCast(_rhs)); const ConstantIntegralType* rhs = static_cast(_rhs); return d_func()->m_value == rhs->d_func()->m_value; } QString ConstantIntegralType::toString() const { QString ret; switch(dataType()) { case TypeNone: - ret += "none"; + ret += QStringLiteral("none"); break; case TypeChar: ret += QStringLiteral("char"); break; case TypeWchar_t: ret += QStringLiteral("wchar_t"); break; case TypeChar16_t: ret += QStringLiteral("char16_t"); break; case TypeChar32_t: ret += QStringLiteral("char32_t"); break; case TypeBoolean: - ret += d_func()->m_value ? "true" : "false"; + ret += d_func()->m_value ? QStringLiteral("true") : QStringLiteral("false"); break; case TypeInt: ret += (modifiers() & UnsignedModifier) ? QStringLiteral("unsigned") : QStringLiteral("int"); break; case TypeFloat: ret += QStringLiteral("float"); break; case TypeDouble: ret += QStringLiteral("double"); break; case TypeVoid: - ret += "void"; + ret += QStringLiteral("void"); break; default: - ret += ""; + ret += QStringLiteral(""); break; } return ret; } QString ConstantIntegralType::valueAsString() const { switch(dataType()) { case TypeNone: - return "none"; + return QStringLiteral("none"); case TypeChar: return QString::number((char)d_func()->m_value); case TypeWchar_t: return QString::number((wchar_t)d_func()->m_value); case TypeChar16_t: return QString::number((char16_t)d_func()->m_value); case TypeChar32_t: return QString::number((char32_t)d_func()->m_value); case TypeBoolean: - return d_func()->m_value ? "true" : "false"; + return d_func()->m_value ? QStringLiteral("true") : QStringLiteral("false"); case TypeInt: return (modifiers() & UnsignedModifier) ? QString::number((uint)d_func()->m_value) : QString::number((int)d_func()->m_value); case TypeFloat: return QString::number(value()); case TypeDouble: return QString::number(value()); default: return QString(); } } uint ConstantIntegralType::hash() const { return KDevHash(IntegralType::hash()) << d_func()->m_value; } template<> KDEVPLATFORMLANGUAGE_EXPORT void ConstantIntegralType::setValueInternal(qint64 value) { if((modifiers() & UnsignedModifier)) { qCWarning(LANGUAGE) << "setValue(signed) called on unsigned type"; } d_func_dynamic()->m_value = value; } template<> KDEVPLATFORMLANGUAGE_EXPORT void ConstantIntegralType::setValueInternal(quint64 value) { if(!(modifiers() & UnsignedModifier)) { qCWarning(LANGUAGE) << "setValue(unsigned) called on not unsigned type"; } d_func_dynamic()->m_value = (qint64)value; } template<> KDEVPLATFORMLANGUAGE_EXPORT void ConstantIntegralType::setValueInternal(float value) { if(dataType() != TypeFloat) { qCWarning(LANGUAGE) << "setValue(float) called on non-float type"; } memcpy(&d_func_dynamic()->m_value, &value, sizeof(float)); } template<> KDEVPLATFORMLANGUAGE_EXPORT void ConstantIntegralType::setValueInternal(double value) { if(dataType() != TypeDouble) { qCWarning(LANGUAGE) << "setValue(double) called on non-double type"; } memcpy(&d_func_dynamic()->m_value, &value, sizeof(double)); } } diff --git a/language/duchain/types/functiontype.cpp b/language/duchain/types/functiontype.cpp index cfe404492..d72ea852d 100644 --- a/language/duchain/types/functiontype.cpp +++ b/language/duchain/types/functiontype.cpp @@ -1,197 +1,197 @@ /* This file is part of KDevelop Copyright 2006 Roberto Raggi Copyright 2006-2008 Hamish Rodda Copyright 2007-2008 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "functiontype.h" #include "typerepository.h" #include "typesystemdata.h" #include "typeregister.h" #include "typesystem.h" namespace KDevelop { REGISTER_TYPE(FunctionType); DEFINE_LIST_MEMBER_HASH(FunctionTypeData, m_arguments, IndexedType) FunctionType::FunctionType(const FunctionType& rhs) : AbstractType(copyData(*rhs.d_func())) { } FunctionType::FunctionType(FunctionTypeData& data) : AbstractType(data) { } AbstractType* FunctionType::clone() const { return new FunctionType(*this); } bool FunctionType::equals(const AbstractType* _rhs) const { if( this == _rhs ) return true; if (!AbstractType::equals(_rhs)) return false; Q_ASSERT(fastCast(_rhs)); const FunctionType* rhs = static_cast(_rhs); TYPE_D(FunctionType); if( d->m_argumentsSize() != rhs->d_func()->m_argumentsSize() ) return false; if( (bool)rhs->d_func()->m_returnType != (bool)d->m_returnType ) return false; if( d->m_returnType != rhs->d_func()->m_returnType ) return false; for(unsigned int a = 0; a < d->m_argumentsSize(); ++a) if(d->m_arguments()[a] != rhs->d_func()->m_arguments()[a]) return false; return true; } FunctionType::FunctionType() : AbstractType(createData()) { } FunctionType::~FunctionType() { } void FunctionType::addArgument(AbstractType::Ptr argument, int index) { if ( index == -1 ) d_func_dynamic()->m_argumentsList().append(argument->indexed()); else d_func_dynamic()->m_argumentsList().insert(index, argument->indexed()); } void FunctionType::removeArgument(int i) { d_func_dynamic()->m_argumentsList().remove(i); } void FunctionType::setReturnType(AbstractType::Ptr returnType) { d_func_dynamic()->m_returnType = returnType->indexed(); } AbstractType::Ptr FunctionType::returnType () const { return d_func()->m_returnType.abstractType(); } QList FunctionType::arguments () const { ///@todo Don't do the conversion QList ret; FOREACH_FUNCTION(const IndexedType& arg, d_func()->m_arguments) ret << arg.abstractType(); return ret; } const IndexedType* FunctionType::indexedArguments() const { return d_func()->m_arguments(); } uint FunctionType::indexedArgumentsSize() const { return d_func()->m_argumentsSize(); } void FunctionType::accept0 (TypeVisitor *v) const { TYPE_D(FunctionType); if (v->visit (this)) { acceptType (d->m_returnType.abstractType(), v); for (unsigned int i = 0; i < d->m_argumentsSize (); ++i) acceptType (d->m_arguments()[i].abstractType(), v); } v->endVisit (this); } void FunctionType::exchangeTypes( TypeExchanger* exchanger ) { TYPE_D_DYNAMIC(FunctionType); for (uint i = 0; i < d->m_argumentsSize (); ++i) d->m_argumentsList()[i] = exchanger->exchange( d->m_arguments()[i].abstractType() )->indexed(); d->m_returnType = exchanger->exchange(d->m_returnType.abstractType())->indexed(); } QString FunctionType::partToString( SignaturePart sigPart ) const { QString args; TYPE_D(FunctionType); if( sigPart == SignatureArguments || sigPart == SignatureWhole ) { args += '('; bool first = true; FOREACH_FUNCTION(const IndexedType& type, d->m_arguments) { if (first) first = false; else args.append(", "); args.append(type ? type.abstractType()->toString() : QStringLiteral("")); } args += ')'; } if( sigPart == SignatureArguments ) return args; else if( sigPart == SignatureWhole ) - return QStringLiteral("function %1 %2").arg(returnType() ? returnType()->toString() : QStringLiteral("")).arg(args); + return QStringLiteral("function %1 %2").arg(returnType() ? returnType()->toString() : QStringLiteral(""), args); else if( sigPart == SignatureReturn ) return returnType() ? returnType()->toString() : QString(); return QStringLiteral("ERROR"); } QString FunctionType::toString() const { return partToString(SignatureWhole) + AbstractType::toString(true); } AbstractType::WhichType FunctionType::whichType() const { return TypeFunction; } uint FunctionType::hash() const { KDevHash kdevhash(AbstractType::hash()); kdevhash << d_func()->m_returnType.hash(); FOREACH_FUNCTION(const IndexedType& t, d_func()->m_arguments) { kdevhash << t.hash(); } return kdevhash; } } diff --git a/language/duchain/types/integraltype.cpp b/language/duchain/types/integraltype.cpp index 15fc8a88f..e1f2731ad 100644 --- a/language/duchain/types/integraltype.cpp +++ b/language/duchain/types/integraltype.cpp @@ -1,159 +1,159 @@ /* This file is part of KDevelop Copyright 2006 Roberto Raggi Copyright 2006-2008 Hamish Rodda Copyright 2007-2008 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "integraltype.h" #include "typesystemdata.h" #include "typeregister.h" #include "typesystem.h" namespace KDevelop { REGISTER_TYPE(IntegralType); IntegralType::IntegralType(const IntegralType& rhs) : AbstractType(copyData(*rhs.d_func())) { } IntegralType::IntegralType(IntegralTypeData& data) : AbstractType(data) { } uint IntegralType::dataType() const { return d_func()->m_dataType; } void IntegralType::setDataType(uint dataType) { d_func_dynamic()->m_dataType = dataType; } AbstractType* IntegralType::clone() const { return new IntegralType(*this); } bool IntegralType::equals(const AbstractType* _rhs) const { if( this == _rhs ) return true; if (!AbstractType::equals(_rhs)) return false; Q_ASSERT(fastCast(_rhs)); const IntegralType* rhs = static_cast(_rhs); return d_func()->m_dataType == rhs->d_func()->m_dataType; } IntegralType::IntegralType(uint type) : AbstractType(createData()) { d_func_dynamic()->setTypeClassId(); setDataType(type); } IntegralType::~IntegralType() { } QString IntegralType::toString() const { TYPE_D(IntegralType); QString name; switch (d->m_dataType) { case TypeChar: - name = "char"; + name = QStringLiteral("char"); break; case TypeChar16_t: - name = "char16_t"; + name = QStringLiteral("char16_t"); break; case TypeChar32_t: - name = "char32_t"; + name = QStringLiteral("char32_t"); break; case TypeWchar_t: - name = "wchar_t"; + name = QStringLiteral("wchar_t"); break; case TypeBoolean: - name = "bool"; + name = QStringLiteral("bool"); break; case TypeInt: - name = "int"; + name = QStringLiteral("int"); break; case TypeFloat: - name = "float"; + name = QStringLiteral("float"); break; case TypeDouble: - name = "double"; + name = QStringLiteral("double"); break; case TypeVoid: - name = "void"; + name = QStringLiteral("void"); break; case TypeMixed: - name = "mixed"; + name = QStringLiteral("mixed"); break; case TypeString: - name = "string"; + name = QStringLiteral("string"); break; case TypeArray: - name = "array"; + name = QStringLiteral("array"); break; case TypeNull: - name = "null"; + name = QStringLiteral("null"); break; default: - name = ""; + name = QStringLiteral(""); break; } if (modifiers() & UnsignedModifier) name.prepend("unsigned "); else if (modifiers() & SignedModifier) name.prepend("signed "); if (modifiers() & ShortModifier) name.prepend("short "); else if (modifiers() & LongLongModifier) name.prepend("long long "); else if (modifiers() & LongModifier) name.prepend("long "); return AbstractType::toString() + name; } void IntegralType::accept0(TypeVisitor *v) const { v->visit (this); } AbstractType::WhichType IntegralType::whichType() const { return TypeIntegral; } uint IntegralType::hash() const { return KDevHash(AbstractType::hash()) << d_func()->m_dataType; } } diff --git a/language/duchain/types/pointertype.cpp b/language/duchain/types/pointertype.cpp index 1e9fe884f..3e9d0c5cf 100644 --- a/language/duchain/types/pointertype.cpp +++ b/language/duchain/types/pointertype.cpp @@ -1,105 +1,105 @@ /* This file is part of KDevelop Copyright 2006 Roberto Raggi Copyright 2006-2008 Hamish Rodda Copyright 2007-2008 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "pointertype.h" #include "typerepository.h" #include "typesystemdata.h" #include "typeregister.h" #include "typesystem.h" namespace KDevelop { REGISTER_TYPE(PointerType); PointerType::PointerType(const PointerType& rhs) : AbstractType(copyData(*rhs.d_func())) { } PointerType::PointerType(PointerTypeData& data) : AbstractType(data) { } AbstractType* PointerType::clone() const { return new PointerType(*this); } bool PointerType::equals(const AbstractType* _rhs) const { if( this == _rhs ) return true; if (!AbstractType::equals(_rhs)) return false; Q_ASSERT(fastCast(_rhs)); const PointerType* rhs = static_cast(_rhs); return d_func()->m_baseType == rhs->d_func()->m_baseType; } PointerType::PointerType() : AbstractType(createData()) { } void PointerType::accept0 (TypeVisitor *v) const { if (v->visit (this)) acceptType (d_func()->m_baseType.abstractType(), v); v->endVisit (this); } void PointerType::exchangeTypes( TypeExchanger* exchanger ) { d_func_dynamic()->m_baseType = exchanger->exchange( d_func()->m_baseType.abstractType() )->indexed(); } PointerType::~PointerType() { } AbstractType::Ptr PointerType::baseType () const { return d_func()->m_baseType.abstractType(); } void PointerType::setBaseType(AbstractType::Ptr type) { d_func_dynamic()->m_baseType = type->indexed(); } QString PointerType::toString() const { - QString baseString = (baseType() ? baseType()->toString() : ""); + QString baseString = (baseType() ? baseType()->toString() : QStringLiteral("")); return QStringLiteral("%1*").arg(baseString) + AbstractType::toString(true); } AbstractType::WhichType PointerType::whichType() const { return TypePointer; } uint PointerType::hash() const { return KDevHash(AbstractType::hash()) << d_func()->m_baseType.hash(); } } diff --git a/language/duchain/types/referencetype.cpp b/language/duchain/types/referencetype.cpp index 12715ca25..06c9fe575 100644 --- a/language/duchain/types/referencetype.cpp +++ b/language/duchain/types/referencetype.cpp @@ -1,122 +1,122 @@ /* This file is part of KDevelop Copyright 2006 Roberto Raggi Copyright 2006-2008 Hamish Rodda Copyright 2007-2008 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "referencetype.h" #include "typerepository.h" #include "typesystemdata.h" #include "typeregister.h" #include "typesystem.h" #include "integraltype.h" #include "structuretype.h" namespace KDevelop { REGISTER_TYPE(ReferenceType); ReferenceType::ReferenceType(const ReferenceType& rhs) : AbstractType(copyData(*rhs.d_func())) { } ReferenceType::ReferenceType(ReferenceTypeData& data) : AbstractType(data) { } AbstractType* ReferenceType::clone() const { return new ReferenceType(*this); } bool ReferenceType::equals(const AbstractType* _rhs) const { if( this == _rhs ) return true; if (!AbstractType::equals(_rhs)) return false; Q_ASSERT(fastCast(_rhs)); const ReferenceType* rhs = static_cast(_rhs); return d_func()->m_baseType == rhs->d_func()->m_baseType; } ReferenceType::ReferenceType() : AbstractType(createData()) { } ReferenceType::~ReferenceType() { } AbstractType::Ptr ReferenceType::baseType () const { return d_func()->m_baseType.abstractType(); } void ReferenceType::setBaseType(AbstractType::Ptr type) { d_func_dynamic()->m_baseType = type->indexed(); } bool ReferenceType::isRValue() const { return d_func()->m_isRValue; } void ReferenceType::setIsRValue(bool isRValue) { d_func_dynamic()->m_isRValue = isRValue; } void ReferenceType::accept0 (TypeVisitor *v) const { if (v->visit (this)) acceptType (d_func()->m_baseType.abstractType(), v); v->endVisit (this); } void ReferenceType::exchangeTypes( TypeExchanger* exchanger ) { d_func_dynamic()->m_baseType = exchanger->exchange( d_func()->m_baseType.abstractType() )->indexed(); } QString ReferenceType::toString() const { AbstractType::Ptr base = baseType(); - QString baseString = (base ? base->toString() : ""); - const QString ampersands = d_func()->m_isRValue ? "&&" : "&"; + QString baseString = (base ? base->toString() : QStringLiteral("")); + const QString ampersands = d_func()->m_isRValue ? QStringLiteral("&&") : QStringLiteral("&"); if(base.cast() || base.cast()) return AbstractType::toString(false) + baseString + ampersands; else return baseString + AbstractType::toString(true) + ampersands; } AbstractType::WhichType ReferenceType::whichType() const { return TypeReference; } uint ReferenceType::hash() const { return KDevHash(AbstractType::hash()) << d_func()->m_baseType.hash() << d_func()->m_isRValue; } } diff --git a/language/duchain/types/structuretype.cpp b/language/duchain/types/structuretype.cpp index d6f85f091..40acf6626 100644 --- a/language/duchain/types/structuretype.cpp +++ b/language/duchain/types/structuretype.cpp @@ -1,101 +1,101 @@ /* This file is part of KDevelop Copyright 2006 Roberto Raggi 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 "structuretype.h" #include "typerepository.h" #include "typesystemdata.h" #include "typeregister.h" #include "typesystem.h" namespace KDevelop { REGISTER_TYPE(StructureType); StructureType::StructureType(const StructureType& rhs) : StructureTypeBase(copyData(*rhs.d_func())) { } StructureType::StructureType(StructureTypeData& data) : StructureTypeBase(data) { } AbstractType* StructureType::clone() const { return new StructureType(*this); } bool StructureType::equals(const AbstractType* _rhs) const { if( this == _rhs ) return true; if (!StructureTypeBase::equals(_rhs)) return false; // Q_ASSERT(fastCast(_rhs)); // const StructureType* rhs = static_cast(_rhs); return true; } StructureType::StructureType() : StructureTypeBase(createData()) { } StructureType::~StructureType() { } void StructureType::accept0 (TypeVisitor *v) const { // TYPE_D(StructureType); v->visit (this); v->endVisit (this); } QString StructureType::toString() const { QualifiedIdentifier id = qualifiedIdentifier(); if (!id.isEmpty()) { return AbstractType::toString() + id.toString(); } - QString type = "class"; + QString type = QStringLiteral("class"); return QStringLiteral("<%1>").arg(type) + AbstractType::toString(true); } AbstractType::WhichType StructureType::whichType() const { return TypeStructure; } uint StructureType::hash() const { return KDevHash(AbstractType::hash()) << IdentifiedType::hash(); } } diff --git a/language/duchain/types/typealiastype.cpp b/language/duchain/types/typealiastype.cpp index d3cc9b8dc..c300e1f0c 100644 --- a/language/duchain/types/typealiastype.cpp +++ b/language/duchain/types/typealiastype.cpp @@ -1,98 +1,98 @@ /* Copyright 2009 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "typealiastype.h" #include "typeregister.h" #include "typesystem.h" namespace KDevelop { REGISTER_TYPE(TypeAliasType); AbstractType* TypeAliasType::clone() const { return new TypeAliasType(*this); } bool TypeAliasType::equals(const AbstractType* _rhs) const { if( !fastCast(_rhs)) return false; const TypeAliasType* rhs = static_cast(_rhs); if( this == rhs ) return true; if( AbstractType::equals(rhs) && IdentifiedType::equals(rhs) ) { if( (bool)d_func()->m_type != (bool)rhs->d_func()->m_type ) return false; if( !d_func()->m_type ) return true; return d_func()->m_type == rhs->d_func()->m_type; } else { return false; } } AbstractType::Ptr TypeAliasType::type() const { return d_func()->m_type.abstractType(); } void TypeAliasType::setType(AbstractType::Ptr type) { d_func_dynamic()->m_type = type->indexed(); } uint TypeAliasType::hash() const { return KDevHash(AbstractType::hash()) << IdentifiedType::hash() << d_func()->m_type.hash(); } QString TypeAliasType::toString() const { QualifiedIdentifier id = qualifiedIdentifier(); if (!id.isEmpty()) return AbstractType::toString(false) + id.toString(); if (type()) return AbstractType::toString(false) + type()->toString(); - return "typedef "; + return QStringLiteral("typedef "); } void TypeAliasType::accept0 (KDevelop::TypeVisitor *v) const { if (v->visit (this)) acceptType (d_func()->m_type.abstractType(), v); // v->endVisit (this); } KDevelop::AbstractType::WhichType TypeAliasType::whichType() const { return TypeAlias; } void TypeAliasType::exchangeTypes(KDevelop::TypeExchanger* exchanger) { d_func_dynamic()->m_type = exchanger->exchange( d_func()->m_type.abstractType() )->indexed(); } } diff --git a/language/duchain/types/typerepository.cpp b/language/duchain/types/typerepository.cpp index dc81ba34d..008652550 100644 --- a/language/duchain/types/typerepository.cpp +++ b/language/duchain/types/typerepository.cpp @@ -1,156 +1,156 @@ /* Copyright 2008 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "typerepository.h" #include #include #include #include "util/debug.h" #include "../types/typesystemdata.h" #include "../types/typeregister.h" #include #include #define DEBUG_TYPE_REPOSITORY #define ASSERT_ON_PROBLEM namespace KDevelop { class AbstractTypeDataRequest { public: AbstractTypeDataRequest(const AbstractType& type) : m_item(type) { } enum { AverageSize = sizeof(AbstractTypeData) + 12 }; unsigned int hash() const { return m_item.hash(); } uint itemSize() const { return TypeSystem::self().dynamicSize(*m_item.d_ptr); } void createItem(AbstractTypeData* item) const { TypeSystem::self().copy(*m_item.d_ptr, *item, true); Q_ASSERT(!item->m_dynamic); #ifdef DEBUG_TYPE_REPOSITORY AbstractType::Ptr otherType( TypeSystem::self().create(const_cast(item)) ); if(!otherType->equals(&m_item)) { //For debugging, so one can trace what happened qCWarning(LANGUAGE) << "created type in repository does not equal source type:" << m_item.toString() << otherType->toString(); TypeSystem::self().copy(*m_item.d_ptr, *item, true); otherType->equals(&m_item); } #ifdef ASSERT_ON_PROBLEM Q_ASSERT(otherType->equals(&m_item)); #endif #endif item->inRepository = true; } static void destroy(AbstractTypeData* item, KDevelop::AbstractItemRepository&) { TypeSystem::self().callDestructor(item); } static bool persistent(const AbstractTypeData* item) { // Don't try to delete release items for which the factory is not loaded, as that will lead to a crash/assertion later return (bool)item->refCount || !TypeSystem::self().isFactoryLoaded(*item); } bool equals(const AbstractTypeData* item) const { AbstractType::Ptr otherType( TypeSystem::self().create(const_cast(item)) ); if(!otherType) return false; return m_item.equals(otherType.data()); } const AbstractType& m_item; }; //The object is created in a function, to prevent initialization-order issues static RepositoryManager< ItemRepository, false>& typeRepository() { - static RepositoryManager< ItemRepository, false> repository("Type Repository"); + static RepositoryManager< ItemRepository, false> repository(QStringLiteral("Type Repository")); return repository; } void initTypeRepository() { typeRepository(); } AbstractRepositoryManager* typeRepositoryManager() { return &typeRepository(); } uint TypeRepository::indexForType(AbstractType::Ptr input) { if(!input) return 0; uint i = typeRepository()->index(AbstractTypeDataRequest(*input)); #ifdef DEBUG_TYPE_REPOSITORY AbstractType::Ptr t = typeForIndex(i); if(!t->equals(input.data())) { qCWarning(LANGUAGE) << "found type in repository does not equal source type:" << input->toString() << t->toString(); t->equals(input.data()); } #ifdef ASSERT_ON_PROBLEM Q_ASSERT(t->equals(input.data())); Q_ASSERT(input->equals(t.data())); #endif #endif return i; } AbstractType::Ptr TypeRepository::typeForIndex(uint index) { if(index == 0) return AbstractType::Ptr(); return AbstractType::Ptr( TypeSystem::self().create(const_cast(typeRepository()->itemFromIndex(index))) ); } void TypeRepository::increaseReferenceCount(uint index, ReferenceCountManager* manager) { if(!index) return; QMutexLocker lock(typeRepository()->mutex()); AbstractTypeData* data = typeRepository()->dynamicItemFromIndexSimple(index); Q_ASSERT(data); if(manager) manager->increase(data->refCount, index); else ++data->refCount; } void TypeRepository::decreaseReferenceCount(uint index, ReferenceCountManager* manager) { if(!index) return; QMutexLocker lock(typeRepository()->mutex()); AbstractTypeData* data = typeRepository()->dynamicItemFromIndexSimple(index); Q_ASSERT(data); Q_ASSERT(data->refCount > 0); if(manager) manager->decrease(data->refCount, index); else --data->refCount; } } diff --git a/language/duchain/types/unsuretype.cpp b/language/duchain/types/unsuretype.cpp index b3e14bd97..0a1f2fcc2 100644 --- a/language/duchain/types/unsuretype.cpp +++ b/language/duchain/types/unsuretype.cpp @@ -1,123 +1,123 @@ /* Copyright 2009 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "unsuretype.h" #include "typeregister.h" #include "typesystem.h" namespace KDevelop { REGISTER_TYPE(UnsureType); DEFINE_LIST_MEMBER_HASH(UnsureTypeData, m_types, IndexedType) UnsureType::UnsureType(const KDevelop::UnsureType& rhs): AbstractType(copyData(*rhs.d_func())) { } UnsureType::UnsureType() : AbstractType(createData()) { } void UnsureType::accept0(KDevelop::TypeVisitor* v) const { FOREACH_FUNCTION(const IndexedType& type, d_func()->m_types) { AbstractType::Ptr t = type.abstractType(); v->visit(t.data()); } } KDevelop::AbstractType* UnsureType::clone() const { return new UnsureType(*this); } QString UnsureType::toString() const { - QString ret = "unsure ("; + QString ret = QStringLiteral("unsure ("); bool first = true; FOREACH_FUNCTION(const IndexedType& type, d_func()->m_types) { if(!first) - ret += ", "; + ret += QLatin1String(", "); first = false; AbstractType::Ptr t = type.abstractType(); if(t) ret += t->toString(); else - ret += "none"; + ret += QLatin1String("none"); } ret += ')'; return ret; } bool UnsureType::equals(const KDevelop::AbstractType* rhs) const { const UnsureType* rhsU = dynamic_cast(rhs); if(!rhsU) return false; if(d_func()->typeClassId != rhsU->d_func()->typeClassId) return false; if(d_func()->m_typesSize() != rhsU->d_func()->m_typesSize()) return false; for(uint a = 0; a < d_func()->m_typesSize(); ++a) if(d_func()->m_types()[a] != rhsU->d_func()->m_types()[a]) return false; return KDevelop::AbstractType::equals(rhs); } uint UnsureType::hash() const { KDevHash kdevhash(AbstractType::hash()); FOREACH_FUNCTION(const IndexedType& type, d_func()->m_types) kdevhash << type.hash(); return kdevhash << d_func()->m_typesSize(); } KDevelop::AbstractType::WhichType UnsureType::whichType() const { return TypeUnsure; } void UnsureType::exchangeTypes(KDevelop::TypeExchanger* exchanger) { for(uint a = 0; a < d_func()->m_typesSize(); ++a) { AbstractType::Ptr from = d_func()->m_types()[a].abstractType(); AbstractType::Ptr exchanged = exchanger->exchange(from); if(exchanged != from) d_func_dynamic()->m_typesList()[a] = exchanged->indexed(); } KDevelop::AbstractType::exchangeTypes(exchanger); } void UnsureType::addType(KDevelop::IndexedType type) { if ( !d_func_dynamic()->m_typesList().contains(type) ) { d_func_dynamic()->m_typesList().append(type); } } void UnsureType::removeType(KDevelop::IndexedType type) { d_func_dynamic()->m_typesList().removeOne(type); } const KDevelop::IndexedType* UnsureType::types() const { return d_func()->m_types(); } uint UnsureType::typesSize() const { return d_func()->m_typesSize(); } UnsureType::UnsureType(KDevelop::UnsureTypeData& data): AbstractType(data) { } } diff --git a/language/duchain/uses.cpp b/language/duchain/uses.cpp index f4a5600b5..263a2b338 100644 --- a/language/duchain/uses.cpp +++ b/language/duchain/uses.cpp @@ -1,200 +1,200 @@ /* 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 "uses.h" #include #include #include "declarationid.h" #include "duchainpointer.h" #include "serialization/itemrepository.h" #include "topducontext.h" namespace KDevelop { DEFINE_LIST_MEMBER_HASH(UsesItem, uses, IndexedTopDUContext) class UsesItem { public: UsesItem() { initializeAppendedLists(); } UsesItem(const UsesItem& rhs, bool dynamic = true) : declaration(rhs.declaration) { initializeAppendedLists(dynamic); copyListsFrom(rhs); } ~UsesItem() { 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 declaration.hash(); } unsigned int itemSize() const { return dynamicSize(); } uint classSize() const { return sizeof(UsesItem); } DeclarationId declaration; START_APPENDED_LISTS(UsesItem); APPENDED_LIST_FIRST(UsesItem, IndexedTopDUContext, uses); END_APPENDED_LISTS(UsesItem, uses); }; class UsesRequestItem { public: UsesRequestItem(const UsesItem& 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(UsesItem* item) const { new (item) UsesItem(m_item, false); } static void destroy(UsesItem* item, KDevelop::AbstractItemRepository&) { item->~UsesItem(); } static bool persistent(const UsesItem* /*item*/) { return true; } bool equals(const UsesItem* item) const { return m_item.declaration == item->declaration; } const UsesItem& m_item; }; class UsesPrivate { public: - UsesPrivate() : m_uses("Use Map") { + UsesPrivate() : m_uses(QStringLiteral("Use Map")) { } //Maps declaration-ids to Uses ItemRepository m_uses; }; Uses::Uses() : d(new UsesPrivate()) { } Uses::~Uses() { delete d; } void Uses::addUse(const DeclarationId& id, const IndexedTopDUContext& use) { UsesItem item; item.declaration = id; item.usesList().append(use); UsesRequestItem request(item); uint index = d->m_uses.findIndex(item); if(index) { //Check whether the item is already in the mapped list, else copy the list into the new created item const UsesItem* oldItem = d->m_uses.itemFromIndex(index); for(unsigned int a = 0; a < oldItem->usesSize(); ++a) { if(oldItem->uses()[a] == use) return; //Already there item.usesList().append(oldItem->uses()[a]); } d->m_uses.deleteItem(index); } //This inserts the changed item d->m_uses.index(request); } void Uses::removeUse(const DeclarationId& id, const IndexedTopDUContext& use) { UsesItem item; item.declaration = id; UsesRequestItem request(item); uint index = d->m_uses.findIndex(item); if(index) { //Check whether the item is already in the mapped list, else copy the list into the new created item const UsesItem* oldItem = d->m_uses.itemFromIndex(index); for(unsigned int a = 0; a < oldItem->usesSize(); ++a) if(!(oldItem->uses()[a] == use)) item.usesList().append(oldItem->uses()[a]); d->m_uses.deleteItem(index); Q_ASSERT(d->m_uses.findIndex(item) == 0); //This inserts the changed item if(item.usesSize() != 0) d->m_uses.index(request); } } bool Uses::hasUses(const DeclarationId& id) const { UsesItem item; item.declaration = id; return (bool) d->m_uses.findIndex(item); } KDevVarLengthArray Uses::uses(const DeclarationId& id) const { KDevVarLengthArray ret; UsesItem item; item.declaration = id; UsesRequestItem request(item); uint index = d->m_uses.findIndex(item); if(index) { const UsesItem* repositoryItem = d->m_uses.itemFromIndex(index); FOREACH_FUNCTION(const IndexedTopDUContext& decl, repositoryItem->uses) ret.append(decl); } return ret; } } diff --git a/language/editor/modificationrevisionset.cpp b/language/editor/modificationrevisionset.cpp index af23f32ab..c45c45657 100644 --- a/language/editor/modificationrevisionset.cpp +++ b/language/editor/modificationrevisionset.cpp @@ -1,329 +1,329 @@ /* Copyright 2008 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "modificationrevisionset.h" #include "util/debug.h" #include #include #include //When uncommented, the reason for needed updates is printed // #define DEBUG_NEEDSUPDATE namespace KDevelop { QMutex modificationRevisionSetMutex(QMutex::Recursive); struct FileModificationPair { KDevelop::IndexedString file; KDevelop::ModificationRevision revision; FileModificationPair() { } FileModificationPair(KDevelop::IndexedString _file, KDevelop::ModificationRevision _revision) : file(_file), revision(_revision) { } unsigned int hash() const { return ((file.hash() + revision.modificationTime) * 17 + revision.revision) * 73; } unsigned short int itemSize() const { return sizeof(FileModificationPair); } bool operator==(const FileModificationPair& rhs) const { return file == rhs.file && revision == rhs.revision; } }; struct FileModificationPairRequest { FileModificationPairRequest(const FileModificationPair& data) : m_data(data) { } const FileModificationPair& m_data; enum { AverageSize = sizeof(FileModificationPair) }; unsigned int hash() const { return m_data.hash(); } uint itemSize() const { return m_data.itemSize(); } void createItem(FileModificationPair* item) const { new (item) FileModificationPair(m_data); } bool equals(const FileModificationPair* item) const { return *item == m_data; } static void destroy(FileModificationPair* item, KDevelop::AbstractItemRepository&) { item->~FileModificationPair(); } static bool persistent(const FileModificationPair* /*item*/) { return true; //Reference-counting is done implicitly using the set-repository } }; typedef KDevelop::ItemRepository FileModificationPairRepository; static FileModificationPairRepository& fileModificationPairRepository() { - static FileModificationPairRepository rep("file modification repository"); + static FileModificationPairRepository rep(QStringLiteral("file modification repository")); rep.setMutex(&modificationRevisionSetMutex); return rep; } void initModificationRevisionSetRepository() { fileModificationPairRepository(); } QHash > needsUpdateCache; void ModificationRevisionSet::clearCache() { QMutexLocker lock(&modificationRevisionSetMutex); ///@todo More intelligent clearing. We actually need to watch the directory for changes, and if there are changes, clear the cache. needsUpdateCache.clear(); } struct FileModificationSetRepository : public Utils::BasicSetRepository { - FileModificationSetRepository() : Utils::BasicSetRepository("file modification sets", &globalItemRepositoryRegistry(), true) { + FileModificationSetRepository() : Utils::BasicSetRepository(QStringLiteral("file modification sets"), &globalItemRepositoryRegistry(), true) { } void itemRemovedFromSets(uint index) override; }; //FileModificationSetRepository fileModificationSetRepository; struct FileModificationSetRepositoryRepresenter { static FileModificationSetRepository& repository() { static FileModificationSetRepository fileModificationSetRepository; return fileModificationSetRepository; } }; ModificationRevisionSet::ModificationRevisionSet(unsigned int index) : m_index(index) { } uint ModificationRevisionSet::size() const { Utils::Set set = Utils::Set(m_index, &FileModificationSetRepositoryRepresenter::repository()); return set.count(); } void ModificationRevisionSet::clear() { QMutexLocker lock(&modificationRevisionSetMutex); if(m_index) { Utils::Set oldModificationTimes = Utils::Set(m_index, &FileModificationSetRepositoryRepresenter::repository()); oldModificationTimes.staticUnref(); m_index = 0; } } void ModificationRevisionSet::addModificationRevision(const IndexedString& url, const KDevelop::ModificationRevision& revision) { QMutexLocker lock(&modificationRevisionSetMutex); if(m_index == 0) { Utils::Set set = FileModificationSetRepositoryRepresenter::repository().createSet(fileModificationPairRepository().index(FileModificationPair(url, revision))); set.staticRef(); m_index = set.setIndex(); }else{ Utils::Set oldModificationTimes = Utils::Set(m_index, &FileModificationSetRepositoryRepresenter::repository()); Utils::Set newModificationTimes = oldModificationTimes; Utils::Set tempSet = FileModificationSetRepositoryRepresenter::repository().createSet(fileModificationPairRepository().index(FileModificationPair(url, revision))); tempSet.staticRef(); newModificationTimes += tempSet; newModificationTimes.staticRef(); oldModificationTimes.staticUnref(); tempSet.staticUnref(); m_index = newModificationTimes.setIndex(); } } bool ModificationRevisionSet::removeModificationRevision(const IndexedString& url, const KDevelop::ModificationRevision& revision) { QMutexLocker lock(&modificationRevisionSetMutex); if(!m_index) return false; Utils::Set oldModificationTimes = Utils::Set(m_index, &FileModificationSetRepositoryRepresenter::repository()); Utils::Set newModificationTimes = oldModificationTimes; Utils::Set tempSet = FileModificationSetRepositoryRepresenter::repository().createSet(fileModificationPairRepository().index(FileModificationPair(url, revision))); tempSet.staticRef(); newModificationTimes -= tempSet; newModificationTimes.staticRef(); oldModificationTimes.staticUnref(); tempSet.staticUnref(); m_index = newModificationTimes.setIndex(); return m_index != oldModificationTimes.setIndex(); } // const QMap ModificationRevisionSet::allModificationTimes() const { // QMap ret; // Utils::Set::Iterator it = m_allModificationTimes.iterator(); // while(it) { // const FileModificationPair* data = fileModificationPairRepository().itemFromIndex(*it); // ret[data->file] = data->revision; // ++it; // } // return ret; // } typedef Utils::VirtualSetNode, FileModificationSetRepositoryRepresenter> ModificationRevisionSetNode; // static bool (const Utils::SetNodeData* node) { // ModificationRevisionSetNode // if(!node) // return false; // } static bool nodeNeedsUpdate(uint index) { QMutexLocker lock(&modificationRevisionSetMutex); if(!index) return false; const auto currentTime = QDateTime::currentDateTime(); auto cached = needsUpdateCache.constFind(index); if(cached != needsUpdateCache.constEnd()) { if((*cached).first.secsTo(currentTime) < cacheModificationTimesForSeconds ) { return cached->second; } } bool result = false; const Utils::SetNodeData* nodeData = FileModificationSetRepositoryRepresenter::repository().nodeFromIndex(index); if(nodeData->contiguous()) { //Do the actual checking for(unsigned int a = nodeData->start(); a < nodeData->end(); ++a) { const FileModificationPair* data = fileModificationPairRepository().itemFromIndex(a); ModificationRevision revision = KDevelop::ModificationRevision::revisionForFile( data->file ); if( revision != data->revision ) { result = true; break; } } }else{ result = nodeNeedsUpdate(nodeData->leftNode()) || nodeNeedsUpdate(nodeData->rightNode()); } needsUpdateCache.insert(index, std::make_pair(currentTime, result)); return result; } QString ModificationRevisionSet::toString() const { QMutexLocker lock(&modificationRevisionSetMutex); - QString ret = "["; // krazy:exclude=doublequote_chars + QString ret = QStringLiteral("["); // krazy:exclude=doublequote_chars Utils::Set set(m_index, &FileModificationSetRepositoryRepresenter::repository()); Utils::Set::Iterator it = set.iterator(); bool first = true; while(it) { if(!first) - ret += ", "; + ret += QLatin1String(", "); first = false; const FileModificationPair* data = fileModificationPairRepository().itemFromIndex(*it); ret += data->file.str() + ':' + data->revision.toString(); ++it; } ret += ']'; return ret; } bool ModificationRevisionSet::needsUpdate() const { QMutexLocker lock(&modificationRevisionSetMutex); #ifdef DEBUG_NEEDSUPDATE Utils::Set set(m_index, &FileModificationSetRepositoryRepresenter::repository()); Utils::Set::Iterator it = set.iterator(); while(it) { const FileModificationPair* data = fileModificationPairRepository().itemFromIndex(*it); ModificationRevision revision = KDevelop::ModificationRevision::revisionForFile( data->file ); if( revision != data->revision ) { qCDebug(LANGUAGE) << "dependency" << data->file.str() << "has changed, stored stamp:" << data->revision << "new time:" << revision ; return true; } ++it; } return false; #else return nodeNeedsUpdate(m_index); #endif } ModificationRevisionSet& ModificationRevisionSet::operator+=(const ModificationRevisionSet& rhs) { QMutexLocker lock(&modificationRevisionSetMutex); Utils::Set oldModificationTimes = Utils::Set(m_index, &FileModificationSetRepositoryRepresenter::repository()); Utils::Set otherModificationTimes = Utils::Set(rhs.m_index, &FileModificationSetRepositoryRepresenter::repository()); Utils::Set newModificationTimes = oldModificationTimes; newModificationTimes += otherModificationTimes; newModificationTimes.staticRef(); oldModificationTimes.staticUnref(); m_index = newModificationTimes.setIndex(); return *this; } ModificationRevisionSet& ModificationRevisionSet::operator-=(const ModificationRevisionSet& rhs) { QMutexLocker lock(&modificationRevisionSetMutex); Utils::Set oldModificationTimes = Utils::Set(m_index, &FileModificationSetRepositoryRepresenter::repository()); Utils::Set otherModificationTimes = Utils::Set(rhs.m_index, &FileModificationSetRepositoryRepresenter::repository()); Utils::Set newModificationTimes = oldModificationTimes; newModificationTimes -= otherModificationTimes; newModificationTimes.staticRef(); oldModificationTimes.staticUnref(); m_index = newModificationTimes.setIndex(); return *this; } void FileModificationSetRepository::itemRemovedFromSets(uint index) { fileModificationPairRepository().deleteItem(index); needsUpdateCache.remove(index); } } diff --git a/language/highlighting/codehighlighting.cpp b/language/highlighting/codehighlighting.cpp index d674741f3..3f42b0edd 100644 --- a/language/highlighting/codehighlighting.cpp +++ b/language/highlighting/codehighlighting.cpp @@ -1,630 +1,630 @@ /* * This file is part of KDevelop * * Copyright 2007-2010 David Nolden * Copyright 2006 Hamish Rodda * Copyright 2009 Milian Wolff * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "codehighlighting.h" #include #include "../../interfaces/icore.h" #include "../../interfaces/ilanguagecontroller.h" #include "../../interfaces/icompletionsettings.h" #include "../../util/foregroundlock.h" #include "util/debug.h" #include "../duchain/declaration.h" #include "../duchain/types/functiontype.h" #include "../duchain/types/enumeratortype.h" #include "../duchain/types/typealiastype.h" #include "../duchain/types/enumerationtype.h" #include "../duchain/types/structuretype.h" #include "../duchain/functiondefinition.h" #include "../duchain/use.h" #include "colorcache.h" #include "configurablecolors.h" #include #include #include #include using namespace KTextEditor; static const float highlightingZDepth = -500; #define ifDebug(x) namespace KDevelop { ///@todo Don't highlighting everything, only what is visible on-demand CodeHighlighting::CodeHighlighting( QObject * parent ) : QObject(parent), m_localColorization(true), m_globalColorization(true), m_dataMutex(QMutex::Recursive) { qRegisterMetaType("KDevelop::IndexedString"); adaptToColorChanges(); connect(ColorCache::self(), &ColorCache::colorsGotChanged, this, &CodeHighlighting::adaptToColorChanges); } CodeHighlighting::~CodeHighlighting( ) { - qDeleteAll(m_highlights.values()); + qDeleteAll(m_highlights); } void CodeHighlighting::adaptToColorChanges() { QMutexLocker lock(&m_dataMutex); // disable local highlighting if the ratio is set to 0 m_localColorization = ICore::self()->languageController()->completionSettings()->localColorizationLevel() > 0; // disable global highlighting if the ratio is set to 0 m_globalColorization = ICore::self()->languageController()->completionSettings()->globalColorizationLevel() > 0; m_declarationAttributes.clear(); m_definitionAttributes.clear(); m_depthAttributes.clear(); m_referenceAttributes.clear(); } KTextEditor::Attribute::Ptr CodeHighlighting::attributeForType( Types type, Contexts context, const QColor &color ) const { QMutexLocker lock(&m_dataMutex); KTextEditor::Attribute::Ptr a; switch (context) { case DefinitionContext: a = m_definitionAttributes[type]; break; case DeclarationContext: a = m_declarationAttributes[type]; break; case ReferenceContext: a = m_referenceAttributes[type]; break; } if ( !a || color.isValid() ) { a = KTextEditor::Attribute::Ptr(new KTextEditor::Attribute(*ColorCache::self()->defaultColors()->getAttribute(type))); if ( context == DefinitionContext || context == DeclarationContext ) { if (ICore::self()->languageController()->completionSettings()->boldDeclarations()) { a->setFontBold(); } } if( color.isValid() ) { a->setForeground(color); // a->setBackground(QColor(mix(0xffffff-color, backgroundColor(), 255-backgroundTinting))); } else { switch (context) { case DefinitionContext: m_definitionAttributes.insert(type, a); break; case DeclarationContext: m_declarationAttributes.insert(type, a); break; case ReferenceContext: m_referenceAttributes.insert(type, a); break; } } } return a; } ColorMap emptyColorMap() { ColorMap ret(ColorCache::self()->validColorCount()+1, 0); return ret; } CodeHighlightingInstance* CodeHighlighting::createInstance() const { return new CodeHighlightingInstance(this); } bool CodeHighlighting::hasHighlighting(IndexedString url) const { DocumentChangeTracker* tracker = ICore::self()->languageController()->backgroundParser()->trackerForUrl(url); if(tracker) { QMutexLocker lock(&m_dataMutex); return m_highlights.contains(tracker) && !m_highlights[tracker]->m_highlightedRanges.isEmpty(); } return false; } void CodeHighlighting::highlightDUChain(ReferencedTopDUContext context) { ENSURE_CHAIN_NOT_LOCKED IndexedString url; { DUChainReadLocker lock; if (!context) return; url = context->url(); } // This prevents the background-parser from updating the top-context while we're working with it UrlParseLock urlLock(context->url()); DUChainReadLocker lock; qint64 revision = context->parsingEnvironmentFile()->modificationRevision().revision; qCDebug(LANGUAGE) << "highlighting du chain" << url.toUrl(); if ( !m_localColorization && !m_globalColorization ) { qCDebug(LANGUAGE) << "highlighting disabled"; QMetaObject::invokeMethod(this, "clearHighlightingForDocument", Qt::QueuedConnection, Q_ARG(KDevelop::IndexedString, url)); return; } CodeHighlightingInstance* instance = createInstance(); lock.unlock(); instance->highlightDUChain(context.data()); DocumentHighlighting* highlighting = new DocumentHighlighting; highlighting->m_document = url; highlighting->m_waitingRevision = revision; highlighting->m_waiting = instance->m_highlight; std::sort(highlighting->m_waiting.begin(), highlighting->m_waiting.end()); QMetaObject::invokeMethod(this, "applyHighlighting", Qt::QueuedConnection, Q_ARG(void*, highlighting)); delete instance; } void CodeHighlightingInstance::highlightDUChain(TopDUContext* context) { m_contextClasses.clear(); m_useClassCache = true; //Highlight highlightDUChain(context, QHash(), emptyColorMap()); m_functionColorsForDeclarations.clear(); m_functionDeclarationsForColors.clear(); m_useClassCache = false; m_contextClasses.clear(); } void CodeHighlightingInstance::highlightDUChain(DUContext* context, QHash colorsForDeclarations, ColorMap declarationsForColors) { DUChainReadLocker lock; TopDUContext* top = context->topContext(); //Merge the colors from the function arguments foreach( const DUContext::Import &imported, context->importedParentContexts() ) { if(!imported.context(top) || (imported.context(top)->type() != DUContext::Other && imported.context(top)->type() != DUContext::Function)) continue; //For now it's enough simply copying them, because we only pass on colors within function bodies. if (m_functionColorsForDeclarations.contains(imported.context(top))) colorsForDeclarations = m_functionColorsForDeclarations[imported.context(top)]; if (m_functionDeclarationsForColors.contains(imported.context(top))) declarationsForColors = m_functionDeclarationsForColors[imported.context(top)]; } QList takeFreeColors; foreach (Declaration* dec, context->localDeclarations()) { if (!useRainbowColor(dec)) { highlightDeclaration(dec, QColor(QColor::Invalid)); continue; } //Initially pick a color using the hash, so the chances are good that the same identifier gets the same color always. uint colorNum = dec->identifier().hash() % ColorCache::self()->primaryColorCount(); if( declarationsForColors[colorNum] ) { takeFreeColors << dec; //Use one of the colors that stays free continue; } colorsForDeclarations[dec] = colorNum; declarationsForColors[colorNum] = dec; highlightDeclaration(dec, ColorCache::self()->generatedColor(colorNum)); } foreach (Declaration* dec, takeFreeColors) { uint colorNum = dec->identifier().hash() % ColorCache::self()->primaryColorCount(); uint oldColorNum = colorNum; while (declarationsForColors[colorNum]) { colorNum = (colorNum + 1) % ColorCache::self()->primaryColorCount(); if (colorNum == oldColorNum) { colorNum = ColorCache::self()->primaryColorCount(); break; } } if (colorNum < ColorCache::self()->primaryColorCount()) { // Use primary color colorsForDeclarations[dec] = colorNum; declarationsForColors[colorNum] = dec; highlightDeclaration(dec, ColorCache::self()->generatedColor(colorNum)); } else { // Try to use supplementary color colorNum = ColorCache::self()->primaryColorCount(); while (declarationsForColors[colorNum]) { colorNum++; if (colorNum == ColorCache::self()->validColorCount()) { //If no color could be found, use default color highlightDeclaration(dec, QColor(QColor::Invalid)); break; } } if (colorNum < ColorCache::self()->validColorCount()) { // Use supplementary color colorsForDeclarations[dec] = colorNum; declarationsForColors[colorNum] = dec; highlightDeclaration(dec, ColorCache::self()->generatedColor(colorNum)); } } } for(int a = 0; a < context->usesCount(); ++a) { Declaration* decl = context->topContext()->usedDeclarationForIndex(context->uses()[a].m_declarationIndex); QColor color(QColor::Invalid); if( colorsForDeclarations.contains(decl) ) color = ColorCache::self()->generatedColor(colorsForDeclarations[decl]); highlightUse(context, a, color); } if(context->type() == DUContext::Other || context->type() == DUContext::Function) { m_functionColorsForDeclarations[IndexedDUContext(context)] = colorsForDeclarations; m_functionDeclarationsForColors[IndexedDUContext(context)] = declarationsForColors; } QVector< DUContext* > children = context->childContexts(); lock.unlock(); // Periodically release the lock, so that the UI won't be blocked too much foreach (DUContext* child, children) highlightDUChain(child, colorsForDeclarations, declarationsForColors ); } KTextEditor::Attribute::Ptr CodeHighlighting::attributeForDepth(int depth) const { while (depth >= m_depthAttributes.count()) { KTextEditor::Attribute::Ptr a(new KTextEditor::Attribute()); a->setBackground(QColor(Qt::white).dark(100 + (m_depthAttributes.count() * 25))); a->setBackgroundFillWhitespace(true); if (depth % 2) a->setOutline(Qt::red); m_depthAttributes.append(a); } return m_depthAttributes[depth]; } KDevelop::Declaration* CodeHighlightingInstance::localClassFromCodeContext(KDevelop::DUContext* context) const { if(!context) return 0; if(m_contextClasses.contains(context)) return m_contextClasses[context]; DUContext* startContext = context; while( context->parentContext() && context->type() == DUContext::Other && context->parentContext()->type() == DUContext::Other ) { //Move context to the top context of type "Other". This is needed because every compound-statement creates a new sub-context. context = context->parentContext(); } ///Step 1: Find the function-declaration for the function we are in Declaration* functionDeclaration = 0; if( FunctionDefinition* def = dynamic_cast(context->owner()) ) { if(m_contextClasses.contains(context)) return m_contextClasses[context]; functionDeclaration = def->declaration(startContext->topContext()); } if( !functionDeclaration && context->owner() ) functionDeclaration = context->owner(); if(!functionDeclaration) { if(m_useClassCache) m_contextClasses[context] = 0; return 0; } Declaration* decl = functionDeclaration->context()->owner(); if(m_useClassCache) m_contextClasses[context] = decl; return decl; } CodeHighlightingInstance::Types CodeHighlightingInstance::typeForDeclaration(Declaration * dec, DUContext* context) const { /** * We highlight in 3 steps by priority: * 1. Is the item in the local class or an inherited class? If yes, highlight. * 2. What kind of item is it? If it's a type/function/enumerator, highlight by type. * 3. Else, highlight by scope. * * */ // if(ClassMemberDeclaration* classMember = dynamic_cast(dec)) // if(!Cpp::isAccessible(context, classMember)) // return ErrorVariableType; if(!dec) return ErrorVariableType; Types type = LocalVariableType; if(dec->kind() == Declaration::Namespace) return NamespaceType; if(dec->kind() == Declaration::Macro){ return MacroType; } if (context && dec->context() && dec->context()->type() == DUContext::Class) { //It is a use. //Determine the class we're in Declaration* klass = localClassFromCodeContext(context); if(klass) { if (klass->internalContext() == dec->context()) type = LocalClassMemberType; //Using Member of the local class else if (dec->context()->type() == DUContext::Class && klass->internalContext() && klass->internalContext()->imports(dec->context())) type = InheritedClassMemberType; //Using Member of an inherited class } } if (type == LocalVariableType) { if (dec->kind() == Declaration::Type || dec->type() || dec->type()) { if (dec->isForwardDeclaration()) type = ForwardDeclarationType; else if (dec->type()) type = FunctionType; else if(dec->type()) type = ClassType; else if(dec->type()) type = TypeAliasType; else if(dec->type()) type = EnumType; else if(dec->type()) type = EnumeratorType; } } if (type == LocalVariableType) { switch (dec->context()->type()) { case DUContext::Namespace: type = NamespaceVariableType; break; case DUContext::Class: type = MemberVariableType; break; case DUContext::Function: type = FunctionVariableType; break; case DUContext::Global: type = GlobalVariableType; break; default: break; } } return type; } bool CodeHighlightingInstance::useRainbowColor(Declaration* dec) const { return dec->context()->type() == DUContext::Function || (dec->context()->type() == DUContext::Other && dec->context()->owner()); } void CodeHighlightingInstance::highlightDeclaration(Declaration * declaration, const QColor &color) { HighlightedRange h; h.range = declaration->range(); h.attribute = m_highlighting->attributeForType(typeForDeclaration(declaration, 0), DeclarationContext, color); m_highlight.push_back(h); } void CodeHighlightingInstance::highlightUse(DUContext* context, int index, const QColor &color) { Types type = ErrorVariableType; Declaration* decl = context->topContext()->usedDeclarationForIndex(context->uses()[index].m_declarationIndex); type = typeForDeclaration(decl, context); if(type != ErrorVariableType || ICore::self()->languageController()->completionSettings()->highlightSemanticProblems()) { HighlightedRange h; h.range = context->uses()[index].m_range; h.attribute = m_highlighting->attributeForType(type, ReferenceContext, color); m_highlight.push_back(h); } } void CodeHighlightingInstance::highlightUses(DUContext* context) { for(int a = 0; a < context->usesCount(); ++a) highlightUse(context, a, QColor(QColor::Invalid)); } void CodeHighlighting::clearHighlightingForDocument(IndexedString document) { VERIFY_FOREGROUND_LOCKED QMutexLocker lock(&m_dataMutex); DocumentChangeTracker* tracker = ICore::self()->languageController()->backgroundParser()->trackerForUrl(document); if(m_highlights.contains(tracker)) { disconnect(tracker, &DocumentChangeTracker::destroyed, this, &CodeHighlighting::trackerDestroyed); qDeleteAll(m_highlights[tracker]->m_highlightedRanges); delete m_highlights[tracker]; m_highlights.remove(tracker); } } void CodeHighlighting::applyHighlighting(void* _highlighting) { CodeHighlighting::DocumentHighlighting* highlighting = static_cast(_highlighting); VERIFY_FOREGROUND_LOCKED QMutexLocker lock(&m_dataMutex); DocumentChangeTracker* tracker = ICore::self()->languageController()->backgroundParser()->trackerForUrl(highlighting->m_document); if(!tracker) { qCDebug(LANGUAGE) << "no document found for the planned highlighting of" << highlighting->m_document.str(); delete highlighting; return; } QVector< MovingRange* > oldHighlightedRanges; if(m_highlights.contains(tracker)) { oldHighlightedRanges = m_highlights[tracker]->m_highlightedRanges; delete m_highlights[tracker]; }else{ // we newly add this tracker, so add the connection // This can't use new style connect syntax since MovingInterface is not a QObject connect(tracker->document(), SIGNAL(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document*)), this, SLOT(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document*))); connect(tracker->document(), SIGNAL(aboutToRemoveText(KTextEditor::Range)), this, SLOT(aboutToRemoveText(KTextEditor::Range))); connect(tracker, &DocumentChangeTracker::destroyed, this, &CodeHighlighting::trackerDestroyed); } m_highlights[tracker] = highlighting; // Now create MovingRanges (match old ones with the incoming ranges) KTextEditor::Range tempRange; QVector::iterator movingIt = oldHighlightedRanges.begin(); QVector::iterator rangeIt = highlighting->m_waiting.begin(); while(rangeIt != highlighting->m_waiting.end()) { // Translate the range into the current revision KTextEditor::Range transformedRange = tracker->transformToCurrentRevision(rangeIt->range, highlighting->m_waitingRevision); while(movingIt != oldHighlightedRanges.end() && ((*movingIt)->start().line() < transformedRange.start().line() || ((*movingIt)->start().line() == transformedRange.start().line() && (*movingIt)->start().column() < transformedRange.start().column()))) { delete *movingIt; // Skip ranges that are in front of the current matched range ++movingIt; } tempRange = transformedRange; if(movingIt == oldHighlightedRanges.end() || transformedRange.start().line() != (*movingIt)->start().line() || transformedRange.start().column() != (*movingIt)->start().column() || transformedRange.end().line() != (*movingIt)->end().line() || transformedRange.end().column() != (*movingIt)->end().column()) { Q_ASSERT(rangeIt->attribute); // The moving range is behind or unequal, create a new range highlighting->m_highlightedRanges.push_back(tracker->documentMovingInterface()->newMovingRange(tempRange)); highlighting->m_highlightedRanges.back()->setAttribute(rangeIt->attribute); highlighting->m_highlightedRanges.back()->setZDepth(highlightingZDepth); } else { // Update the existing moving range (*movingIt)->setAttribute(rangeIt->attribute); (*movingIt)->setRange(tempRange); highlighting->m_highlightedRanges.push_back(*movingIt); ++movingIt; } ++rangeIt; } for(; movingIt != oldHighlightedRanges.end(); ++movingIt) delete *movingIt; // Delete unmatched moving ranges behind } void CodeHighlighting::trackerDestroyed(QObject* object) { // Called when a document is destroyed VERIFY_FOREGROUND_LOCKED QMutexLocker lock(&m_dataMutex); DocumentChangeTracker* tracker = static_cast(object); Q_ASSERT(m_highlights.contains(tracker)); delete m_highlights[tracker]; // No need to care about the individual ranges, as the document is being destroyed m_highlights.remove(tracker); } void CodeHighlighting::aboutToInvalidateMovingInterfaceContent(Document* doc) { clearHighlightingForDocument(IndexedString(doc->url())); } void CodeHighlighting::aboutToRemoveText( const KTextEditor::Range& range ) { if (range.onSingleLine()) // don't try to optimize this return; VERIFY_FOREGROUND_LOCKED QMutexLocker lock(&m_dataMutex); Q_ASSERT(dynamic_cast(sender())); KTextEditor::Document* doc = static_cast(sender()); DocumentChangeTracker* tracker = ICore::self()->languageController()->backgroundParser() ->trackerForUrl(IndexedString(doc->url())); if(m_highlights.contains(tracker)) { QVector& ranges = m_highlights.value(tracker)->m_highlightedRanges; QVector::iterator it = ranges.begin(); while(it != ranges.end()) { if (range.contains((*it)->toRange())) { delete (*it); it = ranges.erase(it); } else { ++it; } } } } } // kate: space-indent on; indent-width 2; replace-trailing-space-save on; show-tabs on; tab-indents on; tab-width 2; diff --git a/language/highlighting/colorcache.cpp b/language/highlighting/colorcache.cpp index fa9c7d579..a5b42829d 100644 --- a/language/highlighting/colorcache.cpp +++ b/language/highlighting/colorcache.cpp @@ -1,334 +1,334 @@ /* * This file is part of KDevelop * * Copyright 2009 Milian Wolff * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "colorcache.h" #include "configurablecolors.h" #include #include "../../interfaces/icore.h" #include "../../interfaces/ilanguagecontroller.h" #include "../../interfaces/icompletionsettings.h" #include "../../interfaces/idocument.h" #include "../../interfaces/idocumentcontroller.h" #include "../interfaces/ilanguagesupport.h" #include "../duchain/duchain.h" #include "../duchain/duchainlock.h" #include "util/debug.h" #include #include #include #include #define ifDebug(x) namespace KDevelop { ColorCache* ColorCache::m_self = 0; ColorCache::ColorCache(QObject* parent) : QObject(parent), m_defaultColors(0), m_validColorCount(0), m_colorOffset(0), m_localColorRatio(0), m_globalColorRatio(0), m_boldDeclarations(true) { Q_ASSERT(m_self == 0); updateColorsFromScheme(); // default / fallback updateColorsFromSettings(); connect(ICore::self()->languageController()->completionSettings(), &ICompletionSettings::settingsChanged, this, &ColorCache::updateColorsFromSettings, Qt::QueuedConnection); connect(ICore::self()->documentController(), &IDocumentController::documentActivated, this, &ColorCache::slotDocumentActivated); bool hadDoc = tryActiveDocument(); updateInternal(); m_self = this; if (!hadDoc) { // try to update later on again QMetaObject::invokeMethod(this, "tryActiveDocument", Qt::QueuedConnection); } } bool ColorCache::tryActiveDocument() { KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView(); if ( view ) { updateColorsFromView(view); return true; } return false; } ColorCache::~ColorCache() { m_self = 0; delete m_defaultColors; m_defaultColors = 0; } ColorCache* ColorCache::self() { if (!m_self) { m_self = new ColorCache; } return m_self; } void ColorCache::generateColors() { if ( m_defaultColors ) { delete m_defaultColors; } m_defaultColors = new CodeHighlightingColors(this); // Primary colors taken from: http://colorbrewer2.org/?type=qualitative&scheme=Paired&n=12 const QColor colors[] = { {"#b15928"}, {"#ff7f00"}, {"#b2df8a"}, {"#33a02c"}, {"#a6cee3"}, {"#1f78b4"}, {"#6a3d9a"}, {"#cab2d6"}, {"#e31a1c"}, {"#fb9a99"} }; // Supplementary colors generated by: http://tools.medialab.sciences-po.fr/iwanthue/ const QColor supplementaryColors[] = { {"#D33B67"}, {"#5EC764"}, {"#6CC82D"}, {"#995729"}, {"#FB4D84"}, {"#4B8828"}, {"#D847D0"}, {"#B56AC5"}, {"#E96F0C"}, {"#DC7161"}, {"#4D7279"}, {"#01AAF1"}, {"#D2A237"}, {"#F08CA5"}, {"#C83E93"}, {"#5D7DF7"}, {"#EFBB51"}, {"#108BBB"}, {"#5C84B8"}, {"#02F8BC"}, {"#A5A9F7"}, {"#F28E64"}, {"#A461E6"}, {"#6372D3"} }; m_colors.clear(); for(const auto& color: colors){ m_colors.append(blendLocalColor(color)); } m_primaryColorCount = m_colors.count(); for(const auto& color: supplementaryColors){ m_colors.append(blendLocalColor(color)); } m_validColorCount = m_colors.count(); } void ColorCache::slotDocumentActivated() { KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView(); ifDebug(qCDebug(LANGUAGE) << "doc activated:" << doc;) if ( view ) { updateColorsFromView(view); } } void ColorCache::slotViewSettingsChanged() { KTextEditor::View* view = qobject_cast(sender()); Q_ASSERT(view); ifDebug(qCDebug(LANGUAGE) << "settings changed" << view;) updateColorsFromView(view); } void ColorCache::updateColorsFromView(KTextEditor::View* view) { if ( !view ) { // yeah, the HighlightInterface methods returning an Attribute // require a View... kill me for that mess return; } QColor foreground(QColor::Invalid); QColor background(QColor::Invalid); KTextEditor::Attribute::Ptr style = view->defaultStyleAttribute(KTextEditor::dsNormal); foreground = style->foreground().color(); if (style->hasProperty(QTextFormat::BackgroundBrush)) { background = style->background().color(); } // FIXME: this is in kateview // qCDebug(LANGUAGE) << "got foreground:" << foreground.name() << "old is:" << m_foregroundColor.name(); //NOTE: this slot is defined in KatePart > 4.4, see ApiDocs of the ConfigInterface // the signal is not defined in ConfigInterface, but according to the docs it should be // can't use new signal slot syntax here, since ConfigInterface is not a QObject if ( KTextEditor::View* view = m_view.data() ) { Q_ASSERT(qobject_cast(view)); // we only listen to a single view, i.e. the active one disconnect(view, SIGNAL(configChanged()), this, SLOT(slotViewSettingsChanged())); } Q_ASSERT(qobject_cast(view)); connect(view, SIGNAL(configChanged()), this, SLOT(slotViewSettingsChanged())); m_view = view; if ( !foreground.isValid() ) { // fallback to colorscheme variant ifDebug(qCDebug(LANGUAGE) << "updating from scheme";) updateColorsFromScheme(); } else if ( m_foregroundColor != foreground || m_backgroundColor != background ) { m_foregroundColor = foreground; m_backgroundColor = background; ifDebug(qCDebug(LANGUAGE) << "updating from document";) update(); } } void ColorCache::updateColorsFromScheme() { KColorScheme scheme(QPalette::Normal, KColorScheme::View); QColor foreground = scheme.foreground(KColorScheme::NormalText).color(); QColor background = scheme.background(KColorScheme::NormalBackground).color(); if ( foreground != m_foregroundColor || background != m_backgroundColor ) { m_foregroundColor = foreground; m_backgroundColor = background; update(); } } void ColorCache::updateColorsFromSettings() { int localRatio = ICore::self()->languageController()->completionSettings()->localColorizationLevel(); int globalRatio = ICore::self()->languageController()->completionSettings()->globalColorizationLevel(); bool boldDeclartions = ICore::self()->languageController()->completionSettings()->boldDeclarations(); if ( localRatio != m_localColorRatio || globalRatio != m_globalColorRatio ) { m_localColorRatio = localRatio; m_globalColorRatio = globalRatio; update(); } if (boldDeclartions != m_boldDeclarations) { m_boldDeclarations = boldDeclartions; update(); } } void ColorCache::update() { if ( !m_self ) { ifDebug(qCDebug(LANGUAGE) << "not updating - still initializating";) // don't update on startup, updateInternal is called directly there return; } QMetaObject::invokeMethod(this, "updateInternal", Qt::QueuedConnection); } void ColorCache::updateInternal() { ifDebug(qCDebug(LANGUAGE) << "update internal" << m_self;) generateColors(); if ( !m_self ) { // don't do anything else fancy on startup return; } emit colorsGotChanged(); // rehighlight open documents foreach (IDocument* doc, ICore::self()->documentController()->openDocuments()) { - foreach (const auto& lang, ICore::self()->languageController()->languagesForUrl(doc->url())) { + foreach (const auto lang, ICore::self()->languageController()->languagesForUrl(doc->url())) { ReferencedTopDUContext top; { DUChainReadLocker lock; top = lang->standardContext(doc->url()); } if(top) { if ( ICodeHighlighting* highlighting = lang->codeHighlighting() ) { highlighting->highlightDUChain(top); } } } } } QColor ColorCache::blend(QColor color, uchar ratio) const { Q_ASSERT(m_backgroundColor.isValid()); Q_ASSERT(m_foregroundColor.isValid()); if ( KColorUtils::luma(m_foregroundColor) > KColorUtils::luma(m_backgroundColor) ) { // for dark color schemes, produce a fitting color first color = KColorUtils::tint(m_foregroundColor, color, 0.5); } // adapt contrast return KColorUtils::mix( m_foregroundColor, color, float(ratio) / float(0xff) ); } QColor ColorCache::blendBackground(QColor color, uchar ratio) const { /* if ( KColorUtils::luma(m_foregroundColor) > KColorUtils::luma(m_backgroundColor) ) { // for dark color schemes, produce a fitting color first color = KColorUtils::tint(m_foregroundColor, color, 0.5).rgb(); }*/ // adapt contrast return KColorUtils::mix( m_backgroundColor, color, float(ratio) / float(0xff) ); } QColor ColorCache::blendGlobalColor(QColor color) const { return blend(color, m_globalColorRatio); } QColor ColorCache::blendLocalColor(QColor color) const { return blend(color, m_localColorRatio); } CodeHighlightingColors* ColorCache::defaultColors() const { Q_ASSERT(m_defaultColors); return m_defaultColors; } QColor ColorCache::generatedColor(uint num) const { return num > (uint)m_colors.size() ? foregroundColor() : m_colors[num]; } uint ColorCache::validColorCount() const { return m_validColorCount; } uint ColorCache::primaryColorCount() const { return m_primaryColorCount; } QColor ColorCache::foregroundColor() const { return m_foregroundColor; } } // kate: space-indent on; indent-width 2; replace-trailing-space-save on; show-tabs on; tab-indents on; tab-width 2; diff --git a/language/highlighting/configurablecolors.cpp b/language/highlighting/configurablecolors.cpp index 964eb9f67..05acab2ab 100644 --- a/language/highlighting/configurablecolors.cpp +++ b/language/highlighting/configurablecolors.cpp @@ -1,96 +1,96 @@ /* * This file is part of KDevelop * * Copyright 2007-2008 David Nolden * Copyright 2006 Hamish Rodda * Copyright 2009 Milian Wolff * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "configurablecolors.h" #include "util/debug.h" #define ifDebug(x) namespace KDevelop { KTextEditor::Attribute::Ptr ConfigurableHighlightingColors::defaultAttribute() const { return m_defaultAttribute; } void ConfigurableHighlightingColors::setDefaultAttribute(KTextEditor::Attribute::Ptr defaultAttrib) { m_defaultAttribute = defaultAttrib; } KTextEditor::Attribute::Ptr ConfigurableHighlightingColors::getAttribute(int number) const { return m_attributes[number]; } void ConfigurableHighlightingColors::addAttribute(int number, KTextEditor::Attribute::Ptr attribute) { m_attributes[number] = attribute; } ConfigurableHighlightingColors::ConfigurableHighlightingColors(QString highlightingName) : m_highlightingName(highlightingName) { KTextEditor::Attribute::Ptr a(new KTextEditor::Attribute); setDefaultAttribute(a); } #define ADD_COLOR(type, color_) \ { \ KTextEditor::Attribute::Ptr a(new KTextEditor::Attribute); \ a->setForeground(QColor(cache->blendGlobalColor(color_))); \ addAttribute(CodeHighlighting::type, a); \ ifDebug(qCDebug(LANGUAGE) << #type << "color: " << ((void*) color_) << "->" << a->foreground().color().name();) \ } -CodeHighlightingColors::CodeHighlightingColors(ColorCache* cache) : ConfigurableHighlightingColors("KDev Semantic Highlighting") +CodeHighlightingColors::CodeHighlightingColors(ColorCache* cache) : ConfigurableHighlightingColors(QStringLiteral("KDev Semantic Highlighting")) { // TODO: The set of colors doesn't work very well. Many colors simply too dark (even on the maximum "Global colorization intensity" they hardly distinguishable from grey) and look alike. ADD_COLOR(ClassType, 0x005912) //Dark green ADD_COLOR(TypeAliasType, 0x35938d) ADD_COLOR(EnumType, 0x6c101e) //Dark red ADD_COLOR(EnumeratorType, 0x862a38) //Greyish red ADD_COLOR(FunctionType, 0x21005A) //Navy blue ADD_COLOR(MemberVariableType, 0x443069) //Dark Burple (blue/purple) ADD_COLOR(LocalClassMemberType, 0xae7d00) //Light orange ADD_COLOR(InheritedClassMemberType, 0x705000) //Dark orange ADD_COLOR(LocalVariableType, 0x0C4D3C) ADD_COLOR(FunctionVariableType, 0x300085) //Less dark navy blue ADD_COLOR(NamespaceVariableType, 0x9F3C5F) //Rose ADD_COLOR(GlobalVariableType, 0x12762B) //Grass green ADD_COLOR(NamespaceType, 0x6B2840) //Dark rose ADD_COLOR(ErrorVariableType, 0x8b0019) //Pure red ADD_COLOR(ForwardDeclarationType, 0x5C5C5C) //Gray ADD_COLOR(MacroType, 0xA41239) ADD_COLOR(MacroFunctionLikeType, 0x008080) /* case ScopeType: case TemplateType: case TemplateParameterType: case CodeType: case FileType:*/ } } // kate: space-indent on; indent-width 2; replace-trailing-space-save on; show-tabs on; tab-indents on; tab-width 2; diff --git a/language/interfaces/quickopendataprovider.cpp b/language/interfaces/quickopendataprovider.cpp index 415574b28..0138e0a8b 100644 --- a/language/interfaces/quickopendataprovider.cpp +++ b/language/interfaces/quickopendataprovider.cpp @@ -1,80 +1,80 @@ /* * 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 "quickopendataprovider.h" #include #include namespace KDevelop { QuickOpenFileSetInterface::~QuickOpenFileSetInterface() { } QuickOpenEmbeddedWidgetInterface::~QuickOpenEmbeddedWidgetInterface() { } QuickOpenDataBase::~QuickOpenDataBase() { } QIcon QuickOpenDataBase::icon() const { return QIcon(); } bool QuickOpenDataBase::isExpandable() const { return false; } QWidget* QuickOpenDataBase::expandingWidget() const { return 0; } QList QuickOpenDataBase::highlighting() const { return QList(); } QuickOpenDataProviderBase::~QuickOpenDataProviderBase() { } void QuickOpenDataProviderBase::enableData( const QStringList& , const QStringList& ) { } bool extractLineNumber(const QString& from, QString& path, uint& lineNumber) { int colonIndex = from.indexOf(':'); if (colonIndex != -1) { if (colonIndex == from.count() - 1) { path = from.mid(0, colonIndex); lineNumber = 0; } else { bool ok; - uint number = from.mid(colonIndex + 1).toUInt(&ok); + uint number = from.midRef(colonIndex + 1).toUInt(&ok); if (ok) { path = from.mid(0, colonIndex); lineNumber = number; } else { return false; } } return true; } else { return false; } } } diff --git a/language/interfaces/quickopenfilter.h b/language/interfaces/quickopenfilter.h index 011000809..949487348 100644 --- a/language/interfaces/quickopenfilter.h +++ b/language/interfaces/quickopenfilter.h @@ -1,281 +1,281 @@ /* * This file is part of KDevelop * * Copyright 2007 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_QUICKOPEN_FILTER_H #define KDEVPLATFORM_QUICKOPEN_FILTER_H #include #include #include "abbreviations.h" #include namespace KDevelop { /** * This is a simple filter-implementation that helps you implementing own quickopen data-providers. * You should use it when possible, because that way additional features(like regexp filtering) can * be implemented in a central place. * * This implementation does incremental filtering while * typing text, so it quite efficient for the most common case. * * The simplest way of using this is by reimplementing your data-provider * based on QuickOpenDataProviderBase and KDevelop::Filter. * * YourType should be the type that holds all the information you need. * The filter will hold the data, and you can access it through "items()". * * What you need to do to use it: * * Reimplement itemText(..) to provide the text filtering * should be performend on(This must be efficient). * * Call setItems(..) when starting a new quickopen session, or when the content * changes, to initialize the filter with your data. * * Call setFilter(..) with the text that should be filtered for on user-input. * * Use filteredItems() to provide data to quickopen. * */ template class Filter { public: virtual ~Filter() { } ///Clears the filter, but not the data. void clearFilter() { m_filtered = m_items; m_oldFilterText.clear(); } ///Clears the filter and sets new data. The filter-text will be lost. void setItems( const QList& data ) { m_items = data; clearFilter(); } const QList& items() const { return m_items; } ///Returns the data that is left after the filtering const QList& filteredItems() const { return m_filtered; } ///Changes the filter-text and refilters the data void setFilter( const QString& text ) { if (m_oldFilterText == text) { return; } if (text.isEmpty()) { clearFilter(); return; } QList filterBase = m_filtered; if( !text.startsWith( m_oldFilterText ) ) { filterBase = m_items; //Start filtering based on the whole data } m_filtered.clear(); - QStringList typedFragments = text.split("::", QString::SkipEmptyParts); + QStringList typedFragments = text.split(QStringLiteral("::"), QString::SkipEmptyParts); if (typedFragments.isEmpty()) { clearFilter(); return; } if ( typedFragments.last().endsWith(':') ) { // remove the trailing colon if there's only one; otherwise, // this breaks incremental filtering typedFragments.last().chop(1); } if (typedFragments.size() == 1 && typedFragments.last().isEmpty()) { clearFilter(); return; } foreach( const Item& data, filterBase ) { const QString& itemData = itemText( data ); if( itemData.contains(text, Qt::CaseInsensitive) || matchesAbbreviationMulti(itemData, typedFragments) ) { m_filtered << data; } } m_oldFilterText = text; } protected: ///Should return the text an item should be filtered by. virtual QString itemText( const Item& data ) const = 0; private: QString m_oldFilterText; QList m_filtered; QList m_items; }; } namespace KDevelop { template class PathFilter { public: ///Clears the filter, but not the data. void clearFilter() { m_filtered = m_items; m_oldFilterText.clear(); } ///Clears the filter and sets new data. The filter-text will be lost. void setItems( const QList& data ) { m_items = data; clearFilter(); } const QList& items() const { return m_items; } ///Returns the data that is left after the filtering const QList& filteredItems() const { return m_filtered; } ///Changes the filter-text and refilters the data void setFilter( const QStringList& text ) { if (m_oldFilterText == text) { return; } if (text.isEmpty()) { clearFilter(); return; } const QString joinedText = text.join(QString()); QList filterBase = m_filtered; if ( m_oldFilterText.isEmpty()) { filterBase = m_items; } else if (m_oldFilterText.mid(0, m_oldFilterText.count() - 1) == text.mid(0, text.count() - 1) && text.last().startsWith(m_oldFilterText.last())) { //Good, the prefix is the same, and the last item has been extended } else if (m_oldFilterText.size() == text.size() - 1 && m_oldFilterText == text.mid(0, text.size() - 1)) { //Good, an item has been added } else { //Start filtering based on the whole data, there was a big change to the filter filterBase = m_items; } // filterBase is correctly sorted, to keep it that way we add // exact matches to this list in sorted way and then prepend the whole list in one go. QList exactMatches; // similar for starting matches QList startMatches; // all other matches QList otherMatches; foreach( const Item& data, filterBase ) { const Path toFilter = static_cast(this)->itemPath(data); const QVector& segments = toFilter.segments(); if (text.count() > segments.count()) { // number of segments mismatches, thus item cannot match continue; } { bool allMatched = true; // try to put exact matches up front for(int i = segments.count() - 1, j = text.count() - 1; i >= 0 && j >= 0; --i, --j) { if (segments.at(i) != text.at(j)) { allMatched = false; break; } } if (allMatched) { exactMatches << data; continue; } } int searchIndex = 0; int pathIndex = 0; int lastMatchIndex = -1; // stop early if more search fragments remain than available after path index while (pathIndex < segments.size() && searchIndex < text.size() && (pathIndex + text.size() - searchIndex - 1) < segments.size() ) { const QString& segment = segments.at(pathIndex); const QString& typedSegment = text.at(searchIndex); lastMatchIndex = segment.indexOf(typedSegment, 0, Qt::CaseInsensitive); if (lastMatchIndex == -1 && !matchesAbbreviation(segment.midRef(0), typedSegment)) { // no match, try with next path segment ++pathIndex; continue; } // else we matched ++searchIndex; ++pathIndex; } if (searchIndex != text.size()) { if ( ! matchesPath(segments.last(), joinedText) ) { continue; } } // prefer matches whose last element starts with the filter if (pathIndex == segments.size() && lastMatchIndex == 0) { startMatches << data; } else { otherMatches << data; } } m_filtered = exactMatches + startMatches + otherMatches; m_oldFilterText = text; } private: QStringList m_oldFilterText; QList m_filtered; QList m_items; }; } #endif diff --git a/language/util/setrepository.cpp b/language/util/setrepository.cpp index 14497dc97..dca8ddd1a 100644 --- a/language/util/setrepository.cpp +++ b/language/util/setrepository.cpp @@ -1,1122 +1,1122 @@ /*************************************************************************** Copyright 2007 David Nolden ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "setrepository.h" #include "util/debug.h" #include #include #include #include #include #include #include #include #include //#define DEBUG #ifdef DEBUG #define ifDebug(X) X #else #define ifDebug(x) #undef Q_ASSERT #define Q_ASSERT(x) #endif #ifndef DEBUG #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 = 0, const SetNodeData* right = 0); 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 //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) { } ~Private() { } QString name; private: }; Set::Set() : m_tree(0), m_repository(0) { } 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 = "blue"; + QString color = QStringLiteral("blue"); if(master) - color = "red"; + color = QStringLiteral("red"); QString label = QStringLiteral("%1 -> %2").arg(node.start()).arg(node.end()); if(!node.contiguous()) - label += ", with gaps"; + label += QLatin1String(", with gaps"); - QString ret = QStringLiteral("%1[label=\"%2\", color=\"%3\"];\n").arg(shortLabel(node)).arg(label).arg(color); + 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)).arg(shortLabel(left)); - ret += QStringLiteral("%1 -> %2;\n").arg(shortLabel(node)).arg(shortLabel(right)); + 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 = "digraph Repository {\n"; + QString ret = QStringLiteral("digraph Repository {\n"); ret += dumpDotGraphInternal(nodeIndex, true); - ret += "}\n"; + ret += QLatin1String("}\n"); return ret; } const int nodeStackAlloc = 500; class Set::Iterator::IteratorPrivate { public: IteratorPrivate() : nodeStackSize(0), currentIndex(0), repository(0) { 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, 0, 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, 0, 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, 0, 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(0), m_delayedDeletion(delayedDeletion) { m_mutex = dataRepository.mutex(); } struct StatisticsVisitor { StatisticsVisitor(const SetDataRepository& _rep) : nodeCount(0), badSplitNodeCount(0), zeroRefCountNodes(0), rep(_rep) { } bool operator() (const SetNodeData* item) { if(item->m_refCount == 0) ++zeroRefCountNodes; ++nodeCount; uint split = splitPositionForRange(item->start(), item->end()); if(item->hasSlaves()) if(split < rep.itemFromIndex(item->leftNode())->end() || split > rep.itemFromIndex(item->rightNode())->start()) ++badSplitNodeCount; return true; } uint nodeCount; uint badSplitNodeCount; uint zeroRefCountNodes; const SetDataRepository& rep; }; void BasicSetRepository::printStatistics() const { StatisticsVisitor stats(dataRepository); dataRepository.visitAllItems(stats); qCDebug(LANGUAGE) << "count of nodes:" << stats.nodeCount << "count of nodes with bad split:" << stats.badSplitNodeCount << "count of nodes with zero reference-count:" << stats.zeroRefCountNodes; } BasicSetRepository::~BasicSetRepository() { delete d; } void BasicSetRepository::itemRemovedFromSets(uint /*index*/) { } void BasicSetRepository::itemAddedToSets(uint /*index*/) { } ////////////Set convenience functions////////////////// bool Set::contains(Index index) const { if(!m_tree || !m_repository) return false; QMutexLocker lock(m_repository->m_mutex); SetRepositoryAlgorithms alg(m_repository->dataRepository, m_repository); return alg.set_contains(m_repository->dataRepository.itemFromIndex(m_tree), index); } Set Set::operator +(const Set& first) const { if(!first.m_tree) return *this; else if(!m_tree || !m_repository) return first; Q_ASSERT(m_repository == first.m_repository); QMutexLocker lock(m_repository->m_mutex); SetRepositoryAlgorithms alg(m_repository->dataRepository, m_repository); uint retNode = alg.set_union(m_tree, first.m_tree, m_repository->dataRepository.itemFromIndex(m_tree), m_repository->dataRepository.itemFromIndex(first.m_tree)); ifDebug(alg.check(retNode)); return Set(retNode, m_repository); } Set& Set::operator +=(const Set& first) { if(!first.m_tree) return *this; else if(!m_tree || !m_repository) { m_tree = first.m_tree; m_repository = first.m_repository; return *this; } QMutexLocker lock(m_repository->m_mutex); SetRepositoryAlgorithms alg(m_repository->dataRepository, m_repository); m_tree = alg.set_union(m_tree, first.m_tree, m_repository->dataRepository.itemFromIndex(m_tree), m_repository->dataRepository.itemFromIndex(first.m_tree)); ifDebug(alg.check(m_tree)); return *this; } Set Set::operator &(const Set& first) const { if(!first.m_tree || !m_tree) return Set(); Q_ASSERT(m_repository); QMutexLocker lock(m_repository->m_mutex); SetRepositoryAlgorithms alg(m_repository->dataRepository, m_repository); Set ret( alg.set_intersect(m_tree, first.m_tree, m_repository->dataRepository.itemFromIndex(m_tree), m_repository->dataRepository.itemFromIndex(first.m_tree)), m_repository ); ifDebug(alg.check(ret.m_tree)); return ret; } Set& Set::operator &=(const Set& first) { if(!first.m_tree || !m_tree) { m_tree = 0; return *this; } Q_ASSERT(m_repository); QMutexLocker lock(m_repository->m_mutex); SetRepositoryAlgorithms alg(m_repository->dataRepository, m_repository); m_tree = alg.set_intersect(m_tree, first.m_tree, m_repository->dataRepository.itemFromIndex(m_tree), m_repository->dataRepository.itemFromIndex(first.m_tree)); ifDebug(alg.check(m_tree)); return *this; } Set Set::operator -(const Set& rhs) const { if(!m_tree || !rhs.m_tree) return *this; Q_ASSERT(m_repository); QMutexLocker lock(m_repository->m_mutex); SetRepositoryAlgorithms alg(m_repository->dataRepository, m_repository); Set ret( alg.set_subtract(m_tree, rhs.m_tree, m_repository->dataRepository.itemFromIndex(m_tree), m_repository->dataRepository.itemFromIndex(rhs.m_tree)), m_repository ); ifDebug( alg.check(ret.m_tree) ); return ret; } Set& Set::operator -=(const Set& rhs) { if(!m_tree || !rhs.m_tree) return *this; Q_ASSERT(m_repository); QMutexLocker lock(m_repository->m_mutex); SetRepositoryAlgorithms alg(m_repository->dataRepository, m_repository); m_tree = alg.set_subtract(m_tree, rhs.m_tree, m_repository->dataRepository.itemFromIndex(m_tree), m_repository->dataRepository.itemFromIndex(rhs.m_tree)); ifDebug(alg.check(m_tree)); return *this; } BasicSetRepository* Set::repository() const { return m_repository; } void Set::staticRef() { if(!m_tree) return; QMutexLocker lock(m_repository->m_mutex); SetNodeData* data = m_repository->dataRepository.dynamicItemFromIndexSimple(m_tree); ++data->m_refCount; } ///Mutex must be locked void Set::unrefNode(uint current) { SetNodeData* data = m_repository->dataRepository.dynamicItemFromIndexSimple(current); Q_ASSERT(data->m_refCount); --data->m_refCount; if(!m_repository->delayedDeletion()) { if(data->m_refCount == 0) { if(data->leftNode()){ Q_ASSERT(data->rightNode()); unrefNode(data->rightNode()); unrefNode(data->leftNode()); }else { //Deleting a leaf Q_ASSERT(data->end() - data->start() == 1); m_repository->itemRemovedFromSets(data->start()); } m_repository->dataRepository.deleteItem(current); } } } ///Decrease the static reference-count of this set by one. This set must have a reference-count > 1. ///If this set reaches the reference-count zero, it will be deleted, and all sub-nodes that also reach the reference-count zero ///will be deleted as well. @warning Either protect ALL your sets by using reference-counting, or don't use it at all. void Set::staticUnref() { if(!m_tree) return; QMutexLocker lock(m_repository->m_mutex); unrefNode(m_tree); } StringSetRepository::StringSetRepository(QString name) : Utils::BasicSetRepository(name) { } void StringSetRepository::itemRemovedFromSets(uint index) { ///Call the IndexedString destructor with enabled reference-counting KDevelop::IndexedString string = KDevelop::IndexedString::fromIndex(index); KDevelop::enableDUChainReferenceCounting(&string, sizeof(KDevelop::IndexedString)); string.~IndexedString(); //Call destructor with enabled reference-counting KDevelop::disableDUChainReferenceCounting(&string); } void StringSetRepository::itemAddedToSets(uint index) { ///Call the IndexedString constructor with enabled reference-counting KDevelop::IndexedString string = KDevelop::IndexedString::fromIndex(index); char data[sizeof(KDevelop::IndexedString)]; KDevelop::enableDUChainReferenceCounting(data, sizeof(KDevelop::IndexedString)); new (data) KDevelop::IndexedString(string); //Call constructor with enabled reference-counting KDevelop::disableDUChainReferenceCounting(data); } } diff --git a/outputview/outputdelegate.h b/outputview/outputdelegate.h index 6fedf5f30..f2046b003 100644 --- a/outputview/outputdelegate.h +++ b/outputview/outputdelegate.h @@ -1,56 +1,57 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright (C) 2007 Andreas Pakulat * * Copyright (C) 2012 Morten Danielsen Volden mvolden2@gmail.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef KDEVPLATFORM_OUTPUTDELEGATE_H #define KDEVPLATFORM_OUTPUTDELEGATE_H #include "outputviewexport.h" #include #include namespace KDevelop { struct OutputDelegatePrivate { OutputDelegatePrivate(); KStatefulBrush errorBrush; KStatefulBrush warningBrush; KStatefulBrush informationBrush; KStatefulBrush builtBrush; }; class KDEVPLATFORMOUTPUTVIEW_EXPORT OutputDelegate : public QItemDelegate { + Q_OBJECT public: explicit OutputDelegate( QObject* parent = 0 ); virtual ~OutputDelegate(); void paint( QPainter*, const QStyleOptionViewItem&, const QModelIndex& ) const override; private: OutputDelegatePrivate* const d; }; } #endif diff --git a/outputview/outputexecutejob.cpp b/outputview/outputexecutejob.cpp index 2fd80fd57..4b054790a 100644 --- a/outputview/outputexecutejob.cpp +++ b/outputview/outputexecutejob.cpp @@ -1,530 +1,530 @@ /* 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 ); 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; 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() { if( d->m_process->state() != QProcess::NotRunning ) { doKill(); } Q_ASSERT( d->m_process->state() == QProcess::NotRunning ); 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() ); if (!d->effectiveCommandLine().isEmpty()) { d->m_process->setProgram( d->effectiveCommandLine() ); - qCDebug(OUTPUTVIEW) << "Starting:" << d->m_process->program().join(" ") << "in" << d->m_process->workingDirectory(); + 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... model()->appendLine( i18n( "*** Warning: could not kill the process ***" ) ); } 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) { m_owner->emitPercent(progress.percent, 100); if (progress.percent == 100) { m_owner->infoMessage(m_owner, i18n("Build finished")); } else { 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 { 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" ) ); } 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/outputfilteringstrategies.cpp b/outputview/outputfilteringstrategies.cpp index 4fdb684ed..e7df155e2 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 { 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() ) { item.url = QUrl::fromUserInput(match.captured( curErrFilter.fileGroup )); item.lineNo = match.captured( curErrFilter.lineGroup ).toInt() - 1; if(curErrFilter.columnGroup >= 0) { item.columnNo = match.captured( curErrFilter.columnGroup ).toInt() - 1; } else { item.columnNo = 0; } QString txt = match.captured(curErrFilter.textGroup); item.type = FilteredItem::ErrorItem; // Make the item clickable if it comes with the necessary file & line number information if (curErrFilter.fileGroup > 0 && curErrFilter.lineGroup > 0) { item.isActivatable = true; } break; } } return item; } /// --- No filter strategy --- NoFilterStrategy::NoFilterStrategy() { } FilteredItem NoFilterStrategy::actionInLine(const QString& line) { return FilteredItem( line ); } FilteredItem NoFilterStrategy::errorInLine(const QString& line) { return FilteredItem( line ); } /// --- Compiler error filter strategy --- /// Impl. of CompilerFilterStrategy. struct CompilerFilterStrategyPrivate { CompilerFilterStrategyPrivate(const 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(currentPath.toLocalFile()).exists() ); 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()); - for (const auto& path : d->m_currentDirs) { + 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 == "cd" ) { + 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 == "cmake" && line.contains(QStringLiteral("Building"))) { + 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 ), // 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 == "cmake" ) { // Unfortunately we cannot know if an error or an action comes first in cmake, and therefore we need to do this + 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(); } item.lineNo = match.captured( curErrFilter.lineGroup ).toInt() - 1; if(curErrFilter.columnGroup >= 0) { item.columnNo = match.captured( curErrFilter.columnGroup ).toInt() - 1; } else { item.columnNo = 0; } 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 ErrorFormat(QStringLiteral("(file:\\/\\/(?:.+)):([1-9][0-9]*):([1-9][0-9]*): (.+) (?:is not a type|is ambiguous\\.|is instantiated recursively)"), 1, 2, -1, 3) // 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/tests/test_filteringstrategy.cpp b/outputview/tests/test_filteringstrategy.cpp index 63409ac34..42d5a6bde 100644 --- a/outputview/tests/test_filteringstrategy.cpp +++ b/outputview/tests/test_filteringstrategy.cpp @@ -1,475 +1,476 @@ /* This file is part of KDevelop Copyright 2012 Milian Wolff Copyright (C) 2012 Morten Danielsen Volden mvolden2@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "test_filteringstrategy.h" #include "testlinebuilderfunctions.h" #include #include #include using namespace KDevelop; QTEST_GUILESS_MAIN(TestFilteringStrategy) namespace QTest { template<> inline char* toString(const FilteredItem::FilteredOutputItemType& type) { switch (type) { case FilteredItem::ActionItem: return qstrdup("ActionItem"); case FilteredItem::CustomItem: return qstrdup("CustomItem"); case FilteredItem::ErrorItem: return qstrdup("ErrorItem"); case FilteredItem::InformationItem: return qstrdup("InformationItem"); case FilteredItem::InvalidItem: return qstrdup("InvalidItem"); case FilteredItem::StandardItem: return qstrdup("StandardItem"); case FilteredItem::WarningItem: return qstrdup("WarningItem"); } return qstrdup("unknown"); } } void TestFilteringStrategy::testNoFilterStrategy_data() { QTest::addColumn("line"); QTest::addColumn("expected"); QTest::newRow("cppcheck-info-line") << buildCppCheckInformationLine() << FilteredItem::InvalidItem; QTest::newRow("cppcheck-error-line") << buildCppCheckErrorLine() << FilteredItem::InvalidItem; QTest::newRow("compiler-line") << buildCompilerLine() << FilteredItem::InvalidItem; QTest::newRow("compiler-error-line") << buildCompilerErrorLine() << FilteredItem::InvalidItem; QTest::newRow("compiler-action-line") << buildCompilerActionLine() << FilteredItem::InvalidItem; QTest::newRow("compiler-information-line") << buildCompilerInformationLine() << FilteredItem::InvalidItem; QTest::newRow("python-error-line") << buildPythonErrorLine() << FilteredItem::InvalidItem; } void TestFilteringStrategy::testNoFilterStrategy() { QFETCH(QString, line); QFETCH(FilteredItem::FilteredOutputItemType, expected); NoFilterStrategy testee; FilteredItem item1 = testee.errorInLine(line); QCOMPARE(item1.type, expected); item1 = testee.actionInLine(line); QCOMPARE(item1.type, expected); } void TestFilteringStrategy::testCompilerFilterStrategy_data() { QTest::addColumn("line"); QTest::addColumn("expectedError"); QTest::addColumn("expectedAction"); QTest::newRow("cppcheck-info-line") << buildCppCheckInformationLine() << FilteredItem::InvalidItem << FilteredItem::InvalidItem; QTest::newRow("cppcheck-error-line") << buildCppCheckErrorLine() << FilteredItem::InvalidItem << FilteredItem::InvalidItem; QTest::newRow("compiler-line") << buildCompilerLine() << FilteredItem::InvalidItem << FilteredItem::InvalidItem; QTest::newRow("compiler-error-line") << buildCompilerErrorLine() << FilteredItem::ErrorItem << FilteredItem::InvalidItem; QTest::newRow("compiler-information-line") << buildCompilerInformationLine() << FilteredItem::InformationItem << FilteredItem::InvalidItem; QTest::newRow("compiler-information-line2") << buildInfileIncludedFromFirstLine() << FilteredItem::InformationItem << FilteredItem::InvalidItem; QTest::newRow("compiler-information-line3") << buildInfileIncludedFromSecondLine() << FilteredItem::InformationItem << FilteredItem::InvalidItem; QTest::newRow("cmake-error-line1") << "CMake Error at CMakeLists.txt:2 (cmake_minimum_required):" << FilteredItem::ErrorItem << FilteredItem::InvalidItem; QTest::newRow("cmake-error-multiline1") << "CMake Error: Error in cmake code at" << FilteredItem::InvalidItem << FilteredItem::InvalidItem; QTest::newRow("cmake-error-multiline2") << buildCmakeConfigureMultiLine() << FilteredItem::ErrorItem << FilteredItem::InvalidItem; QTest::newRow("cmake-warning-line") << "CMake Warning (dev) in CMakeLists.txt:" << FilteredItem::WarningItem << FilteredItem::InvalidItem; QTest::newRow("cmake-automoc-error") << "AUTOMOC: error: /foo/bar.cpp The file includes the moc file \"moc_bar1.cpp\"" << FilteredItem::ErrorItem << FilteredItem::InvalidItem; QTest::newRow("cmake-automoc4-error") << "automoc4: The file \"/foo/bar.cpp\" includes the moc file \"bar1.moc\"" << FilteredItem::InformationItem << FilteredItem::InvalidItem; QTest::newRow("cmake-autogen-error") << "AUTOGEN: error: /foo/bar.cpp The file includes the moc file \"moc_bar1.cpp\"" << FilteredItem::ErrorItem << FilteredItem::InvalidItem; QTest::newRow("linker-action-line") << "linking testCustombuild (g++)" << FilteredItem::InvalidItem << FilteredItem::ActionItem; QTest::newRow("linker-error-line") << buildLinkerErrorLine() << FilteredItem::ErrorItem << FilteredItem::InvalidItem; QTest::newRow("python-error-line") << buildPythonErrorLine() << FilteredItem::InvalidItem << FilteredItem::InvalidItem; } void TestFilteringStrategy::testCompilerFilterStrategy() { QFETCH(QString, line); QFETCH(FilteredItem::FilteredOutputItemType, expectedError); QFETCH(FilteredItem::FilteredOutputItemType, expectedAction); QUrl projecturl = QUrl::fromLocalFile( projectPath() ); CompilerFilterStrategy testee(projecturl); FilteredItem item1 = testee.errorInLine(line); QCOMPARE(item1.type, expectedError); item1 = testee.actionInLine(line); QCOMPARE(item1.type, expectedAction); } void TestFilteringStrategy::testCompilerFilterstrategyMultipleKeywords_data() { QTest::addColumn("line"); QTest::addColumn("expectedError"); QTest::addColumn("expectedAction"); QTest::newRow("warning-containing-error-word") << "RingBuffer.cpp:64:6: warning: unused parameter ‘errorItem’ [-Wunused-parameter]" << FilteredItem::WarningItem << FilteredItem::InvalidItem; QTest::newRow("error-containing-info-word") << "NodeSet.hpp:89:27: error: ‘Info’ was not declared in this scope" << FilteredItem::ErrorItem << FilteredItem::InvalidItem; QTest::newRow("warning-in-filename-containing-error-word") << "ErrorHandling.cpp:100:56: warning: unused parameter ‘item’ [-Wunused-parameter]" << FilteredItem::WarningItem << FilteredItem::InvalidItem; QTest::newRow("error-in-filename-containing-warning-word") << "WarningHandling.cpp:100:56: error: ‘Item’ was not declared in this scope" << FilteredItem::ErrorItem << FilteredItem::InvalidItem; } void TestFilteringStrategy::testCompilerFilterstrategyMultipleKeywords() { QFETCH(QString, line); QFETCH(FilteredItem::FilteredOutputItemType, expectedError); QFETCH(FilteredItem::FilteredOutputItemType, expectedAction); QUrl projecturl = QUrl::fromLocalFile( projectPath() ); CompilerFilterStrategy testee(projecturl); FilteredItem item1 = testee.errorInLine(line); QCOMPARE(item1.type, expectedError); item1 = testee.actionInLine(line); QCOMPARE(item1.type, expectedAction); } void TestFilteringStrategy::testScriptErrorFilterStrategy_data() { QTest::addColumn("line"); QTest::addColumn("expectedError"); QTest::addColumn("expectedAction"); QTest::newRow("cppcheck-info-line") << buildCppCheckInformationLine() << FilteredItem::InvalidItem << FilteredItem::InvalidItem; QTest::newRow("cppcheck-error-line") << buildCppCheckErrorLine() << FilteredItem::ErrorItem << FilteredItem::InvalidItem; QTest::newRow("compiler-line") << buildCompilerLine() << FilteredItem::InvalidItem << FilteredItem::InvalidItem; QTest::newRow("compiler-error-line") << buildCompilerErrorLine() << FilteredItem::ErrorItem << FilteredItem::InvalidItem; QTest::newRow("compiler-action-line") << "linking testCustombuild (g++)" << FilteredItem::InvalidItem << FilteredItem::InvalidItem; QTest::newRow("python-error-line") << buildPythonErrorLine() << FilteredItem::InvalidItem << FilteredItem::InvalidItem; } void TestFilteringStrategy::testScriptErrorFilterStrategy() { QFETCH(QString, line); QFETCH(FilteredItem::FilteredOutputItemType, expectedError); QFETCH(FilteredItem::FilteredOutputItemType, expectedAction); ScriptErrorFilterStrategy testee; FilteredItem item1 = testee.errorInLine(line); QCOMPARE(item1.type, expectedError); item1 = testee.actionInLine(line); QCOMPARE(item1.type, expectedAction); } void TestFilteringStrategy::testNativeAppErrorFilterStrategy_data() { QTest::addColumn("line"); QTest::addColumn("file"); QTest::addColumn("lineNo"); QTest::addColumn("column"); QTest::addColumn("itemtype"); // BEGIN: C++ QTest::newRow("cassert") << "a.out: /foo/bar/test.cpp:5: int main(): Assertion `false' failed." << "/foo/bar/test.cpp" << 4 << 0 << FilteredItem::ErrorItem; // END: C++ // BEGIN: Qt // TODO: qt-connect-* and friends shouldn't be error items but warnings items instead // this needs refactoring in outputfilteringstrategies, though... QTest::newRow("qt-connect-nosuch-slot") << "QObject::connect: No such slot Foo::bar() in /foo/bar.cpp:313" << "/foo/bar.cpp" << 312 << 0 << FilteredItem::ErrorItem; QTest::newRow("qt-connect-nosuch-signal") << "QObject::connect: No such signal Foo::bar() in /foo/bar.cpp:313" << "/foo/bar.cpp" << 312 << 0 << FilteredItem::ErrorItem; QTest::newRow("qt-connect-parentheses-slot") << "QObject::connect: Parentheses expected, slot Foo::bar() in /foo/bar.cpp:313" << "/foo/bar.cpp" << 312 << 0 << FilteredItem::ErrorItem; QTest::newRow("qt-connect-parentheses-signal") << "QObject::connect: Parentheses expected, signal Foo::bar() in /foo/bar.cpp:313" << "/foo/bar.cpp" << 312 << 0 << FilteredItem::ErrorItem; QTest::newRow("qt-assert") << "ASSERT: \"errors().isEmpty()\" in file /tmp/foo/bar.cpp, line 49" << "/tmp/foo/bar.cpp" << 48 << 0 << FilteredItem::ErrorItem; QTest::newRow("qttest-assert") << "QFATAL : FooTest::testBar() ASSERT: \"index.isValid()\" in file /foo/bar.cpp, line 32" << "/foo/bar.cpp" << 31 << 0 << FilteredItem::ErrorItem; QTest::newRow("qttest-loc") << " Loc: [/foo/bar.cpp(33)]" << "/foo/bar.cpp" << 32 << 0 << FilteredItem::ErrorItem; QTest::newRow("qttest-loc-nocatch") << " Loc: [Unknown file(0)]" << "" << -1 << -1 << FilteredItem::InvalidItem; QTest::newRow("qml-import-unix") << "file:///path/to/foo.qml:7:1: Bar is not a type" << "/path/to/foo.qml" << 6 << 0 << FilteredItem::ErrorItem; QTest::newRow("qml-import-unix1") << "file:///path/to/foo.qml:7:1: Bar is ambiguous. Found in A and in B" << "/path/to/foo.qml" << 6 << 0 << FilteredItem::ErrorItem; QTest::newRow("qml-import-unix2") << "file:///path/to/foo.qml:7:1: Bar is instantiated recursively" << "/path/to/foo.qml" << 6 << 0 << FilteredItem::ErrorItem; // END: Qt } void TestFilteringStrategy::testNativeAppErrorFilterStrategy() { QFETCH(QString, line); QFETCH(QString, file); QFETCH(int, lineNo); QFETCH(int, column); QFETCH(FilteredItem::FilteredOutputItemType, itemtype); NativeAppErrorFilterStrategy testee; FilteredItem item = testee.errorInLine(line); QCOMPARE(item.url.path(), file); QCOMPARE(item.lineNo , lineNo); QCOMPARE(item.columnNo , column); QCOMPARE(item.type , itemtype); } void TestFilteringStrategy::testStaticAnalysisFilterStrategy_data() { QTest::addColumn("line"); QTest::addColumn("expectedError"); QTest::addColumn("expectedAction"); QTest::newRow("cppcheck-info-line") << buildCppCheckInformationLine() << FilteredItem::InvalidItem << FilteredItem::InvalidItem; QTest::newRow("cppcheck-error-line") << buildCppCheckErrorLine() << FilteredItem::ErrorItem << FilteredItem::InvalidItem; QTest::newRow("krazy2-error-line") << buildKrazyErrorLine() << FilteredItem::ErrorItem << FilteredItem::InvalidItem; QTest::newRow("krazy2-error-line-two-colons") << buildKrazyErrorLine2() << FilteredItem::ErrorItem << FilteredItem::InvalidItem; QTest::newRow("krazy2-error-line-error-description") << buildKrazyErrorLine3() << FilteredItem::ErrorItem << FilteredItem::InvalidItem; QTest::newRow("krazy2-error-line-wo-line-info") << buildKrazyErrorLineNoLineInfo() << FilteredItem::ErrorItem << FilteredItem::InvalidItem; QTest::newRow("compiler-line") << buildCompilerLine() << FilteredItem::InvalidItem << FilteredItem::InvalidItem; QTest::newRow("compiler-error-line") << buildCompilerErrorLine() << FilteredItem::InvalidItem << FilteredItem::InvalidItem; QTest::newRow("compiler-action-line") << "linking testCustombuild (g++)" << FilteredItem::InvalidItem << FilteredItem::InvalidItem; QTest::newRow("python-error-line") << buildPythonErrorLine() << FilteredItem::InvalidItem << FilteredItem::InvalidItem; } void TestFilteringStrategy::testStaticAnalysisFilterStrategy() { // Test that url's are extracted correctly as well QString referencePath = projectPath() + "main.cpp"; QFETCH(QString, line); QFETCH(FilteredItem::FilteredOutputItemType, expectedError); QFETCH(FilteredItem::FilteredOutputItemType, expectedAction); StaticAnalysisFilterStrategy testee; FilteredItem item1 = testee.errorInLine(line); QString extractedPath = item1.url.toLocalFile(); QVERIFY((item1.type != FilteredItem::ErrorItem) || ( extractedPath == referencePath)); QCOMPARE(item1.type, expectedError); item1 = testee.actionInLine(line); QCOMPARE(item1.type, expectedAction); } void TestFilteringStrategy::testCompilerFilterstrategyUrlFromAction_data() { QTest::addColumn("line"); QTest::addColumn("expectedLastDir"); QString basepath = projectPath(); QTest::newRow("cmake-line1") << "[ 25%] Building CXX object path/to/one/CMakeFiles/file.o" << QString( basepath + "/path/to/one" ); QTest::newRow("cmake-line2") << "[ 26%] Building CXX object path/to/two/CMakeFiles/file.o" << QString( basepath + "/path/to/two"); QTest::newRow("cmake-line3") << "[ 26%] Building CXX object path/to/three/CMakeFiles/file.o" << QString( basepath + "/path/to/three"); QTest::newRow("cmake-line4") << "[ 26%] Building CXX object path/to/four/CMakeFiles/file.o" << QString( basepath + "/path/to/four"); QTest::newRow("cmake-line5") << "[ 26%] Building CXX object path/to/two/CMakeFiles/file.o" << QString( basepath + "/path/to/two"); QTest::newRow("cd-line6") << QString("make[4]: Entering directory '" + basepath + "/path/to/one/'") << QString( basepath + "/path/to/one"); QTest::newRow("waf-cd") << QString("Waf: Entering directory `" + basepath + "/path/to/two/'") << QString( basepath + "/path/to/two"); QTest::newRow("cmake-line7") << QStringLiteral("[ 50%] Building CXX object CMakeFiles/testdeque.dir/RingBuffer.cpp.o") << QString( basepath); } void TestFilteringStrategy::testCompilerFilterstrategyUrlFromAction() { QFETCH(QString, line); QFETCH(QString, expectedLastDir); QUrl projecturl = QUrl::fromLocalFile( projectPath() ); static CompilerFilterStrategy testee(projecturl); FilteredItem item1 = testee.actionInLine(line); - QCOMPARE(testee.getCurrentDirs().last(), expectedLastDir); + int last = testee.getCurrentDirs().size() - 1; + QCOMPARE(testee.getCurrentDirs().at(last), expectedLastDir); } void TestFilteringStrategy::benchMarkCompilerFilterAction() { QString projecturl = projectPath(); QStringList outputlines; const int numLines(10000); int j(0), k(0), l(0), m(0); do { ++j; ++k; ++l; QString tmp; if(m % 2 == 0) { tmp = QStringLiteral( "[ 26%] Building CXX object /this/is/the/path/to/the/files/%1/%2/%3/CMakeFiles/file.o").arg( j ).arg( k ).arg( l ); } else { tmp = QString( "make[4]: Entering directory '" + projecturl + "/this/is/the/path/to/the/files/%1/%2/%3/").arg( j ).arg( k ).arg( l ); } outputlines << tmp; if(j % 6 == 0) { j = 0; ++m; } if(k % 9 == 0) { k = 0; ++m; } if(l % 13 == 0) { l = 0; ++m; } } while(outputlines.size() < numLines ); // gives us numLines (-ish) QElapsedTimer totalTime; totalTime.start(); static CompilerFilterStrategy testee(QUrl::fromLocalFile(projecturl)); - FilteredItem item1("dummyline", FilteredItem::InvalidItem); + FilteredItem item1(QStringLiteral("dummyline"), FilteredItem::InvalidItem); QBENCHMARK { for(int i = 0; i < outputlines.size(); ++i) { item1 = testee.actionInLine(outputlines.at(i)); } } const qint64 elapsed = totalTime.elapsed(); qDebug() << "ms elapsed to add directories: " << elapsed; qDebug() << "total number of directories: " << outputlines.count(); const double avgDirectoryInsertion = double(elapsed) / outputlines.count(); qDebug() << "average ms spend pr. dir: " << avgDirectoryInsertion; QVERIFY(avgDirectoryInsertion < 2); } void TestFilteringStrategy::testExtractionOfLineAndColumn_data() { QTest::addColumn("line"); QTest::addColumn("file"); QTest::addColumn("lineNr"); QTest::addColumn("column"); QTest::addColumn("itemtype"); #ifdef Q_OS_WIN QTest::newRow("msvc-compiler-error-line") << "Z:\\kderoot\\download\\git\\kcoreaddons\\src\\lib\\jobs\\kjob.cpp(3): error C2065: 'dadsads': undeclared identifier" << "Z:/kderoot/download/git/kcoreaddons/src/lib/jobs/kjob.cpp" << 2 << 0 << FilteredItem::ErrorItem; QTest::newRow("msvc-compiler-warning-line") << "c:\\program files\\microsoft visual studio 10.0\\vc\\include\\crtdefs.h(527): warning C4229: anachronism used : modifiers on data are ignored" << "c:/program files/microsoft visual studio 10.0/vc/include/crtdefs.h" << 526 << 0 << FilteredItem::WarningItem; #else QTest::newRow("gcc-with-col") << "/path/to/file.cpp:123:45: fatal error: ..." << "/path/to/file.cpp" << 122 << 44 << FilteredItem::ErrorItem; QTest::newRow("gcc-no-col") << "/path/to/file.cpp:123: error ..." << "/path/to/file.cpp" << 122 << 0 << FilteredItem::ErrorItem; QTest::newRow("fortcom") << "fortcom: Error: Ogive8.f90, line 123: ..." << QString(projectPath() + "/Ogive8.f90") << 122 << 0 << FilteredItem::ErrorItem; QTest::newRow("fortcomError") << "fortcom: Error: ./Ogive8.f90, line 123: ..." << QString(projectPath() + "/Ogive8.f90") << 122 << 0 << FilteredItem::ErrorItem; QTest::newRow("fortcomWarning") << "fortcom: Warning: /path/Ogive8.f90, line 123: ..." << "/path/Ogive8.f90" << 122 << 0 << FilteredItem::WarningItem; QTest::newRow("fortcomInfo") << "fortcom: Info: Ogive8.f90, line 123: ..." << QString(projectPath() + "/Ogive8.f90") << 122 << 0 << FilteredItem::InformationItem; QTest::newRow("libtool") << "libtool: link: warning: ..." << "" << -1 << 0 << FilteredItem::WarningItem; QTest::newRow("gfortranError1") << "/path/to/file.f90:123.456:Error: ...." << "/path/to/file.f90" << 122 << 455 << FilteredItem::ErrorItem; QTest::newRow("gfortranError2") << "/path/flib.f90:3567.22:" << "/path/flib.f90" << 3566 << 21 << FilteredItem::ErrorItem; #endif } void TestFilteringStrategy::testExtractionOfLineAndColumn() { QFETCH(QString, line); QFETCH(QString, file); QFETCH(int, lineNr); QFETCH(int, column); QFETCH(FilteredItem::FilteredOutputItemType, itemtype); QUrl projecturl = QUrl::fromLocalFile( projectPath() ); CompilerFilterStrategy testee(projecturl); FilteredItem item1 = testee.errorInLine(line); QCOMPARE(item1.type , itemtype); QCOMPARE(KDevelop::toUrlOrLocalFile(item1.url), file); QCOMPARE(item1.lineNo , lineNr); QCOMPARE(item1.columnNo , column); } diff --git a/outputview/tests/test_outputmodel.cpp b/outputview/tests/test_outputmodel.cpp index 7125f05f6..b1ae82d50 100644 --- a/outputview/tests/test_outputmodel.cpp +++ b/outputview/tests/test_outputmodel.cpp @@ -1,117 +1,117 @@ /* This file is part of KDevelop Copyright 2012 Milian Wolff Copyright (C) 2012 Morten Danielsen Volden mvolden2@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "test_outputmodel.h" #include "testlinebuilderfunctions.h" #include "../outputmodel.h" #include QTEST_MAIN(KDevelop::TestOutputModel) namespace KDevelop { TestOutputModel::TestOutputModel(QObject* parent): QObject(parent) { } QStringList generateLines() { const int numLines = 10000; QStringList outputlines; do { outputlines << buildCompilerErrorLine(); outputlines << buildCompilerLine(); outputlines << buildCompilerActionLine(); outputlines << buildCppCheckErrorLine(); outputlines << buildCppCheckInformationLine(); outputlines << buildPythonErrorLine(); } while(outputlines.size() < numLines ); // gives us numLines (-ish) return outputlines; } QStringList generateLongLine() { const int objects = 100; // *.o files const int libs = 20; // -l... const int libPaths = 20; // -L... - QString line = "g++ -m64 -Wl,-rpath,/home/gabo/md/qt/lib -o bin/flap_ui"; + QString line = QStringLiteral("g++ -m64 -Wl,-rpath,/home/gabo/md/qt/lib -o bin/flap_ui"); for(int i = 0; i < objects; ++i) { line += QStringLiteral(" .obj/file%1.o").arg(i); } for(int i = 0; i < libPaths; ++i) { line += QStringLiteral(" -Lsome/path/to/lib%1").arg(i); } for(int i = 0; i < libs; ++i) { line += QStringLiteral(" -lsomelib%1").arg(i); } return QStringList() << line; } void TestOutputModel::bench() { QFETCH(KDevelop::OutputModel::OutputFilterStrategy, strategy); QFETCH(QStringList, lines); - OutputModel testee(QUrl::fromLocalFile("/tmp/build-foo")); + OutputModel testee(QUrl::fromLocalFile(QStringLiteral("/tmp/build-foo"))); testee.setFilteringStrategy(strategy); quint64 processEventsCounter = 1; QElapsedTimer totalTime; totalTime.start(); testee.appendLines(lines); while(testee.rowCount() != lines.count()) { QCoreApplication::instance()->processEvents(); processEventsCounter++; } QVERIFY(testee.rowCount() == lines.count()); const qint64 elapsed = totalTime.elapsed(); qDebug() << "ms elapsed to add lines: " << elapsed; qDebug() << "total number of added lines: " << lines.count(); const double avgUiLockup = double(elapsed) / processEventsCounter; qDebug() << "average UI lockup in ms: " << avgUiLockup; QVERIFY(avgUiLockup < 200); } void TestOutputModel::bench_data() { QTest::addColumn("strategy"); QTest::addColumn("lines"); const QStringList lines = generateLines(); const QStringList longLine = generateLongLine(); QTest::newRow("no-filter") << OutputModel::NoFilter << lines; QTest::newRow("no-filter-longline") << OutputModel::NoFilter << longLine; QTest::newRow("compiler-filter") << OutputModel::CompilerFilter << lines; QTest::newRow("compiler-filter-longline") << OutputModel::CompilerFilter << longLine; QTest::newRow("script-error-filter") << OutputModel::ScriptErrorFilter << lines; QTest::newRow("script-error-filter-longline") << OutputModel::ScriptErrorFilter << longLine; QTest::newRow("static-analysis-filter") << OutputModel::StaticAnalysisFilter << lines; QTest::newRow("static-analysis-filter-longline") << OutputModel::StaticAnalysisFilter << longLine; } } diff --git a/outputview/tests/testlinebuilderfunctions.h b/outputview/tests/testlinebuilderfunctions.h index 40266dc89..a51319f86 100644 --- a/outputview/tests/testlinebuilderfunctions.h +++ b/outputview/tests/testlinebuilderfunctions.h @@ -1,159 +1,159 @@ /* This file is part of KDevelop Copyright (C) 2012 Morten Danielsen Volden mvolden2@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef KDEVPLATFORM_TESTLINEBUILDERFUNCTIONS_H #define KDEVPLATFORM_TESTLINEBUILDERFUNCTIONS_H #include #include #include #define PROJECTS_SOURCE_DIR namespace KDevelop { static QString projectPath() { /// Use existing directory with one file - return QFileInfo(__FILE__).absolutePath() + QLatin1String("/onefileproject"); + return QFileInfo(QStringLiteral(__FILE__)).absolutePath() + QLatin1String("/onefileproject"); } QString buildCppCheckErrorLine() { /// Test CPP check output - QString outputline("["); + QString outputline(QStringLiteral("[")); outputline.append(projectPath()); outputline.append("main.cpp:26]: (error) Memory leak: str"); return outputline; } QString buildKrazyErrorLine() { /// Test krazy2 output - QString outputline("\t"); + QString outputline(QStringLiteral("\t")); outputline.append(projectPath()); outputline.append("main.cpp: line#22 (1)"); return outputline; } QString buildKrazyErrorLine2() { /// Test krazy2 output - QString outputline("\t"); + QString outputline(QStringLiteral("\t")); outputline.append(projectPath()); outputline.append("main.cpp: missing tags: email address line#2 (1)"); return outputline; } QString buildKrazyErrorLine3() { /// Test krazy2 output - QString outputline("\t"); + QString outputline(QStringLiteral("\t")); outputline.append(projectPath()); outputline.append("main.cpp: non-const ref iterator line#451 (1)"); return outputline; } QString buildKrazyErrorLineNoLineInfo() { /// Test krazy2 output - QString outputline("\t"); + QString outputline(QStringLiteral("\t")); outputline.append(projectPath()); outputline.append("main.cpp: missing license"); return outputline; } QString buildCompilerLine() { /// Test with compiler output QString outputline; outputline.append(projectPath()); outputline.append(">make"); return outputline; } QString buildCompilerErrorLine() { QString outputline; outputline.append(projectPath()); outputline.append("main.cpp:5:5: error: ‘RingBuffer’ was not declared in this scope"); return outputline; } QString buildCompilerInformationLine() { QString outputline; outputline.append(projectPath()); outputline.append("main.cpp:6:14: instantiated from here"); return outputline; } QString buildInfileIncludedFromFirstLine() { - QString outputline("In file included from "); + QString outputline(QStringLiteral("In file included from ")); outputline.append(projectPath()); outputline.append("PriorityFactory.h:52:0,"); return outputline; } QString buildInfileIncludedFromSecondLine() { - QString outputline(" from "); + QString outputline(QStringLiteral(" from ")); outputline.append(projectPath()); outputline.append("PatchBasedInpainting.hxx:29,"); return outputline; } QString buildCompilerActionLine() { return QStringLiteral("linking testCustombuild (g++)"); } QString buildCmakeConfigureMultiLine() { QString outputline; outputline.append(projectPath()); outputline.append("CMakeLists.txt:10:"); return outputline; } QString buildLinkerErrorLine() { return QStringLiteral("/path/to/file/Buffer.cpp:66: undefined reference to `Buffer::does_not_exist()'"); } QString buildPythonErrorLine() { - QString outputline("File \""); + QString outputline(QStringLiteral("File \"")); outputline.append(projectPath()); outputline.append("pythonExample.py\", line 10"); return outputline; } QString buildCppCheckInformationLine() { return QStringLiteral("(information) Cppcheck cannot find all the include files. Cpppcheck can check the code without the include\ files found. But the results will probably be more accurate if all the include files are found. Please check your project's \ include directories and add all of them as include directories for Cppcheck. To see what files Cppcheck cannot find use --check-config."); } } #endif diff --git a/plugins/appwizard/appwizardplugin.cpp b/plugins/appwizard/appwizardplugin.cpp index 87c9a6585..63f31b145 100644 --- a/plugins/appwizard/appwizardplugin.cpp +++ b/plugins/appwizard/appwizardplugin.cpp @@ -1,515 +1,516 @@ /*************************************************************************** * Copyright 2001 Bernd Gehrmann * * Copyright 2004-2005 Sascha Cunz * * Copyright 2005 Ian Reinhart Geiser * * Copyright 2007 Alexander Dymo * * Copyright 2008 Evgeniy Ivanov * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "appwizardplugin.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "appwizarddialog.h" #include "projectselectionpage.h" #include "projectvcspage.h" #include "projecttemplatesmodel.h" #include "debug.h" using namespace KDevelop; Q_LOGGING_CATEGORY(PLUGIN_APPWIZARD, "kdevplatform.plugins.appwizard") K_PLUGIN_FACTORY_WITH_JSON(AppWizardFactory, "kdevappwizard.json", registerPlugin();) AppWizardPlugin::AppWizardPlugin(QObject *parent, const QVariantList &) - : KDevelop::IPlugin("kdevappwizard", parent) + : KDevelop::IPlugin(QStringLiteral("kdevappwizard"), parent) , m_templatesModel(0) { KDEV_USE_EXTENSION_INTERFACE(KDevelop::ITemplateProvider); - setXMLFile("kdevappwizard.rc"); + setXMLFile(QStringLiteral("kdevappwizard.rc")); - m_newFromTemplate = actionCollection()->addAction("project_new"); - m_newFromTemplate->setIcon(QIcon::fromTheme("project-development-new-template")); + m_newFromTemplate = actionCollection()->addAction(QStringLiteral("project_new")); + m_newFromTemplate->setIcon(QIcon::fromTheme(QStringLiteral("project-development-new-template"))); m_newFromTemplate->setText(i18n("New From Template...")); connect(m_newFromTemplate, &QAction::triggered, this, &AppWizardPlugin::slotNewProject); m_newFromTemplate->setToolTip( i18n("Generate a new project from a template") ); m_newFromTemplate->setWhatsThis( i18n("This starts KDevelop's application wizard. " "It helps you to generate a skeleton for your " "application from a set of templates.") ); } AppWizardPlugin::~AppWizardPlugin() { } void AppWizardPlugin::slotNewProject() { model()->refresh(); AppWizardDialog dlg(core()->pluginController(), m_templatesModel); if (dlg.exec() == QDialog::Accepted) { QString project = createProject( dlg.appInfo() ); if (!project.isEmpty()) { core()->projectController()->openProject(QUrl::fromLocalFile(project)); KConfig templateConfig(dlg.appInfo().appTemplate); KConfigGroup general(&templateConfig, "General"); QString file = general.readEntry("ShowFilesAfterGeneration"); if (!file.isEmpty()) { file = KMacroExpander::expandMacros(file, m_variables); core()->documentController()->openDocument(QUrl::fromUserInput(file)); } } else { KMessageBox::error( ICore::self()->uiController()->activeMainWindow(), i18n("Could not create project from template\n"), i18n("Failed to create project") ); } } } namespace { IDistributedVersionControl* toDVCS(IPlugin* plugin) { Q_ASSERT(plugin); return plugin->extension(); } ICentralizedVersionControl* toCVCS(IPlugin* plugin) { Q_ASSERT(plugin); return plugin->extension(); } /*! Trouble while initializing version control. Show failure message to user. */ void vcsError(const QString &errorMsg, QTemporaryDir &tmpdir, const QUrl &dest, const QString &details = QString()) { QString displayDetails = details; if (displayDetails.isEmpty()) { displayDetails = i18n("Please see the Version Control toolview"); } KMessageBox::detailedError(0, errorMsg, displayDetails, i18n("Version Control System Error")); KIO::del(dest)->exec(); tmpdir.remove(); } /*! Setup distributed version control for a new project defined by @p info. Use @p scratchArea for temporary files */ bool initializeDVCS(IDistributedVersionControl* dvcs, const ApplicationInfo& info, QTemporaryDir& scratchArea) { Q_ASSERT(dvcs); qCDebug(PLUGIN_APPWIZARD) << "DVCS system is used, just initializing DVCS"; const QUrl& dest = info.location; //TODO: check if we want to handle KDevelop project files (like now) or only SRC dir VcsJob* job = dvcs->init(dest); if (!job || !job->exec() || job->status() != VcsJob::JobSucceeded) { vcsError(i18n("Could not initialize DVCS repository"), scratchArea, dest); return false; } qCDebug(PLUGIN_APPWIZARD) << "Initializing DVCS repository:" << dest; job = dvcs->add({dest}, KDevelop::IBasicVersionControl::Recursive); if (!job || !job->exec() || job->status() != VcsJob::JobSucceeded) { vcsError(i18n("Could not add files to the DVCS repository"), scratchArea, dest); return false; } job = dvcs->commit(QStringLiteral("initial project import from KDevelop"), {dest}, KDevelop::IBasicVersionControl::Recursive); if (!job || !job->exec() || job->status() != VcsJob::JobSucceeded) { vcsError(i18n("Could not import project into %1.", dvcs->name()), scratchArea, dest, job ? job->errorString() : QString()); return false; } return true; // We're good } /*! Setup version control for a new project defined by @p info. Use @p scratchArea for temporary files */ bool initializeCVCS(ICentralizedVersionControl* cvcs, const ApplicationInfo& info, QTemporaryDir& scratchArea) { Q_ASSERT(cvcs); qCDebug(PLUGIN_APPWIZARD) << "Importing" << info.sourceLocation << "to" << info.repository.repositoryServer(); VcsJob* job = cvcs->import( info.importCommitMessage, QUrl::fromLocalFile(scratchArea.path()), info.repository); if (!job || !job->exec() || job->status() != VcsJob::JobSucceeded ) { vcsError(i18n("Could not import project"), scratchArea, QUrl::fromUserInput(info.repository.repositoryServer())); return false; } qCDebug(PLUGIN_APPWIZARD) << "Checking out"; job = cvcs->createWorkingCopy( info.repository, info.location, IBasicVersionControl::Recursive); if (!job || !job->exec() || job->status() != VcsJob::JobSucceeded ) { vcsError(i18n("Could not checkout imported project"), scratchArea, QUrl::fromUserInput(info.repository.repositoryServer())); return false; } return true; // initialization phase complete } QString generateIdentifier( const QString& appname ) { QString tmp = appname; QRegExp re("[^a-zA-Z0-9_]"); - return tmp.replace(re, "_"); + return tmp.replace(re, QStringLiteral("_")); } } // end anonymous namespace QString AppWizardPlugin::createProject(const ApplicationInfo& info) { QFileInfo templateInfo(info.appTemplate); if (!templateInfo.exists()) { qWarning() << "Project app template does not exist:" << info.appTemplate; return QString(); } QString templateName = templateInfo.baseName(); QString templateArchive; const QStringList filters = {templateName + QStringLiteral(".*")}; const QStringList matchesPaths = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("kdevappwizard/templates/"), QStandardPaths::LocateDirectory); foreach(const QString& matchesPath, matchesPaths) { const QStringList files = QDir(matchesPath).entryList(filters); if(!files.isEmpty()) { templateArchive = matchesPath + files.first(); } } if(templateArchive.isEmpty()) { qWarning() << "Template name does not exist in the template list"; return QString(); } QUrl dest = info.location; //prepare variable substitution hash m_variables.clear(); - m_variables["APPNAME"] = info.name; - m_variables["APPNAMEUC"] = info.name.toUpper(); - m_variables["APPNAMELC"] = info.name.toLower(); - m_variables["APPNAMEID"] = generateIdentifier(info.name); - m_variables["PROJECTDIR"] = dest.toLocalFile(); + m_variables[QStringLiteral("APPNAME")] = info.name; + m_variables[QStringLiteral("APPNAMEUC")] = info.name.toUpper(); + m_variables[QStringLiteral("APPNAMELC")] = info.name.toLower(); + m_variables[QStringLiteral("APPNAMEID")] = generateIdentifier(info.name); + m_variables[QStringLiteral("PROJECTDIR")] = dest.toLocalFile(); // backwards compatibility - m_variables["dest"] = m_variables["PROJECTDIR"]; - m_variables["PROJECTDIRNAME"] = dest.fileName(); - m_variables["VERSIONCONTROLPLUGIN"] = info.vcsPluginName; + m_variables[QStringLiteral("dest")] = m_variables[QStringLiteral("PROJECTDIR")]; + m_variables[QStringLiteral("PROJECTDIRNAME")] = dest.fileName(); + m_variables[QStringLiteral("VERSIONCONTROLPLUGIN")] = info.vcsPluginName; KArchive* arch = 0; - if( templateArchive.endsWith(".zip") ) + if( templateArchive.endsWith(QLatin1String(".zip")) ) { arch = new KZip(templateArchive); } else { - arch = new KTar(templateArchive, "application/x-bzip"); + arch = new KTar(templateArchive, QStringLiteral("application/x-bzip")); } if (arch->open(QIODevice::ReadOnly)) { QTemporaryDir tmpdir; QString unpackDir = tmpdir.path(); //the default value for all Centralized VCS IPlugin* plugin = core()->pluginController()->loadPlugin( info.vcsPluginName ); if( info.vcsPluginName.isEmpty() || ( plugin && plugin->extension() ) ) { if( !QFileInfo( dest.toLocalFile() ).exists() ) { QDir::root().mkpath( dest.toLocalFile() ); } unpackDir = dest.toLocalFile(); //in DVCS we unpack template directly to the project's directory } else { QUrl url = KIO::upUrl(dest); if(!QFileInfo(url.toLocalFile()).exists()) { QDir::root().mkpath(url.toLocalFile()); } } if ( !unpackArchive( arch->directory(), unpackDir ) ) { QString errorMsg = i18n("Could not create new project"); vcsError(errorMsg, tmpdir, QUrl::fromLocalFile(unpackDir)); return QString(); } if( !info.vcsPluginName.isEmpty() ) { if (!plugin) { // Red Alert, serious program corruption. // This should never happen, the vcs dialog presented a list of vcs // systems and now the chosen system doesn't exist anymore?? tmpdir.remove(); return QString(); } IDistributedVersionControl* dvcs = toDVCS(plugin); ICentralizedVersionControl* cvcs = toCVCS(plugin); bool success = false; if (dvcs) { success = initializeDVCS(dvcs, info, tmpdir); } else if (cvcs) { success = initializeCVCS(cvcs, info, tmpdir); } else { if (KMessageBox::Continue == KMessageBox::warningContinueCancel(0, - "Failed to initialize version control system, " - "plugin is neither VCS nor DVCS.")) + QStringLiteral("Failed to initialize version control system, " + "plugin is neither VCS nor DVCS."))) success = true; } if (!success) return QString(); } tmpdir.remove(); }else { qCDebug(PLUGIN_APPWIZARD) << "failed to open template archive"; return QString(); } QString projectFileName = QDir::cleanPath( dest.toLocalFile() + '/' + info.name + ".kdev4" ); // Loop through the new project directory and try to detect the first .kdev4 file. // If one is found this file will be used. So .kdev4 file can be stored in any subdirectory and the // project templates can be more complex. - QDirIterator it(QDir::cleanPath( dest.toLocalFile()), QStringList() << "*.kdev4", QDir::NoFilter, QDirIterator::Subdirectories); + QDirIterator it(QDir::cleanPath( dest.toLocalFile()), QStringList() << QStringLiteral("*.kdev4"), QDir::NoFilter, QDirIterator::Subdirectories); if(it.hasNext() == true) { projectFileName = it.next(); } qCDebug(PLUGIN_APPWIZARD) << "Returning" << projectFileName << QFileInfo( projectFileName ).exists() ; if( ! QFileInfo( projectFileName ).exists() ) { qCDebug(PLUGIN_APPWIZARD) << "creating .kdev4 file"; KSharedConfigPtr cfg = KSharedConfig::openConfig( projectFileName, KConfig::SimpleConfig ); KConfigGroup project = cfg->group( "Project" ); project.writeEntry( "Name", info.name ); - QString manager = "KDevGenericManager"; + QString manager = QStringLiteral("KDevGenericManager"); QDir d( dest.toLocalFile() ); - foreach(const KPluginMetaData& info, ICore::self()->pluginController()->queryExtensionPlugins("org.kdevelop.IProjectFileManager")) { - QStringList filter = KPluginMetaData::readStringList(info.rawData(), "X-KDevelop-ProjectFilesFilter"); + auto data = ICore::self()->pluginController()->queryExtensionPlugins(QStringLiteral("org.kdevelop.IProjectFileManager")); + foreach(const KPluginMetaData& info, data) { + QStringList filter = KPluginMetaData::readStringList(info.rawData(), QStringLiteral("X-KDevelop-ProjectFilesFilter")); if (!filter.isEmpty()) { if (!d.entryList(filter).isEmpty()) { manager = info.pluginId(); break; } } } project.writeEntry( "Manager", manager ); project.sync(); cfg->sync(); KConfigGroup project2 = cfg->group( "Project" ); qCDebug(PLUGIN_APPWIZARD) << "kdev4 file contents:" << project2.readEntry("Name", "") << project2.readEntry("Manager", "" ); } return projectFileName; } bool AppWizardPlugin::unpackArchive(const KArchiveDirectory *dir, const QString &dest) { qCDebug(PLUGIN_APPWIZARD) << "unpacking dir:" << dir->name() << "to" << dest; const QStringList entries = dir->entries(); - qCDebug(PLUGIN_APPWIZARD) << "entries:" << entries.join(","); + qCDebug(PLUGIN_APPWIZARD) << "entries:" << entries.join(QStringLiteral(",")); //This extra tempdir is needed just for the files files have special names, //which may contain macros also files contain content with macros. So the //easiest way to extract the files from the archive and then rename them //and replace the macros is to use a tempdir and copy the file (and //replacing while copying). This also allows to easily remove all files, by //just unlinking the tempdir QTemporaryDir tdir; bool ret = true; foreach (const QString& entry, entries) { - if (entry.endsWith(".kdevtemplate")) + if (entry.endsWith(QLatin1String(".kdevtemplate"))) continue; if (dir->entry(entry)->isDirectory()) { const KArchiveDirectory *file = (KArchiveDirectory *)dir->entry(entry); QString newdest = dest + '/' + KMacroExpander::expandMacros(file->name(), m_variables); if( !QFileInfo( newdest ).exists() ) { QDir::root().mkdir( newdest ); } ret |= unpackArchive(file, newdest); } else if (dir->entry(entry)->isFile()) { const KArchiveFile *file = (KArchiveFile *)dir->entry(entry); file->copyTo(tdir.path()); QString destName = dest + '/' + file->name(); if (!copyFileAndExpandMacros(QDir::cleanPath(tdir.path()+'/'+file->name()), KMacroExpander::expandMacros(destName, m_variables))) { KMessageBox::sorry(0, i18n("The file %1 cannot be created.", dest)); return false; } } } tdir.remove(); return ret; } bool AppWizardPlugin::copyFileAndExpandMacros(const QString &source, const QString &dest) { qCDebug(PLUGIN_APPWIZARD) << "copy:" << source << "to" << dest; QMimeDatabase db; QMimeType mime = db.mimeTypeForFile(source); - if( !mime.inherits("text/plain") ) + if( !mime.inherits(QStringLiteral("text/plain")) ) { KIO::CopyJob* job = KIO::copy( QUrl::fromUserInput(source), QUrl::fromUserInput(dest), KIO::HideProgressInfo ); if( !job->exec() ) { return false; } return true; } else { QFile inputFile(source); QFile outputFile(dest); if (inputFile.open(QFile::ReadOnly) && outputFile.open(QFile::WriteOnly)) { QTextStream input(&inputFile); input.setCodec(QTextCodec::codecForName("UTF-8")); QTextStream output(&outputFile); output.setCodec(QTextCodec::codecForName("UTF-8")); while(!input.atEnd()) { QString line = input.readLine(); output << KMacroExpander::expandMacros(line, m_variables) << "\n"; } #ifndef Q_OS_WIN // Preserve file mode... QT_STATBUF statBuf; QT_FSTAT(inputFile.handle(), &statBuf); // Unix only, won't work in Windows, maybe KIO::chmod could be used ::fchmod(outputFile.handle(), statBuf.st_mode); #endif return true; } else { inputFile.close(); outputFile.close(); return false; } } } KDevelop::ContextMenuExtension AppWizardPlugin::contextMenuExtension(KDevelop::Context* context) { KDevelop::ContextMenuExtension ext; if ( context->type() != KDevelop::Context::ProjectItemContext || !static_cast(context)->items().isEmpty() ) { return ext; } ext.addAction(KDevelop::ContextMenuExtension::ProjectGroup, m_newFromTemplate); return ext; } ProjectTemplatesModel* AppWizardPlugin::model() { if(!m_templatesModel) m_templatesModel = new ProjectTemplatesModel(this); return m_templatesModel; } QAbstractItemModel* AppWizardPlugin::templatesModel() { return model(); } QString AppWizardPlugin::knsConfigurationFile() const { - return "kdevappwizard.knsrc"; + return QStringLiteral("kdevappwizard.knsrc"); } QStringList AppWizardPlugin::supportedMimeTypes() const { QStringList types; - types << "application/x-desktop"; - types << "application/x-bzip-compressed-tar"; - types << "application/zip"; + types << QStringLiteral("application/x-desktop"); + types << QStringLiteral("application/x-bzip-compressed-tar"); + types << QStringLiteral("application/zip"); return types; } QIcon AppWizardPlugin::icon() const { - return QIcon::fromTheme("project-development-new-template"); + return QIcon::fromTheme(QStringLiteral("project-development-new-template")); } QString AppWizardPlugin::name() const { return i18n("Project Templates"); } void AppWizardPlugin::loadTemplate(const QString& fileName) { model()->loadTemplateFile(fileName); } void AppWizardPlugin::reload() { model()->refresh(); } #include "appwizardplugin.moc" diff --git a/plugins/appwizard/projectselectionpage.cpp b/plugins/appwizard/projectselectionpage.cpp index 949dd048e..dd6ab7700 100644 --- a/plugins/appwizard/projectselectionpage.cpp +++ b/plugins/appwizard/projectselectionpage.cpp @@ -1,363 +1,363 @@ /*************************************************************************** * Copyright 2007 Alexander Dymo * * Copyright 2011 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) any later version. * * * ***************************************************************************/ #include "projectselectionpage.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include #include #include "ui_projectselectionpage.h" #include "projecttemplatesmodel.h" using namespace KDevelop; ProjectSelectionPage::ProjectSelectionPage(ProjectTemplatesModel *templatesModel, AppWizardDialog *wizardDialog) : AppWizardPageWidget(wizardDialog), m_templatesModel(templatesModel) { ui = new Ui::ProjectSelectionPage(); ui->setupUi(this); setContentsMargins(0,0,0,0); ui->descriptionContent->setBackgroundRole(QPalette::Base); ui->descriptionContent->setForegroundRole(QPalette::Text); ui->locationUrl->setMode(KFile::Directory | KFile::ExistingOnly | KFile::LocalOnly ); ui->locationUrl->setUrl(KDevelop::ICore::self()->projectController()->projectsBaseDirectory()); ui->locationValidWidget->hide(); ui->locationValidWidget->setMessageType(KMessageWidget::Error); ui->locationValidWidget->setCloseButtonVisible(false); connect( ui->locationUrl->lineEdit(), &KLineEdit::textEdited, this, &ProjectSelectionPage::urlEdited); connect( ui->locationUrl, &KUrlRequester::urlSelected, this, &ProjectSelectionPage::urlEdited); connect( ui->appNameEdit, &QLineEdit::textEdited, this, &ProjectSelectionPage::nameChanged ); m_listView = new KDevelop::MultiLevelListView(this); m_listView->setLevels(2); m_listView->setHeaderLabels(QStringList() << i18n("Category") << i18n("Project Type")); m_listView->setModel(templatesModel); m_listView->setLastModelsFilterBehavior(KSelectionProxyModel::ChildrenOfExactSelection); m_listView->setContentsMargins(0, 0, 0, 0); connect (m_listView, &MultiLevelListView::currentIndexChanged, this, &ProjectSelectionPage::typeChanged); ui->gridLayout->addWidget(m_listView, 0, 0, 1, 1); typeChanged(m_listView->currentIndex()); connect( ui->templateType, static_cast(&QComboBox::currentIndexChanged), this, &ProjectSelectionPage::templateChanged ); QPushButton* getMoreButton = new QPushButton(i18n("Get More Templates"), m_listView); - getMoreButton->setIcon(QIcon::fromTheme("get-hot-new-stuff")); + getMoreButton->setIcon(QIcon::fromTheme(QStringLiteral("get-hot-new-stuff"))); connect (getMoreButton, &QPushButton::clicked, this, &ProjectSelectionPage::moreTemplatesClicked); m_listView->addWidget(0, getMoreButton); QPushButton* loadButton = new QPushButton(m_listView); loadButton->setText(i18n("Load Template From File")); - loadButton->setIcon(QIcon::fromTheme("application-x-archive")); + loadButton->setIcon(QIcon::fromTheme(QStringLiteral("application-x-archive"))); connect (loadButton, &QPushButton::clicked, this, &ProjectSelectionPage::loadFileClicked); m_listView->addWidget(0, loadButton); m_wizardDialog = wizardDialog; } void ProjectSelectionPage::nameChanged() { validateData(); emit locationChanged( location() ); } ProjectSelectionPage::~ProjectSelectionPage() { delete ui; } void ProjectSelectionPage::typeChanged(const QModelIndex& idx) { if (!idx.model()) { qCDebug(PLUGIN_APPWIZARD) << "Index with no model"; return; } int children = idx.model()->rowCount(idx); ui->templateType->setVisible(children); ui->templateType->setEnabled(children > 1); if (children) { ui->templateType->setModel(m_templatesModel); ui->templateType->setRootModelIndex(idx); ui->templateType->setCurrentIndex(0); itemChanged(idx.child(0, 0)); } else { itemChanged(idx); } } void ProjectSelectionPage::templateChanged(int current) { QModelIndex idx=m_templatesModel->index(current, 0, ui->templateType->rootModelIndex()); itemChanged(idx); } void ProjectSelectionPage::itemChanged( const QModelIndex& current) { QString picPath = current.data( KDevelop::TemplatesModel::IconNameRole ).toString(); if( picPath.isEmpty() ) { - QIcon icon("kdevelop"); + QIcon icon(QStringLiteral("kdevelop")); ui->icon->setPixmap(icon.pixmap(128, 128)); ui->icon->setFixedHeight(128); } else { QPixmap pixmap( picPath ); ui->icon->setPixmap( pixmap ); ui->icon->setFixedHeight( pixmap.height() ); } // header name is either from this index directly or the parents if we show the combo box const QVariant headerData = ui->templateType->isVisible() ? current.parent().data() : current.data(); ui->header->setText(QStringLiteral("

%1

").arg(headerData.toString().trimmed())); ui->description->setText(current.data(KDevelop::TemplatesModel::CommentRole).toString()); validateData(); ui->propertiesBox->setEnabled(true); } QString ProjectSelectionPage::selectedTemplate() { QStandardItem *item = getCurrentItem(); if (item) return item->data().toString(); else - return ""; + return QString(); } QUrl ProjectSelectionPage::location() { QUrl url = ui->locationUrl->url().adjusted(QUrl::StripTrailingSlash); url.setPath(url.path() + '/' + encodedAppName()); return url; } QString ProjectSelectionPage::appName() { return ui->appNameEdit->text(); } void ProjectSelectionPage::urlEdited() { validateData(); emit locationChanged( location() ); } void ProjectSelectionPage::validateData() { QUrl url = ui->locationUrl->url(); if( !url.isLocalFile() || url.isEmpty() ) { ui->locationValidWidget->setText( i18n("Invalid location") ); ui->locationValidWidget->animatedShow(); emit invalid(); return; } if( appName().isEmpty() ) { ui->locationValidWidget->setText( i18n("Empty project name") ); ui->locationValidWidget->animatedShow(); emit invalid(); return; } if( !appName().isEmpty() ) { QString appname = appName(); QString templatefile = m_wizardDialog->appInfo().appTemplate; // Read template file KConfig config(templatefile); KConfigGroup configgroup(&config, "General"); QString pattern = configgroup.readEntry( "ValidProjectName" , "^[a-zA-Z][a-zA-Z0-9_]+$" ); // Validation int pos = 0; QRegExp regex( pattern ); QRegExpValidator validator( regex ); if( validator.validate(appname, pos) == QValidator::Invalid ) { ui->locationValidWidget->setText( i18n("Invalid project name") ); emit invalid(); return; } } QDir tDir(url.toLocalFile()); while (!tDir.exists() && !tDir.isRoot()) { if (!tDir.cdUp()) { break; } } if (tDir.exists()) { QFileInfo tFileInfo(tDir.absolutePath()); if (!tFileInfo.isWritable() || !tFileInfo.isExecutable()) { ui->locationValidWidget->setText( i18n("Unable to create subdirectories, " "missing permissions on: %1", tDir.absolutePath()) ); ui->locationValidWidget->animatedShow(); emit invalid(); return; } } QStandardItem* item = getCurrentItem(); if( item && !item->hasChildren() ) { ui->locationValidWidget->animatedHide(); emit valid(); } else { ui->locationValidWidget->setText( i18n("Invalid project template, please choose a leaf item") ); ui->locationValidWidget->animatedShow(); emit invalid(); return; } // Check for non-empty target directory. Not an error, but need to display a warning. url.setPath( url.path() + '/' + encodedAppName() ); QFileInfo fi( url.toLocalFile() ); if( fi.exists() && fi.isDir() ) { if( !QDir( fi.absoluteFilePath()).entryList( QDir::NoDotAndDotDot | QDir::AllEntries ).isEmpty() ) { ui->locationValidWidget->setText( i18n("Path already exists and contains files. Open it as a project.") ); ui->locationValidWidget->animatedShow(); emit invalid(); return; } } } QByteArray ProjectSelectionPage::encodedAppName() { // : < > * ? / \ | " are invalid on windows QByteArray tEncodedName = appName().toUtf8(); for (int i = 0; i < tEncodedName.size(); ++i) { QChar tChar(tEncodedName.at( i )); if (tChar.isDigit() || tChar.isSpace() || tChar.isLetter() || tChar == '%') continue; QByteArray tReplace = QUrl::toPercentEncoding( tChar ); tEncodedName.replace( tEncodedName.at( i ) ,tReplace ); i = i + tReplace.size() - 1; } return tEncodedName; } QStandardItem* ProjectSelectionPage::getCurrentItem() const { QStandardItem* item = m_templatesModel->itemFromIndex( m_listView->currentIndex() ); if ( item && item->hasChildren() ) { const int currect = ui->templateType->currentIndex(); const QModelIndex idx = m_templatesModel->index( currect, 0, ui->templateType->rootModelIndex() ); item = m_templatesModel->itemFromIndex(idx); } return item; } bool ProjectSelectionPage::shouldContinue() { QFileInfo fi(location().toLocalFile()); if (fi.exists() && fi.isDir()) { if (!QDir(fi.absoluteFilePath()).entryList(QDir::NoDotAndDotDot | QDir::AllEntries).isEmpty()) { int res = KMessageBox::questionYesNo(this, i18n("The specified path already exists and contains files. " "Are you sure you want to proceed?")); return res == KMessageBox::Yes; } } return true; } void ProjectSelectionPage::loadFileClicked() { - QString filter = "application/x-desktop application/x-bzip-compressed-tar application/zip"; + QString filter = QStringLiteral("application/x-desktop application/x-bzip-compressed-tar application/zip"); const QString fileName = QFileDialog::getOpenFileName(this, i18n("Load Template From File"), QString(), filter); if (!fileName.isEmpty()) { QString destination = m_templatesModel->loadTemplateFile(fileName); QModelIndexList indexes = m_templatesModel->templateIndexes(destination); if (indexes.size() > 2) { m_listView->setCurrentIndex(indexes.at(1)); ui->templateType->setCurrentIndex(indexes.at(2).row()); } } } void ProjectSelectionPage::moreTemplatesClicked() { - KNS3::DownloadDialog dialog("kdevappwizard.knsrc", this); + KNS3::DownloadDialog dialog(QStringLiteral("kdevappwizard.knsrc"), this); dialog.exec(); auto entries = dialog.changedEntries(); if (entries.isEmpty()) { return; } m_templatesModel->refresh(); bool updated = false; foreach (const KNS3::Entry& entry, entries) { if (!entry.installedFiles().isEmpty()) { updated = true; - setCurrentTemplate(entry.installedFiles().first()); + setCurrentTemplate(entry.installedFiles().at(0)); break; } } if (!updated) { m_listView->setCurrentIndex(QModelIndex()); } } void ProjectSelectionPage::setCurrentTemplate (const QString& fileName) { QModelIndexList indexes = m_templatesModel->templateIndexes(fileName); if (indexes.size() > 1) { m_listView->setCurrentIndex(indexes.at(1)); } if (indexes.size() > 2) { ui->templateType->setCurrentIndex(indexes.at(2).row()); } } diff --git a/plugins/appwizard/projecttemplatesmodel.cpp b/plugins/appwizard/projecttemplatesmodel.cpp index d9bb7df5e..d45c11418 100644 --- a/plugins/appwizard/projecttemplatesmodel.cpp +++ b/plugins/appwizard/projecttemplatesmodel.cpp @@ -1,17 +1,17 @@ /*************************************************************************** * Copyright 2007 Alexander Dymo * * * * 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 "projecttemplatesmodel.h" #include "appwizardplugin.h" ProjectTemplatesModel::ProjectTemplatesModel(AppWizardPlugin *parent) -: KDevelop::TemplatesModel("kdevappwizard", parent) +: KDevelop::TemplatesModel(QStringLiteral("kdevappwizard"), parent) { refresh(); } diff --git a/plugins/appwizard/projecttemplatesmodel.h b/plugins/appwizard/projecttemplatesmodel.h index 4bb46d133..5cb95c612 100644 --- a/plugins/appwizard/projecttemplatesmodel.h +++ b/plugins/appwizard/projecttemplatesmodel.h @@ -1,26 +1,27 @@ /*************************************************************************** * Copyright 2007 Alexander Dymo * * * * 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 _PROJECTTEMPLATESMODEL_H_ #define _PROJECTTEMPLATESMODEL_H_ #include "language/codegen/templatesmodel.h" #include #include class AppWizardPlugin; class ProjectTemplatesModel: public KDevelop::TemplatesModel { + Q_OBJECT public: explicit ProjectTemplatesModel(AppWizardPlugin *parent); }; #endif diff --git a/plugins/appwizard/projectvcspage.cpp b/plugins/appwizard/projectvcspage.cpp index d411320a4..82a378ca4 100644 --- a/plugins/appwizard/projectvcspage.cpp +++ b/plugins/appwizard/projectvcspage.cpp @@ -1,153 +1,154 @@ /*************************************************************************** * 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 "projectvcspage.h" #include "ui_projectvcspage.h" #include #include #include #include #include #include #include using namespace KDevelop; ProjectVcsPage::ProjectVcsPage( KDevelop::IPluginController* controller, QWidget * parent ) : AppWizardPageWidget( parent ), m_ui( new Ui::ProjectVcsPage ) { m_ui->setupUi( this ); - QList vcsplugins = controller->allPluginsForExtension ( "org.kdevelop.IBasicVersionControl" ); + QList vcsplugins = controller->allPluginsForExtension ( QStringLiteral("org.kdevelop.IBasicVersionControl") ); int idx = 1; m_ui->vcsImportOptions->insertWidget( 0, new QWidget(this) ); m_ui->vcsTypes->insertItem( 0, i18nc("No Version Control Support chosen", "None") ); foreach( KDevelop::IPlugin* plugin, vcsplugins ) { KDevelop::IBasicVersionControl* iface = plugin->extension(); if( iface ) { KDevelop::VcsImportMetadataWidget* widget = iface->createImportMetadataWidget( m_ui->vcsImportOptions ); if( widget ) { widget->setSourceLocationEditable( false ); widget->setUseSourceDirForDestination( true ); m_ui->vcsTypes->insertItem( idx, iface->name() ); importWidgets.push_back( widget ); vcsPlugins.push_back( qMakePair( controller->pluginInfo( plugin ).pluginId(), iface->name() ) ); m_ui->vcsImportOptions->insertWidget( idx, widget ); idx++; } } } connect( m_ui->vcsTypes, static_cast(&KComboBox::activated), m_ui->vcsImportOptions, &QStackedWidget::setCurrentIndex ); connect( m_ui->vcsTypes, static_cast(&KComboBox::activated), this, &ProjectVcsPage::vcsTypeChanged ); validateData(); } void ProjectVcsPage::vcsTypeChanged( int idx ) { validateData(); int widgetidx = idx - 1; disconnect( this, static_cast(nullptr), this, &ProjectVcsPage::validateData ); if ( widgetidx < 0 || widgetidx >= importWidgets.size()) return; connect( importWidgets[widgetidx], &VcsImportMetadataWidget::changed, this, &ProjectVcsPage::validateData ); } void ProjectVcsPage::validateData() { if( shouldContinue() ) { emit valid(); } else { emit invalid(); } } ProjectVcsPage::~ProjectVcsPage( ) { delete m_ui; } void ProjectVcsPage::setSourceLocation( const QUrl& s ) { foreach(KDevelop::VcsImportMetadataWidget* widget, importWidgets) { widget->setSourceLocation( KDevelop::VcsLocation( s ) ); } } QString ProjectVcsPage::pluginName() const { int idx = m_ui->vcsTypes->currentIndex() - 1; if ( idx < 0 || idx >= vcsPlugins.size()) - return ""; + return QString(); + // FIXME: Two return statements return vcsPlugins[idx].first; } QString ProjectVcsPage::commitMessage() const { int idx = m_ui->vcsTypes->currentIndex() - 1; if ( idx < 0 || idx >= importWidgets.size()) return QString(); return importWidgets[idx]->message(); } QUrl ProjectVcsPage::source() const { int idx = m_ui->vcsTypes->currentIndex() - 1; if ( idx < 0 || idx >= importWidgets.size()) return QUrl(); return importWidgets[idx]->source(); } KDevelop::VcsLocation ProjectVcsPage::destination() const { int idx = m_ui->vcsTypes->currentIndex() - 1; if ( idx < 0 || idx >= importWidgets.size()) return KDevelop::VcsLocation(); return importWidgets[idx]->destination(); } bool ProjectVcsPage::shouldContinue() { int idx = m_ui->vcsTypes->currentIndex() - 1; if ( idx < 0 || idx >= importWidgets.size()) return true; KDevelop::VcsImportMetadataWidget* widget = importWidgets[idx]; return widget->hasValidData(); } diff --git a/plugins/bazaar/bazaarplugin.cpp b/plugins/bazaar/bazaarplugin.cpp index a10a33cff..944dcc4fe 100644 --- a/plugins/bazaar/bazaarplugin.cpp +++ b/plugins/bazaar/bazaarplugin.cpp @@ -1,350 +1,351 @@ /*************************************************************************** * Copyright 2013-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 "bazaarplugin.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "bazaarutils.h" #include "bzrannotatejob.h" #include "copyjob.h" #include "diffjob.h" using namespace KDevelop; BazaarPlugin::BazaarPlugin(QObject* parent, const QVariantList& args) : IPlugin(QStringLiteral("kdevbazaar"), parent), m_vcsPluginHelper(new KDevelop::VcsPluginHelper(this, this)), m_hasError(false) { Q_UNUSED(args); // What is this? if (QStandardPaths::findExecutable(QStringLiteral("bzr")).isEmpty()) { m_hasError = true; m_errorDescription = i18n("Bazaar is not installed (bzr executable not" " found)"); return; } KDEV_USE_EXTENSION_INTERFACE(KDevelop::IBasicVersionControl) KDEV_USE_EXTENSION_INTERFACE(KDevelop::IDistributedVersionControl) setObjectName(QStringLiteral("Bazaar")); } BazaarPlugin::~BazaarPlugin() { } QString BazaarPlugin::name() const { - return QString::fromUtf8("Bazaar"); + return QStringLiteral("Bazaar"); } VcsJob* BazaarPlugin::add(const QList& localLocations, IBasicVersionControl::RecursionMode recursion) { DVcsJob* job = new DVcsJob(BazaarUtils::workingCopy(localLocations[0]), this); job->setType(VcsJob::Add); *job << "bzr" << "add"; if(recursion == NonRecursive) *job << "--no-recurse"; *job << localLocations; return job; } VcsJob* BazaarPlugin::annotate(const QUrl& localLocation, const VcsRevision& rev) { VcsJob* job = new BzrAnnotateJob(BazaarUtils::workingCopy(localLocation), BazaarUtils::getRevisionSpec(rev), localLocation, this, KDevelop::OutputJob::Silent); return job; } VcsJob* BazaarPlugin::commit(const QString& message, const QList& localLocations, IBasicVersionControl::RecursionMode recursion) { QDir dir = BazaarUtils::workingCopy(localLocations[0]); DVcsJob* job = new DVcsJob(dir, this); job->setType(VcsJob::Commit); *job << "bzr" << "commit" << BazaarUtils::handleRecursion(localLocations, recursion) << "-m" << message; return job; } VcsJob* BazaarPlugin::copy(const QUrl& localLocationSrc, const QUrl& localLocationDstn) { return new CopyJob(localLocationSrc, localLocationDstn, this); } VcsImportMetadataWidget* BazaarPlugin::createImportMetadataWidget(QWidget* parent) { return new DvcsImportMetadataWidget(parent); } VcsJob* BazaarPlugin::createWorkingCopy(const VcsLocation& sourceRepository, const QUrl& destinationDirectory, IBasicVersionControl::RecursionMode recursion) { Q_UNUSED(recursion); // What is the purpose of recursion parameter? DVcsJob* job = new DVcsJob(BazaarUtils::toQDir(sourceRepository.localUrl()), this); job->setType(VcsJob::Import); *job << "bzr" << "branch" << sourceRepository.localUrl().url() << destinationDirectory; return job; } VcsJob* BazaarPlugin::diff(const QUrl& fileOrDirectory, const VcsRevision& srcRevision, const VcsRevision& dstRevision, VcsDiff::Type, IBasicVersionControl::RecursionMode recursion) { Q_UNUSED(recursion); VcsJob* job = new DiffJob(BazaarUtils::workingCopy(fileOrDirectory), BazaarUtils::getRevisionSpecRange(srcRevision, dstRevision), fileOrDirectory, this); return job; } VcsJob* BazaarPlugin::init(const QUrl& localRepositoryRoot) { DVcsJob* job = new DVcsJob(BazaarUtils::toQDir(localRepositoryRoot), this); job->setType(VcsJob::Import); *job << "bzr" << "init"; return job; } bool BazaarPlugin::isVersionControlled(const QUrl& localLocation) { QDir workCopy = BazaarUtils::workingCopy(localLocation); DVcsJob* job = new DVcsJob(workCopy, this, OutputJob::Silent); job->setType(VcsJob::Unknown); job->setIgnoreError(true); *job << "bzr" << "ls" << "--from-root" << "-R" << "-V"; job->exec(); if (job->status() == VcsJob::JobSucceeded) { QList filesAndDirectoriesList; - for (QString fod : job->output().split('\n')) { + foreach (const QString& fod, job->output().split('\n')) { filesAndDirectoriesList.append(QFileInfo(workCopy.absolutePath() + QDir::separator() + fod)); } QFileInfo fi(localLocation.toLocalFile()); if (fi.isDir() || fi.isFile()) { QFileInfo file(localLocation.toLocalFile()); return filesAndDirectoriesList.contains(file); } } return false; } VcsJob* BazaarPlugin::log(const QUrl& localLocation, const VcsRevision& rev, long unsigned int limit) { DVcsJob* job = new DVcsJob(BazaarUtils::workingCopy(localLocation), this); job->setType(VcsJob::Log); *job << "bzr" << "log" << "--long" << "-v" << localLocation << BazaarUtils::getRevisionSpecRange(rev) << "-l" << QString::number(limit); connect(job, &DVcsJob::readyForParsing, this, &BazaarPlugin::parseBzrLog); return job; } VcsJob* BazaarPlugin::log(const QUrl& localLocation, const VcsRevision& rev, const VcsRevision& limit) { DVcsJob* job = new DVcsJob(BazaarUtils::workingCopy(localLocation), this); job->setType(VcsJob::Log); *job << "bzr" << "log" << "--long" << "-v" << localLocation << BazaarUtils::getRevisionSpecRange(limit, rev); connect(job, &DVcsJob::readyForParsing, this, &BazaarPlugin::parseBzrLog); return job; } void BazaarPlugin::parseBzrLog(DVcsJob* job) { QVariantList result; - for (QString part : job->output().split(QStringLiteral("------------------------------------------------------------"), QString::SkipEmptyParts)) { + auto parts = job->output().split(QStringLiteral("------------------------------------------------------------"), QString::SkipEmptyParts); + foreach (const QString& part, parts) { auto event = BazaarUtils::parseBzrLogPart(part); if (event.revision().revisionType() != VcsRevision::Invalid) result.append(QVariant::fromValue(event)); } job->setResults(result); } VcsJob* BazaarPlugin::move(const QUrl& localLocationSrc, const QUrl& localLocationDst) { DVcsJob* job = new DVcsJob(BazaarUtils::workingCopy(localLocationSrc), this); job->setType(VcsJob::JobType::Move); *job << "bzr" << "move" << localLocationSrc << localLocationDst; return job; } VcsJob* BazaarPlugin::pull(const VcsLocation& localOrRepoLocationSrc, const QUrl& localRepositoryLocation) { // API describes hg pull which is git fetch equivalent // bzr has pull, but it succeeds only if fast-forward is possible // in other cases bzr merge should be used instead (bzr pull would fail) // Information about repository must be provided at least once. DVcsJob* job = new DVcsJob(BazaarUtils::workingCopy(localRepositoryLocation), this); job->setType(VcsJob::JobType::Pull); *job << "bzr" << "pull"; if (!localOrRepoLocationSrc.localUrl().isEmpty()) { *job << localOrRepoLocationSrc.localUrl(); } // localUrl always makes sense. Even on remote repositores which are handled // transparently. return job; } VcsJob* BazaarPlugin::push(const QUrl& localRepositoryLocation, const VcsLocation& localOrRepoLocationDst) { DVcsJob* job = new DVcsJob(BazaarUtils::workingCopy(localRepositoryLocation), this); job->setType(VcsJob::JobType::Push); *job << "bzr" << "push" << localOrRepoLocationDst.localUrl(); // localUrl always makes sense. Even on remote repositores which are handled // transparently. return job; } VcsJob* BazaarPlugin::remove(const QList& localLocations) { DVcsJob* job = new DVcsJob(BazaarUtils::workingCopy(localLocations[0]), this); job->setType(VcsJob::JobType::Remove); *job << "bzr" << "remove" << localLocations; return job; } VcsJob* BazaarPlugin::repositoryLocation(const QUrl& localLocation) { DVcsJob* job = new DVcsJob(BazaarUtils::workingCopy(localLocation), this); job->setType(VcsJob::JobType::Unknown); *job << "bzr" << "root" << localLocation; // It is only to make sure connect(job, &DVcsJob::readyForParsing, this, &BazaarPlugin::parseBzrRoot); return job; } void BazaarPlugin::parseBzrRoot(DVcsJob* job) { - QString filename = job->dvcsCommand()[2]; + QString filename = job->dvcsCommand().at(2); QString rootDirectory = job->output(); QString localFilename = QFileInfo(QUrl::fromLocalFile(filename).toLocalFile()).absoluteFilePath(); QString localRootDirectory = QFileInfo(rootDirectory).absolutePath(); QString result = localFilename.mid(localFilename.indexOf(rootDirectory) + rootDirectory.length()); job->setResults(QVariant::fromValue(result)); } VcsJob* BazaarPlugin::resolve(const QList& localLocations, IBasicVersionControl::RecursionMode recursion) { return add(localLocations, recursion); // How to provide "a conflict solving dialog to the user"? // In any case this plugin is unable to make any conflict. } VcsJob* BazaarPlugin::revert(const QList& localLocations, IBasicVersionControl::RecursionMode recursion) { DVcsJob* job = new DVcsJob(BazaarUtils::workingCopy(localLocations[0]), this); job->setType(VcsJob::JobType::Revert); *job << "bzr" << "revert" << BazaarUtils::handleRecursion(localLocations, recursion); return job; } VcsJob* BazaarPlugin::status(const QList& localLocations, IBasicVersionControl::RecursionMode recursion) { Q_UNUSED(recursion); DVcsJob* job = new DVcsJob(BazaarUtils::workingCopy(localLocations[0]), this); job->setType(VcsJob::Status); *job << "bzr" << "status" << "--short" << "--no-pending" << "--no-classify" << localLocations; connect(job, &DVcsJob::readyForParsing, this, &BazaarPlugin::parseBzrStatus); return job; } void BazaarPlugin::parseBzrStatus(DVcsJob* job) { QVariantList result; QSet filesWithStatus; QDir workingCopy = job->directory(); - for (QString line : job->output().split('\n')) { + foreach (const QString& line, job->output().split('\n')) { auto status = BazaarUtils::parseVcsStatusInfoLine(line); result.append(QVariant::fromValue(status)); filesWithStatus.insert(BazaarUtils::concatenatePath(workingCopy, status.url())); } QStringList command = job->dvcsCommand(); for (auto it = command.constBegin() + command.indexOf(QStringLiteral("--no-classify")) + 1, itEnd = command.constEnd(); it != itEnd; ++it) { QString path = QFileInfo(*it).absoluteFilePath(); if (!filesWithStatus.contains(path)) { filesWithStatus.insert(path); KDevelop::VcsStatusInfo status; status.setState(VcsStatusInfo::ItemUpToDate); status.setUrl(QUrl::fromLocalFile(*it)); result.append(QVariant::fromValue(status)); } } job->setResults(result); } VcsJob* BazaarPlugin::update(const QList& localLocations, const VcsRevision& rev, IBasicVersionControl::RecursionMode recursion) { // bzr update is stronger than API (it's effectively merge) // the best approximation is bzr pull DVcsJob* job = new DVcsJob(BazaarUtils::workingCopy(localLocations[0]), this); Q_UNUSED(recursion); // recursion and file locations are ignored - we can update only whole // working copy job->setType(VcsJob::JobType::Update); *job << "bzr" << "pull" << BazaarUtils::getRevisionSpec(rev); return job; } VcsLocationWidget* BazaarPlugin::vcsLocation(QWidget* parent) const { return new KDevelop::StandardVcsLocationWidget(parent); } ContextMenuExtension BazaarPlugin::contextMenuExtension(Context* context) { m_vcsPluginHelper->setupFromContext(context); QList const& ctxUrlList = m_vcsPluginHelper->contextUrlList(); bool isWorkingDirectory = false; for (const QUrl & url : ctxUrlList) { if (BazaarUtils::isValidDirectory(url)) { isWorkingDirectory = true; break; } } if (!isWorkingDirectory) { // Not part of a repository return ContextMenuExtension(); } QMenu* menu = m_vcsPluginHelper->commonActions(); ContextMenuExtension menuExt; menuExt.addAction(ContextMenuExtension::VcsGroup, menu->menuAction()); return menuExt; } bool BazaarPlugin::hasError() const { return m_hasError; } QString BazaarPlugin::errorDescription() const { return m_errorDescription; } diff --git a/plugins/bazaar/bazaarutils.cpp b/plugins/bazaar/bazaarutils.cpp index edf13aa8a..527bd2cf4 100644 --- a/plugins/bazaar/bazaarutils.cpp +++ b/plugins/bazaar/bazaarutils.cpp @@ -1,244 +1,244 @@ /*************************************************************************** * Copyright 2013-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 "bazaarutils.h" #include #include #include #include #include QDir BazaarUtils::toQDir(const QUrl& url) { return QDir(url.toLocalFile()); } QDir BazaarUtils::workingCopy(const QUrl& path) { QDir dir = BazaarUtils::toQDir(path); while (!dir.exists(QStringLiteral(".bzr")) && dir.cdUp()); return dir; } QString BazaarUtils::getRevisionSpec(const KDevelop::VcsRevision& revision) { if (revision.revisionType() == KDevelop::VcsRevision::Special) { if (revision.specialType() == KDevelop::VcsRevision::Head) - return "-rlast:1"; + return QStringLiteral("-rlast:1"); else if (revision.specialType() == KDevelop::VcsRevision::Base) return QString(); // Workaround strange KDevelop behaviour else if (revision.specialType() == KDevelop::VcsRevision::Working) return QString(); else if (revision.specialType() == KDevelop::VcsRevision::Start) - return "-r1"; + return QStringLiteral("-r1"); else return QString(); // Don't know how to handle this situation } else if (revision.revisionType() == KDevelop::VcsRevision::GlobalNumber) return QStringLiteral("-r") + QString::number(revision.revisionValue().toLongLong()); else return QString(); // Don't know how to handle this situation } QString BazaarUtils::getRevisionSpecRange(const KDevelop::VcsRevision& end) { if (end.revisionType() == KDevelop::VcsRevision::Special) { if (end.specialType() == KDevelop::VcsRevision::Head) { - return "-r..last:1"; + return QStringLiteral("-r..last:1"); } else if (end.specialType() == KDevelop::VcsRevision::Base) { - return "-r..last:1"; // Workaround strange KDevelop behaviour + return QStringLiteral("-r..last:1"); // Workaround strange KDevelop behaviour } else if (end.specialType() == KDevelop::VcsRevision::Working) { return QString(); } else if (end.specialType() == KDevelop::VcsRevision::Start) { - return "-..r1"; + return QStringLiteral("-..r1"); } else { return QString(); // Don't know how to handle this situation } } else if (end.revisionType() == KDevelop::VcsRevision::GlobalNumber) { return QStringLiteral("-r") + QString::number(end.revisionValue().toLongLong()); } return QString(); // Don't know how to handle this situation } QString BazaarUtils::getRevisionSpecRange(const KDevelop::VcsRevision& begin, const KDevelop::VcsRevision& end) { if (begin.revisionType() == KDevelop::VcsRevision::Special) { if (begin.specialType() == KDevelop::VcsRevision::Previous) { if (end.revisionType() == KDevelop::VcsRevision::Special) { if (end.specialType() == KDevelop::VcsRevision::Base || end.specialType() == KDevelop::VcsRevision::Head) - return "-rlast:2..last:1"; + return QStringLiteral("-rlast:2..last:1"); else if (end.specialType() == KDevelop::VcsRevision::Working) return QString(); else if (end.specialType() == KDevelop::VcsRevision::Start) - return "-r0..1"; // That's wrong revision range + return QStringLiteral("-r0..1"); // That's wrong revision range } else if (end.revisionType() == KDevelop::VcsRevision::GlobalNumber) return QStringLiteral("-r") + QString::number(end.revisionValue().toLongLong() - 1) + ".." + QString::number(end.revisionValue().toLongLong()); else return QString(); // Don't know how to handle this situation } else if (begin.specialType() == KDevelop::VcsRevision::Base || begin.specialType() == KDevelop::VcsRevision::Head) { // Only one possibility: comparing working copy to last commit return QString(); } } else if (begin.revisionType() == KDevelop::VcsRevision::GlobalNumber) { if (end.revisionType() == KDevelop::VcsRevision::Special) { // Assuming working copy return QStringLiteral("-r") + QString::number(begin.revisionValue().toLongLong()); } else { return QStringLiteral("-r") + QString::number(begin.revisionValue().toLongLong()) + ".." + QString::number(end.revisionValue().toLongLong()); } } return QString(); // Don't know how to handle this situation } bool BazaarUtils::isValidDirectory(const QUrl& dirPath) { QDir dir = BazaarUtils::workingCopy(dirPath); return dir.cd(QStringLiteral(".bzr")) && dir.exists(QStringLiteral("branch")); } KDevelop::VcsStatusInfo BazaarUtils::parseVcsStatusInfoLine(const QString& line) { QStringList tokens = line.split(' ', QString::SkipEmptyParts); KDevelop::VcsStatusInfo result; if (tokens.size() < 2) // Don't know how to handle this situation (it is an error) return result; result.setUrl(QUrl::fromLocalFile(tokens.back())); - if (tokens[0] == "M") { + if (tokens[0] == QLatin1String("M")) { result.setState(KDevelop::VcsStatusInfo::ItemModified); - } else if (tokens[0] == "C") { + } else if (tokens[0] == QLatin1String("C")) { result.setState(KDevelop::VcsStatusInfo::ItemHasConflicts); - } else if (tokens[0] == "+N") { + } else if (tokens[0] == QLatin1String("+N")) { result.setState(KDevelop::VcsStatusInfo::ItemAdded); - } else if (tokens[0] == "?") { + } else if (tokens[0] == QLatin1String("?")) { result.setState(KDevelop::VcsStatusInfo::ItemUnknown); - } else if (tokens[0] == "D") { + } else if (tokens[0] == QLatin1String("D")) { result.setState(KDevelop::VcsStatusInfo::ItemDeleted); } else { result.setState(KDevelop::VcsStatusInfo::ItemUserState); qWarning() << "Unsupported status: " << tokens[0]; } return result; } QString BazaarUtils::concatenatePath(const QDir& workingCopy, const QUrl& pathInWorkingCopy) { return QFileInfo(workingCopy.absolutePath() + QDir::separator() + pathInWorkingCopy.toLocalFile()).absoluteFilePath(); } KDevelop::VcsEvent BazaarUtils::parseBzrLogPart(const QString& output) { const QStringList outputLines = output.split('\n'); KDevelop::VcsEvent commitInfo; bool atMessage = false; QString message; bool afterMessage = false; QHash fileToActionsMapping; KDevelop::VcsItemEvent::Action currentAction; for (const QString &line : outputLines) { if (!atMessage) { if (line.startsWith(QStringLiteral("revno"))) { QString revno = line.mid(QStringLiteral("revno: ").length()); revno = revno.left(revno.indexOf(' ')); KDevelop::VcsRevision revision; revision.setRevisionValue(revno.toLongLong(), KDevelop::VcsRevision::GlobalNumber); commitInfo.setRevision(revision); } else if (line.startsWith(QStringLiteral("committer: "))) { QString commiter = line.mid(QStringLiteral("committer: ").length()); commitInfo.setAuthor(commiter); // Author goes after commiter, but only if is different } else if (line.startsWith(QStringLiteral("author"))) { QString author = line.mid(QStringLiteral("author: ").length()); commitInfo.setAuthor(author); // It may override commiter (In fact commiter is not supported by VcsEvent) } else if (line.startsWith(QStringLiteral("timestamp"))) { - const QString formatString = "yyyy-MM-dd hh:mm:ss"; + const QString formatString = QStringLiteral("yyyy-MM-dd hh:mm:ss"); QString timestamp = line.mid(QStringLiteral("timestamp: ddd ").length(), formatString.length()); commitInfo.setDate(QDateTime::fromString(timestamp, formatString)); } else if (line.startsWith(QStringLiteral("message"))) { atMessage = true; } } else if (atMessage && !afterMessage) { if (!line.isEmpty() && line[0].isSpace()) { message += line.trimmed() + "\n"; } else if (!line.isEmpty()) { afterMessage = true; // leave atMessage = true currentAction = BazaarUtils::parseActionDescription(line); } // if line is empty - ignore and get next } else if (afterMessage) { if (!line.isEmpty() && !line[0].isSpace()) { currentAction = BazaarUtils::parseActionDescription(line); } else if (!line.isEmpty()) { fileToActionsMapping[line.trimmed()] |= currentAction; } // if line is empty - ignore and get next } } if (atMessage) commitInfo.setMessage(message.trimmed()); for (auto i = fileToActionsMapping.begin(); i != fileToActionsMapping.end(); ++i) { KDevelop::VcsItemEvent itemEvent; itemEvent.setRepositoryLocation(i.key()); itemEvent.setActions(i.value()); commitInfo.addItem(itemEvent); } return commitInfo; } KDevelop::VcsItemEvent::Action BazaarUtils::parseActionDescription(const QString& action) { - if (action == "added:") { + if (action == QLatin1String("added:")) { return KDevelop::VcsItemEvent::Added; - } else if (action == "modified:") { + } else if (action == QLatin1String("modified:")) { return KDevelop::VcsItemEvent::Modified; - } else if (action == "removed:") { + } else if (action == QLatin1String("removed:")) { return KDevelop::VcsItemEvent::Deleted; - } else if (action == "kind changed:") { + } else if (action == QLatin1String("kind changed:")) { return KDevelop::VcsItemEvent::Replaced; // Best approximation } else if (action.startsWith(QStringLiteral("renamed"))) { return KDevelop::VcsItemEvent::Modified; // Best approximation } else { qCritical("Unsupported action: %s", action.toLocal8Bit().constData()); return KDevelop::VcsItemEvent::Action(); } } QList BazaarUtils::handleRecursion(const QList& listOfUrls, KDevelop::IBasicVersionControl::RecursionMode recursion) { if (recursion == KDevelop::IBasicVersionControl::Recursive) { return listOfUrls; // Nothing to do } else { QList result; - for (const auto url : listOfUrls) { + foreach (const auto& url, listOfUrls) { if (url.isLocalFile() && QFileInfo(url.toLocalFile()).isFile()) { result.push_back(url); } } return result; } } diff --git a/plugins/bazaar/bzrannotatejob.cpp b/plugins/bazaar/bzrannotatejob.cpp index b66856383..bbbaaa0a2 100644 --- a/plugins/bazaar/bzrannotatejob.cpp +++ b/plugins/bazaar/bzrannotatejob.cpp @@ -1,197 +1,197 @@ /*************************************************************************** * Copyright 2013-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 "bzrannotatejob.h" #include #include #include #include #include #include #include #include using namespace KDevelop; BzrAnnotateJob::BzrAnnotateJob(const QDir& workingDir, const QString& revisionSpec, const QUrl& localLocation, KDevelop::IPlugin* parent, KDevelop::OutputJob::OutputJobVerbosity verbosity) : VcsJob(parent, verbosity), m_workingDir(workingDir), m_revisionSpec(revisionSpec), m_localLocation(localLocation), m_vcsPlugin(parent), m_status(KDevelop::VcsJob::JobNotStarted) { setType(JobType::Annotate); setCapabilities(Killable); } bool BzrAnnotateJob::doKill() { m_status = KDevelop::VcsJob::JobCanceled; if (m_job) return m_job->kill(KJob::Quietly); else return true; } void BzrAnnotateJob::start() { if (m_status != KDevelop::VcsJob::JobNotStarted) return; DVcsJob* job = new KDevelop::DVcsJob(m_workingDir, m_vcsPlugin, KDevelop::OutputJob::Silent); *job << "bzr" << "annotate" << "--all" << m_revisionSpec << m_localLocation; connect(job, &DVcsJob::readyForParsing, this, &BzrAnnotateJob::parseBzrAnnotateOutput); m_status = VcsJob::JobRunning; m_job = job; job->start(); } void BzrAnnotateJob::parseBzrAnnotateOutput(KDevelop::DVcsJob* job) { m_outputLines = job->output().split('\n'); m_currentLine = 0; if (m_status == KDevelop::VcsJob::JobRunning) QTimer::singleShot(0, this, SLOT(parseNextLine())); } void BzrAnnotateJob::parseNextLine() { for(;;) { Q_ASSERT(m_currentLine<=m_outputLines.size()); if (m_currentLine == m_outputLines.size()) { m_status = KDevelop::VcsJob::JobSucceeded; emitResult(); emit resultsReady(this); break; } QString currentLine = m_outputLines[m_currentLine]; if (currentLine.isEmpty()) { ++m_currentLine; continue; } bool revOk; - auto revision = currentLine.left(currentLine.indexOf(' ')).toULong(&revOk); + auto revision = currentLine.leftRef(currentLine.indexOf(' ')).toULong(&revOk); if (!revOk) { // Future compatibility - not a revision yet ++m_currentLine; continue; } auto i = m_commits.find(revision); if (i != m_commits.end()) { KDevelop::VcsAnnotationLine line; line.setAuthor(i.value().author()); line.setCommitMessage(i.value().message()); line.setDate(i.value().date()); line.setLineNumber(m_currentLine); line.setRevision(i.value().revision()); m_results.append(QVariant::fromValue(line)); ++m_currentLine; continue; } else { prepareCommitInfo(revision); break; //Will reenter this function when commit info will be ready } } } void BzrAnnotateJob::prepareCommitInfo(std::size_t revision) { if (m_status != KDevelop::VcsJob::JobRunning) return; KDevelop::DVcsJob* job = new KDevelop::DVcsJob(m_workingDir, m_vcsPlugin, KDevelop::OutputJob::Silent); job->setType(KDevelop::VcsJob::Log); *job << "bzr" << "log" << "--long" << "-r" << QString::number(revision); connect(job, &DVcsJob::readyForParsing, this, &BzrAnnotateJob::parseBzrLog); m_job = job; job->start(); } /* * This is slightly different from BazaarUtils::parseBzrLogPart(...). * This function parses only commit general info. It does not parse signle * actions. In fact output parsed by this function is slightly different * from output parsed by BazaarUtils. As a result parsing this output using * BazaarUtils would yield different results. * NOTE: This is all about parsing 'message'. */ void BzrAnnotateJob::parseBzrLog(KDevelop::DVcsJob* job) { QStringList outputLines = job->output().split('\n'); KDevelop::VcsEvent commitInfo; int revision=-1; bool atMessage = false; QString message; - for (const QString &line : outputLines) { + foreach (const QString &line, outputLines) { if (!atMessage) { if (line.startsWith(QStringLiteral("revno"))) { QString revno = line.mid(QStringLiteral("revno: ").length()); // In future there is possibility that "revno: " will change to // "revno??". If that's all, then we recover matching only // "revno" prefix and assuming placeholder of length 2 (": " or // "??"). // The same below with exception of "commiter" which possibly // can have also some suffix which changes meaning like // "commiter-some_property: "... revno = revno.left(revno.indexOf(' ')); revision = revno.toInt(); KDevelop::VcsRevision revision; revision.setRevisionValue(revno.toLongLong(), KDevelop::VcsRevision::GlobalNumber); commitInfo.setRevision(revision); } else if (line.startsWith(QStringLiteral("committer: "))) { QString commiter = line.mid(QStringLiteral("committer: ").length()); commitInfo.setAuthor(commiter); // Author goes after commiter, but only if is different } else if (line.startsWith(QStringLiteral("author"))) { QString author = line.mid(QStringLiteral("author: ").length()); commitInfo.setAuthor(author); // It may override commiter (In fact commiter is not supported by VcsEvent) } else if (line.startsWith(QStringLiteral("timestamp"))) { - const QString formatString = "yyyy-MM-dd hh:mm:ss"; + const QString formatString = QStringLiteral("yyyy-MM-dd hh:mm:ss"); QString timestamp = line.mid(QStringLiteral("timestamp: ddd ").length(), formatString.length()); commitInfo.setDate(QDateTime::fromString(timestamp, formatString)); } else if (line.startsWith(QStringLiteral("message"))) { atMessage = true; } } else { message += line.trimmed() + "\n"; } } if (atMessage) commitInfo.setMessage(message.trimmed()); Q_ASSERT(revision!=-1); m_commits[revision] = commitInfo; // Invoke from event loop to protect against stack overflow (it could happen // on very big files with very big history of changes if tail-recursion // optimization had failed here). QTimer::singleShot(0, this, SLOT(parseNextLine())); } QVariant BzrAnnotateJob::fetchResults() { return m_results; } KDevelop::VcsJob::JobStatus BzrAnnotateJob::status() const { return m_status; } KDevelop::IPlugin* BzrAnnotateJob::vcsPlugin() const { return m_vcsPlugin; } diff --git a/plugins/classbrowser/classbrowserplugin.cpp b/plugins/classbrowser/classbrowserplugin.cpp index 67d07b37b..eb1beb31d 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) {} QWidget* create(QWidget *parent = 0) override { return new ClassWidget(parent, m_plugin); } Qt::DockWidgetArea defaultPosition() override { return Qt::LeftDockWidgetArea; } QString id() const override { - return "org.kdevelop.ClassBrowserView"; + return QStringLiteral("org.kdevelop.ClassBrowserView"); } private: ClassBrowserPlugin *m_plugin; }; ClassBrowserPlugin::ClassBrowserPlugin(QObject *parent, const QVariantList&) - : KDevelop::IPlugin("kdevclassbrowser", parent) + : KDevelop::IPlugin(QStringLiteral("kdevclassbrowser"), parent) , m_factory(new ClassBrowserFactory(this)) , m_activeClassTree(0) { core()->uiController()->addToolView(i18n("Classes"), m_factory); - setXMLFile( "kdevclassbrowser.rc" ); + 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 == 0 ) 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 == 0 ) 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 == 0 ) funcDefinition = FunctionDefinition::definition(decl); if ( funcDefinition ) decl = funcDefinition; } if (decl) { QUrl url = decl->url().toUrl(); KTextEditor::Range range = decl->rangeInCurrentRevision(); readLock.unlock(); ICore::self()->documentController()->openDocument(url, range.start()); } } #include "classbrowserplugin.moc" // kate: space-indent on; indent-width 2; tab-width 4; replace-tabs on; auto-insert-doxygen on diff --git a/plugins/classbrowser/classwidget.cpp b/plugins/classbrowser/classwidget.cpp index a471fa008..65434142d 100644 --- a/plugins/classbrowser/classwidget.cpp +++ b/plugins/classbrowser/classwidget.cpp @@ -1,94 +1,94 @@ /* * KDevelop Class viewer * * Copyright 2006 Adam Treat * Copyright (c) 2006-2007 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 "classwidget.h" #include #include #include #include #include #include #include "language/classmodel/classmodel.h" #include "classtree.h" #include "classbrowserplugin.h" using namespace KDevelop; ClassWidget::ClassWidget(QWidget* parent, ClassBrowserPlugin* plugin) : QWidget(parent) , m_plugin(plugin) , m_model(new ClassModel()) , m_tree(new ClassTree(this, plugin)) , m_searchLine(new QLineEdit(this)) { - setObjectName("Class Browser Tree"); + setObjectName(QStringLiteral("Class Browser Tree")); setWindowTitle(i18n("Classes")); - setWindowIcon(QIcon::fromTheme("code-class", windowIcon())); + setWindowIcon(QIcon::fromTheme(QStringLiteral("code-class"), windowIcon())); // Set tree in the plugin m_plugin->setActiveClassTree(m_tree); // Set model in the tree view m_tree->setModel(m_model); m_tree->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); m_tree->header()->setStretchLastSection(false); // We need notification in the model for the collapse/expansion of nodes. connect(m_tree, &ClassTree::collapsed, m_model, &ClassModel::collapsed); connect(m_tree, &ClassTree::expanded, m_model, &ClassModel::expanded); // Init search box m_searchLine->setClearButtonEnabled( true ); connect(m_searchLine, &QLineEdit::textChanged, m_model, &ClassModel::updateFilterString); QLabel *searchLabel = new QLabel( i18n("S&earch:"), this ); searchLabel->setBuddy( m_searchLine ); QHBoxLayout* layout = new QHBoxLayout(); layout->setSpacing( 5 ); layout->setMargin( 0 ); layout->addWidget(searchLabel); layout->addWidget(m_searchLine); setFocusProxy( m_searchLine ); QVBoxLayout* vbox = new QVBoxLayout(this); vbox->setMargin(0); vbox->addLayout(layout); vbox->addWidget(m_tree); setLayout( vbox ); setWhatsThis( i18n( "Class Browser" ) ); } ClassWidget::~ClassWidget() { delete m_model; } // kate: space-indent on; indent-width 2; tab-width: 4; replace-tabs on; auto-insert-doxygen on diff --git a/plugins/codeutils/codeutilsplugin.cpp b/plugins/codeutils/codeutilsplugin.cpp index c1e793cf5..ad9c0ec35 100644 --- a/plugins/codeutils/codeutilsplugin.cpp +++ b/plugins/codeutils/codeutilsplugin.cpp @@ -1,159 +1,159 @@ /* * This file is part of KDevelop * Copyright 2010 Milian Wolff * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "codeutilsplugin.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include Q_LOGGING_CATEGORY(PLUGIN_CODEUTILS, "kdevplatform.plugins.codeutils") using namespace KDevelop; using namespace KTextEditor; K_PLUGIN_FACTORY_WITH_JSON(CodeUtilsPluginFactory, "kdevcodeutils.json", registerPlugin(); ) CodeUtilsPlugin::CodeUtilsPlugin ( QObject* parent, const QVariantList& ) - : IPlugin ( "kdevcodeutils", parent ) + : IPlugin ( QStringLiteral("kdevcodeutils"), parent ) { - setXMLFile( "kdevcodeutils.rc" ); + setXMLFile( QStringLiteral("kdevcodeutils.rc") ); - QAction* action = actionCollection()->addAction( "document_declaration" ); + QAction* action = actionCollection()->addAction( QStringLiteral("document_declaration") ); // i18n: action name; 'Document' is a verb action->setText( i18n( "Document Declaration" ) ); actionCollection()->setDefaultShortcut(action, i18n( "Alt+Shift+d" )); connect( action, SIGNAL(triggered(bool)), this, SLOT(documentDeclaration()) ); action->setToolTip( i18n( "Add Doxygen skeleton for declaration under cursor." ) ); // i18n: translate title same as the action name action->setWhatsThis( i18n( "Adds a basic Doxygen comment skeleton in front of " "the declaration under the cursor, e.g. with all the " "parameter of a function." ) ); - action->setIcon( QIcon::fromTheme( "documentinfo" ) ); + action->setIcon( QIcon::fromTheme( QStringLiteral("documentinfo") ) ); } void CodeUtilsPlugin::documentDeclaration() { View* view = ICore::self()->documentController()->activeTextDocumentView(); if ( !view ) { return; } DUChainReadLocker lock; TopDUContext* topCtx = DUChainUtils::standardContextForUrl(view->document()->url()); if ( !topCtx ) { return; } Declaration* dec = DUChainUtils::declarationInLine( KTextEditor::Cursor( view->cursorPosition() ), topCtx ); if ( !dec || dec->isForwardDeclaration() ) { return; } // finally - we found the declaration :) int line = dec->range().start.line; Cursor insertPos( line, 0 ); TemplateRenderer renderer; renderer.setEmptyLinesPolicy(TemplateRenderer::TrimEmptyLines); - renderer.addVariable("brief", i18n( "..." )); + renderer.addVariable(QStringLiteral("brief"), i18n( "..." )); /* QString indentation = textDoc->line( insertPos.line() ); if (!indentation.isEmpty()) { int lastSpace = 0; while (indentation.at(lastSpace).isSpace()) { ++lastSpace; } indentation.truncate(lastSpace); } */ if (dec->isFunctionDeclaration()) { FunctionDescription description = FunctionDescription(DeclarationPointer(dec)); - renderer.addVariable("function", QVariant::fromValue(description)); + renderer.addVariable(QStringLiteral("function"), QVariant::fromValue(description)); qCDebug(PLUGIN_CODEUTILS) << "Found function" << description.name << "with" << description.arguments.size() << "arguments"; } lock.unlock(); // TODO: Choose the template based on the language - QString templateName = "doxygen_cpp"; + QString templateName = QStringLiteral("doxygen_cpp"); auto languages = core()->languageController()->languagesForUrl(view->document()->url()); if (!languages.isEmpty()) { QString languageName = languages.first()->name(); - if (languageName == "Php") + if (languageName == QLatin1String("Php")) { - templateName = "phpdoc_php"; + templateName = QStringLiteral("phpdoc_php"); } - else if (languageName == "Python") + else if (languageName == QLatin1String("Python")) { - templateName = "rest_python"; + templateName = QStringLiteral("rest_python"); // Python docstrings appear inside functions and classes, not above them insertPos = Cursor(line+1, 0); } } QString fileName = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kdevcodeutils/templates/" + templateName + ".txt"); if (fileName.isEmpty()) { qWarning() << "No suitable template found" << fileName; return; } const QString comment = renderer.renderFile(QUrl::fromLocalFile(fileName)); view->insertTemplate(insertPos, comment); } CodeUtilsPlugin::~CodeUtilsPlugin() { } #include "codeutilsplugin.moc" diff --git a/plugins/contextbrowser/browsemanager.cpp b/plugins/contextbrowser/browsemanager.cpp index c23b428fc..bce69697c 100644 --- a/plugins/contextbrowser/browsemanager.cpp +++ b/plugins/contextbrowser/browsemanager.cpp @@ -1,334 +1,334 @@ /* * This file is part of KDevelop * * Copyright 2008 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "browsemanager.h" #include #include #include #include #include #include #include #include "contextbrowserview.h" #include #include #include #include #include #include #include #include #include #include "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() { if(m_browsingByKey && m_browingStartedInView) emit startDelayedBrowsing(m_browingStartedInView); } BrowseManager::BrowseManager(ContextBrowserPlugin* controller) : QObject(controller), m_plugin(controller), m_browsing(false), m_browsingByKey(0), m_watcher(this) { m_delayedBrowsingTimer = new QTimer(this); m_delayedBrowsingTimer->setSingleShot(true); connect(m_delayedBrowsingTimer, &QTimer::timeout, this, &BrowseManager::eventuallyStartDelayedBrowsing); foreach(KTextEditor::View* view, m_watcher.allViews()) viewAdded(view); } KTextEditor::View* viewFromWidget(QWidget* widget) { if(!widget) return 0; KTextEditor::View* view = qobject_cast(widget); if(view) return view; else return viewFromWidget(widget->parentWidget()); } bool BrowseManager::eventFilter(QObject * watched, QEvent * event) { QWidget* widget = qobject_cast(watched); Q_ASSERT(widget); KTextEditor::View* view = viewFromWidget(widget); if(!view) return false; QKeyEvent* keyEvent = dynamic_cast(event); const int browseKey = Qt::Key_Control; const int magicModifier = Qt::Key_Alt; //Eventually start key-browsing if(keyEvent && (keyEvent->key() == browseKey || keyEvent->key() == magicModifier) && !m_browsingByKey && keyEvent->type() == QEvent::KeyPress) { m_browsingByKey = keyEvent->key(); if(keyEvent->key() == magicModifier) { if(dynamic_cast(view) && dynamic_cast(view)->isCompletionActive()) { //Do nothing, completion is active. }else{ m_delayedBrowsingTimer->start(300); m_browingStartedInView = view; } } if(!m_browsing) m_plugin->setAllowBrowsing(true); } QFocusEvent* focusEvent = dynamic_cast(event); //Eventually stop key-browsing if((keyEvent && m_browsingByKey && keyEvent->key() == m_browsingByKey && keyEvent->type() == QEvent::KeyRelease) || (focusEvent && focusEvent->lostFocus())) { if(!m_browsing) m_plugin->setAllowBrowsing(false); m_browsingByKey = 0; emit stopDelayedBrowsing(); } QMouseEvent* mouseEvent = dynamic_cast(event); if(mouseEvent) { if (mouseEvent->type() == QEvent::MouseButtonPress && mouseEvent->button() == Qt::XButton1) { m_plugin->historyPrevious(); return true; } if (mouseEvent->type() == QEvent::MouseButtonPress && mouseEvent->button() == Qt::XButton2) { m_plugin->historyNext(); return true; } } if(!m_browsing && !m_browsingByKey) { resetChangedCursor(); return false; } if(mouseEvent) { KTextEditor::View* iface = dynamic_cast(view); if(!iface) { qCDebug(PLUGIN_CONTEXTBROWSER) << "Update kdelibs for the browsing-mode to work"; return false; } QPoint coordinatesInView = widget->mapTo(view, mouseEvent->pos()); KTextEditor::Cursor textCursor = iface->coordinatesToCursor(coordinatesInView); if(textCursor.isValid()) { ///@todo find out why this is needed, fix the code in kate if(textCursor.column() > 0) textCursor.setColumn(textCursor.column()-1); QUrl viewUrl = view->document()->url(); auto languages = ICore::self()->languageController()->languagesForUrl(viewUrl); QPair jumpTo; //Step 1: Look for a special language object(Macro, included header, etc.) - foreach (const auto& language, languages) { + foreach (const auto language, languages) { jumpTo = language->specialLanguageObjectJumpCursor(viewUrl, KTextEditor::Cursor(textCursor)); if(jumpTo.first.isValid() && jumpTo.second.isValid()) break; //Found a special object to jump to } //Step 2: Look for a declaration/use if(!jumpTo.first.isValid() || !jumpTo.second.isValid()) { Declaration* foundDeclaration = 0; KDevelop::DUChainReadLocker lock( DUChain::lock() ); foundDeclaration = DUChainUtils::declarationForDefinition( DUChainUtils::itemUnderCursor(view->document()->url(), KTextEditor::Cursor(textCursor)) ); if(foundDeclaration && (foundDeclaration->url().toUrl() == view->document()->url()) && foundDeclaration->range().contains( foundDeclaration->transformToLocalRevision(KTextEditor::Cursor(textCursor)))) { ///A declaration was clicked directly. Jumping to it is useless, so jump to the definition or something useful bool foundBetter = false; Declaration* definition = FunctionDefinition::definition(foundDeclaration); if(definition) { foundDeclaration = definition; foundBetter = true; } ForwardDeclaration* forward = dynamic_cast(foundDeclaration); if(forward) { TopDUContext* standardContext = DUChainUtils::standardContextForUrl(view->document()->url()); if(standardContext) { Declaration* resolved = forward->resolve(standardContext); if(resolved) { foundDeclaration = resolved; //This probably won't work foundBetter = true; } } } //This will do a search without visibility-restriction, and that search will prefer non forward declarations if(!foundBetter) { Declaration* betterDecl = foundDeclaration->id().getDeclaration(0); if(betterDecl) { foundDeclaration = betterDecl; foundBetter = true; } } } if( foundDeclaration ) { jumpTo.first = foundDeclaration->url().toUrl(); jumpTo.second = foundDeclaration->rangeInCurrentRevision().start(); } } if(jumpTo.first.isValid() && jumpTo.second.isValid()) { if(mouseEvent->button() == Qt::LeftButton) { if(mouseEvent->type() == QEvent::MouseButtonPress) { m_buttonPressPosition = textCursor; // view->setCursorPosition(textCursor); // return false; }else if(mouseEvent->type() == QEvent::MouseButtonRelease && textCursor == m_buttonPressPosition) { ICore::self()->documentController()->openDocument(jumpTo.first, jumpTo.second); // event->accept(); // return true; } }else if(mouseEvent->type() == QEvent::MouseMove) { //Make the cursor a "hand" setHandCursor(widget); return false; } } } resetChangedCursor(); } return false; } void BrowseManager::resetChangedCursor() { QMap, QCursor> cursors = m_oldCursors; m_oldCursors.clear(); for(QMap, QCursor>::iterator it = cursors.begin(); it != cursors.end(); ++it) if(it.key()) it.key()->setCursor(QCursor(Qt::IBeamCursor)); } void BrowseManager::setHandCursor(QWidget* widget) { if(m_oldCursors.contains(widget)) return; //Nothing to do m_oldCursors[widget] = widget->cursor(); widget->setCursor(QCursor(Qt::PointingHandCursor)); } void BrowseManager::applyEventFilter(QWidget* object, bool install) { if(install) object->installEventFilter(this); else object->removeEventFilter(this); foreach(QObject* child, object->children()) if(qobject_cast(child)) applyEventFilter(qobject_cast(child), install); } void BrowseManager::viewAdded(KTextEditor::View* view) { applyEventFilter(view, true); //We need to listen for cursorPositionChanged, to clear the shift-detector. The problem: Kate listens for the arrow-keys using shortcuts, //so those keys are not passed to the event-filter // 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 BrowseManager::Watcher::viewAdded(KTextEditor::View* view) { +void Watcher::viewAdded(KTextEditor::View* view) { m_manager->viewAdded(view); } void BrowseManager::setBrowsing(bool enabled) { if(m_browsingByKey) return; if(enabled == m_browsing) return; m_browsing = enabled; //This collects all the views if(enabled) { qCDebug(PLUGIN_CONTEXTBROWSER) << "Enabled browsing-mode"; }else{ qCDebug(PLUGIN_CONTEXTBROWSER) << "Disabled browsing-mode"; resetChangedCursor(); } } -BrowseManager::Watcher::Watcher(BrowseManager* manager) +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 cbe22bdaa..09b4d66ce 100644 --- a/plugins/contextbrowser/browsemanager.h +++ b/plugins/contextbrowser/browsemanager.h @@ -1,109 +1,113 @@ /* * 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 class QWidget; namespace KTextEditor { class View; class Document; } namespace KDevelop { class IDocument; } class EditorViewWatcher : public QObject { Q_OBJECT public: ///@param sameWindow If this is true, only views that are child of the same window as the given widget are registered explicit EditorViewWatcher(QObject* parent = 0); 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(); public slots: ///Enabled/disables the browsing mode void setBrowsing(bool); private slots: void eventuallyStartDelayedBrowsing(); private: - void viewAdded(KTextEditor::View* view); - class Watcher : public EditorViewWatcher { - public: - explicit Watcher(BrowseManager* manager); - void viewAdded(KTextEditor::View*) override; - private: - BrowseManager* m_manager; - }; - void resetChangedCursor(); void setHandCursor(QWidget* widget); - //Installs/uninstalls the event-filter - void applyEventFilter(QWidget* object, bool install); 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_browingStartedInView; KTextEditor::Cursor m_buttonPressPosition; }; #endif diff --git a/plugins/contextbrowser/contextbrowser.cpp b/plugins/contextbrowser/contextbrowser.cpp index 5bc9a6abf..89001e4ec 100644 --- a/plugins/contextbrowser/contextbrowser.cpp +++ b/plugins/contextbrowser/contextbrowser.cpp @@ -1,1364 +1,1364 @@ /* * 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 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 0; 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()))); return DeclarationPointer(decl); } } class ContextBrowserViewFactory: public KDevelop::IToolViewFactory { public: ContextBrowserViewFactory(ContextBrowserPlugin *plugin): m_plugin(plugin) {} QWidget* create(QWidget *parent = 0) override { ContextBrowserView* ret = new ContextBrowserView(m_plugin, parent); return ret; } Qt::DockWidgetArea defaultPosition() override { return Qt::BottomDockWidgetArea; } QString id() const override { - return "org.kdevelop.ContextBrowser"; + return QStringLiteral("org.kdevelop.ContextBrowser"); } private: ContextBrowserPlugin *m_plugin; }; KXMLGUIClient* ContextBrowserPlugin::createGUIForMainWindow( Sublime::MainWindow* window ) { KXMLGUIClient* ret = KDevelop::IPlugin::createGUIForMainWindow( window ); m_browseManager = new BrowseManager(this); 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("go-previous")); + 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("go-next")); + 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); m_browseButton = new QToolButton(); - m_browseButton->setIcon(QIcon::fromTheme("games-hint")); + m_browseButton->setIcon(QIcon::fromTheme(QStringLiteral("games-hint"))); m_browseButton->setToolTip(i18n("Enable/disable source browse mode")); m_browseButton->setWhatsThis(i18n("When this is enabled, you can browse the source-code by clicking in the editor.")); m_browseButton->setCheckable(true); m_browseButton->setFocusPolicy(Qt::NoFocus); connect(m_browseButton.data(), &QToolButton::clicked, m_browseManager, &BrowseManager::setBrowsing); - IQuickOpen* quickOpen = KDevelop::ICore::self()->pluginController()->extensionForPlugin("org.kdevelop.IQuickOpen"); + 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); 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_browseButton->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); m_toolbarWidgetLayout->addWidget(m_browseButton); 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 = "kdevcontextbrowser.rc" ; + xmlFile = QStringLiteral("kdevcontextbrowser.rc") ; - QAction* previousContext = actions.addAction("previous_context"); + QAction* previousContext = actions.addAction(QStringLiteral("previous_context")); previousContext->setText( i18n("&Previous Visited Context") ); - previousContext->setIcon( QIcon::fromTheme("go-previous-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("next_context"); + QAction* nextContext = actions.addAction(QStringLiteral("next_context")); nextContext->setText( i18n("&Next Visited Context") ); - nextContext->setIcon( QIcon::fromTheme("go-next-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("previous_use"); + QAction* previousUse = actions.addAction(QStringLiteral("previous_use")); previousUse->setText( i18n("&Previous Use") ); - previousUse->setIcon( QIcon::fromTheme("go-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("next_use"); + QAction* nextUse = actions.addAction(QStringLiteral("next_use")); nextUse->setText( i18n("&Next Use") ); - nextUse->setIcon( QIcon::fromTheme("go-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("outline_line", outline); + actions.addAction(QStringLiteral("outline_line"), outline); // Add to the actioncollection so one can set global shortcuts for the action - actions.addAction("find_uses", m_findUses); + 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("kdevcontextbrowser", parent) + : KDevelop::IPlugin(QStringLiteral("kdevcontextbrowser"), parent) , m_viewFactory(new ContextBrowserViewFactory(this)) , m_nextHistoryIndex(0) { KDEV_USE_EXTENSION_INTERFACE( IContextBrowser ) core()->uiController()->addToolView(i18n("Code Browser"), m_viewFactory); connect( core()->documentController(), &IDocumentController::textDocumentCreated, this, &ContextBrowserPlugin::textDocumentCreated ); connect( core()->languageController()->backgroundParser(), &BackgroundParser::parseJobFinished, this, &ContextBrowserPlugin::parseJobFinished); 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; delete m_browseButton; } 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::startDelayedBrowsing(KTextEditor::View* view) { if(!m_currentToolTip) { showToolTip(view, view->cursorPosition()); } } void ContextBrowserPlugin::hideToolTip() { if(m_currentToolTip) { m_currentToolTip->deleteLater(); m_currentToolTip = 0; m_currentNavigationWidget = 0; } } 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 QUrl viewUrl = view->document()->url(); auto languages = ICore::self()->languageController()->languagesForUrl(viewUrl); QWidget* navigationWidget = 0; { DUChainReadLocker lock(DUChain::lock()); - foreach (const auto& language, languages) { + foreach (const auto language, languages) { auto widget = language->specialLanguageObjectNavigationWidget(viewUrl, KTextEditor::Cursor(position)); navigationWidget = qobject_cast(widget); if(navigationWidget) break; } if(!navigationWidget) { Declaration* decl = DUChainUtils::declarationForDefinition( DUChainUtils::itemUnderCursor(viewUrl, KTextEditor::Cursor(position)) ); 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; m_currentToolTipDeclaration = IndexedDeclaration(decl); navigationWidget = decl->context()->createNavigationWidget(decl, DUChainUtils::standardContextForUrl(viewUrl)); } } } 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 = 0; m_currentNavigationWidget = 0; } KDevelop::NavigationToolTip* tooltip = new KDevelop::NavigationToolTip(view, view->mapToGlobal(view->cursorToCoordinate(position)) + QPoint(20, 40), navigationWidget); KTextEditor::Range itemRange; { DUChainReadLocker lock; itemRange = DUChainUtils::itemRangeUnderCursor(viewUrl, KTextEditor::Cursor(position)); } tooltip->setHandleRect(getItemBoundingRect(view, itemRange)); tooltip->resize( navigationWidget->sizeHint() + QSize(10, 10) ); 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 highlightedUseAttribute() { static Attribute::Ptr standardAttribute = Attribute::Ptr(); if( !standardAttribute ) { standardAttribute= Attribute::Ptr( new Attribute() ); standardAttribute->setBackgroundFillWhitespace(true); // mixing (255, 255, 0, 100) with white yields this: standardAttribute->setBackground(QColor(251, 250, 150)); // force a foreground color to overwrite default Kate highlighting, i.e. of Q_OBJECT or similar // foreground color could change, hence apply it everytime standardAttribute->setForeground(QColor(0, 0, 0, 255)); //Don't use alpha here, as kate uses the alpha only to blend with the document background color } return standardAttribute; } Attribute::Ptr highlightedSpecialObjectAttribute() { static Attribute::Ptr standardAttribute = Attribute::Ptr(); if( !standardAttribute ) { standardAttribute = Attribute::Ptr( new Attribute() ); standardAttribute->setBackgroundFillWhitespace(true); // mixing (90, 255, 0, 100) with white yields this: standardAttribute->setBackground(QColor(190, 255, 155)); // force a foreground color to overwrite default Kate highlighting, i.e. of Q_OBJECT or similar // foreground color could change, hence apply it everytime standardAttribute->setForeground(QColor(0, 0, 0, 255)); //Don't use alpha here, as kate uses the alpha only to blend with the document background color } return standardAttribute; } 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()); 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()); highlights.highlights.back()->setZDepth(highlightingZDepth); } } } if( FunctionDefinition* def = FunctionDefinition::definition(decl) ) { highlights.highlights << def->createRangeMoving(); highlights.highlights.back()->setAttribute(highlightedUseAttribute()); highlights.highlights.back()->setZDepth(highlightingZDepth); } } Declaration* ContextBrowserPlugin::findDeclaration(View* view, const KTextEditor::Cursor& position, bool mouseHighlight) { Q_UNUSED(mouseHighlight); Declaration* foundDeclaration = 0; 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) ); 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 0; } 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) : 0; if(specialRange.isValid()) { // Highlight a special language object if(allowHighlight) { highlights.highlights << PersistentMovingRange::Ptr(new PersistentMovingRange(specialRange, IndexedString(url))); highlights.highlights.back()->setAttribute(highlightedSpecialObjectAttribute()); 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::parseJobFinished(KDevelop::ParseJob* job) { for(QMap< View*, ViewHighlights >::iterator it = m_highlightedRanges.begin(); it != m_highlightedRanges.end(); ++it) { if(it.key()->document()->url() == job->document().toUrl()) { 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 = 0; 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(new ContextBrowserHintProvider(this)); } 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 = 0; //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); if (decl && decl->kind() == Declaration::Alias) { AliasDeclaration* alias = dynamic_cast(decl); Q_ASSERT(alias); DUChainReadLocker lock; decl = alias->aliasedDeclaration().declaration(); } if(decl) { Declaration* target = 0; 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 = ""; - actionText += " @ "; + 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::setAllowBrowsing(bool allow) { m_browseButton->setChecked(allow); } 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/contextbrowserview.cpp b/plugins/contextbrowser/contextbrowserview.cpp index 57c966f77..46e48b581 100644 --- a/plugins/contextbrowser/contextbrowserview.cpp +++ b/plugins/contextbrowser/contextbrowserview.cpp @@ -1,309 +1,309 @@ /* * 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 "contextbrowserview.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "contextbrowser.h" #include "debug.h" #include #include #include #include "browsemanager.h" #include #include #include #include #include #include using namespace KDevelop; QWidget* ContextBrowserView::createWidget(KDevelop::DUContext* context) { m_context = IndexedDUContext(context); if(m_context.data()) { return m_context.data()->createNavigationWidget(); } return 0; } KDevelop::IndexedDeclaration ContextBrowserView::declaration() const { return m_declaration; } QWidget* ContextBrowserView::createWidget(Declaration* decl, TopDUContext* topContext) { m_declaration = IndexedDeclaration(decl); return decl->context()->createNavigationWidget(decl, topContext); } void ContextBrowserView::resetWidget() { if (m_navigationWidget) { delete m_navigationWidget; m_navigationWidget = 0; } } void ContextBrowserView::declarationMenu() { DUChainReadLocker lock(DUChain::lock()); AbstractNavigationWidget* navigationWidget = dynamic_cast(m_navigationWidget.data()); if(navigationWidget) { AbstractDeclarationNavigationContext* navigationContext = dynamic_cast(navigationWidget->context().data()); if(navigationContext && navigationContext->declaration().data()) { KDevelop::DeclarationContext* c = new KDevelop::DeclarationContext(navigationContext->declaration().data()); lock.unlock(); QMenu menu; QList extensions = ICore::self()->pluginController()->queryPluginsForContextMenuExtensions( c ); ContextMenuExtension::populateMenu(&menu, extensions); menu.exec(QCursor::pos()); } } } void ContextBrowserView::updateLockIcon(bool checked) { - m_lockButton->setIcon(QIcon::fromTheme(checked ? "document-encrypt" : "document-decrypt")); + m_lockButton->setIcon(QIcon::fromTheme(checked ? QStringLiteral("document-encrypt") : QStringLiteral("document-decrypt"))); } ContextBrowserView::ContextBrowserView( ContextBrowserPlugin* plugin, QWidget* parent ) : QWidget(parent), m_plugin(plugin), m_navigationWidget(new QTextBrowser()), m_autoLocked(false) { - setWindowIcon( QIcon::fromTheme("code-context", windowIcon()) ); + setWindowIcon( QIcon::fromTheme(QStringLiteral("code-context"), windowIcon()) ); m_allowLockedUpdate = false; m_buttons = new QHBoxLayout; m_buttons->addStretch(); m_declarationMenuButton = new QToolButton(); - m_declarationMenuButton->setIcon(QIcon::fromTheme("code-class")); + m_declarationMenuButton->setIcon(QIcon::fromTheme(QStringLiteral("code-class"))); m_declarationMenuButton->setToolTip(i18n("Declaration menu")); connect(m_declarationMenuButton, &QToolButton::clicked, this, &ContextBrowserView::declarationMenu); m_buttons->addWidget(m_declarationMenuButton); m_lockButton = new QToolButton(); m_lockButton->setCheckable(true); m_lockButton->setChecked(false); m_lockButton->setToolTip(i18n("Lock current view")); updateLockIcon(m_lockButton->isChecked()); connect(m_lockButton, &QToolButton::toggled, this, &ContextBrowserView::updateLockIcon); m_buttons->addWidget(m_lockButton); m_layout = new QVBoxLayout; m_layout->setSpacing(0); m_layout->setMargin(0); m_layout->addLayout(m_buttons); m_layout->addWidget(m_navigationWidget); //m_layout->addStretch(); setLayout(m_layout); m_plugin->registerToolView(this); } ContextBrowserView::~ContextBrowserView() { m_plugin->unRegisterToolView(this); } void ContextBrowserView::focusInEvent(QFocusEvent* event) { //Indicate that we have focus qCDebug(PLUGIN_CONTEXTBROWSER) << "got focus"; // parentWidget()->setBackgroundRole(QPalette::ToolTipBase); /* m_layout->removeItem(m_buttons);*/ return QWidget::focusInEvent(event); } void ContextBrowserView::focusOutEvent(QFocusEvent* event) { qCDebug(PLUGIN_CONTEXTBROWSER) << "lost focus"; // parentWidget()->setBackgroundRole(QPalette::Background); /* m_layout->insertLayout(0, m_buttons); for(int a = 0; a < m_buttons->count(); ++a) { QWidgetItem* item = dynamic_cast(m_buttons->itemAt(a)); }*/ QWidget::focusOutEvent(event); } bool ContextBrowserView::event(QEvent* event) { QKeyEvent* keyEvent = dynamic_cast(event); if(hasFocus() && keyEvent) { AbstractNavigationWidget* navigationWidget = dynamic_cast(m_navigationWidget.data()); if(navigationWidget && event->type() == QEvent::KeyPress) { int key = keyEvent->key(); if(key == Qt::Key_Left) navigationWidget->previous(); if(key == Qt::Key_Right) navigationWidget->next(); if(key == Qt::Key_Up) navigationWidget->up(); if(key == Qt::Key_Down) navigationWidget->down(); if(key == Qt::Key_Return || key == Qt::Key_Enter) navigationWidget->accept(); if(key == Qt::Key_L) m_lockButton->toggle(); } } return QWidget::event(event); } void ContextBrowserView::showEvent(QShowEvent* event) { DUChainReadLocker lock(DUChain::lock()); TopDUContext* top = m_lastUsedTopContext.data(); if(top && m_navigationWidgetDeclaration.isValid() && m_navigationWidgetDeclaration.getDeclaration(top)) { if(top) { //Update the navigation-widget Declaration* decl = m_navigationWidgetDeclaration.getDeclaration(top); setDeclaration(decl, top, true); } } QWidget::showEvent(event); } bool ContextBrowserView::isLocked() const { bool isLocked; if (m_allowLockedUpdate) { isLocked = false; } else { isLocked = m_lockButton->isChecked(); } return isLocked; } void ContextBrowserView::updateMainWidget(QWidget* widget) { if (widget) { setUpdatesEnabled(false); qCDebug(PLUGIN_CONTEXTBROWSER) << ""; resetWidget(); m_navigationWidget = widget; m_layout->insertWidget(1, widget, 1); m_allowLockedUpdate = false; setUpdatesEnabled(true); if (widget->metaObject()->indexOfSignal(QMetaObject::normalizedSignature("contextChanged(bool,bool)")) != -1) { connect(widget, SIGNAL(contextChanged(bool,bool)), this, SLOT(navigationContextChanged(bool,bool))); } } } void ContextBrowserView::navigationContextChanged(bool wasInitial, bool isInitial) { if(wasInitial && !isInitial && !m_lockButton->isChecked()) { m_autoLocked = true; m_lockButton->setChecked(true); }else if(!wasInitial && isInitial && m_autoLocked) { m_autoLocked = false; m_lockButton->setChecked(false); }else if(isInitial) { m_autoLocked = false; } } void ContextBrowserView::setDeclaration(KDevelop::Declaration* decl, KDevelop::TopDUContext* topContext, bool force) { m_lastUsedTopContext = IndexedTopDUContext(topContext); if(isLocked() && (!m_navigationWidget.data() || !isVisible())) { // Automatically remove the locked state if the view is not visible or the widget was deleted, // because the locked state has side-effects on other navigation functionality. m_autoLocked = false; m_lockButton->setChecked(false); } if(m_navigationWidgetDeclaration == decl->id() && !force) return; m_navigationWidgetDeclaration = decl->id(); if (!isLocked() && (isVisible() || force)) { // NO-OP if toolview is hidden, for performance reasons QWidget* w = createWidget(decl, topContext); updateMainWidget(w); } } KDevelop::IndexedDeclaration ContextBrowserView::lockedDeclaration() const { if(m_lockButton->isChecked()) return declaration(); else return KDevelop::IndexedDeclaration(); } void ContextBrowserView::allowLockedUpdate() { m_allowLockedUpdate = true; } void ContextBrowserView::setNavigationWidget(QWidget* widget) { updateMainWidget(widget); } void ContextBrowserView::setContext(KDevelop::DUContext* context) { if(!context) return; m_lastUsedTopContext = IndexedTopDUContext(context->topContext()); if(context->owner()) { if(context->owner()->id() == m_navigationWidgetDeclaration) return; m_navigationWidgetDeclaration = context->owner()->id(); }else{ m_navigationWidgetDeclaration = DeclarationId(); } if (!isLocked() && isVisible()) { // NO-OP if toolview is hidden, for performance reasons QWidget* w = createWidget(context); updateMainWidget(w); } } void ContextBrowserView::setSpecialNavigationWidget(QWidget* widget) { if (!isLocked() && isVisible()) { Q_ASSERT(widget); updateMainWidget(widget); } else if(widget) { widget->deleteLater(); } } diff --git a/plugins/cvs/checkoutdialog.cpp b/plugins/cvs/checkoutdialog.cpp index 421f5ad78..0e01f8696 100644 --- a/plugins/cvs/checkoutdialog.cpp +++ b/plugins/cvs/checkoutdialog.cpp @@ -1,86 +1,86 @@ /*************************************************************************** * 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 "checkoutdialog.h" #include #include #include #include #include "cvsplugin.h" #include "cvsjob.h" #include "cvsproxy.h" #include "debug.h" CheckoutDialog::CheckoutDialog(CvsPlugin* plugin, QWidget *parent) : QDialog(parent), Ui::CheckoutDialogBase(), m_plugin(plugin) { Ui::CheckoutDialogBase::setupUi(this); localWorkingDir->setMode(KFile::Directory); } CheckoutDialog::~CheckoutDialog() { } void CheckoutDialog::accept() { CvsJob *job = m_plugin->proxy()->checkout( localWorkingDir->url(), serverPath->text(), module->currentText(), - "", + QString(), tag->text()); if (job) { connect(job, &CvsJob::result, this, &CheckoutDialog::jobFinished); KDevelop::ICore::self()->runController()->registerJob(job); } } void CheckoutDialog::jobFinished(KJob * job) { if (job->error()) { KMessageBox::error(this, i18n("Error on checkout"), i18n("Checkout Error")); return; } // The job finished, now let's check the output is everything was OK CvsJob* cvsjob = dynamic_cast(job); static QRegExp re_file("^.\\s(.*)"); bool error = false; QStringList lines = cvsjob->output().split('\n'); foreach(const QString &line, lines) { if (line.isEmpty()) { // ignore empty lines continue; } else if (re_file.exactMatch(line)) { // line that tell us that a file has been checkedout continue; } else { // any other line must mean that an error occurred qCDebug(PLUGIN_CVS) << line; error = true; } } if (error) { KMessageBox::error(this, i18n("Some errors occurred while checking out into %1", localWorkingDir->url().toLocalFile()), i18n("Checkout Error")); } else { QDialog::accept(); } } diff --git a/plugins/cvs/cvsannotatejob.cpp b/plugins/cvs/cvsannotatejob.cpp index 8ba726e52..b1a684dbc 100644 --- a/plugins/cvs/cvsannotatejob.cpp +++ b/plugins/cvs/cvsannotatejob.cpp @@ -1,90 +1,90 @@ /*************************************************************************** * Copyright 2008 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 "cvsannotatejob.h" #include "debug.h" #include #include #include #include #include CvsAnnotateJob::CvsAnnotateJob(KDevelop::IPlugin* parent, KDevelop::OutputJob::OutputJobVerbosity verbosity) : CvsJob(parent, verbosity) { } CvsAnnotateJob::~CvsAnnotateJob() { } QVariant CvsAnnotateJob::fetchResults() { // Convert job's output into KDevelop::VcsAnnotation KDevelop::VcsAnnotation annotateInfo; parseOutput(output(), getDirectory(), annotateInfo); QList lines; for(int i=0; i < annotateInfo.lineCount(); i++) { KDevelop::VcsAnnotationLine line = annotateInfo.line(i); lines.append( qVariantFromValue( line ) ); } return lines; } void CvsAnnotateJob::parseOutput(const QString& jobOutput, const QString& workingDirectory, KDevelop::VcsAnnotation& annotateInfo) { // each annotation line looks like this: // 1.1 (kdedev 10-Nov-07): #include static QRegExp re("([^\\s]+)\\s+\\(([^\\s]+)\\s+([^\\s]+)\\):\\s(.*)"); // the file is pomoted like this: // Annotations for main.cpp static QRegExp reFile("Annotations for\\s(.*)"); QStringList lines = jobOutput.split('\n'); QString filename; for (int i=0, linenumber=0; i * * * * 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 "cvsdiffjob.h" #include CvsDiffJob::CvsDiffJob(KDevelop::IPlugin* parent, KDevelop::OutputJob::OutputJobVerbosity verbosity) : CvsJob(parent, verbosity) { } CvsDiffJob::~CvsDiffJob() { } QVariant CvsDiffJob::fetchResults() { KDevelop::VcsDiff diff; diff.setBaseDiff( QUrl::fromLocalFile(process()->workingDirectory()) ); diff.setDiff( output() ); /// @todo check output of "cvs diff" if it reported binary files diff.setContentType( KDevelop::VcsDiff::Text ); /// @todo hmmm, we always call "cvs diff" with it's -u option /// but if this option would be omitted cvs would return an other format diff.setType( KDevelop::VcsDiff::DiffUnified ); return qVariantFromValue( diff ); } void CvsDiffJob::slotProcessError(QProcess::ProcessError error) { // Do not blindly raise an error on non-zero return code of "cvs diff". // If its output contains the "Index:" mark, the diff is probably intact, // and non-zero return code indicates just that there are changes. - if (error == QProcess::UnknownError && output().contains("Index:")) + if (error == QProcess::UnknownError && output().contains(QStringLiteral("Index:"))) return; KDevelop::DVcsJob::slotProcessError(error); } diff --git a/plugins/cvs/cvsjob.cpp b/plugins/cvs/cvsjob.cpp index 68f9909c7..145e0f06e 100644 --- a/plugins/cvs/cvsjob.cpp +++ b/plugins/cvs/cvsjob.cpp @@ -1,88 +1,88 @@ /*************************************************************************** * This file was partly taken from cervisia's cvsservice * * Copyright 2002-2003 Christian Loose * * * * Adapted for KDevelop * * 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 "cvsjob.h" #include #include #include #include class CvsJobPrivate { public: QString server; QString rsh; }; CvsJob::CvsJob(const QDir& workingDir, KDevelop::IPlugin* parent, KDevelop::OutputJob::OutputJobVerbosity verbosity) : DVcsJob(workingDir, parent, verbosity), d(new CvsJobPrivate) { } CvsJob::CvsJob(KDevelop::IPlugin* parent, KDevelop::OutputJob::OutputJobVerbosity verbosity) : DVcsJob(QDir::home(), parent, verbosity), d(new CvsJobPrivate) { } CvsJob::~CvsJob() { delete d; } QString CvsJob::cvsCommand() { - return dvcsCommand().join(" "); + return dvcsCommand().join(QLatin1Char(' ')); } void CvsJob::clear() { process()->clearEnvironment(); } void CvsJob::setDirectory(const QString& directory) { process()->setWorkingDirectory(directory); } QString CvsJob::getDirectory() { return directory().absolutePath(); } void CvsJob::setRSH(const QString& rsh) { d->rsh = rsh; } void CvsJob::setServer(const QString& server) { d->server = server; } void CvsJob::start() { if( !d->rsh.isEmpty() ) { - process()->setEnv("CVS_RSH", d->rsh); + process()->setEnv(QStringLiteral("CVS_RSH"), d->rsh); } if( !d->server.isEmpty() ) { - process()->setEnv("CVS_SERVER", d->server); + process()->setEnv(QStringLiteral("CVS_SERVER"), d->server); } DVcsJob::start(); } diff --git a/plugins/cvs/cvsmainview.cpp b/plugins/cvs/cvsmainview.cpp index 4fe0c4fcb..8a3a3f618 100644 --- a/plugins/cvs/cvsmainview.cpp +++ b/plugins/cvs/cvsmainview.cpp @@ -1,85 +1,85 @@ /*************************************************************************** * 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 "cvsmainview.h" #include #include "cvsplugin.h" #include "cvsjob.h" #include "cvsgenericoutputview.h" #include "debug.h" CvsMainView::CvsMainView( CvsPlugin *plugin, QWidget* parent ) : QWidget( parent ), Ui::CvsMainViewBase(), m_plugin(plugin) { Ui::CvsMainViewBase::setupUi(this); setWindowTitle(i18n("CVS")); // CvsPlugin will notify when a job finished connect(m_plugin, &CvsPlugin::jobFinished, this, &CvsMainView::slotJobFinished); // allow appending of new views connect(m_plugin, &CvsPlugin::addNewTabToMainView, this, &CvsMainView::slotAddTab ); // create a default output view m_mainview = new CvsGenericOutputView; tabwidget->addTab( m_mainview, i18n("CVS") ); // add a close button as corner widget m_closeButton = new QToolButton(tabwidget); - m_closeButton->setIcon( QIcon::fromTheme( "tab-close" ) ); + m_closeButton->setIcon( QIcon::fromTheme( QStringLiteral("tab-close") ) ); m_closeButton->adjustSize(); m_closeButton->setAutoRaise(true); m_closeButton->setEnabled(false); tabwidget->setCornerWidget( m_closeButton ); connect(m_closeButton, &QToolButton::clicked, this, &CvsMainView::slotTabClose); } CvsMainView::~CvsMainView() { delete m_mainview; } void CvsMainView::slotAddTab(QWidget * tab, const QString& label) { qCDebug(PLUGIN_CVS) << "adding tab:" << label; int idx = tabwidget->addTab( tab, label ); tabwidget->setCurrentIndex(idx); if (tabwidget->count() > 1) m_closeButton->setEnabled(true); } void CvsMainView::slotJobFinished(KJob * job) { m_mainview->slotJobFinished(job); tabwidget->setCurrentIndex(0); } void CvsMainView::slotTabClose() { int idx = tabwidget->currentIndex(); // don't allow to close the first tab if (idx != 0) tabwidget->removeTab( idx ); // if only the first tab remains, disable the close button if (tabwidget->count() <= 1) m_closeButton->setEnabled(false); } diff --git a/plugins/cvs/cvsplugin.cpp b/plugins/cvs/cvsplugin.cpp index 356eb85c1..44b307b23 100644 --- a/plugins/cvs/cvsplugin.cpp +++ b/plugins/cvs/cvsplugin.cpp @@ -1,489 +1,489 @@ /*************************************************************************** * 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) {} QWidget* create(QWidget *parent = 0) override { return new CvsMainView(m_plugin, parent); } Qt::DockWidgetArea defaultPosition() override { return Qt::BottomDockWidgetArea; } QString id() const override { - return "org.kdevelop.CVSView"; + 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("kdevcvs", parent) + : KDevelop::IPlugin(QStringLiteral("kdevcvs"), parent) , d(new CvsPluginPrivate(this)) { KDEV_USE_EXTENSION_INTERFACE(KDevelop::IBasicVersionControl) KDEV_USE_EXTENSION_INTERFACE(KDevelop::ICentralizedVersionControl) core()->uiController()->addToolView(i18n("CVS"), d->m_factory); - setXMLFile("kdevcvs.rc"); + 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("cvs_import"); + action = actionCollection()->addAction(QStringLiteral("cvs_import")); action->setText(i18n("Import Directory...")); connect(action, &QAction::triggered, this, &CvsPlugin::slotImport); - action = actionCollection()->addAction("cvs_checkout"); + action = actionCollection()->addAction(QStringLiteral("cvs_checkout")); action->setText(i18n("Checkout...")); connect(action, &QAction::triggered, this, &CvsPlugin::slotCheckout); - action = actionCollection()->addAction("cvs_status"); + 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 NULL; } 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 NULL; } 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 NULL; } 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 NULL; } 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, - "-C", + 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, "-uN"/*always unified*/); + 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 NULL; } 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 0; } 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 0; } qCDebug(PLUGIN_CVS) << "CVS Checkout requested " << "dest:" << destinationDirectory.toLocalFile() << "srv:" << sourceRepository.repositoryServer() << "module:" << sourceRepository.repositoryModule() << "branch:" << sourceRepository.repositoryBranch() << endl; CvsJob* job = d->m_proxy->checkout(destinationDirectory, sourceRepository.repositoryServer(), sourceRepository.repositoryModule(), - "", + QString(), sourceRepository.repositoryBranch(), true, true); return job; } QString CvsPlugin::name() const { return i18n("CVS"); } KDevelop::VcsImportMetadataWidget* CvsPlugin::createImportMetadataWidget(QWidget* parent) { return new ImportMetadataWidget(parent); } KDevelop::VcsLocationWidget* CvsPlugin::vcsLocation(QWidget* parent) const { return new KDevelop::StandardVcsLocationWidget(parent); } // End: KDevelop::IBasicVersionControl #include "cvsplugin.moc" diff --git a/plugins/cvs/cvsproxy.cpp b/plugins/cvs/cvsproxy.cpp index d6810c4ab..14c7f0a5d 100644 --- a/plugins/cvs/cvsproxy.cpp +++ b/plugins/cvs/cvsproxy.cpp @@ -1,480 +1,480 @@ /*************************************************************************** * Copyright 2007 Robert Gruber * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "cvsproxy.h" #include #include #include #include #include #include #include #include "cvsjob.h" #include "cvsannotatejob.h" #include "cvslogjob.h" #include "cvsstatusjob.h" #include "cvsdiffjob.h" #include "debug.h" #include CvsProxy::CvsProxy(KDevelop::IPlugin* parent) : QObject(parent), vcsplugin(parent) { } CvsProxy::~CvsProxy() { } bool CvsProxy::isValidDirectory(QUrl dirPath) const { const QFileInfo fsObject( dirPath.toLocalFile() ); QDir dir = fsObject.isDir() ? fsObject.absoluteDir() : fsObject.dir(); - return dir.exists("CVS"); + return dir.exists(QStringLiteral("CVS")); } bool CvsProxy::isVersionControlled(QUrl filePath) const { const QFileInfo fsObject( filePath.toLocalFile() ); QDir dir = fsObject.isDir() ? fsObject.absoluteDir() : fsObject.dir(); - if( !dir.cd("CVS") ) + if( !dir.cd(QStringLiteral("CVS")) ) return false; if( fsObject.isDir() ) return true; - QFile cvsEntries( dir.absoluteFilePath("Entries") ); + QFile cvsEntries( dir.absoluteFilePath(QStringLiteral("Entries")) ); cvsEntries.open( QIODevice::ReadOnly ); QString cvsEntriesData = cvsEntries.readAll(); cvsEntries.close(); return ( cvsEntriesData.indexOf( fsObject.fileName() ) != -1 ); } bool CvsProxy::prepareJob(CvsJob* job, const QString& repository, enum RequestedOperation op) { // Only do this check if it's a normal operation like diff, log ... // For other operations like "cvs import" isValidDirectory() would fail as the // directory is not yet under CVS control if (op == CvsProxy::NormalOperation && !isValidDirectory(QUrl::fromLocalFile(repository))) { qCDebug(PLUGIN_CVS) << repository << " is not a valid CVS repository"; return false; } // clear commands and args from a possible previous run job->clear(); // setup the working directory for the new job job->setDirectory(repository); return true; } bool CvsProxy::addFileList(CvsJob* job, const QString& repository, const QList& urls) { QStringList args; QDir repoDir(repository); foreach(const QUrl &url, urls) { ///@todo this is ok for now, but what if some of the urls are not /// to the given repository const QString file = repoDir.relativeFilePath(url.toLocalFile()); args << KShell::quoteArg( file ); } *job << args; return true; } QString CvsProxy::convertVcsRevisionToString(const KDevelop::VcsRevision & rev) { QString str; switch (rev.revisionType()) { case KDevelop::VcsRevision::Special: break; case KDevelop::VcsRevision::FileNumber: if (rev.revisionValue().isValid()) str = "-r"+rev.revisionValue().toString(); break; case KDevelop::VcsRevision::Date: if (rev.revisionValue().isValid()) str = "-D"+rev.revisionValue().toString(); break; case KDevelop::VcsRevision::GlobalNumber: // !! NOT SUPPORTED BY CVS !! default: break; } return str; } QString CvsProxy::convertRevisionToPrevious(const KDevelop::VcsRevision& rev) { QString str; // this only works if the revision is a real revisionnumber and not a date or special switch (rev.revisionType()) { case KDevelop::VcsRevision::FileNumber: if (rev.revisionValue().isValid()) { QString orig = rev.revisionValue().toString(); // First we need to find the base (aka branch-part) of the revision number which will not change QString base(orig); - base.truncate(orig.lastIndexOf(".")); + base.truncate(orig.lastIndexOf(QLatin1Char('.'))); // next we need to cut off the last part of the revision number // this number is a count of revisions with a branch // so if we want to diff to the previous we just need to lower it by one - int number = orig.mid(orig.lastIndexOf(".")+1).toInt(); + int number = orig.midRef(orig.lastIndexOf(QLatin1Char('.'))+1).toInt(); if (number > 1) // of course this is only possible if our revision is not the first on the branch number--; - str = QString("-r") + base + '.' + QString::number(number); + str = QStringLiteral("-r") + base + '.' + QString::number(number); qCDebug(PLUGIN_CVS) << "Converted revision "<(); if (specialtype == KDevelop::VcsRevision::Previous) { rA = convertRevisionToPrevious(revB); } } else { rA = convertVcsRevisionToString(revA); } if (!rA.isEmpty()) *job << rA; QString rB = convertVcsRevisionToString(revB); if (!rB.isEmpty()) *job << rB; // in case the QUrl is a directory there is no filename if (!info.fileName().isEmpty()) *job << KShell::quoteArg(info.fileName()); return job; } if (job) delete job; return NULL; } CvsJob * CvsProxy::annotate(const QUrl& url, const KDevelop::VcsRevision& rev) { QFileInfo info(url.toLocalFile()); CvsAnnotateJob* job = new CvsAnnotateJob(vcsplugin); if ( prepareJob(job, info.absolutePath()) ) { *job << "cvs"; *job << "annotate"; QString revision = convertVcsRevisionToString(rev); if (!revision.isEmpty()) *job << revision; *job << KShell::quoteArg(info.fileName()); return job; } if (job) delete job; return NULL; } CvsJob* CvsProxy::edit(const QString& repo, const QList& files) { CvsJob* job = new CvsJob(vcsplugin); if ( prepareJob(job, repo) ) { *job << "cvs"; *job << "edit"; addFileList(job, repo, files); return job; } if (job) delete job; return NULL; } CvsJob* CvsProxy::unedit(const QString& repo, const QList& files) { CvsJob* job = new CvsJob(vcsplugin); if ( prepareJob(job, repo) ) { *job << "cvs"; *job << "unedit"; addFileList(job, repo, files); return job; } if (job) delete job; return NULL; } CvsJob* CvsProxy::editors(const QString& repo, const QList& files) { CvsJob* job = new CvsJob(vcsplugin); if ( prepareJob(job, repo) ) { *job << "cvs"; *job << "editors"; addFileList(job, repo, files); return job; } if (job) delete job; return NULL; } CvsJob* CvsProxy::commit(const QString& repo, const QList& files, const QString& message) { CvsJob* job = new CvsJob(vcsplugin); if ( prepareJob(job, repo) ) { *job << "cvs"; *job << "commit"; *job << "-m"; *job << KShell::quoteArg( message ); addFileList(job, repo, files); return job; } if (job) delete job; return NULL; } CvsJob* CvsProxy::add(const QString& repo, const QList& files, bool recursiv, bool binary) { Q_UNUSED(recursiv); // FIXME recursiv is not implemented yet CvsJob* job = new CvsJob(vcsplugin); if ( prepareJob(job, repo) ) { *job << "cvs"; *job << "add"; if (binary) { *job << "-kb"; } addFileList(job, repo, files); return job; } if (job) delete job; return NULL; } CvsJob * CvsProxy::remove(const QString& repo, const QList& files) { CvsJob* job = new CvsJob(vcsplugin); if ( prepareJob(job, repo) ) { *job << "cvs"; *job << "remove"; *job << "-f"; //existing files will be deleted addFileList(job, repo, files); return job; } if (job) delete job; return NULL; } CvsJob * CvsProxy::update(const QString& repo, const QList& files, const KDevelop::VcsRevision & rev, const QString & updateOptions, bool recursive, bool pruneDirs, bool createDirs) { CvsJob* job = new CvsJob(vcsplugin); if ( prepareJob(job, repo) ) { *job << "cvs"; *job << "update"; if (recursive) *job << "-R"; else *job << "-L"; if (pruneDirs) *job << "-P"; if (createDirs) *job << "-d"; if (!updateOptions.isEmpty()) *job << updateOptions; QString revision = convertVcsRevisionToString(rev); if (!revision.isEmpty()) *job << revision; addFileList(job, repo, files); return job; } if (job) delete job; return NULL; } CvsJob * CvsProxy::import(const QUrl& directory, const QString & server, const QString & repositoryName, const QString & vendortag, const QString & releasetag, const QString& message) { CvsJob* job = new CvsJob(vcsplugin); if ( prepareJob(job, directory.toLocalFile(), CvsProxy::Import) ) { *job << "cvs"; *job << "-q"; // don't print directory changes *job << "-d"; *job << server; *job << "import"; *job << "-m"; *job << KShell::quoteArg( message ); *job << repositoryName; *job << vendortag; *job << releasetag; return job; } if (job) delete job; return NULL; } CvsJob * CvsProxy::checkout(const QUrl& targetDir, const QString & server, const QString & module, const QString & checkoutOptions, const QString & revision, bool recursive, bool pruneDirs) { CvsJob* job = new CvsJob(vcsplugin); ///@todo when doing a checkout we don't have the targetdir yet, /// for now it'll work to just run the command from the root - if ( prepareJob(job, "/", CvsProxy::CheckOut) ) { + if ( prepareJob(job, QStringLiteral("/"), CvsProxy::CheckOut) ) { *job << "cvs"; *job << "-q"; // don't print directory changes *job << "-d" << server; *job << "checkout"; if (!checkoutOptions.isEmpty()) *job << checkoutOptions; if (!revision.isEmpty()) { *job << "-r" << revision; } if (pruneDirs) *job << "-P"; if (!recursive) *job << "-l"; *job << "-d" << targetDir.toString(QUrl::PreferLocalFile | QUrl::StripTrailingSlash); *job << module; return job; } if (job) delete job; return NULL; } CvsJob * CvsProxy::status(const QString & repo, const QList & files, bool recursive, bool taginfo) { CvsStatusJob* job = new CvsStatusJob(vcsplugin); job->setCommunicationMode( KProcess::MergedChannels ); if ( prepareJob(job, repo) ) { *job << "cvs"; *job << "status"; if (recursive) *job << "-R"; else *job << "-l"; if (taginfo) *job << "-v"; addFileList(job, repo, files); return job; } if (job) delete job; return NULL; } diff --git a/plugins/cvs/cvsproxy.h b/plugins/cvs/cvsproxy.h index 7792938e6..fda23fdef 100644 --- a/plugins/cvs/cvsproxy.h +++ b/plugins/cvs/cvsproxy.h @@ -1,108 +1,108 @@ /*************************************************************************** * 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. * * * ***************************************************************************/ #ifndef KDEVPLATFORM_PLUGIN_CVSPROXY_H #define KDEVPLATFORM_PLUGIN_CVSPROXY_H #include #include #include class CvsJob; namespace KDevelop { class IPlugin; } /** * This proxy acts as a single point of entry for most of the common cvs commands. * It is very easy to use, as the caller does not have to deal which the CvsJob class directly. * All the command line generation and job handling is done internally. The caller gets a CvsJob * object returned from the proxy and can then call it's start() method. * * Here is and example of how to user the proxy: * @code * CvsJob* job = proxy->editors( repo, urls ); * if ( job ) { * connect(job, SIGNAL( result(KJob*) ), * this, SIGNAL( jobFinished(KJob*) )); * job->start(); * } * @endcode * * @note All actions that take a QList also need an url to the repository which * must be a common base directory to all files from the QList. * Actions that just take a single QUrl don't need a repository, the cvs command will be * called directly in the directory of the given file * * @author Robert Gruber */ class CvsProxy : public QObject { Q_OBJECT public: explicit CvsProxy(KDevelop::IPlugin* parent = 0); ~CvsProxy() override; bool isValidDirectory(QUrl dirPath) const; bool isVersionControlled(QUrl filePath) const; CvsJob* import(const QUrl &directory, const QString & server, const QString& repositoryName, const QString& vendortag, const QString& releasetag, const QString& message); CvsJob* log(const QUrl &file, const KDevelop::VcsRevision& rev); CvsJob* diff(const QUrl &url, const KDevelop::VcsRevision& revA, const KDevelop::VcsRevision& revB, - const QString& diffOptions=""); + const QString& diffOptions=QString()); CvsJob* annotate(const QUrl &url, const KDevelop::VcsRevision& rev); CvsJob* edit(const QString& repo, const QList& files); CvsJob* unedit(const QString& repo, const QList& files); CvsJob* editors(const QString& repo, const QList& files); CvsJob* commit(const QString& repo, const QList& files, const QString& message); CvsJob* add(const QString& repo, const QList& files, bool recursiv = true, bool binary = false); CvsJob* remove(const QString& repo, const QList& files); CvsJob* update(const QString& repo, const QList& files, const KDevelop::VcsRevision& rev, const QString& updateOptions, bool resursive = true, bool pruneDirs = true, bool createDirs = true); CvsJob* checkout(const QUrl &targetDir, const QString & server, const QString& module, - const QString& checkoutOptions="", - const QString& revision="", + const QString& checkoutOptions=QString(), + const QString& revision=QString(), bool recursive = true, bool pruneDirs = true); CvsJob* status(const QString & repo, const QList & files, bool recursive=false, bool taginfo=false); private: bool addFileList(CvsJob* job, const QString& repository, const QList& urls); QString convertVcsRevisionToString(const KDevelop::VcsRevision& rev); QString convertRevisionToPrevious(const KDevelop::VcsRevision& rev); enum RequestedOperation { NormalOperation, Import, CheckOut }; bool prepareJob(CvsJob* job, const QString& repository, enum RequestedOperation op = CvsProxy::NormalOperation); KDevelop::IPlugin* vcsplugin; }; #endif diff --git a/plugins/cvs/cvsstatusjob.cpp b/plugins/cvs/cvsstatusjob.cpp index 11d6e1076..5d2ebbaa5 100644 --- a/plugins/cvs/cvsstatusjob.cpp +++ b/plugins/cvs/cvsstatusjob.cpp @@ -1,147 +1,147 @@ /*************************************************************************** * Copyright 2008 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 "cvsstatusjob.h" #include "debug.h" #include #include #include #include CvsStatusJob::CvsStatusJob(KDevelop::IPlugin* parent, KDevelop::OutputJob::OutputJobVerbosity verbosity) : CvsJob(parent, verbosity) { } CvsStatusJob::~CvsStatusJob() { } QVariant CvsStatusJob::fetchResults() { // Convert job's output into KDevelop::VcsStatusInfo QList infos; parseOutput(output(), infos); return infos; } void CvsStatusJob::addInfoToList(QList& infos, const QString& currentDir, const QString& filename, const QString& statusString) { KDevelop::VcsStatusInfo::State cvsState = String2EnumState( statusString ); QString correctedFilename = filename; if (cvsState == KDevelop::VcsStatusInfo::ItemDeleted) { // cvs status writes "no file" in front of the filename // in case the file was locally removed - correctedFilename.remove("no file "); + correctedFilename.remove(QStringLiteral("no file ")); } // join the current directory (if any) and the found filename ... // note: current directy is always relative to the directory where the // cvs command was executed QString file = currentDir; if (file.length() > 0) { file += QDir::separator(); } file += correctedFilename; // ... and create a VcsFileInfo entry KDevelop::VcsStatusInfo info; info.setUrl(QUrl::fromLocalFile(QString(getDirectory() + QDir::separator() + file))); info.setState(cvsState); qCDebug(PLUGIN_CVS) << "Added status of: " << info.url() << endl; infos << qVariantFromValue( info ); } void CvsStatusJob::parseOutput(const QString& jobOutput, QList& infos) { QString filename; QString status; QString reporev; QString workrev; static QRegExp re_start("^=+$"); static QRegExp re_file("File:\\s+(.*)\\s+Status:\\s+(.*)"); static QRegExp re_workrev("\\s+Working revision:\\s+([\\d\\.]*).*"); static QRegExp re_reporev("\\s+Repository revision:\\s+([\\d\\.]*).*"); static QRegExp re_dirchange("cvs status: Examining\\s+(.*)"); QString currentDir; QStringList lines = jobOutput.split('\n'); for (int i=0; i * * * * 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 "editorsview.h" #include #include #include #include #include #include "cvsplugin.h" #include "cvsjob.h" #include "cvsproxy.h" EditorsView::EditorsView(CvsJob* job, QWidget *parent) : QWidget(parent), Ui::EditorsViewBase() { Ui::EditorsViewBase::setupUi(this); if (job) { connect(job, &CvsJob::result, this, &EditorsView::slotJobFinished); } } EditorsView::~EditorsView() { } void EditorsView::slotJobFinished(KJob* job) { if ( job->error() ) { textbrowser->append( i18n("Listing editors failed") ); return; } CvsJob * cvsjob = dynamic_cast(job); if (!cvsjob) { return; } QMultiMap lockedFiles; parseOutput(cvsjob->output(), lockedFiles); if (lockedFiles.size() == 0) { textbrowser->append(i18n("No files from your query are marked as being edited.")); } else { QString html; foreach (const QString &key, lockedFiles.uniqueKeys()) { html += "

"+key+"


"; foreach(const CvsLocker &item, lockedFiles.values( key )) { html += ""+i18n("User")+": "+item.user+"
"; html += ""+i18n("Date")+": "+item.date+"
"; html += ""+i18n("Machine")+": "+item.machine+"
"; html += ""+i18n("Repository")+": "+item.localrepo+"
"; - html += "
"; + html += QLatin1String("
"); } - html += "
"; + html += QLatin1String("
"); } textbrowser->setHtml( html ); } } void EditorsView::parseOutput(const QString& jobOutput, QMultiMap& editorsInfo) { // the first line contains the filename and than the locker information static QRegExp re("([^\\s]+)\\s+([^\\s]+)\\s+([^\\s]+)\\s+([^\\s]+)\\s+([^\\s]+)\\s+" "([^\\s]+)\\s+([^\\s]+)\\s+([^\\s]+)\\s+([^\\s]+)\\s+(.*)"); // if there are more than one locker of a single file, the second line for a file // only contains the locker information (no filename) static QRegExp subre("\\s+([^\\s]+)\\s+([^\\s]+)\\s+([^\\s]+)\\s+([^\\s]+)\\s+" "([^\\s]+)\\s+([^\\s]+)\\s+([^\\s]+)\\s+([^\\s]+)\\s+(.*)"); QString lastfilename; QStringList lines = jobOutput.split('\n'); for (int i=0; i * * * * 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 "importdialog.h" #include #include #include #include #include "cvsplugin.h" #include "cvsproxy.h" #include "cvsjob.h" #include "debug.h" #include #include "importmetadatawidget.h" ImportDialog::ImportDialog(CvsPlugin* plugin, const QUrl &url, QWidget *parent) : QDialog(parent), m_url(url), m_plugin(plugin) { m_widget = new ImportMetadataWidget(this); m_widget->setSourceLocation( KDevelop::VcsLocation(m_url) ); m_widget->setSourceLocationEditable( true ); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); auto layout = new QVBoxLayout(); setLayout(layout); layout->addWidget(m_widget); layout->addWidget(buttonBox); connect(buttonBox, &QDialogButtonBox::accepted, this, &ImportDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &ImportDialog::reject); } ImportDialog::~ImportDialog() { } void ImportDialog::accept() { KDevelop::VcsJob *job = m_plugin->import(m_widget->message(), m_widget->source(), m_widget->destination()); if (job) { connect(job, &KDevelop::VcsJob::result, this, &ImportDialog::jobFinished); KDevelop::ICore::self()->runController()->registerJob(job); } } void ImportDialog::jobFinished(KJob * job) { if (job->error()) { KMessageBox::error(this, i18n("Error on importing"), i18n("Import Error")); return; } // The job finished, now let's check the output is everything was OK CvsJob* cvsjob = dynamic_cast(job); static QRegExp re_file("^[IN]\\s(.*)"); bool error = false; QStringList lines = cvsjob->output().split('\n'); foreach(const QString &line, lines) { if (line.isEmpty()) { // ignore empty lines continue; } else if (re_file.exactMatch(line)) { // line that tell us that a file has been added are OK continue; // this normaly should be the last line - } else if (line.contains("No conflicts created by this import")) { + } else if (line.contains(QStringLiteral("No conflicts created by this import"))) { continue; } else { // any other line must mean that an error occurred qCDebug(PLUGIN_CVS) <<"ERR: "<< line; error = true; } } if (error) { KMessageBox::error(this, i18n("Some errors occurred while importing %1", m_url.toLocalFile()), i18n("Import Error")); } else { QDialog::accept(); } } diff --git a/plugins/documentswitcher/documentswitcherplugin.cpp b/plugins/documentswitcher/documentswitcherplugin.cpp index 938264aed..afff29141 100644 --- a/plugins/documentswitcher/documentswitcherplugin.cpp +++ b/plugins/documentswitcher/documentswitcherplugin.cpp @@ -1,386 +1,386 @@ /*************************************************************************** * Copyright 2009,2013 Andreas Pakulat * * Copyright 2013 Jarosław Sierant * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "documentswitcherplugin.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "documentswitchertreeview.h" #include #include #include Q_LOGGING_CATEGORY(PLUGIN_DOCUMENTSWITCHER, "kdevplatform.plugins.documentswitcher") K_PLUGIN_FACTORY_WITH_JSON(DocumentSwitcherFactory, "kdevdocumentswitcher.json", registerPlugin();) //TODO: Show frame around view's widget while walking through //TODO: Make the widget transparent DocumentSwitcherPlugin::DocumentSwitcherPlugin(QObject *parent, const QVariantList &/*args*/) :KDevelop::IPlugin(QStringLiteral("kdevdocumentswitcher"), parent), view(0) { setXMLFile(QStringLiteral("kdevdocumentswitcher.rc")); qCDebug(PLUGIN_DOCUMENTSWITCHER) << "Adding active mainwindow from constructor" << KDevelop::ICore::self()->uiController()->activeMainWindow(); addMainWindow( qobject_cast( KDevelop::ICore::self()->uiController()->activeMainWindow() ) ); connect( KDevelop::ICore::self()->uiController()->controller(), &Sublime::Controller::mainWindowAdded, this, &DocumentSwitcherPlugin::addMainWindow ); forwardAction = actionCollection()->addAction ( QStringLiteral( "last_used_views_forward" ) ); forwardAction->setText( i18n( "Last Used Views" ) ); forwardAction->setIcon( QIcon::fromTheme( QStringLiteral( "go-next-view-page") ) ); actionCollection()->setDefaultShortcut( forwardAction, Qt::CTRL | Qt::Key_Tab ); forwardAction->setWhatsThis( i18n( "Opens a list to walk through the list of last used views." ) ); forwardAction->setStatusTip( i18n( "Walk through the list of last used views" ) ); connect( forwardAction, &QAction::triggered, this, &DocumentSwitcherPlugin::walkForward ); backwardAction = actionCollection()->addAction ( QStringLiteral( "last_used_views_backward" ) ); backwardAction->setText( i18n( "Last Used Views (Reverse)" ) ); backwardAction->setIcon( QIcon::fromTheme( QStringLiteral( "go-previous-view-page") ) ); actionCollection()->setDefaultShortcut( backwardAction, Qt::CTRL | Qt::SHIFT | Qt::Key_Tab ); backwardAction->setWhatsThis( i18n( "Opens a list to walk through the list of last used views in reverse." ) ); backwardAction->setStatusTip( i18n( "Walk through the list of last used views" ) ); connect( backwardAction, &QAction::triggered, this, &DocumentSwitcherPlugin::walkBackward ); view = new DocumentSwitcherTreeView( this ); view->setSelectionBehavior( QAbstractItemView::SelectRows ); view->setSelectionMode( QAbstractItemView::SingleSelection ); view->setUniformItemSizes( true ); view->setTextElideMode( Qt::ElideMiddle ); view->setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); view->addAction( forwardAction ); view->addAction( backwardAction ); connect( view, &QListView::pressed, this, &DocumentSwitcherPlugin::switchToClicked ); connect( view, &QListView::activated, this, &DocumentSwitcherPlugin::itemActivated ); model = new QStandardItemModel( view ); view->setModel( model ); } void DocumentSwitcherPlugin::setViewGeometry(Sublime::MainWindow* window) { const QSize centralSize = window->centralWidget()->size(); // Maximum size of the view is 3/4th of the central widget (the editor area) so the view does not overlap the // mainwindow since that looks awkward. const QSize viewMaxSize( centralSize.width() * 3/4, centralSize.height() * 3/4 ); // The actual view size should be as big as the columns/rows need it, but smaller than the max-size. This means // the view will get quite high with many open files but I think thats ok. Otherwise one can easily tweak the // max size to be only 1/2th of the central widget size const QSize viewSize( std::min( view->sizeHintForColumn(0) + view->verticalScrollBar()->width(), viewMaxSize.width() ), std::min( std::max( view->sizeHintForRow(0) * view->model()->rowCount(), view->sizeHintForRow(0) * 6 ), viewMaxSize.height() ) ); // Position should be central over the editor area, so map to global from parent of central widget since // the view is positioned in global coords QPoint centralWidgetPos = window->mapToGlobal( window->centralWidget()->pos() ); const int xPos = std::max(0, centralWidgetPos.x() + (centralSize.width() - viewSize.width() ) / 2); const int yPos = std::max(0, centralWidgetPos.y() + (centralSize.height() - viewSize.height() ) / 2); view->setFixedSize(viewSize); view->move(xPos, yPos); } void DocumentSwitcherPlugin::walk(const int from, const int to) { Sublime::MainWindow* window = qobject_cast( KDevelop::ICore::self()->uiController()->activeMainWindow() ); if( !window || !documentLists.contains( window ) || !documentLists[window].contains( window->area() ) ) { qWarning() << "This should not happen, tried to walk through document list of an unknown mainwindow!"; return; } QModelIndex idx; const int step = from < to ? 1 : -1; if(!view->isVisible()) { fillModel(window); setViewGeometry(window); idx = model->index(from + step, 0); if(!idx.isValid()) { idx = model->index(0, 0); } view->show(); } else { int newRow = view->selectionModel()->currentIndex().row() + step; if(newRow == to + step) { newRow = from; } idx = model->index(newRow, 0); } view->selectionModel()->select(idx, QItemSelectionModel::Rows | QItemSelectionModel::ClearAndSelect); view->selectionModel()->setCurrentIndex(idx, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); } void DocumentSwitcherPlugin::walkForward() { walk(0, model->rowCount()-1); } void DocumentSwitcherPlugin::walkBackward() { walk(model->rowCount()-1, 0); } void DocumentSwitcherPlugin::fillModel( Sublime::MainWindow* window ) { model->clear(); foreach( Sublime::View* v, documentLists[window][window->area()] ) { using namespace KDevelop; Sublime::Document const* const slDoc = v->document(); if( !slDoc ) { continue; } QString itemText = slDoc->title();// file name IDocument const* const doc = dynamic_cast(v->document()); if( doc ) { QString path = ICore::self()->projectController()->prettyFilePath(doc->url(), IProjectController::FormatPlain); const bool isPartOfOpenProject = QDir::isRelativePath(path); if( path.endsWith('/') ) { path.remove(path.length() - 1, 1); } if( isPartOfOpenProject ) { const int projectNameSize = path.indexOf(QLatin1Char(':')); // first: project name, second: path to file in project (might be just '/' when the file is in the project root dir) const QPair fileInProjectInfo = (projectNameSize < 0) ? qMakePair(path, QStringLiteral("/")) : qMakePair(path.left(projectNameSize), path.mid(projectNameSize + 1)); itemText = QStringLiteral("%1 (%2:%3)").arg(itemText, fileInProjectInfo.first, fileInProjectInfo.second); } else { itemText = itemText + " (" + path + ')'; } } model->appendRow( new QStandardItem( slDoc->icon(), itemText ) ); } } DocumentSwitcherPlugin::~DocumentSwitcherPlugin() { } void DocumentSwitcherPlugin::switchToClicked( const QModelIndex& idx ) { view->selectionModel()->select(idx, QItemSelectionModel::ClearAndSelect); itemActivated(idx); } void DocumentSwitcherPlugin::itemActivated( const QModelIndex& idx ) { Q_UNUSED( idx ); if( view->selectionModel()->selectedRows().isEmpty() ) { return; } int row = view->selectionModel()->selectedRows().first().row(); Sublime::MainWindow* window = qobject_cast( KDevelop::ICore::self()->uiController()->activeMainWindow() ); Sublime::View* activatedView = 0; if( window && documentLists.contains( window ) && documentLists[window].contains( window->area() ) ) { const QList l = documentLists[window][window->area()]; if( row >= 0 && row < l.size() ) { activatedView = l.at( row ); } } if( activatedView ) { if( QApplication::mouseButtons() & Qt::MiddleButton ) { window->area()->closeView( activatedView ); fillModel( window ); if ( model->rowCount() == 0 ) { view->hide(); } else { view->selectionModel()->select( view->model()->index(0, 0), QItemSelectionModel::ClearAndSelect ); } } else { window->activateView( activatedView ); view->hide(); } } } void DocumentSwitcherPlugin::unload() { foreach( QObject* mw, documentLists.keys() ) { removeMainWindow( mw ); } delete forwardAction; delete backwardAction; view->deleteLater(); } void DocumentSwitcherPlugin::storeAreaViewList( Sublime::MainWindow* mainwindow, Sublime::Area* area ) { if( !documentLists.contains( mainwindow ) || !documentLists[mainwindow].contains(area) ) { - QMap > areas; + QHash > areas; qCDebug(PLUGIN_DOCUMENTSWITCHER) << "adding area views for area:" << area << area->title() << "mainwindow:" << mainwindow << mainwindow->windowTitle(); foreach( Sublime::View* v, area->views() ) { qCDebug(PLUGIN_DOCUMENTSWITCHER) << "view:" << v << v->document()->title(); } qCDebug(PLUGIN_DOCUMENTSWITCHER) << "done"; areas.insert( area, area->views() ); documentLists.insert( mainwindow, areas ); } } void DocumentSwitcherPlugin::addMainWindow( Sublime::MainWindow* mainwindow ) { if( !mainwindow ) { return; } qCDebug(PLUGIN_DOCUMENTSWITCHER) << "adding mainwindow:" << mainwindow << mainwindow->windowTitle(); qCDebug(PLUGIN_DOCUMENTSWITCHER) << "storing all views from area:" << mainwindow->area()->title() << mainwindow->area(); storeAreaViewList( mainwindow, mainwindow->area() ); qCDebug(PLUGIN_DOCUMENTSWITCHER) << "connecting signals on mainwindow"; connect( mainwindow, &Sublime::MainWindow::areaChanged, this, &DocumentSwitcherPlugin::changeArea ); connect( mainwindow, &Sublime::MainWindow::activeViewChanged, this, &DocumentSwitcherPlugin::changeView ); connect( mainwindow, &Sublime::MainWindow::viewAdded, this, &DocumentSwitcherPlugin::addView ); connect( mainwindow, &Sublime::MainWindow::aboutToRemoveView, this, &DocumentSwitcherPlugin::removeView ); connect( mainwindow, &Sublime::MainWindow::destroyed, this, &DocumentSwitcherPlugin::removeMainWindow); mainwindow->installEventFilter( this ); } bool DocumentSwitcherPlugin::eventFilter( QObject* watched, QEvent* ev ) { Sublime::MainWindow* mw = dynamic_cast( watched ); if( mw && ev->type() == QEvent::WindowActivate ) { enableActions(); } return QObject::eventFilter( watched, ev ); } void DocumentSwitcherPlugin::addView( Sublime::View* view ) { Sublime::MainWindow* mainwindow = qobject_cast( sender() ); if( !mainwindow ) return; qCDebug(PLUGIN_DOCUMENTSWITCHER) << "got signal from mainwindow:" << mainwindow << mainwindow->windowTitle() << "its area is:" << mainwindow->area() << mainwindow->area()->title() << "adding view:" << view << view->document()->title(); enableActions(); documentLists[mainwindow][mainwindow->area()].append( view ); } void DocumentSwitcherPlugin::enableActions() { forwardAction->setEnabled(true); backwardAction->setEnabled(true); } void DocumentSwitcherPlugin::removeMainWindow( QObject* obj ) { if( !obj || !documentLists.contains(obj) ) { return; } obj->removeEventFilter( this ); disconnect( obj, 0, this, 0 ); documentLists.remove( obj ); } void DocumentSwitcherPlugin::changeArea( Sublime::Area* area ) { Sublime::MainWindow* mainwindow = qobject_cast( sender() ); Q_ASSERT( mainwindow ); qCDebug(PLUGIN_DOCUMENTSWITCHER) << "area changed:" << area << area->title() << "mainwindow:" << mainwindow << mainwindow->windowTitle(); //Since the main-window only emits aboutToRemoveView for views within the current area, we must forget all areas except the active one documentLists.remove(mainwindow); if( !documentLists[mainwindow].contains( area ) ) { qCDebug(PLUGIN_DOCUMENTSWITCHER) << "got area change, storing its views"; storeAreaViewList( mainwindow, area ); } enableActions(); } void DocumentSwitcherPlugin::changeView( Sublime::View* view ) { if( !view ) return; Sublime::MainWindow* mainwindow = qobject_cast( sender() ); Q_ASSERT( mainwindow ); Sublime::Area* area = mainwindow->area(); int idx = documentLists[mainwindow][area].indexOf( view ); if( idx != -1 ) { documentLists[mainwindow][area].removeAt( idx ); } qCDebug(PLUGIN_DOCUMENTSWITCHER) << "moving view to front, list should now not contain this view anymore" << view << view->document()->title(); qCDebug(PLUGIN_DOCUMENTSWITCHER) << "current area is:" << area << area->title() << "mainwnidow:" << mainwindow << mainwindow->windowTitle();; qCDebug(PLUGIN_DOCUMENTSWITCHER) << "idx of this view in list:" << documentLists[mainwindow][area].indexOf( view ); documentLists[mainwindow][area].prepend( view ); enableActions(); } void DocumentSwitcherPlugin::removeView( Sublime::View* view ) { if( !view ) return; Sublime::MainWindow* mainwindow = qobject_cast( sender() ); Q_ASSERT( mainwindow ); Sublime::Area* area = mainwindow->area(); int idx = documentLists[mainwindow][area].indexOf( view ); if( idx != -1 ) { documentLists[mainwindow][area].removeAt( idx ); } qCDebug(PLUGIN_DOCUMENTSWITCHER) << "removing view, list should now not contain this view anymore" << view << view->document()->title(); qCDebug(PLUGIN_DOCUMENTSWITCHER) << "current area is:" << area << area->title() << "mainwnidow:" << mainwindow << mainwindow->windowTitle();; qCDebug(PLUGIN_DOCUMENTSWITCHER) << "idx of this view in list:" << documentLists[mainwindow][area].indexOf( view ); enableActions(); } #include "documentswitcherplugin.moc" diff --git a/plugins/documentswitcher/documentswitcherplugin.h b/plugins/documentswitcher/documentswitcherplugin.h index 7f7e8af36..fce6ff3a7 100644 --- a/plugins/documentswitcher/documentswitcherplugin.h +++ b/plugins/documentswitcher/documentswitcherplugin.h @@ -1,81 +1,81 @@ /*************************************************************************** * Copyright 2009 Andreas Pakulat * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef KDEVPLATFORM_PLUGIN_DOCUMENTSWITCHERPLUGIN_H #define KDEVPLATFORM_PLUGIN_DOCUMENTSWITCHERPLUGIN_H #include #include #include Q_DECLARE_LOGGING_CATEGORY(PLUGIN_DOCUMENTSWITCHER) class QStandardItemModel; namespace Sublime { class View; class MainWindow; class AreaIndex; class Area; class MainWindow; } class QListView; class QModelIndex; class QStringListModel; class QEvent; class QAction; class DocumentSwitcherPlugin: public KDevelop::IPlugin { Q_OBJECT public: explicit DocumentSwitcherPlugin( QObject *parent, const QVariantList &args = QVariantList() ); ~DocumentSwitcherPlugin() override; void unload() override; public slots: void itemActivated( const QModelIndex& ); void switchToClicked(const QModelIndex& ); private slots: void addView( Sublime::View* ); void changeView( Sublime::View* ); void addMainWindow( Sublime::MainWindow* ); void changeArea( Sublime::Area* ); void removeView( Sublime::View* ); void removeMainWindow(QObject*); void walkForward(); void walkBackward(); protected: bool eventFilter( QObject*, QEvent* ) override; private: void setViewGeometry(Sublime::MainWindow* window); void storeAreaViewList( Sublime::MainWindow* mainwindow, Sublime::Area* area ); void enableActions(); void fillModel( Sublime::MainWindow* window ); void walk(const int from, const int to); // Need to use QObject here as we only have a QObject* in // the removeMainWindow method and cannot cast it to the mainwindow anymore - QMap > > documentLists; + QMap > > documentLists; QListView* view; QStandardItemModel* model; QAction* forwardAction; QAction* backwardAction; }; #endif diff --git a/plugins/documentswitcher/documentswitchertreeview.h b/plugins/documentswitcher/documentswitchertreeview.h index ea349e880..c72f15ee8 100644 --- a/plugins/documentswitcher/documentswitchertreeview.h +++ b/plugins/documentswitcher/documentswitchertreeview.h @@ -1,38 +1,39 @@ /*************************************************************************** * Copyright 2009 Andreas Pakulat * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef KDEVPLATFORM_PLUGIN_DOCUMENTSWITCHERTREEVIEW_H #define KDEVPLATFORM_PLUGIN_DOCUMENTSWITCHERTREEVIEW_H #include class DocumentSwitcherPlugin; class DocumentSwitcherTreeView : public QListView { + Q_OBJECT public: explicit DocumentSwitcherTreeView( DocumentSwitcherPlugin* ); protected: virtual void keyPressEvent(QKeyEvent* event) override; virtual void keyReleaseEvent(QKeyEvent* ) override; private: DocumentSwitcherPlugin* plugin; }; #endif // KDEVPLATFORM_PLUGIN_DOCUMENTSWITCHERTREEVIEW_H diff --git a/plugins/documentview/kdevdocumentview.cpp b/plugins/documentview/kdevdocumentview.cpp index 1fa8156a0..a810a0033 100644 --- a/plugins/documentview/kdevdocumentview.cpp +++ b/plugins/documentview/kdevdocumentview.cpp @@ -1,349 +1,349 @@ /* This file is part of KDevelop Copyright 2005 Adam Treat Copyright 2013 Sebastian Kügler This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kdevdocumentview.h" #include "kdevdocumentviewplugin.h" #include "kdevdocumentmodel.h" #include #include #include #include #include #include #include #include #include "kdevdocumentselection.h" #include "kdevdocumentviewdelegate.h" #include #include #include #include #include #include #include #include using namespace KDevelop; KDevDocumentView::KDevDocumentView( KDevDocumentViewPlugin *plugin, QWidget *parent ) : QTreeView( parent ), m_plugin( plugin ) { connect(ICore::self()->projectController(), &IProjectController::projectOpened, this, &KDevDocumentView::updateProjectPaths); connect(ICore::self()->projectController(), &IProjectController::projectClosed, this, &KDevDocumentView::updateProjectPaths); m_documentModel = new KDevDocumentModel(this); m_delegate = new KDevDocumentViewDelegate( this ); m_proxy = new QSortFilterProxyModel( this ); m_proxy->setSourceModel( m_documentModel ); m_proxy->setDynamicSortFilter( true ); m_proxy->setSortCaseSensitivity( Qt::CaseInsensitive ); m_proxy->sort(0); m_selectionModel = new KDevDocumentSelection( m_proxy ); setModel( m_proxy ); setSelectionModel( m_selectionModel ); setItemDelegate( m_delegate ); setObjectName( i18n( "Documents" ) ); setWindowIcon( QIcon::fromTheme( QStringLiteral( "document-multiple" ), windowIcon() ) ); setWindowTitle( i18n( "Documents" ) ); setFocusPolicy( Qt::NoFocus ); header()->hide(); setSelectionBehavior( QAbstractItemView::SelectRows ); setSelectionMode( QAbstractItemView::ExtendedSelection ); updateProjectPaths(); } KDevDocumentView::~KDevDocumentView() {} KDevDocumentViewPlugin *KDevDocumentView::plugin() const { return m_plugin; } void KDevDocumentView::mousePressEvent( QMouseEvent * event ) { QModelIndex proxyIndex = indexAt( event->pos() ); QModelIndex index = m_proxy->mapToSource( proxyIndex ); if (event->button() == Qt::LeftButton && event->modifiers() == Qt::NoModifier) { if (proxyIndex.parent().isValid()) { // this is a document item KDevelop::IDocumentController* dc = m_plugin->core()->documentController(); QUrl documentUrl = static_cast(m_documentModel->itemFromIndex(index))->fileItem()->url(); if (dc->documentForUrl(documentUrl) != dc->activeDocument()) { dc->openDocument(documentUrl); return; } } else { // this is a folder item setExpanded(proxyIndex, !isExpanded(proxyIndex)); return; } } QTreeView::mousePressEvent( event ); } template void KDevDocumentView::visitItems(F f, bool selectedItems) { KDevelop::IDocumentController* dc = m_plugin->core()->documentController(); QList docs = selectedItems ? m_selectedDocs : m_unselectedDocs; foreach(const QUrl& url, docs) { KDevelop::IDocument* doc = dc->documentForUrl(url); if (doc) f(doc); } } namespace { class DocSaver { public: void operator()(KDevelop::IDocument* doc) { doc->save(); } }; class DocCloser { public: void operator()(KDevelop::IDocument* doc) { doc->close(); } }; class DocReloader { public: void operator()(KDevelop::IDocument* doc) { doc->reload(); } }; } void KDevDocumentView::saveSelected() { visitItems(DocSaver(), true); } void KDevDocumentView::closeSelected() { visitItems(DocCloser(), true); } void KDevDocumentView::closeUnselected() { visitItems(DocCloser(), false); } void KDevDocumentView::reloadSelected() { visitItems(DocReloader(), true); } void KDevDocumentView::contextMenuEvent( QContextMenuEvent * event ) { QModelIndex proxyIndex = indexAt( event->pos() ); // for now, ignore clicks on empty space or folder items if (!proxyIndex.isValid() || !proxyIndex.parent().isValid()) { return; } updateSelectedDocs(); if (!m_selectedDocs.isEmpty()) { QMenu* ctxMenu = new QMenu(this); KDevelop::FileContext context(m_selectedDocs); QList extensions = m_plugin->core()->pluginController()->queryPluginsForContextMenuExtensions( &context ); QList vcsActions; QList fileActions; QList editActions; QList extensionActions; foreach( const KDevelop::ContextMenuExtension& ext, extensions ) { fileActions += ext.actions(KDevelop::ContextMenuExtension::FileGroup); vcsActions += ext.actions(KDevelop::ContextMenuExtension::VcsGroup); editActions += ext.actions(KDevelop::ContextMenuExtension::EditGroup); extensionActions += ext.actions(KDevelop::ContextMenuExtension::ExtensionGroup); } appendActions(ctxMenu, fileActions); QAction* save = KStandardAction::save(this, SLOT(saveSelected()), ctxMenu); save->setEnabled(selectedDocHasChanges()); ctxMenu->addAction(save); ctxMenu->addAction(QIcon::fromTheme(QStringLiteral("view-refresh")), i18n( "Reload" ), this, SLOT(reloadSelected())); appendActions(ctxMenu, editActions); ctxMenu->addAction(KStandardAction::close(this, SLOT(closeSelected()), ctxMenu)); QAction* closeUnselected = ctxMenu->addAction(QIcon::fromTheme(QStringLiteral("document-close")), i18n( "Close Other Files" ), this, SLOT(closeUnselected())); closeUnselected->setEnabled(!m_unselectedDocs.isEmpty()); appendActions(ctxMenu, vcsActions); appendActions(ctxMenu, extensionActions); connect(ctxMenu, &QMenu::aboutToHide, ctxMenu, &QMenu::deleteLater); ctxMenu->popup( event->globalPos() ); } } void KDevDocumentView::appendActions(QMenu* menu, const QList& actions) { foreach( QAction* act, actions ) { menu->addAction(act); } menu->addSeparator(); } bool KDevDocumentView::selectedDocHasChanges() { KDevelop::IDocumentController* dc = m_plugin->core()->documentController(); foreach(const QUrl& url, m_selectedDocs) { KDevelop::IDocument* doc = dc->documentForUrl(url); if (!doc) continue; if (doc->state() != KDevelop::IDocument::Clean) { return true; } } return false; } void KDevDocumentView::updateSelectedDocs() { m_selectedDocs.clear(); m_unselectedDocs.clear(); - QList allItems = m_documentModel->findItems("*", Qt::MatchWildcard | Qt::MatchRecursive); + QList allItems = m_documentModel->findItems(QStringLiteral("*"), Qt::MatchWildcard | Qt::MatchRecursive); foreach (QStandardItem* item, allItems) { if (KDevFileItem * fileItem = dynamic_cast(item)->fileItem()) { if (m_selectionModel->isSelected(m_proxy->mapFromSource(m_documentModel->indexFromItem(fileItem)))) m_selectedDocs << fileItem->url(); else m_unselectedDocs << fileItem->url(); } } } void KDevDocumentView::activated( KDevelop::IDocument* document ) { setCurrentIndex( m_proxy->mapFromSource( m_documentModel->indexFromItem( m_doc2index[ document ] ) ) ); } void KDevDocumentView::saved( KDevelop::IDocument* ) { } void KDevDocumentView::opened( KDevelop::IDocument* document ) { const QString path = QFileInfo( document->url().path() ).path(); KDevCategoryItem *categoryItem = m_documentModel->category( path ); if ( !categoryItem ) { categoryItem = new KDevCategoryItem( path ); categoryItem->setUrl( document->url() ); m_documentModel->insertRow( m_documentModel->rowCount(), categoryItem ); setExpanded( m_proxy->mapFromSource( m_documentModel->indexFromItem( categoryItem ) ), false); updateCategoryItem( categoryItem ); } if ( !categoryItem->file( document->url() ) ) { KDevFileItem * fileItem = new KDevFileItem( document->url() ); categoryItem->setChild( categoryItem->rowCount(), fileItem ); setCurrentIndex( m_proxy->mapFromSource( m_documentModel->indexFromItem( fileItem ) ) ); m_doc2index[ document ] = fileItem; } } void KDevDocumentView::closed( KDevelop::IDocument* document ) { KDevFileItem* file = m_doc2index[ document ]; if ( !file ) return; QStandardItem* categoryItem = file->parent(); qDeleteAll(categoryItem->takeRow(m_documentModel->indexFromItem(file).row())); m_doc2index.remove(document); if ( categoryItem->hasChildren() ) return; qDeleteAll(m_documentModel->takeRow(m_documentModel->indexFromItem(categoryItem).row())); doItemsLayout(); } void KDevDocumentView::updateCategoryItem( KDevCategoryItem *item ) { QString text = KDevelop::ICore::self()->projectController()->prettyFilePath(item->url(), KDevelop::IProjectController::FormatPlain); // remove trailing slash if (text.length() > 1) { text.chop(1); } item->setText(text); } void KDevDocumentView::updateProjectPaths() { foreach ( KDevCategoryItem *it, m_documentModel->categoryList() ) updateCategoryItem( it ); } void KDevDocumentView::contentChanged( KDevelop::IDocument* ) { } void KDevDocumentView::stateChanged( KDevelop::IDocument* document ) { KDevDocumentItem * documentItem = m_doc2index[ document ]; if ( documentItem && documentItem->documentState() != document->state() ) documentItem->setDocumentState( document->state() ); doItemsLayout(); } void KDevDocumentView::documentUrlChanged( KDevelop::IDocument* document ) { closed(document); opened(document); } diff --git a/plugins/documentview/kdevdocumentviewplugin.cpp b/plugins/documentview/kdevdocumentviewplugin.cpp index abbcbef62..15b68ba96 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 ) {} QWidget* create( QWidget *parent = 0 ) 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 "org.kdevelop.DocumentsView"; + 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/executeplugin.cpp b/plugins/execute/executeplugin.cpp index 770e33eef..9532c85df 100644 --- a/plugins/execute/executeplugin.cpp +++ b/plugins/execute/executeplugin.cpp @@ -1,252 +1,252 @@ /* * This file is part of KDevelop * * Copyright 2007 Hamish Rodda * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "executeplugin.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "nativeappconfig.h" #include "debug.h" #include #include #include -QString ExecutePlugin::_nativeAppConfigTypeId = "Native Application"; -QString ExecutePlugin::workingDirEntry = "Working Directory"; -QString ExecutePlugin::executableEntry = "Executable"; -QString ExecutePlugin::argumentsEntry = "Arguments"; -QString ExecutePlugin::isExecutableEntry = "isExecutable"; -QString ExecutePlugin::dependencyEntry = "Dependencies"; -QString ExecutePlugin::environmentGroupEntry = "EnvironmentGroup"; -QString ExecutePlugin::useTerminalEntry = "Use External Terminal"; -QString ExecutePlugin::terminalEntry = "External Terminal"; -QString ExecutePlugin::userIdToRunEntry = "User Id to Run"; -QString ExecutePlugin::dependencyActionEntry = "Dependency Action"; -QString ExecutePlugin::projectTargetEntry = "Project Target"; +QString ExecutePlugin::_nativeAppConfigTypeId = QStringLiteral("Native Application"); +QString ExecutePlugin::workingDirEntry = QStringLiteral("Working Directory"); +QString ExecutePlugin::executableEntry = QStringLiteral("Executable"); +QString ExecutePlugin::argumentsEntry = QStringLiteral("Arguments"); +QString ExecutePlugin::isExecutableEntry = QStringLiteral("isExecutable"); +QString ExecutePlugin::dependencyEntry = QStringLiteral("Dependencies"); +QString ExecutePlugin::environmentGroupEntry = QStringLiteral("EnvironmentGroup"); +QString ExecutePlugin::useTerminalEntry = QStringLiteral("Use External Terminal"); +QString ExecutePlugin::terminalEntry = QStringLiteral("External Terminal"); +QString ExecutePlugin::userIdToRunEntry = QStringLiteral("User Id to Run"); +QString ExecutePlugin::dependencyActionEntry = QStringLiteral("Dependency Action"); +QString ExecutePlugin::projectTargetEntry = QStringLiteral("Project Target"); using namespace KDevelop; Q_LOGGING_CATEGORY(PLUGIN_EXECUTE, "kdevplatform.plugins.execute") K_PLUGIN_FACTORY_WITH_JSON(KDevExecuteFactory, "kdevexecute.json", registerPlugin();) ExecutePlugin::ExecutePlugin(QObject *parent, const QVariantList&) - : KDevelop::IPlugin("kdevexecute", parent) + : KDevelop::IPlugin(QStringLiteral("kdevexecute"), parent) { KDEV_USE_EXTENSION_INTERFACE( IExecutePlugin ) m_configType = new NativeAppConfigType(); m_configType->addLauncher( new NativeAppLauncher() ); qCDebug(PLUGIN_EXECUTE) << "adding native app launch config"; core()->runController()->addConfigurationType( m_configType ); } ExecutePlugin::~ExecutePlugin() { } void ExecutePlugin::unload() { core()->runController()->removeConfigurationType( m_configType ); delete m_configType; m_configType = 0; } QStringList ExecutePlugin::arguments( KDevelop::ILaunchConfiguration* cfg, QString& err_ ) const { if( !cfg ) { return QStringList(); } KShell::Errors err; QStringList args = KShell::splitArgs( cfg->config().readEntry( ExecutePlugin::argumentsEntry, "" ), KShell::TildeExpand | KShell::AbortOnMeta, &err ); if( err != KShell::NoError ) { if( err == KShell::BadQuoting ) { err_ = i18n("There is a quoting error in the arguments for " "the launch configuration '%1'. Aborting start.", cfg->name() ); } else { err_ = i18n("A shell meta character was included in the " "arguments for the launch configuration '%1', " "this is not supported currently. Aborting start.", cfg->name() ); } args = QStringList(); qWarning() << "Launch Configuration:" << cfg->name() << "arguments have meta characters"; } return args; } KJob* ExecutePlugin::dependencyJob( KDevelop::ILaunchConfiguration* cfg ) const { QVariantList deps = KDevelop::stringToQVariant( cfg->config().readEntry( dependencyEntry, QString() ) ).toList(); QString depAction = cfg->config().readEntry( dependencyActionEntry, "Nothing" ); - if( depAction != "Nothing" && !deps.isEmpty() ) + if( depAction != QLatin1String("Nothing") && !deps.isEmpty() ) { KDevelop::ProjectModel* model = KDevelop::ICore::self()->projectController()->projectModel(); QList items; foreach( const QVariant& dep, deps ) { KDevelop::ProjectBaseItem* item = model->itemFromIndex( model->pathToIndex( dep.toStringList() ) ); if( item ) { items << item; } else { KMessageBox::error(core()->uiController()->activeMainWindow(), i18n("Couldn't resolve the dependency: %1", dep.toString())); } } KDevelop::BuilderJob* job = new KDevelop::BuilderJob(); - if( depAction == "Build" ) + if( depAction == QLatin1String("Build") ) { job->addItems( KDevelop::BuilderJob::Build, items ); - } else if( depAction == "Install" ) + } else if( depAction == QLatin1String("Install") ) { job->addItems( KDevelop::BuilderJob::Install, items ); } job->updateJobName(); return job; } return 0; } QString ExecutePlugin::environmentGroup( KDevelop::ILaunchConfiguration* cfg ) const { if( !cfg ) { - return ""; + return QLatin1String(""); } return cfg->config().readEntry( ExecutePlugin::environmentGroupEntry, "" ); } QUrl ExecutePlugin::executable( KDevelop::ILaunchConfiguration* cfg, QString& err ) const { QUrl executable; if( !cfg ) { return executable; } KConfigGroup grp = cfg->config(); if( grp.readEntry(ExecutePlugin::isExecutableEntry, false ) ) { executable = grp.readEntry( ExecutePlugin::executableEntry, QUrl() ); } else { QStringList prjitem = grp.readEntry( ExecutePlugin::projectTargetEntry, QStringList() ); KDevelop::ProjectModel* model = KDevelop::ICore::self()->projectController()->projectModel(); KDevelop::ProjectBaseItem* item = model->itemFromIndex( model->pathToIndex(prjitem) ); if( item && item->executable() ) { // TODO: Need an option in the gui to choose between installed and builddir url here, currently cmake only supports builddir url executable = item->executable()->builtUrl(); } } if( executable.isEmpty() ) { err = i18n("No valid executable specified"); qWarning() << "Launch Configuration:" << cfg->name() << "no valid executable set"; } else { KShell::Errors err_; if( KShell::splitArgs( executable.toLocalFile(), KShell::TildeExpand | KShell::AbortOnMeta, &err_ ).isEmpty() || err_ != KShell::NoError ) { executable = QUrl(); if( err_ == KShell::BadQuoting ) { err = i18n("There is a quoting error in the executable " "for the launch configuration '%1'. " "Aborting start.", cfg->name() ); } else { err = i18n("A shell meta character was included in the " "executable for the launch configuration '%1', " "this is not supported currently. Aborting start.", cfg->name() ); } qWarning() << "Launch Configuration:" << cfg->name() << "executable has meta characters"; } } return executable; } bool ExecutePlugin::useTerminal( KDevelop::ILaunchConfiguration* cfg ) const { if( !cfg ) { return false; } return cfg->config().readEntry( ExecutePlugin::useTerminalEntry, false ); } QString ExecutePlugin::terminal( KDevelop::ILaunchConfiguration* cfg ) const { if( !cfg ) { return QString(); } return cfg->config().readEntry( ExecutePlugin::terminalEntry, QString() ); } QUrl ExecutePlugin::workingDirectory( KDevelop::ILaunchConfiguration* cfg ) const { if( !cfg ) { return QUrl(); } return cfg->config().readEntry( ExecutePlugin::workingDirEntry, QUrl() ); } QString ExecutePlugin::nativeAppConfigTypeId() const { return _nativeAppConfigTypeId; } #include "executeplugin.moc" diff --git a/plugins/execute/nativeappconfig.cpp b/plugins/execute/nativeappconfig.cpp index a47dff782..47f7e7fff 100644 --- a/plugins/execute/nativeappconfig.cpp +++ b/plugins/execute/nativeappconfig.cpp @@ -1,524 +1,524 @@ /* This file is part of KDevelop Copyright 2009 Andreas Pakulat Copyright 2010 Aleix Pol Gonzalez This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "nativeappconfig.h" #include #include #include #include #include "nativeappjob.h" #include #include #include #include #include #include #include #include #include "executeplugin.h" #include "debug.h" #include #include #include #include "projecttargetscombobox.h" #include #include #include #include #include #include #include using namespace KDevelop; QIcon NativeAppConfigPage::icon() const { - return QIcon::fromTheme("system-run"); + return QIcon::fromTheme(QStringLiteral("system-run")); } static KDevelop::ProjectBaseItem* itemForPath(const QStringList& path, KDevelop::ProjectModel* model) { return model->itemFromIndex(model->pathToIndex(path)); } //TODO: Make sure to auto-add the executable target to the dependencies when its used. void NativeAppConfigPage::loadFromConfiguration(const KConfigGroup& cfg, KDevelop::IProject* project ) { bool b = blockSignals( true ); projectTarget->setBaseItem( project ? project->projectItem() : 0, true); projectTarget->setCurrentItemPath( cfg.readEntry( ExecutePlugin::projectTargetEntry, QStringList() ) ); QUrl exe = cfg.readEntry( ExecutePlugin::executableEntry, QUrl()); if( !exe.isEmpty() || project ){ executablePath->setUrl( !exe.isEmpty() ? exe : project->path().toUrl() ); }else{ KDevelop::IProjectController* pc = KDevelop::ICore::self()->projectController(); if( pc ){ - executablePath->setUrl( pc->projects().count() ? pc->projects().first()->path().toUrl() : QUrl() ); + executablePath->setUrl( pc->projects().isEmpty() ? QUrl() : pc->projects().at(0)->path().toUrl() ); } } targetDependency->setSuggestion(project); //executablePath->setFilter("application/x-executable"); executableRadio->setChecked( true ); if ( !cfg.readEntry( ExecutePlugin::isExecutableEntry, false ) && projectTarget->count() ){ projectTargetRadio->setChecked( true ); } arguments->setClearButtonEnabled( true ); arguments->setText( cfg.readEntry( ExecutePlugin::argumentsEntry, "" ) ); workingDirectory->setUrl( cfg.readEntry( ExecutePlugin::workingDirEntry, QUrl() ) ); environment->setCurrentProfile( cfg.readEntry( ExecutePlugin::environmentGroupEntry, QString() ) ); runInTerminal->setChecked( cfg.readEntry( ExecutePlugin::useTerminalEntry, false ) ); terminal->setEditText( cfg.readEntry( ExecutePlugin::terminalEntry, terminal->itemText(0) ) ); QVariantList deps = KDevelop::stringToQVariant( cfg.readEntry( ExecutePlugin::dependencyEntry, QString() ) ).toList(); QStringList strDeps; foreach( const QVariant& dep, deps ) { QStringList deplist = dep.toStringList(); KDevelop::ProjectModel* model = KDevelop::ICore::self()->projectController()->projectModel(); KDevelop::ProjectBaseItem* pitem=itemForPath(deplist, model); QIcon icon; if(pitem) icon=QIcon::fromTheme(pitem->iconName()); QListWidgetItem* item = new QListWidgetItem(icon, KDevelop::joinWithEscaping( deplist, '/', '\\' ), dependencies ); item->setData( Qt::UserRole, dep ); } dependencyAction->setCurrentIndex( dependencyAction->findData( cfg.readEntry( ExecutePlugin::dependencyActionEntry, "Nothing" ) ) ); blockSignals( b ); } NativeAppConfigPage::NativeAppConfigPage( QWidget* parent ) : LaunchConfigurationPage( parent ) { setupUi(this); //Setup data info for combobox dependencyAction->setItemData(0, "Nothing" ); dependencyAction->setItemData(1, "Build" ); dependencyAction->setItemData(2, "Install" ); dependencyAction->setItemData(3, "SudoInstall" ); - addDependency->setIcon( QIcon::fromTheme("list-add") ); - removeDependency->setIcon( QIcon::fromTheme("list-remove") ); - moveDepUp->setIcon( QIcon::fromTheme("go-up") ); - moveDepDown->setIcon( QIcon::fromTheme("go-down") ); - browseProject->setIcon(QIcon::fromTheme("folder-document")); + addDependency->setIcon( QIcon::fromTheme(QStringLiteral("list-add")) ); + removeDependency->setIcon( QIcon::fromTheme(QStringLiteral("list-remove")) ); + moveDepUp->setIcon( QIcon::fromTheme(QStringLiteral("go-up")) ); + moveDepDown->setIcon( QIcon::fromTheme(QStringLiteral("go-down")) ); + browseProject->setIcon(QIcon::fromTheme(QStringLiteral("folder-document"))); //Set workingdirectory widget to ask for directories rather than files workingDirectory->setMode(KFile::Directory | KFile::ExistingOnly | KFile::LocalOnly); configureEnvironment->setSelectionWidget(environment); //connect signals to changed signal connect( projectTarget, static_cast(&ProjectTargetsComboBox::currentIndexChanged), this, &NativeAppConfigPage::changed ); connect( projectTargetRadio, &QRadioButton::toggled, this, &NativeAppConfigPage::changed ); connect( executableRadio, &QRadioButton::toggled, this, &NativeAppConfigPage::changed ); connect( executablePath->lineEdit(), &KLineEdit::textEdited, this, &NativeAppConfigPage::changed ); connect( executablePath, &KUrlRequester::urlSelected, this, &NativeAppConfigPage::changed ); connect( arguments, &QLineEdit::textEdited, this, &NativeAppConfigPage::changed ); connect( workingDirectory, &KUrlRequester::urlSelected, this, &NativeAppConfigPage::changed ); connect( workingDirectory->lineEdit(), &KLineEdit::textEdited, this, &NativeAppConfigPage::changed ); connect( environment, &EnvironmentSelectionWidget::currentProfileChanged, this, &NativeAppConfigPage::changed ); connect( addDependency, &QPushButton::clicked, this, &NativeAppConfigPage::addDep ); connect( addDependency, &QPushButton::clicked, this, &NativeAppConfigPage::changed ); connect( removeDependency, &QPushButton::clicked, this, &NativeAppConfigPage::changed ); connect( removeDependency, &QPushButton::clicked, this, &NativeAppConfigPage::removeDep ); connect( moveDepDown, &QPushButton::clicked, this, &NativeAppConfigPage::changed ); connect( moveDepUp, &QPushButton::clicked, this, &NativeAppConfigPage::changed ); connect( moveDepDown, &QPushButton::clicked, this, &NativeAppConfigPage::moveDependencyDown ); connect( moveDepUp, &QPushButton::clicked, this, &NativeAppConfigPage::moveDependencyUp ); connect( dependencies->selectionModel(), &QItemSelectionModel::selectionChanged, this, &NativeAppConfigPage::checkActions ); connect( dependencyAction, static_cast(&KComboBox::currentIndexChanged), this, &NativeAppConfigPage::changed ); connect( runInTerminal, &QCheckBox::toggled, this, &NativeAppConfigPage::changed ); connect( terminal, &KComboBox::editTextChanged, this, &NativeAppConfigPage::changed ); connect( terminal, static_cast(&KComboBox::currentIndexChanged), this, &NativeAppConfigPage::changed ); connect( dependencyAction, static_cast(&KComboBox::currentIndexChanged), this, &NativeAppConfigPage::activateDeps ); connect( targetDependency, &ProjectItemLineEdit::textChanged, this, &NativeAppConfigPage::depEdited); connect( browseProject, &QPushButton::clicked, this, &NativeAppConfigPage::selectItemDialog); } void NativeAppConfigPage::depEdited( const QString& str ) { int pos; QString tmp = str; addDependency->setEnabled( !str.isEmpty() && ( !targetDependency->validator() || targetDependency->validator()->validate( tmp, pos ) == QValidator::Acceptable ) ); } void NativeAppConfigPage::activateDeps( int idx ) { - browseProject->setEnabled( dependencyAction->itemData( idx ).toString() != "Nothing" ); - dependencies->setEnabled( dependencyAction->itemData( idx ).toString() != "Nothing" ); - targetDependency->setEnabled( dependencyAction->itemData( idx ).toString() != "Nothing" ); + browseProject->setEnabled( dependencyAction->itemData( idx ).toString() != QLatin1String("Nothing") ); + dependencies->setEnabled( dependencyAction->itemData( idx ).toString() != QLatin1String("Nothing") ); + targetDependency->setEnabled( dependencyAction->itemData( idx ).toString() != QLatin1String("Nothing") ); } void NativeAppConfigPage::checkActions( const QItemSelection& selected, const QItemSelection& unselected ) { Q_UNUSED( unselected ); qCDebug(PLUGIN_EXECUTE) << "checkActions"; if( !selected.indexes().isEmpty() ) { qCDebug(PLUGIN_EXECUTE) << "have selection"; Q_ASSERT( selected.indexes().count() == 1 ); QModelIndex idx = selected.indexes().at( 0 ); qCDebug(PLUGIN_EXECUTE) << "index" << idx; moveDepUp->setEnabled( idx.row() > 0 ); moveDepDown->setEnabled( idx.row() < dependencies->count() - 1 ); removeDependency->setEnabled( true ); } else { removeDependency->setEnabled( false ); moveDepUp->setEnabled( false ); moveDepDown->setEnabled( false ); } } void NativeAppConfigPage::moveDependencyDown() { QList list = dependencies->selectedItems(); if( !list.isEmpty() ) { Q_ASSERT( list.count() == 1 ); QListWidgetItem* item = list.at( 0 ); int row = dependencies->row( item ); dependencies->takeItem( row ); dependencies->insertItem( row+1, item ); dependencies->selectionModel()->select( dependencies->model()->index( row+1, 0, QModelIndex() ), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::SelectCurrent ); } } void NativeAppConfigPage::moveDependencyUp() { QList list = dependencies->selectedItems(); if( !list.isEmpty() ) { Q_ASSERT( list.count() == 1 ); QListWidgetItem* item = list.at( 0 ); int row = dependencies->row( item ); dependencies->takeItem( row ); dependencies->insertItem( row-1, item ); dependencies->selectionModel()->select( dependencies->model()->index( row-1, 0, QModelIndex() ), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::SelectCurrent ); } } void NativeAppConfigPage::addDep() { QIcon icon; KDevelop::ProjectBaseItem* pitem = targetDependency->currentItem(); if(pitem) icon = QIcon::fromTheme(pitem->iconName()); QListWidgetItem* item = new QListWidgetItem(icon, targetDependency->text(), dependencies); item->setData( Qt::UserRole, targetDependency->itemPath() ); - targetDependency->setText(""); + targetDependency->setText(QLatin1String("")); addDependency->setEnabled( false ); dependencies->selectionModel()->clearSelection(); item->setSelected(true); // dependencies->selectionModel()->select( dependencies->model()->index( dependencies->model()->rowCount() - 1, 0, QModelIndex() ), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::SelectCurrent ); } void NativeAppConfigPage::selectItemDialog() { if(targetDependency->selectItemDialog()) { addDep(); } } void NativeAppConfigPage::removeDep() { QList list = dependencies->selectedItems(); if( !list.isEmpty() ) { Q_ASSERT( list.count() == 1 ); int row = dependencies->row( list.at(0) ); delete dependencies->takeItem( row ); dependencies->selectionModel()->select( dependencies->model()->index( row - 1, 0, QModelIndex() ), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::SelectCurrent ); } } void NativeAppConfigPage::saveToConfiguration( KConfigGroup cfg, KDevelop::IProject* project ) const { Q_UNUSED( project ); cfg.writeEntry( ExecutePlugin::isExecutableEntry, executableRadio->isChecked() ); cfg.writeEntry( ExecutePlugin::executableEntry, executablePath->url() ); cfg.writeEntry( ExecutePlugin::projectTargetEntry, projectTarget->currentItemPath() ); cfg.writeEntry( ExecutePlugin::argumentsEntry, arguments->text() ); cfg.writeEntry( ExecutePlugin::workingDirEntry, workingDirectory->url() ); cfg.writeEntry( ExecutePlugin::environmentGroupEntry, environment->currentProfile() ); cfg.writeEntry( ExecutePlugin::useTerminalEntry, runInTerminal->isChecked() ); cfg.writeEntry( ExecutePlugin::terminalEntry, terminal->currentText() ); cfg.writeEntry( ExecutePlugin::dependencyActionEntry, dependencyAction->itemData( dependencyAction->currentIndex() ).toString() ); QVariantList deps; for( int i = 0; i < dependencies->count(); i++ ) { deps << dependencies->item( i )->data( Qt::UserRole ); } cfg.writeEntry( ExecutePlugin::dependencyEntry, KDevelop::qvariantToString( QVariant( deps ) ) ); } QString NativeAppConfigPage::title() const { return i18n("Configure Native Application"); } QList< KDevelop::LaunchConfigurationPageFactory* > NativeAppLauncher::configPages() const { return QList(); } QString NativeAppLauncher::description() const { - return "Executes Native Applications"; + return QStringLiteral("Executes Native Applications"); } QString NativeAppLauncher::id() { - return "nativeAppLauncher"; + return QStringLiteral("nativeAppLauncher"); } QString NativeAppLauncher::name() const { return i18n("Native Application"); } NativeAppLauncher::NativeAppLauncher() { } KJob* NativeAppLauncher::start(const QString& launchMode, KDevelop::ILaunchConfiguration* cfg) { Q_ASSERT(cfg); if( !cfg ) { return 0; } - if( launchMode == "execute" ) + if( launchMode == QLatin1String("execute") ) { - IExecutePlugin* iface = KDevelop::ICore::self()->pluginController()->pluginForExtension("org.kdevelop.IExecutePlugin", "kdevexecute")->extension(); + IExecutePlugin* iface = KDevelop::ICore::self()->pluginController()->pluginForExtension(QStringLiteral("org.kdevelop.IExecutePlugin"), QStringLiteral("kdevexecute"))->extension(); Q_ASSERT(iface); KJob* depjob = iface->dependencyJob( cfg ); QList l; if( depjob ) { l << depjob; } l << new NativeAppJob( KDevelop::ICore::self()->runController(), cfg ); return new KDevelop::ExecuteCompositeJob( KDevelop::ICore::self()->runController(), l ); } qWarning() << "Unknown launch mode " << launchMode << "for config:" << cfg->name(); return 0; } QStringList NativeAppLauncher::supportedModes() const { - return QStringList() << "execute"; + return QStringList() << QStringLiteral("execute"); } KDevelop::LaunchConfigurationPage* NativeAppPageFactory::createWidget(QWidget* parent) { return new NativeAppConfigPage( parent ); } NativeAppPageFactory::NativeAppPageFactory() { } NativeAppConfigType::NativeAppConfigType() { factoryList.append( new NativeAppPageFactory() ); } NativeAppConfigType::~NativeAppConfigType() { qDeleteAll(factoryList); factoryList.clear(); } QString NativeAppConfigType::name() const { return i18n("Compiled Binary"); } QList NativeAppConfigType::configPages() const { return factoryList; } QString NativeAppConfigType::id() const { return ExecutePlugin::_nativeAppConfigTypeId; } QIcon NativeAppConfigType::icon() const { - return QIcon::fromTheme("application-x-executable"); + return QIcon::fromTheme(QStringLiteral("application-x-executable")); } bool NativeAppConfigType::canLaunch ( KDevelop::ProjectBaseItem* item ) const { if( item->target() && item->target()->executable() ) { return canLaunch( item->target()->executable()->builtUrl() ); } return false; } bool NativeAppConfigType::canLaunch ( const QUrl& file ) const { return ( file.isLocalFile() && QFileInfo( file.toLocalFile() ).isExecutable() ); } void NativeAppConfigType::configureLaunchFromItem ( KConfigGroup cfg, KDevelop::ProjectBaseItem* item ) const { cfg.writeEntry( ExecutePlugin::isExecutableEntry, false ); KDevelop::ProjectModel* model = KDevelop::ICore::self()->projectController()->projectModel(); cfg.writeEntry( ExecutePlugin::projectTargetEntry, model->pathFromIndex( model->indexFromItem( item ) ) ); cfg.writeEntry( ExecutePlugin::workingDirEntry, item->executable()->builtUrl().adjusted(QUrl::RemoveFilename) ); cfg.sync(); } void NativeAppConfigType::configureLaunchFromCmdLineArguments ( KConfigGroup cfg, const QStringList& args ) const { cfg.writeEntry( ExecutePlugin::isExecutableEntry, true ); Q_ASSERT(QFile::exists(args.first())); // TODO: we probably want to flexibilize, but at least we won't be accepting wrong values anymore cfg.writeEntry( ExecutePlugin::executableEntry, QUrl::fromLocalFile(args.first()) ); QStringList a(args); a.removeFirst(); cfg.writeEntry( ExecutePlugin::argumentsEntry, KShell::joinArgs(a) ); cfg.sync(); } QList targetsInFolder(KDevelop::ProjectFolderItem* folder) { QList ret; foreach(KDevelop::ProjectFolderItem* f, folder->folderList()) ret += targetsInFolder(f); ret += folder->targetList(); return ret; } bool actionLess(QAction* a, QAction* b) { return a->text() < b->text(); } bool menuLess(QMenu* a, QMenu* b) { return a->title() < b->title(); } QMenu* NativeAppConfigType::launcherSuggestions() { QMenu* ret = new QMenu(i18n("Project Executables")); KDevelop::ProjectModel* model = KDevelop::ICore::self()->projectController()->projectModel(); QList projects = KDevelop::ICore::self()->projectController()->projects(); foreach(KDevelop::IProject* project, projects) { if(project->projectFileManager()->features() & KDevelop::IProjectFileManager::Targets) { QList targets=targetsInFolder(project->projectItem()); QHash > targetsContainer; - QMenu* projectMenu = ret->addMenu(QIcon::fromTheme("project-development"), project->name()); + QMenu* projectMenu = ret->addMenu(QIcon::fromTheme(QStringLiteral("project-development")), project->name()); foreach(KDevelop::ProjectTargetItem* target, targets) { if(target->executable()) { QStringList path = model->pathFromIndex(target->index()); if(!path.isEmpty()){ QAction* act = new QAction(projectMenu); act->setData(KDevelop::joinWithEscaping(path, '/','\\')); act->setProperty("name", target->text()); path.removeFirst(); - act->setText(path.join("/")); - act->setIcon(QIcon::fromTheme("system-run")); + act->setText(path.join(QStringLiteral("/"))); + act->setIcon(QIcon::fromTheme(QStringLiteral("system-run"))); connect(act, &QAction::triggered, this, &NativeAppConfigType::suggestionTriggered); targetsContainer[target->parent()].append(act); } } } QList separateActions; QList submenus; foreach(KDevelop::ProjectBaseItem* folder, targetsContainer.keys()) { QList actions = targetsContainer.value(folder); if(actions.size()==1 || !folder->parent()) { separateActions += actions.first(); } else { foreach(QAction* a, actions) { a->setText(a->property("name").toString()); } QStringList path = model->pathFromIndex(folder->index()); path.removeFirst(); - QMenu* submenu = new QMenu(path.join("/")); + QMenu* submenu = new QMenu(path.join(QStringLiteral("/"))); std::sort(actions.begin(), actions.end(), actionLess); submenu->addActions(actions); submenus += submenu; } } std::sort(separateActions.begin(), separateActions.end(), actionLess); std::sort(submenus.begin(), submenus.end(), menuLess); foreach(QMenu* m, submenus) projectMenu->addMenu(m); projectMenu->addActions(separateActions); projectMenu->setEnabled(!projectMenu->isEmpty()); } } return ret; } void NativeAppConfigType::suggestionTriggered() { QAction* action = qobject_cast(sender()); KDevelop::ProjectModel* model = KDevelop::ICore::self()->projectController()->projectModel(); KDevelop::ProjectTargetItem* pitem = dynamic_cast(itemForPath(KDevelop::splitWithEscaping(action->data().toString(),'/', '\\'), model)); if(pitem) { QPair launcher = qMakePair( launchers().at( 0 )->supportedModes().at(0), launchers().at( 0 )->id() ); KDevelop::IProject* p = pitem->project(); KDevelop::ILaunchConfiguration* config = KDevelop::ICore::self()->runController()->createLaunchConfiguration(this, launcher, p, pitem->text()); KConfigGroup cfg = config->config(); QStringList splitPath = model->pathFromIndex(pitem->index()); // QString path = KDevelop::joinWithEscaping(splitPath,'/','\\'); cfg.writeEntry( ExecutePlugin::projectTargetEntry, splitPath ); cfg.writeEntry( ExecutePlugin::dependencyEntry, KDevelop::qvariantToString( QVariantList() << splitPath ) ); cfg.writeEntry( ExecutePlugin::dependencyActionEntry, "Build" ); cfg.sync(); emit signalAddLaunchConfiguration(config); } } diff --git a/plugins/execute/nativeappjob.cpp b/plugins/execute/nativeappjob.cpp index 20f97c62a..fcc0a3841 100644 --- a/plugins/execute/nativeappjob.cpp +++ b/plugins/execute/nativeappjob.cpp @@ -1,146 +1,146 @@ /* This file is part of KDevelop Copyright 2009 Andreas Pakulat This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "nativeappjob.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "iexecuteplugin.h" #include "debug.h" using namespace KDevelop; NativeAppJob::NativeAppJob(QObject* parent, KDevelop::ILaunchConfiguration* cfg) : KDevelop::OutputExecuteJob( parent ) , m_cfgname(cfg->name()) { setCapabilities(Killable); - IExecutePlugin* iface = KDevelop::ICore::self()->pluginController()->pluginForExtension("org.kdevelop.IExecutePlugin", "kdevexecute")->extension(); + IExecutePlugin* iface = KDevelop::ICore::self()->pluginController()->pluginForExtension(QStringLiteral("org.kdevelop.IExecutePlugin"), QStringLiteral("kdevexecute"))->extension(); Q_ASSERT(iface); KDevelop::EnvironmentGroupList l(KSharedConfig::openConfig()); QString envgrp = iface->environmentGroup(cfg); QString err; QUrl executable = iface->executable( cfg, err ); if( !err.isEmpty() ) { setError( -1 ); setErrorText( err ); return; } if( envgrp.isEmpty() ) { qWarning() << "Launch Configuration:" << cfg->name() << i18n("No environment group specified, looks like a broken " "configuration, please check run configuration '%1'. " "Using default environment group.", cfg->name() ); envgrp = l.defaultGroup(); } setEnvironmentProfile(envgrp); QStringList arguments = iface->arguments( cfg, err ); if( !err.isEmpty() ) { setError( -2 ); setErrorText( err ); } if( error() != 0 ) { qWarning() << "Launch Configuration:" << cfg->name() << "oops, problem" << errorText(); return; } setStandardToolView(KDevelop::IOutputView::RunView); setBehaviours(KDevelop::IOutputView::AllowUserClose | KDevelop::IOutputView::AutoScroll); setFilteringStrategy(OutputModel::NativeAppErrorFilter); setProperties(DisplayStdout | DisplayStderr); // Now setup the process parameters QUrl wc = iface->workingDirectory( cfg ); if( !wc.isValid() || wc.isEmpty() ) { wc = QUrl::fromLocalFile( QFileInfo( executable.toLocalFile() ).absolutePath() ); } setWorkingDirectory( wc ); qCDebug(PLUGIN_EXECUTE) << "setting app:" << executable << arguments; if (iface->useTerminal(cfg)) { QStringList args = KShell::splitArgs(iface->terminal(cfg)); for (QStringList::iterator it = args.begin(); it != args.end(); ++it) { - if (*it == "%exe") { + if (*it == QLatin1String("%exe")) { *it = KShell::quoteArg(executable.toLocalFile()); - } else if (*it == "%workdir") { + } else if (*it == QLatin1String("%workdir")) { *it = KShell::quoteArg(wc.toLocalFile()); } } args.append( arguments ); *this << args; } else { *this << executable.toLocalFile(); *this << arguments; } setJobName(cfg->name()); } NativeAppJob* findNativeJob(KJob* j) { NativeAppJob* job = qobject_cast(j); if (!job) { const QList jobs = j->findChildren(); if (!jobs.isEmpty()) job = jobs.first(); } return job; } void NativeAppJob::start() { // we kill any execution of the configuration foreach(KJob* j, ICore::self()->runController()->currentJobs()) { NativeAppJob* job = findNativeJob(j); if (job && job != this && job->m_cfgname == m_cfgname) { QMessageBox::StandardButton button = QMessageBox::question(nullptr, i18n("Job already running"), i18n("'%1' is already being executed. Should we kill the previous instance?", m_cfgname)); if (button != QMessageBox::No) j->kill(); } } OutputExecuteJob::start(); } diff --git a/plugins/execute/projecttargetscombobox.cpp b/plugins/execute/projecttargetscombobox.cpp index a4d962c10..5a67d48ce 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) {} 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("system-run"), item); + 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/executescript/executescriptplugin.cpp b/plugins/executescript/executescriptplugin.cpp index d62533ed6..8b9718cfa 100644 --- a/plugins/executescript/executescriptplugin.cpp +++ b/plugins/executescript/executescriptplugin.cpp @@ -1,263 +1,263 @@ /* * This file is part of KDevelop * * Copyright 2007 Hamish Rodda * Copyright 2009 Niko Sams * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "executescriptplugin.h" #include #include #include #include #include #include #include #include #include #include #include "scriptappconfig.h" #include "debug.h" #include #include #include -QString ExecuteScriptPlugin::_scriptAppConfigTypeId = "Script Application"; -QString ExecuteScriptPlugin::interpreterEntry = "Interpreter"; -QString ExecuteScriptPlugin::workingDirEntry = "Working Directory"; -QString ExecuteScriptPlugin::executableEntry = "Executable"; -QString ExecuteScriptPlugin::executeOnRemoteHostEntry = "Execute on Remote Host"; -QString ExecuteScriptPlugin::runCurrentFileEntry = "Run current file"; -QString ExecuteScriptPlugin::remoteHostEntry = "Remote Host"; -QString ExecuteScriptPlugin::argumentsEntry = "Arguments"; -QString ExecuteScriptPlugin::isExecutableEntry = "isExecutable"; -QString ExecuteScriptPlugin::environmentGroupEntry = "EnvironmentGroup"; +QString ExecuteScriptPlugin::_scriptAppConfigTypeId = QStringLiteral("Script Application"); +QString ExecuteScriptPlugin::interpreterEntry = QStringLiteral("Interpreter"); +QString ExecuteScriptPlugin::workingDirEntry = QStringLiteral("Working Directory"); +QString ExecuteScriptPlugin::executableEntry = QStringLiteral("Executable"); +QString ExecuteScriptPlugin::executeOnRemoteHostEntry = QStringLiteral("Execute on Remote Host"); +QString ExecuteScriptPlugin::runCurrentFileEntry = QStringLiteral("Run current file"); +QString ExecuteScriptPlugin::remoteHostEntry = QStringLiteral("Remote Host"); +QString ExecuteScriptPlugin::argumentsEntry = QStringLiteral("Arguments"); +QString ExecuteScriptPlugin::isExecutableEntry = QStringLiteral("isExecutable"); +QString ExecuteScriptPlugin::environmentGroupEntry = QStringLiteral("EnvironmentGroup"); //QString ExecuteScriptPlugin::useTerminalEntry = "Use External Terminal"; -QString ExecuteScriptPlugin::userIdToRunEntry = "User Id to Run"; -QString ExecuteScriptPlugin::projectTargetEntry = "Project Target"; -QString ExecuteScriptPlugin::outputFilteringEntry = "Output Filtering Mode"; +QString ExecuteScriptPlugin::userIdToRunEntry = QStringLiteral("User Id to Run"); +QString ExecuteScriptPlugin::projectTargetEntry = QStringLiteral("Project Target"); +QString ExecuteScriptPlugin::outputFilteringEntry = QStringLiteral("Output Filtering Mode"); using namespace KDevelop; Q_LOGGING_CATEGORY(PLUGIN_EXECUTESCRIPT, "kdevplatform.plugins.executescript") K_PLUGIN_FACTORY_WITH_JSON(KDevExecuteFactory, "kdevexecutescript.json", registerPlugin();) ExecuteScriptPlugin::ExecuteScriptPlugin(QObject *parent, const QVariantList&) : KDevelop::IPlugin(QStringLiteral("kdevexecutescript"), parent) { KDEV_USE_EXTENSION_INTERFACE( IExecuteScriptPlugin ) m_configType = new ScriptAppConfigType(); m_configType->addLauncher( new ScriptAppLauncher( this ) ); qCDebug(PLUGIN_EXECUTESCRIPT) << "adding script launch config"; core()->runController()->addConfigurationType( m_configType ); } ExecuteScriptPlugin::~ExecuteScriptPlugin() { } void ExecuteScriptPlugin::unload() { core()->runController()->removeConfigurationType( m_configType ); delete m_configType; m_configType = 0; } QUrl ExecuteScriptPlugin::script( KDevelop::ILaunchConfiguration* cfg, QString& err_ ) const { QUrl script; if( !cfg ) { return script; } KConfigGroup grp = cfg->config(); script = grp.readEntry( ExecuteScriptPlugin::executableEntry, QUrl() ); if( !script.isLocalFile() || script.isEmpty() ) { err_ = i18n("No valid executable specified"); qWarning() << "Launch Configuration:" << cfg->name() << "no valid script set"; } else { KShell::Errors err; if( KShell::splitArgs( script.toLocalFile(), KShell::TildeExpand | KShell::AbortOnMeta, &err ).isEmpty() || err != KShell::NoError ) { script = QUrl(); if( err == KShell::BadQuoting ) { err_ = i18n("There is a quoting error in the script " "for the launch configuration '%1'. " "Aborting start.", cfg->name() ); } else { err_ = i18n("A shell meta character was included in the " "script for the launch configuration '%1', " "this is not supported currently. Aborting start.", cfg->name() ); } qWarning() << "Launch Configuration:" << cfg->name() << "script has meta characters"; } } return script; } QString ExecuteScriptPlugin::remoteHost(ILaunchConfiguration* cfg, QString& err) const { if (!cfg) return QString(); KConfigGroup grp = cfg->config(); if(grp.readEntry(ExecuteScriptPlugin::executeOnRemoteHostEntry, false)) { QString host = grp.readEntry(ExecuteScriptPlugin::remoteHostEntry, ""); if (host.isEmpty()) { err = i18n("No remote host set for launch configuration '%1'. " "Aborting start.", cfg->name() ); qWarning() << "Launch Configuration:" << cfg->name() << "no remote host set"; } return host; } return QString(); } QStringList ExecuteScriptPlugin::arguments( KDevelop::ILaunchConfiguration* cfg, QString& err_ ) const { if( !cfg ) { return QStringList(); } KShell::Errors err; QStringList args = KShell::splitArgs( cfg->config().readEntry( ExecuteScriptPlugin::argumentsEntry, "" ), KShell::TildeExpand | KShell::AbortOnMeta, &err ); if( err != KShell::NoError ) { if( err == KShell::BadQuoting ) { err_ = i18n("There is a quoting error in the arguments for " "the launch configuration '%1'. Aborting start.", cfg->name() ); } else { err_ = i18n("A shell meta character was included in the " "arguments for the launch configuration '%1', " "this is not supported currently. Aborting start.", cfg->name() ); } args = QStringList(); qWarning() << "Launch Configuration:" << cfg->name() << "arguments have meta characters"; } return args; } QString ExecuteScriptPlugin::environmentGroup( KDevelop::ILaunchConfiguration* cfg ) const { if( !cfg ) { - return ""; + return QString(); } return cfg->config().readEntry( ExecuteScriptPlugin::environmentGroupEntry, "" ); } int ExecuteScriptPlugin::outputFilterModeId( KDevelop::ILaunchConfiguration* cfg ) const { if( !cfg ) { return 0; } return cfg->config().readEntry( ExecuteScriptPlugin::outputFilteringEntry, 0 ); } bool ExecuteScriptPlugin::runCurrentFile(ILaunchConfiguration* cfg) const { if( !cfg ) { return false; } return cfg->config().readEntry( ExecuteScriptPlugin::runCurrentFileEntry, true ); } QString ExecuteScriptPlugin::interpreter( KDevelop::ILaunchConfiguration* cfg, QString& err ) const { QString interpreter; if( !cfg ) { return interpreter; } KConfigGroup grp = cfg->config(); interpreter = grp.readEntry( ExecuteScriptPlugin::interpreterEntry, QString() ); if( interpreter.isEmpty() ) { err = i18n("No valid interpreter specified"); qWarning() << "Launch Configuration:" << cfg->name() << "no valid interpreter set"; } else { KShell::Errors err_; if( KShell::splitArgs( interpreter, KShell::TildeExpand | KShell::AbortOnMeta, &err_ ).isEmpty() || err_ != KShell::NoError ) { interpreter.clear(); if( err_ == KShell::BadQuoting ) { err = i18n("There is a quoting error in the interpreter " "for the launch configuration '%1'. " "Aborting start.", cfg->name() ); } else { err = i18n("A shell meta character was included in the " "interpreter for the launch configuration '%1', " "this is not supported currently. Aborting start.", cfg->name() ); } qWarning() << "Launch Configuration:" << cfg->name() << "interpreter has meta characters"; } } return interpreter; } /* bool ExecuteScriptPlugin::useTerminal( KDevelop::ILaunchConfiguration* cfg ) const { if( !cfg ) { return false; } return cfg->config().readEntry( ExecuteScriptPlugin::useTerminalEntry, false ); } */ QUrl ExecuteScriptPlugin::workingDirectory( KDevelop::ILaunchConfiguration* cfg ) const { if( !cfg ) { return QUrl(); } return cfg->config().readEntry( ExecuteScriptPlugin::workingDirEntry, QUrl() ); } QString ExecuteScriptPlugin::scriptAppConfigTypeId() const { return _scriptAppConfigTypeId; } #include "executescriptplugin.moc" diff --git a/plugins/executescript/scriptappconfig.cpp b/plugins/executescript/scriptappconfig.cpp index d4d6c8e87..94f9af1f8 100644 --- a/plugins/executescript/scriptappconfig.cpp +++ b/plugins/executescript/scriptappconfig.cpp @@ -1,254 +1,254 @@ /* This file is part of KDevelop Copyright 2009 Andreas Pakulat Copyright 2009 Niko Sams This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "scriptappconfig.h" #include #include #include #include #include #include #include #include #include #include #include "scriptappjob.h" #include #include #include #include #include #include #include #include #include "executescriptplugin.h" #include #include #include using namespace KDevelop; static const QString interpreterForUrl(const QUrl& url) { auto mimetype = QMimeDatabase().mimeTypeForUrl(url); static QHash knownMimetypes; if ( knownMimetypes.isEmpty() ) { - knownMimetypes[QStringLiteral("text/x-python")] = "python"; - knownMimetypes[QStringLiteral("application/x-php")] = "php"; - knownMimetypes[QStringLiteral("application/x-ruby")] = "ruby"; - knownMimetypes[QStringLiteral("application/x-shellscript")] = "bash"; - knownMimetypes[QStringLiteral("application/x-perl")] = "perl -e"; + knownMimetypes[QStringLiteral("text/x-python")] = QStringLiteral("python"); + knownMimetypes[QStringLiteral("application/x-php")] = QStringLiteral("php"); + knownMimetypes[QStringLiteral("application/x-ruby")] = QStringLiteral("ruby"); + knownMimetypes[QStringLiteral("application/x-shellscript")] = QStringLiteral("bash"); + knownMimetypes[QStringLiteral("application/x-perl")] = QStringLiteral("perl -e"); } const QString& interp = knownMimetypes.value(mimetype.name()); return interp; } QIcon ScriptAppConfigPage::icon() const { return QIcon::fromTheme(QStringLiteral("system-run")); } void ScriptAppConfigPage::loadFromConfiguration(const KConfigGroup& cfg, KDevelop::IProject* project ) { bool b = blockSignals( true ); if( project ) { executablePath->setStartDir( project->path().toUrl() ); } auto doc = KDevelop::ICore::self()->documentController()->activeDocument(); interpreter->lineEdit()->setText( cfg.readEntry( ExecuteScriptPlugin::interpreterEntry, - doc ? interpreterForUrl(doc->url()) : "" ) ); + doc ? interpreterForUrl(doc->url()) : QString() ) ); executablePath->setUrl( QUrl::fromLocalFile(cfg.readEntry( ExecuteScriptPlugin::executableEntry, QString() )) ); remoteHostCheckbox->setChecked( cfg.readEntry( ExecuteScriptPlugin::executeOnRemoteHostEntry, false ) ); remoteHost->setText( cfg.readEntry( ExecuteScriptPlugin::remoteHostEntry, "" ) ); bool runCurrent = cfg.readEntry( ExecuteScriptPlugin::runCurrentFileEntry, true ); if ( runCurrent ) { runCurrentFile->setChecked( true ); } else { runFixedFile->setChecked( true ); } arguments->setText( cfg.readEntry( ExecuteScriptPlugin::argumentsEntry, "" ) ); workingDirectory->setUrl( cfg.readEntry( ExecuteScriptPlugin::workingDirEntry, QUrl() ) ); environment->setCurrentProfile( cfg.readEntry( ExecuteScriptPlugin::environmentGroupEntry, QString() ) ); outputFilteringMode->setCurrentIndex( cfg.readEntry( ExecuteScriptPlugin::outputFilteringEntry, 2u )); //runInTerminal->setChecked( cfg.readEntry( ExecuteScriptPlugin::useTerminalEntry, false ) ); blockSignals( b ); } ScriptAppConfigPage::ScriptAppConfigPage( QWidget* parent ) : LaunchConfigurationPage( parent ) { setupUi(this); interpreter->lineEdit()->setPlaceholderText(i18n("Type or select an interpreter")); //Set workingdirectory widget to ask for directories rather than files workingDirectory->setMode(KFile::Directory | KFile::ExistingOnly | KFile::LocalOnly); //connect signals to changed signal connect( interpreter->lineEdit(), &QLineEdit::textEdited, this, &ScriptAppConfigPage::changed ); connect( executablePath->lineEdit(), &KLineEdit::textEdited, this, &ScriptAppConfigPage::changed ); connect( executablePath, &KUrlRequester::urlSelected, this, &ScriptAppConfigPage::changed ); connect( arguments, &QLineEdit::textEdited, this, &ScriptAppConfigPage::changed ); connect( workingDirectory, &KUrlRequester::urlSelected, this, &ScriptAppConfigPage::changed ); connect( workingDirectory->lineEdit(), &KLineEdit::textEdited, this, &ScriptAppConfigPage::changed ); connect( environment, &EnvironmentSelectionWidget::currentProfileChanged, this, &ScriptAppConfigPage::changed ); //connect( runInTerminal, SIGNAL(toggled(bool)), SIGNAL(changed()) ); } void ScriptAppConfigPage::saveToConfiguration( KConfigGroup cfg, KDevelop::IProject* project ) const { Q_UNUSED( project ); cfg.writeEntry( ExecuteScriptPlugin::interpreterEntry, interpreter->lineEdit()->text() ); cfg.writeEntry( ExecuteScriptPlugin::executableEntry, executablePath->url() ); cfg.writeEntry( ExecuteScriptPlugin::executeOnRemoteHostEntry, remoteHostCheckbox->isChecked() ); cfg.writeEntry( ExecuteScriptPlugin::remoteHostEntry, remoteHost->text() ); cfg.writeEntry( ExecuteScriptPlugin::runCurrentFileEntry, runCurrentFile->isChecked() ); cfg.writeEntry( ExecuteScriptPlugin::argumentsEntry, arguments->text() ); cfg.writeEntry( ExecuteScriptPlugin::workingDirEntry, workingDirectory->url() ); cfg.writeEntry( ExecuteScriptPlugin::environmentGroupEntry, environment->currentProfile() ); cfg.writeEntry( ExecuteScriptPlugin::outputFilteringEntry, outputFilteringMode->currentIndex() ); //cfg.writeEntry( ExecuteScriptPlugin::useTerminalEntry, runInTerminal->isChecked() ); } QString ScriptAppConfigPage::title() const { return i18n("Configure Script Application"); } QList< KDevelop::LaunchConfigurationPageFactory* > ScriptAppLauncher::configPages() const { return QList(); } QString ScriptAppLauncher::description() const { return i18n("Executes Script Applications"); } QString ScriptAppLauncher::id() { - return "scriptAppLauncher"; + return QStringLiteral("scriptAppLauncher"); } QString ScriptAppLauncher::name() const { return i18n("Script Application"); } ScriptAppLauncher::ScriptAppLauncher(ExecuteScriptPlugin* plugin) : m_plugin( plugin ) { } KJob* ScriptAppLauncher::start(const QString& launchMode, KDevelop::ILaunchConfiguration* cfg) { Q_ASSERT(cfg); if( !cfg ) { return 0; } - if( launchMode == "execute" ) + if( launchMode == QLatin1String("execute") ) { return new ScriptAppJob( m_plugin, cfg); } qWarning() << "Unknown launch mode " << launchMode << "for config:" << cfg->name(); return 0; } QStringList ScriptAppLauncher::supportedModes() const { return QStringList() << QStringLiteral("execute"); } KDevelop::LaunchConfigurationPage* ScriptAppPageFactory::createWidget(QWidget* parent) { return new ScriptAppConfigPage( parent ); } ScriptAppPageFactory::ScriptAppPageFactory() { } ScriptAppConfigType::ScriptAppConfigType() { factoryList.append( new ScriptAppPageFactory() ); } ScriptAppConfigType::~ScriptAppConfigType() { qDeleteAll(factoryList); factoryList.clear(); } QString ScriptAppConfigType::name() const { return i18n("Script Application"); } QList ScriptAppConfigType::configPages() const { return factoryList; } QString ScriptAppConfigType::id() const { return ExecuteScriptPlugin::_scriptAppConfigTypeId; } QIcon ScriptAppConfigType::icon() const { return QIcon::fromTheme(QStringLiteral("preferences-plugin-script")); } bool ScriptAppConfigType::canLaunch(const QUrl& file) const { return ! interpreterForUrl(file).isEmpty(); } bool ScriptAppConfigType::canLaunch(KDevelop::ProjectBaseItem* item) const { return ! interpreterForUrl(item->path().toUrl()).isEmpty(); } void ScriptAppConfigType::configureLaunchFromItem(KConfigGroup config, KDevelop::ProjectBaseItem* item) const { config.writeEntry(ExecuteScriptPlugin::executableEntry, item->path().toUrl()); config.writeEntry(ExecuteScriptPlugin::interpreterEntry, interpreterForUrl(item->path().toUrl())); config.writeEntry(ExecuteScriptPlugin::outputFilteringEntry, 2u); config.writeEntry(ExecuteScriptPlugin::runCurrentFileEntry, false); config.sync(); } void ScriptAppConfigType::configureLaunchFromCmdLineArguments(KConfigGroup cfg, const QStringList &args) const { QStringList a(args); cfg.writeEntry( ExecuteScriptPlugin::interpreterEntry, a.takeFirst() ); cfg.writeEntry( ExecuteScriptPlugin::executableEntry, a.takeFirst() ); cfg.writeEntry( ExecuteScriptPlugin::argumentsEntry, KShell::joinArgs(a) ); cfg.writeEntry( ExecuteScriptPlugin::runCurrentFileEntry, false ); cfg.sync(); } diff --git a/plugins/executescript/scriptappconfig.h b/plugins/executescript/scriptappconfig.h index 9a044fa6d..dae32587e 100644 --- a/plugins/executescript/scriptappconfig.h +++ b/plugins/executescript/scriptappconfig.h @@ -1,89 +1,90 @@ /* 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 KDEVPLATFORM_PLUGIN_SCRIPTAPPCONFIGTYPE_H #define KDEVPLATFORM_PLUGIN_SCRIPTAPPCONFIGTYPE_H #include #include #include #include #include "ui_scriptappconfig.h" class ExecuteScriptPlugin; class ScriptAppConfigPage : public KDevelop::LaunchConfigurationPage, Ui::ScriptAppPage { Q_OBJECT public: explicit ScriptAppConfigPage( QWidget* parent ); void loadFromConfiguration( const KConfigGroup& cfg, KDevelop::IProject* project = 0 ) override; void saveToConfiguration( KConfigGroup cfg, KDevelop::IProject* project = 0 ) const override; QString title() const override; QIcon icon() const override; }; class ScriptAppLauncher : public KDevelop::ILauncher { public: explicit ScriptAppLauncher( ExecuteScriptPlugin* ); QList< KDevelop::LaunchConfigurationPageFactory* > configPages() const override; QString description() const override; QString id() override; QString name() const override; KJob* start(const QString& launchMode, KDevelop::ILaunchConfiguration* cfg) override; QStringList supportedModes() const override; private: ExecuteScriptPlugin* m_plugin; }; class ScriptAppPageFactory : public KDevelop::LaunchConfigurationPageFactory { public: ScriptAppPageFactory(); KDevelop::LaunchConfigurationPage* createWidget(QWidget* parent) override; }; /** * A specific configuration to start a launchable, this could be a native * compiled application, or some script file or byte-compiled file or something else * Provides access to the various configured informations, as well as its type and a name */ class ScriptAppConfigType : public KDevelop::LaunchConfigurationType { + Q_OBJECT public: ScriptAppConfigType(); ~ScriptAppConfigType() override; QString id() const override; QString name() const override; QList configPages() const override; QIcon icon() const override; bool canLaunch( const QUrl& file ) const override; bool canLaunch(KDevelop::ProjectBaseItem* item) const override; void configureLaunchFromItem(KConfigGroup config, KDevelop::ProjectBaseItem* item) const override; void configureLaunchFromCmdLineArguments(KConfigGroup config, const QStringList& args) const override; private: QList factoryList; }; #endif diff --git a/plugins/executescript/scriptappjob.cpp b/plugins/executescript/scriptappjob.cpp index 7dd24202b..528430a9b 100644 --- a/plugins/executescript/scriptappjob.cpp +++ b/plugins/executescript/scriptappjob.cpp @@ -1,256 +1,256 @@ /* This file is part of KDevelop Copyright 2009 Andreas Pakulat Copyright 2009 Niko Sams This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "scriptappjob.h" #include "executescriptplugin.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "iexecutescriptplugin.h" #include "debug.h" using namespace KDevelop; ScriptAppJob::ScriptAppJob(ExecuteScriptPlugin* parent, KDevelop::ILaunchConfiguration* cfg) : KDevelop::OutputJob( parent ), proc(0) { qCDebug(PLUGIN_EXECUTESCRIPT) << "creating script app job"; setCapabilities(Killable); - IExecuteScriptPlugin* iface = KDevelop::ICore::self()->pluginController()->pluginForExtension("org.kdevelop.IExecuteScriptPlugin")->extension(); + IExecuteScriptPlugin* iface = KDevelop::ICore::self()->pluginController()->pluginForExtension(QStringLiteral("org.kdevelop.IExecuteScriptPlugin"))->extension(); Q_ASSERT(iface); KDevelop::EnvironmentGroupList l(KSharedConfig::openConfig()); QString envgrp = iface->environmentGroup(cfg); QString err; QString interpreterString = iface->interpreter( cfg, err ); // check for errors happens in the executescript plugin already KShell::Errors err_; QStringList interpreter = KShell::splitArgs( interpreterString, KShell::TildeExpand | KShell::AbortOnMeta, &err_ ); if ( interpreter.isEmpty() ) { // This should not happen, because of the checks done in the executescript plugin qWarning() << "no interpreter specified"; return; } if( !err.isEmpty() ) { setError( -1 ); setErrorText( err ); return; } QUrl script; if( !iface->runCurrentFile( cfg ) ) { script = iface->script( cfg, err ); } else { KDevelop::IDocument* document = KDevelop::ICore::self()->documentController()->activeDocument(); if( !document ) { setError( -1 ); setErrorText( i18n( "There is no active document to launch." ) ); return; } script = document->url(); } if( !err.isEmpty() ) { setError( -3 ); setErrorText( err ); return; } QString remoteHost = iface->remoteHost( cfg, err ); if( !err.isEmpty() ) { setError( -4 ); setErrorText( err ); return; } if( envgrp.isEmpty() ) { qWarning() << "Launch Configuration:" << cfg->name() << i18n("No environment group specified, looks like a broken " "configuration, please check run configuration '%1'. " "Using default environment group.", cfg->name() ); envgrp = l.defaultGroup(); } QStringList arguments = iface->arguments( cfg, err ); if( !err.isEmpty() ) { setError( -2 ); setErrorText( err ); } if( error() != 0 ) { qWarning() << "Launch Configuration:" << cfg->name() << "oops, problem" << errorText(); return; } KDevelop::OutputModel::OutputFilterStrategy currentFilterMode = static_cast( iface->outputFilterModeId( cfg ) ); proc = new KProcess( this ); lineMaker = new KDevelop::ProcessLineMaker( proc, this ); setStandardToolView(KDevelop::IOutputView::RunView); setBehaviours(KDevelop::IOutputView::AllowUserClose | KDevelop::IOutputView::AutoScroll); KDevelop::OutputModel* m = new KDevelop::OutputModel; m->setFilteringStrategy(currentFilterMode); setModel( m ); setDelegate( new KDevelop::OutputDelegate ); connect( lineMaker, &ProcessLineMaker::receivedStdoutLines, model(), &OutputModel::appendLines ); connect( proc, static_cast(&KProcess::error), this, &ScriptAppJob::processError ); connect( proc, static_cast(&KProcess::finished), this, &ScriptAppJob::processFinished ); // Now setup the process parameters proc->setEnvironment( l.createEnvironment( envgrp, proc->systemEnvironment()) ); QUrl wc = iface->workingDirectory( cfg ); if( !wc.isValid() || wc.isEmpty() ) { wc = QUrl::fromLocalFile( QFileInfo( script.toLocalFile() ).absolutePath() ); } proc->setWorkingDirectory( wc.toLocalFile() ); proc->setProperty( "executable", interpreter.first() ); QStringList program; if (!remoteHost.isEmpty()) { program << QStringLiteral("ssh"); QStringList parts = remoteHost.split(QLatin1Char(':')); program << parts.first(); if (parts.length() > 1) { program << "-p "+parts.at(1); } } program << interpreter; program << script.toLocalFile(); program << arguments; qCDebug(PLUGIN_EXECUTESCRIPT) << "setting app:" << program; proc->setOutputChannelMode(KProcess::MergedChannels); proc->setProgram( program ); setTitle(cfg->name()); } void ScriptAppJob::start() { qCDebug(PLUGIN_EXECUTESCRIPT) << "launching?" << proc; if( proc ) { startOutput(); appendLine( i18n("Starting: %1", proc->program().join(QLatin1Char( ' ' ) ) ) ); proc->start(); } else { qWarning() << "No process, something went wrong when creating the job"; // No process means we've returned early on from the constructor, some bad error happened emitResult(); } } bool ScriptAppJob::doKill() { if( proc ) { proc->kill(); appendLine( i18n( "*** Killed Application ***" ) ); } return true; } void ScriptAppJob::processFinished( int exitCode , QProcess::ExitStatus status ) { lineMaker->flushBuffers(); if (exitCode == 0 && status == QProcess::NormalExit) { appendLine( i18n("*** Exited normally ***") ); } else if (status == QProcess::NormalExit) { appendLine( i18n("*** Exited with return code: %1 ***", QString::number(exitCode)) ); setError(OutputJob::FailedShownError); } else if (error() == KJob::KilledJobError) { appendLine( i18n("*** Process aborted ***") ); setError(KJob::KilledJobError); } else { appendLine( i18n("*** Crashed with return code: %1 ***", QString::number(exitCode)) ); setError(OutputJob::FailedShownError); } qCDebug(PLUGIN_EXECUTESCRIPT) << "Process done"; emitResult(); } void ScriptAppJob::processError( QProcess::ProcessError error ) { qCDebug(PLUGIN_EXECUTESCRIPT) << proc->readAllStandardError(); qCDebug(PLUGIN_EXECUTESCRIPT) << proc->readAllStandardOutput(); qCDebug(PLUGIN_EXECUTESCRIPT) << proc->errorString(); if( error == QProcess::FailedToStart ) { setError( FailedShownError ); QString errmsg = i18n("*** Could not start program '%1'. Make sure that the " "path is specified correctly ***", proc->program().join(QLatin1Char( ' ' ) ) ); appendLine( errmsg ); setErrorText( errmsg ); emitResult(); } qCDebug(PLUGIN_EXECUTESCRIPT) << "Process error"; } void ScriptAppJob::appendLine(const QString& l) { if (KDevelop::OutputModel* m = model()) { m->appendLine(l); } } KDevelop::OutputModel* ScriptAppJob::model() { return dynamic_cast( OutputJob::model() ); } diff --git a/plugins/externalscript/externalscriptjob.cpp b/plugins/externalscript/externalscriptjob.cpp index 06e483078..5b48c2bfb 100644 --- a/plugins/externalscript/externalscriptjob.cpp +++ b/plugins/externalscript/externalscriptjob.cpp @@ -1,399 +1,399 @@ /* This file is part of KDevelop Copyright 2009 Andreas Pakulat Copyright 2010 Milian Wolff This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "externalscriptjob.h" #include "externalscriptitem.h" #include "externalscriptdebug.h" #include "externalscriptplugin.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; ExternalScriptJob::ExternalScriptJob( ExternalScriptItem* item, const QUrl& url, ExternalScriptPlugin* parent ) : KDevelop::OutputJob( parent ), m_proc( 0 ), m_lineMaker( 0 ), m_outputMode( item->outputMode() ), m_inputMode( item->inputMode() ), m_errorMode( item->errorMode() ), m_filterMode( item->filterMode() ), m_document( 0 ), m_url( url ), m_selectionRange( KTextEditor::Range::invalid() ), m_showOutput( item->showOutput() ) { qCDebug(PLUGIN_EXTERNALSCRIPT) << "creating external script job"; setCapabilities( Killable ); setStandardToolView( KDevelop::IOutputView::RunView ); setBehaviours( KDevelop::IOutputView::AllowUserClose | KDevelop::IOutputView::AutoScroll ); KDevelop::OutputModel* model = new KDevelop::OutputModel; model->setFilteringStrategy(static_cast(m_filterMode)); setModel( model ); setDelegate( new KDevelop::OutputDelegate ); // also merge when error mode "equals" output mode if ( (m_outputMode == ExternalScriptItem::OutputInsertAtCursor && m_errorMode == ExternalScriptItem::ErrorInsertAtCursor) || (m_outputMode == ExternalScriptItem::OutputReplaceDocument && m_errorMode == ExternalScriptItem::ErrorReplaceDocument) || (m_outputMode == ExternalScriptItem::OutputReplaceSelectionOrDocument && m_errorMode == ExternalScriptItem::ErrorReplaceSelectionOrDocument) || (m_outputMode == ExternalScriptItem::OutputReplaceSelectionOrInsertAtCursor && m_errorMode == ExternalScriptItem::ErrorReplaceSelectionOrInsertAtCursor) || // also these two otherwise they clash... (m_outputMode == ExternalScriptItem::OutputReplaceSelectionOrInsertAtCursor && m_errorMode == ExternalScriptItem::ErrorReplaceSelectionOrDocument) || (m_outputMode == ExternalScriptItem::OutputReplaceSelectionOrDocument && m_errorMode == ExternalScriptItem::ErrorReplaceSelectionOrInsertAtCursor) ) { m_errorMode = ExternalScriptItem::ErrorMergeOutput; } KTextEditor::View* view = KDevelop::ICore::self()->documentController()->activeTextDocumentView(); if ( m_outputMode != ExternalScriptItem::OutputNone || m_inputMode != ExternalScriptItem::InputNone || m_errorMode != ExternalScriptItem::ErrorNone ) { if ( !view ) { KMessageBox::error( QApplication::activeWindow(), i18n( "Cannot run script '%1' since it tries to access " "the editor contents but no document is open.", item->text() ), i18n( "No Document Open" ) ); return; } m_document = view->document(); connect(m_document, &KTextEditor::Document::aboutToClose, this, [&] { kill(); }); m_selectionRange = view->selectionRange(); m_cursorPosition = view->cursorPosition(); } if ( item->saveMode() == ExternalScriptItem::SaveCurrentDocument && view ) { view->document()->save(); } else if ( item->saveMode() == ExternalScriptItem::SaveAllDocuments ) { foreach ( KDevelop::IDocument* doc, KDevelop::ICore::self()->documentController()->openDocuments() ) { doc->save(); } } QString command = item->command(); QString workingDir = item->workingDirectory(); if(item->performParameterReplacement()) - command.replace( "%i", QString::number( QCoreApplication::applicationPid() ) ); + command.replace( QLatin1String("%i"), QString::number( QCoreApplication::applicationPid() ) ); if ( !m_url.isEmpty() ) { const QUrl url = m_url; KDevelop::ProjectFolderItem* folder = 0; if ( KDevelop::ICore::self()->projectController()->findProjectForUrl( url ) ) { QList folders = KDevelop::ICore::self()->projectController()->findProjectForUrl(url)->foldersForPath(KDevelop::IndexedString(url)); if ( !folders.isEmpty() ) { folder = folders.first(); } } if ( folder ) { if ( folder->path().isLocalFile() && workingDir.isEmpty() ) { ///TODO: make configurable, use fallback to project dir workingDir = folder->path().toLocalFile(); } ///TODO: make those placeholders escapeable if(item->performParameterReplacement()) { - command.replace( "%d", KShell::quoteArg( m_url.toString(QUrl::PreferLocalFile) ) ); + command.replace( QLatin1String("%d"), KShell::quoteArg( m_url.toString(QUrl::PreferLocalFile) ) ); if ( KDevelop::IProject* project = KDevelop::ICore::self()->projectController()->findProjectForUrl( m_url ) ) { - command.replace( "%p", project->path().pathOrUrl() ); + command.replace( QLatin1String("%p"), project->path().pathOrUrl() ); } } } else { if ( m_url.isLocalFile() && workingDir.isEmpty() ) { ///TODO: make configurable, use fallback to project dir workingDir = view->document()->url().adjusted(QUrl::RemoveFilename).toLocalFile(); } ///TODO: make those placeholders escapeable if(item->performParameterReplacement()) { - command.replace( "%u", KShell::quoteArg( m_url.toString() ) ); + command.replace( QLatin1String("%u"), KShell::quoteArg( m_url.toString() ) ); ///TODO: does that work with remote files? QFileInfo info( m_url.toString(QUrl::PreferLocalFile) ); - command.replace( "%f", KShell::quoteArg( info.filePath() ) ); - command.replace( "%b", KShell::quoteArg( info.baseName() ) ); - command.replace( "%n", KShell::quoteArg( info.fileName() ) ); - command.replace( "%d", KShell::quoteArg( info.path() ) ); + command.replace( QLatin1String("%f"), KShell::quoteArg( info.filePath() ) ); + command.replace( QLatin1String("%b"), KShell::quoteArg( info.baseName() ) ); + command.replace( QLatin1String("%n"), KShell::quoteArg( info.fileName() ) ); + command.replace( QLatin1String("%d"), KShell::quoteArg( info.path() ) ); if ( view->document() && view->selection() ) { - command.replace( "%s", KShell::quoteArg( view->selectionText() ) ); + command.replace( QLatin1String("%s"), KShell::quoteArg( view->selectionText() ) ); } if ( KDevelop::IProject* project = KDevelop::ICore::self()->projectController()->findProjectForUrl( m_url ) ) { - command.replace( "%p", project->path().pathOrUrl() ); + command.replace( QLatin1String("%p"), project->path().pathOrUrl() ); } } } } m_proc = new KProcess( this ); if ( !workingDir.isEmpty() ) { m_proc->setWorkingDirectory( workingDir ); } m_lineMaker = new ProcessLineMaker( m_proc, this ); connect( m_lineMaker, &ProcessLineMaker::receivedStdoutLines, model, &OutputModel::appendLines ); connect( m_lineMaker, &ProcessLineMaker::receivedStdoutLines, this, &ExternalScriptJob::receivedStdoutLines ); connect( m_lineMaker, &ProcessLineMaker::receivedStderrLines, model, &OutputModel::appendLines ); connect( m_lineMaker, &ProcessLineMaker::receivedStderrLines, this, &ExternalScriptJob::receivedStderrLines ); connect( m_proc, static_cast(&KProcess::error), this, &ExternalScriptJob::processError ); connect( m_proc, static_cast(&KProcess::finished), this, &ExternalScriptJob::processFinished ); // Now setup the process parameters qCDebug(PLUGIN_EXTERNALSCRIPT) << "setting command:" << command; if ( m_errorMode == ExternalScriptItem::ErrorMergeOutput ) { m_proc->setOutputChannelMode( KProcess::MergedChannels ); } else { m_proc->setOutputChannelMode( KProcess::SeparateChannels ); } m_proc->setShellCommand( command ); setObjectName( command ); } void ExternalScriptJob::start() { qCDebug(PLUGIN_EXTERNALSCRIPT) << "launching?" << m_proc; if ( m_proc ) { if ( m_showOutput ) { startOutput(); } - appendLine( i18n( "Running external script: %1", m_proc->program().join( " " ) ) ); + appendLine( i18n( "Running external script: %1", m_proc->program().join( QStringLiteral( " " ) ) ) ); m_proc->start(); if ( m_inputMode != ExternalScriptItem::InputNone ) { QString inputText; switch ( m_inputMode ) { case ExternalScriptItem::InputNone: // do nothing; break; case ExternalScriptItem::InputSelectionOrNone: if ( m_selectionRange.isValid() ) { inputText = m_document->text(m_selectionRange); } // else nothing break; case ExternalScriptItem::InputSelectionOrDocument: if ( m_selectionRange.isValid() ) { inputText = m_document->text(m_selectionRange); } else { inputText = m_document->text(); } break; case ExternalScriptItem::InputDocument: inputText = m_document->text(); break; } ///TODO: what to do with the encoding here? /// maybe ask Christoph for what kate returns... m_proc->write( inputText.toUtf8() ); m_proc->closeWriteChannel(); } } else { qWarning() << "No process, something went wrong when creating the job"; // No process means we've returned early on from the constructor, some bad error happened emitResult(); } } bool ExternalScriptJob::doKill() { if ( m_proc ) { m_proc->kill(); appendLine( i18n( "*** Killed Application ***" ) ); } return true; } void ExternalScriptJob::processFinished( int exitCode , QProcess::ExitStatus status ) { m_lineMaker->flushBuffers(); if ( exitCode == 0 && status == QProcess::NormalExit ) { if ( m_outputMode != ExternalScriptItem::OutputNone ) { if ( !m_stdout.isEmpty() ) { - QString output = m_stdout.join( "\n" ); + QString output = m_stdout.join( QStringLiteral("\n") ); switch ( m_outputMode ) { case ExternalScriptItem::OutputNone: // do nothing; break; case ExternalScriptItem::OutputCreateNewFile: KDevelop::ICore::self()->documentController()->openDocumentFromText( output ); break; case ExternalScriptItem::OutputInsertAtCursor: m_document->insertText( m_cursorPosition, output ); break; case ExternalScriptItem::OutputReplaceSelectionOrInsertAtCursor: if ( m_selectionRange.isValid() ) { m_document->replaceText( m_selectionRange, output ); } else { m_document->insertText( m_cursorPosition, output ); } break; case ExternalScriptItem::OutputReplaceSelectionOrDocument: if ( m_selectionRange.isValid() ) { m_document->replaceText( m_selectionRange, output ); } else { m_document->setText( output ); } break; case ExternalScriptItem::OutputReplaceDocument: m_document->setText( output ); break; } } } if ( m_errorMode != ExternalScriptItem::ErrorNone && m_errorMode != ExternalScriptItem::ErrorMergeOutput ) { - QString output = m_stderr.join( "\n" ); + QString output = m_stderr.join( QStringLiteral("\n") ); if ( !output.isEmpty() ) { switch ( m_errorMode ) { case ExternalScriptItem::ErrorNone: case ExternalScriptItem::ErrorMergeOutput: // do nothing; break; case ExternalScriptItem::ErrorCreateNewFile: KDevelop::ICore::self()->documentController()->openDocumentFromText( output ); break; case ExternalScriptItem::ErrorInsertAtCursor: m_document->insertText( m_cursorPosition, output ); break; case ExternalScriptItem::ErrorReplaceSelectionOrInsertAtCursor: if ( m_selectionRange.isValid() ) { m_document->replaceText( m_selectionRange, output ); } else { m_document->insertText( m_cursorPosition, output ); } break; case ExternalScriptItem::ErrorReplaceSelectionOrDocument: if ( m_selectionRange.isValid() ) { m_document->replaceText( m_selectionRange, output ); } else { m_document->setText( output ); } break; case ExternalScriptItem::ErrorReplaceDocument: m_document->setText( output ); break; } } } appendLine( i18n( "*** Exited normally ***" ) ); } else { if ( status == QProcess::NormalExit ) appendLine( i18n( "*** Exited with return code: %1 ***", QString::number( exitCode ) ) ); else if ( error() == KJob::KilledJobError ) appendLine( i18n( "*** Process aborted ***" ) ); else appendLine( i18n( "*** Crashed with return code: %1 ***", QString::number( exitCode ) ) ); } qCDebug(PLUGIN_EXTERNALSCRIPT) << "Process done"; emitResult(); } void ExternalScriptJob::processError( QProcess::ProcessError error ) { if ( error == QProcess::FailedToStart ) { setError( -1 ); QString errmsg = i18n("*** Could not start program '%1'. Make sure that the " - "path is specified correctly ***", m_proc->program().join(" ") ); + "path is specified correctly ***", m_proc->program().join( QLatin1Char(' ') ) ); appendLine( errmsg ); setErrorText( errmsg ); emitResult(); } qCDebug(PLUGIN_EXTERNALSCRIPT) << "Process error"; } void ExternalScriptJob::appendLine( const QString& l ) { if ( KDevelop::OutputModel* m = model() ) { m->appendLine( l ); } } KDevelop::OutputModel* ExternalScriptJob::model() { return dynamic_cast( OutputJob::model() ); } void ExternalScriptJob::receivedStderrLines(const QStringList& lines) { m_stderr += lines; } void ExternalScriptJob::receivedStdoutLines(const QStringList& lines) { m_stdout += lines; } // kate: indent-mode cstyle; space-indent on; indent-width 2; replace-tabs on; diff --git a/plugins/externalscript/externalscriptplugin.cpp b/plugins/externalscript/externalscriptplugin.cpp index 6838522f7..7ff1d2f0c 100644 --- a/plugins/externalscript/externalscriptplugin.cpp +++ b/plugins/externalscript/externalscriptplugin.cpp @@ -1,368 +1,368 @@ /* 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 ) {} QWidget* create( QWidget *parent = 0 ) override { return new ExternalScriptView( m_plugin, parent ); } Qt::DockWidgetArea defaultPosition() override { return Qt::RightDockWidgetArea; } QString id() const override { - return "org.kdevelop.ExternalScriptView"; + return QStringLiteral("org.kdevelop.ExternalScriptView"); } private: ExternalScriptPlugin *m_plugin; }; ExternalScriptPlugin* ExternalScriptPlugin::m_self = 0; ExternalScriptPlugin::ExternalScriptPlugin( QObject* parent, const QVariantList& /*args*/ ) - : IPlugin( "kdevexternalscript", parent ), + : IPlugin( QStringLiteral("kdevexternalscript"), parent ), m_model( new QStandardItemModel( this ) ), m_factory( new ExternalScriptViewFactory( this ) ) { Q_ASSERT( !m_self ); m_self = this; - QDBusConnection::sessionBus().registerObject( "/org/kdevelop/ExternalScriptPlugin", this, QDBusConnection::ExportScriptableSlots ); + QDBusConnection::sessionBus().registerObject( QStringLiteral("/org/kdevelop/ExternalScriptPlugin"), this, QDBusConnection::ExportScriptableSlots ); - setXMLFile( "kdevexternalscript.rc" ); + 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( "g++ -o %b %f && ./%b" ); + item->setCommand( QStringLiteral("g++ -o %b %f && ./%b") ); m_model->appendRow( item ); item = new ExternalScriptItem; item->setText( i18n("Google Selection") ); - item->setCommand( "xdg-open \"http://www.google.de/search?q=%s\"" ); + 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( "sort" ); + 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 = 0; } 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("%s")) { + 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("%d") || - item->command().contains("%s") || - item->command().contains("%u") || - item->command().contains("%f") || - item->command().contains("%b") || - item->command().contains("%n") + (!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 ); } 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/externalscript/externalscriptview.cpp b/plugins/externalscript/externalscriptview.cpp index ee717a604..5e222b2a6 100644 --- a/plugins/externalscript/externalscriptview.cpp +++ b/plugins/externalscript/externalscriptview.cpp @@ -1,178 +1,178 @@ /* 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 "externalscriptview.h" #include "externalscriptplugin.h" #include "externalscriptitem.h" #include "editexternalscript.h" #include #include #include #include #include #include ExternalScriptView::ExternalScriptView( ExternalScriptPlugin* plugin, QWidget* parent ) : QWidget( parent ), m_plugin( plugin ) { Ui::ExternalScriptViewBase::setupUi( this ); setWindowTitle( i18n( "External Scripts" ) ); - setWindowIcon( QIcon::fromTheme("dialog-scripts", windowIcon()) ); + setWindowIcon( QIcon::fromTheme(QStringLiteral("dialog-scripts"), windowIcon()) ); m_model = new QSortFilterProxyModel( this ); m_model->setSourceModel( m_plugin->model() ); m_model->setDynamicSortFilter( true ); m_model->sort( 0 ); connect( filterText, &QLineEdit::textEdited, m_model, &QSortFilterProxyModel::setFilterWildcard ); scriptTree->setModel( m_model ); scriptTree->setContextMenuPolicy( Qt::CustomContextMenu ); scriptTree->viewport()->installEventFilter( this ); scriptTree->header()->hide(); connect(scriptTree, &QTreeView::customContextMenuRequested, this, &ExternalScriptView::contextMenu); - m_addScriptAction = new QAction(QIcon::fromTheme("document-new"), i18n("Add External Script"), this); + m_addScriptAction = new QAction(QIcon::fromTheme(QStringLiteral("document-new")), i18n("Add External Script"), this); connect(m_addScriptAction, &QAction::triggered, this, &ExternalScriptView::addScript); addAction(m_addScriptAction); - m_editScriptAction = new QAction(QIcon::fromTheme("document-edit"), i18n("Edit External Script"), this); + m_editScriptAction = new QAction(QIcon::fromTheme(QStringLiteral("document-edit")), i18n("Edit External Script"), this); connect(m_editScriptAction, &QAction::triggered, this, &ExternalScriptView::editScript); addAction(m_editScriptAction); - m_removeScriptAction = new QAction(QIcon::fromTheme("document-close"), i18n("Remove External Script"), this); + m_removeScriptAction = new QAction(QIcon::fromTheme(QStringLiteral("document-close")), i18n("Remove External Script"), this); connect(m_removeScriptAction, &QAction::triggered, this, &ExternalScriptView::removeScript); addAction(m_removeScriptAction); connect(scriptTree->selectionModel(), &QItemSelectionModel::selectionChanged, this, &ExternalScriptView::validateActions); validateActions(); } ExternalScriptView::~ExternalScriptView() { } ExternalScriptItem* ExternalScriptView::currentItem() const { return itemForIndex( scriptTree->currentIndex() ); } ExternalScriptItem* ExternalScriptView::itemForIndex( const QModelIndex& index ) const { if ( !index.isValid() ) { return 0; } const QModelIndex mappedIndex = m_model->mapToSource( index ); return static_cast( m_plugin->model()->itemFromIndex( mappedIndex ) ); } void ExternalScriptView::validateActions() { bool itemSelected = currentItem(); m_removeScriptAction->setEnabled( itemSelected ); m_editScriptAction->setEnabled( itemSelected ); } void ExternalScriptView::contextMenu( const QPoint& pos ) { QMenu menu; menu.addActions( actions() ); menu.exec( scriptTree->mapToGlobal( pos ) ); } bool ExternalScriptView::eventFilter( QObject* obj, QEvent* e ) { // no, listening to activated() is not enough since that would also trigger the edit mode which we _dont_ want here // users may still rename stuff via select + F2 though if ( obj == scriptTree->viewport() ) { // const bool singleClick = KGlobalSettings::singleClick(); const bool singleClick = true; //FIXME: enable singleClick for the sake of porting, should find a proper way if ( ( !singleClick && e->type() == QEvent::MouseButtonDblClick ) || ( singleClick && e->type() == QEvent::MouseButtonRelease ) ) { QMouseEvent* mouseEvent = dynamic_cast(e); Q_ASSERT( mouseEvent ); ExternalScriptItem* item = itemForIndex( scriptTree->indexAt( mouseEvent->pos() ) ); if ( item ) { m_plugin->execute( item ); e->accept(); return true; } } } return QObject::eventFilter( obj, e ); } void ExternalScriptView::addScript() { ExternalScriptItem* item = new ExternalScriptItem; EditExternalScript dlg( item, this ); int ret = dlg.exec(); if ( ret == QDialog::Accepted) { m_plugin->model()->appendRow( item ); } else { delete item; } } void ExternalScriptView::removeScript() { ExternalScriptItem* item = currentItem(); if ( !item ) { return; } int ret = KMessageBox::questionYesNo( this, i18n("

Do you really want to remove the external script configuration for %1?

" "

Note: The script itself will not be removed.

", item->text()), i18n("Confirm External Script Removal") ); if ( ret == KMessageBox::Yes ) { m_plugin->model()->removeRow( m_plugin->model()->indexFromItem( item ).row() ); } } void ExternalScriptView::editScript() { ExternalScriptItem* item = currentItem(); if ( !item ) { return; } EditExternalScript dlg( item, this ); int ret = dlg.exec(); if (ret == QDialog::Accepted) { item->save(); } } // 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 a39f71ce5..617402edb 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) {} QWidget* create(QWidget *parent = 0) 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 "org.kdevelop.FileManagerView"; + 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/classmemberspage.cpp b/plugins/filetemplates/classmemberspage.cpp index dcd7ca6d5..ac8600ade 100644 --- a/plugins/filetemplates/classmemberspage.cpp +++ b/plugins/filetemplates/classmemberspage.cpp @@ -1,109 +1,109 @@ /* 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 "classmemberspage.h" #include "debug.h" #include #include #include #include using namespace KDevelop; class ClassMembersPagePrivate { public: KEditListWidget* editListWidget; }; ClassMembersPage::ClassMembersPage(QWidget* parent) : QWidget(parent) , d(new ClassMembersPagePrivate) { d->editListWidget = new KEditListWidget(this); d->editListWidget->lineEdit()->setPlaceholderText(i18n("Variable type and identifier")); QVBoxLayout* layout = new QVBoxLayout(this); layout->addWidget(d->editListWidget); setLayout(layout); } ClassMembersPage::~ClassMembersPage() { delete d; } void ClassMembersPage::setMembers(const VariableDescriptionList& members) { QStringList memberItems; foreach (const VariableDescription& variable, members) { QStringList items; if (!variable.access.isEmpty()) { items << variable.access; } if (!variable.type.isEmpty()) { items << variable.type; } items << variable.name; - memberItems << items.join(" "); + memberItems << items.join(QLatin1Char(' ')); } d->editListWidget->setItems(memberItems); } VariableDescriptionList ClassMembersPage::members() const { VariableDescriptionList list; foreach (const QString& item, d->editListWidget->items()) { VariableDescription var; QStringList parts = item.split(' '); switch (parts.size()) { case 1: var.name = parts[0]; break; case 2: var.type = parts[0]; var.name = parts[1]; break; case 3: var.access = parts[0]; var.type = parts[1]; var.name = parts[2]; break; default: qCDebug(PLUGIN_FILETEMPLATES) << "Malformed class member" << item; break; } if (!var.name.isEmpty()) { list << var; } } return list; } diff --git a/plugins/filetemplates/filetemplatesplugin.cpp b/plugins/filetemplates/filetemplatesplugin.cpp index 4c734b164..8d41c81ab 100644 --- a/plugins/filetemplates/filetemplatesplugin.cpp +++ b/plugins/filetemplates/filetemplatesplugin.cpp @@ -1,289 +1,289 @@ #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) : KDevelop::IToolViewFactory() , m_plugin(plugin) { } QWidget* create(QWidget* parent = 0) override { return new TemplatePreviewToolView(m_plugin, parent); } QString id() const override { - return "org.kdevelop.TemplateFilePreview"; + return QStringLiteral("org.kdevelop.TemplateFilePreview"); } Qt::DockWidgetArea defaultPosition() override { return Qt::RightDockWidgetArea; } private: FileTemplatesPlugin* m_plugin; }; FileTemplatesPlugin::FileTemplatesPlugin(QObject* parent, const QVariantList& args) - : IPlugin("kdevfiletemplates", parent) + : IPlugin(QStringLiteral("kdevfiletemplates"), parent) , m_model(0) { Q_UNUSED(args); KDEV_USE_EXTENSION_INTERFACE(ITemplateProvider) - setXMLFile("kdevfiletemplates.rc"); - QAction* action = actionCollection()->addAction("new_from_template"); + setXMLFile(QStringLiteral("kdevfiletemplates.rc")); + QAction* action = actionCollection()->addAction(QStringLiteral("new_from_template")); action->setText( i18n( "New From Template" ) ); - action->setIcon( QIcon::fromTheme( "code-class" ) ); + 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("code-class")); + 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("document-preview")); + 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("code-class"); + return QIcon::fromTheme(QStringLiteral("code-class")); } QAbstractItemModel* FileTemplatesPlugin::templatesModel() { if(!m_model) { - m_model = new TemplatesModel("kdevfiletemplates", this); + m_model = new TemplatesModel(QStringLiteral("kdevfiletemplates"), this); } return m_model; } QString FileTemplatesPlugin::knsConfigurationFile() const { - return "kdevfiletemplates.knsrc"; + return QStringLiteral("kdevfiletemplates.knsrc"); } QStringList FileTemplatesPlugin::supportedMimeTypes() const { QStringList types; - types << "application/x-desktop"; - types << "application/x-bzip-compressed-tar"; - types << "application/zip"; + 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().value(); + 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 << "*.kdevtemplate" << "*.desktop"; + 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(".kdevtemplate")) + 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().value().isValid()) + 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().value())); + 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 ddd58298c..95908410a 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_) : license(0) , 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, "kdevcodegen/licenses", QStandardPaths::LocateDirectory); + 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 = "Error, could not open license file.\n Was it deleted?"; + 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("", QDate::currentDate().toString("yyyy")); - licenseText.replace("", QDate::currentDate().toString("MM")); - licenseText.replace("", QDate::currentDate().toString("dd")); - QString developer("%1 <%2>"); + 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 = ""; + name = QStringLiteral(""); } developer = developer.arg(name); QString email = emailSettings.getSetting(KEMailSettings::EmailAddress); if (email.isEmpty()) { - email = "email"; //no < > as they are already through the email field + email = QStringLiteral("email"); //no < > as they are already through the email field } developer = developer.arg(email); - licenseText.replace("", developer); + licenseText.replace(QLatin1String(""), developer); return licenseText; } } Q_DECLARE_TYPEINFO(KDevelop::LicensePagePrivate::LicenseInfo, Q_MOVABLE_TYPE); #include "moc_licensepage.cpp" diff --git a/plugins/filetemplates/main.cpp b/plugins/filetemplates/main.cpp index 058510fe2..a5a371c9f 100644 --- a/plugins/filetemplates/main.cpp +++ b/plugins/filetemplates/main.cpp @@ -1,17 +1,17 @@ #include #include #include "templateclassassistant.h" #include using namespace KDevelop; int main(int argc, char** argv) { QApplication app(argc, argv); AutoTestShell::init(); TestCore::initialize(Core::NoUi); - TemplateClassAssistant* assistant = new TemplateClassAssistant(QApplication::activeWindow(), QUrl::fromLocalFile("/tmp/")); + TemplateClassAssistant* assistant = new TemplateClassAssistant(QApplication::activeWindow(), QUrl::fromLocalFile(QStringLiteral("/tmp/"))); assistant->setAttribute(Qt::WA_DeleteOnClose); assistant->show(); return app.exec(); } diff --git a/plugins/filetemplates/outputpage.cpp b/plugins/filetemplates/outputpage.cpp index aaac5533a..10dbb2b93 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_) : page(page_) , output(0) { } 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(", "))); + 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/overridespage.cpp b/plugins/filetemplates/overridespage.cpp index 5e39627cc..730738c52 100644 --- a/plugins/filetemplates/overridespage.cpp +++ b/plugins/filetemplates/overridespage.cpp @@ -1,266 +1,266 @@ /* Copyright 2008 Hamish Rodda This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "overridespage.h" #include "ui_overridevirtuals.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; enum Column { ClassOrFunctionColumn, ///< Column represents either a base class item or a function item AccessColumn, ///< Column represents the access policy of a function PropertiesColumn ///< Column represents the properties of a function (e.g. if it's a ctor, dtor, signal, slot, ...) }; static QString accessPolicyToString(Declaration::AccessPolicy accessPolicy) { switch (accessPolicy) { case Declaration::DefaultAccess: case Declaration::Public: return i18n("Public"); case Declaration::Protected: return i18n("Protected"); case Declaration::Private: return i18n("Private"); default: qCritical("Unexpected value for Declaration::AccessPolicy: %d", accessPolicy); Q_ASSERT(false); return QString(); } } static QString functionPropertiesToString(ClassFunctionDeclaration* decl) { Q_ASSERT(decl); QStringList properties; if (decl->isConstructor()) { properties << i18n("Constructor"); } else if (decl->isDestructor()) { properties << i18n("Destructor"); } else if (decl->isSignal()) { properties << i18n("Signal"); } else if (decl->isSlot()) { properties << i18n("Slot"); } else if (decl->isAbstract()) { properties << i18n("Abstract function"); } - return properties.join(", "); + return properties.join(QStringLiteral(", ")); } struct KDevelop::OverridesPagePrivate { OverridesPagePrivate() : overrides(0) { } Ui::OverridesDialog* overrides; QMultiHash overriddenFunctions; QMap declarationMap; QList chosenOverrides; }; OverridesPage::OverridesPage(QWidget* parent) : QWidget(parent) , d(new OverridesPagePrivate) { d->overrides = new Ui::OverridesDialog; d->overrides->setupUi(this); connect(d->overrides->selectAllPushButton, &QPushButton::pressed, this, &OverridesPage::selectAll); connect(d->overrides->deselectAllPushButton, &QPushButton::pressed, this, &OverridesPage::deselectAll); } OverridesPage::~OverridesPage() { delete d->overrides; delete d; } QList< DeclarationPointer > OverridesPage::selectedOverrides() const { QList declarations; for (int i = 0; i < d->overrides->overridesTree->topLevelItemCount(); ++i) { QTreeWidgetItem* item = d->overrides->overridesTree->topLevelItem(i); for (int j = 0; j < item->childCount(); ++j) { QTreeWidgetItem* child = item->child(j); if (child->checkState(ClassOrFunctionColumn) == Qt::Checked) { qCDebug(PLUGIN_FILETEMPLATES) << "Adding declaration" << d->declarationMap[child]->toString(); declarations << d->declarationMap[child]; } } } qCDebug(PLUGIN_FILETEMPLATES) << declarations.size(); return declarations; } void OverridesPage::clear() { d->overriddenFunctions.clear(); overrideTree()->clear(); d->chosenOverrides.clear(); d->declarationMap.clear(); } void OverridesPage::addBaseClasses(const QList& directBases, const QList& allBases) { DUChainReadLocker lock; foreach(const DeclarationPointer& baseClass, allBases) { DUContext* context = baseClass->internalContext(); QTreeWidgetItem* classItem = new QTreeWidgetItem(overrideTree(), QStringList() << baseClass->qualifiedIdentifier().toString()); classItem->setIcon(ClassOrFunctionColumn, DUChainUtils::iconForDeclaration(baseClass.data())); //For this internal context get all the function declarations inside the class foreach (Declaration * childDeclaration, context->localDeclarations()) { if (AbstractFunctionDeclaration * func = dynamic_cast(childDeclaration)) { if (func->isVirtual()) { // Its a virtual function, add it to the list unless it's a destructor ClassFunctionDeclaration* cFunc = dynamic_cast(childDeclaration); if (cFunc && !cFunc->isDestructor()) { addPotentialOverride(classItem, DeclarationPointer(childDeclaration)); } } else if (directBases.contains(baseClass)) { // add ctors of direct parents ClassFunctionDeclaration* cFunc = dynamic_cast(childDeclaration); if (cFunc && cFunc->isConstructor()) { addPotentialOverride(classItem, DeclarationPointer(childDeclaration)); } } } } } overrideTree()->expandAll(); overrideTree()->header()->resizeSections(QHeaderView::ResizeToContents); } void OverridesPage::addPotentialOverride(QTreeWidgetItem* classItem, const DeclarationPointer& childDeclaration) { ClassFunctionDeclaration* function = dynamic_cast(childDeclaration.data()); if (!function) { qCDebug(PLUGIN_FILETEMPLATES) << "Declaration is not a function:" << childDeclaration->identifier().toString(); return; } if (function->accessPolicy() == Declaration::Private) { qCDebug(PLUGIN_FILETEMPLATES) << "Declaration is private, returning:" << function->identifier().toString(); return; } qCDebug(PLUGIN_FILETEMPLATES) << childDeclaration->toString(); if (d->overriddenFunctions.contains(childDeclaration->identifier())) { - foreach (DeclarationPointer decl, d->overriddenFunctions.values(childDeclaration->identifier())) + foreach (const DeclarationPointer& decl, d->overriddenFunctions.values(childDeclaration->identifier())) { if (decl->indexedType() == childDeclaration->indexedType()) { qCDebug(PLUGIN_FILETEMPLATES) << "Declaration is already shown"; return; } } } d->overriddenFunctions.insert(childDeclaration->identifier(), childDeclaration); QTreeWidgetItem* overrideItem = new QTreeWidgetItem(classItem, QStringList() << childDeclaration->toString()); overrideItem->setFlags( Qt::ItemFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable) ); overrideItem->setCheckState(ClassOrFunctionColumn, d->chosenOverrides.contains(childDeclaration) ? Qt::Checked : Qt::Unchecked); overrideItem->setIcon(ClassOrFunctionColumn, DUChainUtils::iconForDeclaration(childDeclaration.data())); overrideItem->setData(ClassOrFunctionColumn, Qt::UserRole, QVariant::fromValue(IndexedDeclaration(childDeclaration.data()))); overrideItem->setText(AccessColumn, accessPolicyToString(function->accessPolicy())); overrideItem->setText(PropertiesColumn, functionPropertiesToString(function)); if (function->isAbstract()) { - overrideItem->setIcon(ClassOrFunctionColumn, QIcon::fromTheme("flag-red")); + overrideItem->setIcon(ClassOrFunctionColumn, QIcon::fromTheme(QStringLiteral("flag-red"))); overrideItem->setCheckState(ClassOrFunctionColumn, Qt::Checked); classItem->removeChild(overrideItem); classItem->insertChild(0, overrideItem); } d->declarationMap[overrideItem] = childDeclaration; } QTreeWidget* OverridesPage::overrideTree() const { return d->overrides->overridesTree; } QWidget* OverridesPage::extraFunctionsContainer() const { return d->overrides->extraFunctionWidget; } void OverridesPage::selectAll() { for (int i = 0; i < d->overrides->overridesTree->topLevelItemCount(); ++i) { QTreeWidgetItem* item = d->overrides->overridesTree->topLevelItem(i); for (int j = 0; j < item->childCount(); ++j) item->child(j)->setCheckState(ClassOrFunctionColumn, Qt::Checked); } } void OverridesPage::deselectAll() { for (int i = 0; i < d->overrides->overridesTree->topLevelItemCount(); ++i) { QTreeWidgetItem* item = d->overrides->overridesTree->topLevelItem(i); for (int j = 0; j < item->childCount(); ++j) item->child(j)->setCheckState(ClassOrFunctionColumn, Qt::Unchecked); } } void OverridesPage::addCustomDeclarations (const QString& category, const QList& declarations) { qCDebug(PLUGIN_FILETEMPLATES) << category << declarations.size(); DUChainReadLocker lock(DUChain::lock()); QTreeWidgetItem* item = new QTreeWidgetItem(overrideTree(), QStringList() << category); foreach (const DeclarationPointer& declaration, declarations) { addPotentialOverride(item, declaration); } overrideTree()->expandAll(); overrideTree()->header()->resizeSections(QHeaderView::ResizeToContents); } diff --git a/plugins/filetemplates/templateclassassistant.cpp b/plugins/filetemplates/templateclassassistant.cpp index 08309d197..7e2335ae3 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 = 0; \ d->name##PageWidget = 0; \ } #define ZERO_PAGE(name) \ d->name##Page = 0; \ d->name##PageWidget = 0; using namespace KDevelop; class KDevelop::TemplateClassAssistantPrivate { public: TemplateClassAssistantPrivate(const 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(0) , generator(0) , renderer(0) { } TemplateClassAssistantPrivate::~TemplateClassAssistantPrivate() { delete helper; if (generator) { delete generator; } else { // if we got a generator, it should keep ownership of the renderer // otherwise, we created a templaterenderer on our own delete renderer; } } void TemplateClassAssistantPrivate::addFilesToTarget (const QHash< QString, 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 = 0; foreach (ProjectBaseItem* item, items) { if (ProjectTargetItem* target = item->target()) { targets << target; } } if (targets.isEmpty()) { // If no target was explicitly found yet, try all the targets in the current folder foreach (ProjectBaseItem* item, items) { targets << item->targetList(); } } if (targets.isEmpty()) { // If still no targets, we traverse the tree up to the first directory with targets ProjectBaseItem* item = items.first()->parent(); while (targets.isEmpty() && item) { targets = item->targetList(); item = item->parent(); } } if (targets.size() == 1) { 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("project-development-new-template")); + d->templateSelectionPage->setIcon(QIcon::fromTheme(QStringLiteral("project-development-new-template"))); - d->dummyPage = addPage(new QWidget(this), QLatin1String("Dummy Page")); + 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 = 0; 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 == "Class") + if (d->type == QLatin1String("Class")) { d->classIdentifierPageWidget = new ClassIdentifierPage(this); d->classIdentifierPage = addPage(d->classIdentifierPageWidget, i18n("Class Basics")); - d->classIdentifierPage->setIcon(QIcon::fromTheme("classnew")); + 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("code-class")); + 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("field")); + d->membersPage->setIcon(QIcon::fromTheme(QStringLiteral("field"))); setValid(d->membersPage, true); d->helper = 0; 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 == "Test") + 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("text-x-copying")); + 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("document-save")); + 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("license", d->licensePageWidget->license()); + 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("name", d->testCasesPageWidget->name()); - d->renderer->addVariable("testCases", d->testCasesPageWidget->testCases()); + 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 = 0; if (d->generator) { delete d->generator; } else { delete d->renderer; } d->generator = 0; d->renderer = 0; 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), QLatin1String("Dummy Page")); + 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/templateoptionspage.cpp b/plugins/filetemplates/templateoptionspage.cpp index 2d95f333d..73c48bb5b 100644 --- a/plugins/filetemplates/templateoptionspage.cpp +++ b/plugins/filetemplates/templateoptionspage.cpp @@ -1,137 +1,137 @@ /* 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 "templateoptionspage.h" #include "templateclassassistant.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; class KDevelop::TemplateOptionsPagePrivate { public: QList entries; QHash controls; QHash typeProperties; }; TemplateOptionsPage::TemplateOptionsPage(QWidget* parent, Qt::WindowFlags f) : QWidget(parent, f) , d(new TemplateOptionsPagePrivate) { - d->typeProperties.insert("String", "text"); - d->typeProperties.insert("Int", "value"); - d->typeProperties.insert("Bool", "checked"); + d->typeProperties.insert(QStringLiteral("String"), "text"); + d->typeProperties.insert(QStringLiteral("Int"), "value"); + d->typeProperties.insert(QStringLiteral("Bool"), "checked"); } TemplateOptionsPage::~TemplateOptionsPage() { delete d; } void TemplateOptionsPage::load(const SourceFileTemplate& fileTemplate, TemplateRenderer* renderer) { d->entries.clear(); QLayout* layout = new QVBoxLayout(); QHash > options = fileTemplate.customOptions(renderer); QHash >::const_iterator it; for (it = options.constBegin(); it != options.constEnd(); ++it) { QGroupBox* box = new QGroupBox(this); box->setTitle(it.key()); QFormLayout* formLayout = new QFormLayout; d->entries << it.value(); foreach (const SourceFileTemplate::ConfigOption& entry, it.value()) { QLabel* label = new QLabel(entry.label, box); QWidget* control = 0; const QString type = entry.type; - if (type == "String") + if (type == QLatin1String("String")) { control = new QLineEdit(entry.value.toString(), box); } - else if (type == "Int") + else if (type == QLatin1String("Int")) { auto input = new QSpinBox(box); input->setValue(entry.value.toInt()); if (!entry.minValue.isEmpty()) { input->setMinimum(entry.minValue.toInt()); } if (!entry.maxValue.isEmpty()) { input->setMaximum(entry.maxValue.toInt()); } control = input; } - else if (type == "Bool") + else if (type == QLatin1String("Bool")) { - bool checked = (QString::compare(entry.value.toString(), "true", Qt::CaseInsensitive) == 0); + bool checked = (QString::compare(entry.value.toString(), QStringLiteral("true"), Qt::CaseInsensitive) == 0); QCheckBox* checkBox = new QCheckBox(entry.label, box); checkBox->setCheckState(checked ? Qt::Checked : Qt::Unchecked); } else { qCDebug(PLUGIN_FILETEMPLATES) << "Unrecognized option type" << entry.type; } if (control) { formLayout->addRow(label, control); d->controls.insert(entry.name, control); } } box->setLayout(formLayout); layout->addWidget(box); } setLayout(layout); } QVariantHash TemplateOptionsPage::templateOptions() const { QVariantHash values; foreach (const SourceFileTemplate::ConfigOption& entry, d->entries) { Q_ASSERT(d->controls.contains(entry.name)); Q_ASSERT(d->typeProperties.contains(entry.type)); values.insert(entry.name, d->controls[entry.name]->property(d->typeProperties[entry.type])); } qCDebug(PLUGIN_FILETEMPLATES) << values.size() << d->entries.size(); return values; } diff --git a/plugins/filetemplates/templatepreview.cpp b/plugins/filetemplates/templatepreview.cpp index 705964b80..204bbc995 100644 --- a/plugins/filetemplates/templatepreview.cpp +++ b/plugins/filetemplates/templatepreview.cpp @@ -1,147 +1,147 @@ /* * 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 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 "templatepreview.h" #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; TemplatePreviewRenderer::TemplatePreviewRenderer() { QVariantHash vars; - vars["name"] = "Example"; - vars["license"] = "This file is licensed under the ExampleLicense 3.0"; + vars[QStringLiteral("name")] = "Example"; + vars[QStringLiteral("license")] = "This file is licensed under the ExampleLicense 3.0"; // TODO: More variables, preferably the ones from TemplateClassGenerator VariableDescriptionList publicMembers; VariableDescriptionList protectedMembers; VariableDescriptionList privateMembers; - publicMembers << VariableDescription("int", "number"); - protectedMembers << VariableDescription("string", "name"); - privateMembers << VariableDescription("float", "variable"); - vars["members"] = CodeDescription::toVariantList(publicMembers + protectedMembers + privateMembers); - vars["public_members"] = CodeDescription::toVariantList(publicMembers); - vars["protected_members"] = CodeDescription::toVariantList(protectedMembers); - vars["private_members"] = CodeDescription::toVariantList(privateMembers); + publicMembers << VariableDescription(QStringLiteral("int"), QStringLiteral("number")); + protectedMembers << VariableDescription(QStringLiteral("string"), QStringLiteral("name")); + privateMembers << VariableDescription(QStringLiteral("float"), QStringLiteral("variable")); + vars[QStringLiteral("members")] = CodeDescription::toVariantList(publicMembers + protectedMembers + privateMembers); + vars[QStringLiteral("public_members")] = CodeDescription::toVariantList(publicMembers); + vars[QStringLiteral("protected_members")] = CodeDescription::toVariantList(protectedMembers); + vars[QStringLiteral("private_members")] = CodeDescription::toVariantList(privateMembers); FunctionDescriptionList publicFunctions; FunctionDescriptionList protectedFunctions; FunctionDescriptionList privateFunctions; - FunctionDescription complexFunction("doBar", VariableDescriptionList(), VariableDescriptionList()); - complexFunction.arguments << VariableDescription("bool", "really"); - complexFunction.arguments << VariableDescription("int", "howMuch"); - complexFunction.returnArguments << VariableDescription("double", QString()); + FunctionDescription complexFunction(QStringLiteral("doBar"), VariableDescriptionList(), VariableDescriptionList()); + complexFunction.arguments << VariableDescription(QStringLiteral("bool"), QStringLiteral("really")); + complexFunction.arguments << VariableDescription(QStringLiteral("int"), QStringLiteral("howMuch")); + complexFunction.returnArguments << VariableDescription(QStringLiteral("double"), QString()); - publicFunctions << FunctionDescription("doFoo", VariableDescriptionList(), VariableDescriptionList()); + publicFunctions << FunctionDescription(QStringLiteral("doFoo"), VariableDescriptionList(), VariableDescriptionList()); publicFunctions << complexFunction; - protectedFunctions << FunctionDescription("onUpdate", VariableDescriptionList(), VariableDescriptionList()); + protectedFunctions << FunctionDescription(QStringLiteral("onUpdate"), VariableDescriptionList(), VariableDescriptionList()); - vars["functions"] = CodeDescription::toVariantList(publicFunctions + protectedFunctions + privateFunctions); - vars["public_functions"] = CodeDescription::toVariantList(publicFunctions); - vars["protected_functions"] = CodeDescription::toVariantList(protectedFunctions); - vars["private_functions"] = CodeDescription::toVariantList(privateFunctions); + vars[QStringLiteral("functions")] = CodeDescription::toVariantList(publicFunctions + protectedFunctions + privateFunctions); + vars[QStringLiteral("public_functions")] = CodeDescription::toVariantList(publicFunctions); + vars[QStringLiteral("protected_functions")] = CodeDescription::toVariantList(protectedFunctions); + vars[QStringLiteral("private_functions")] = CodeDescription::toVariantList(privateFunctions); addVariables(vars); } TemplatePreviewRenderer::~TemplatePreviewRenderer() { } TemplatePreview::TemplatePreview(QWidget* parent, Qt::WindowFlags f) : QWidget(parent, f) { - m_variables["APPNAME"] = "Example"; - m_variables["APPNAMELC"] = "example"; - m_variables["APPNAMEUC"] = "EXAMPLE"; - m_variables["APPNAMEID"] = "Example"; + m_variables[QStringLiteral("APPNAME")] = QStringLiteral("Example"); + m_variables[QStringLiteral("APPNAMELC")] = QStringLiteral("example"); + m_variables[QStringLiteral("APPNAMEUC")] = QStringLiteral("EXAMPLE"); + m_variables[QStringLiteral("APPNAMEID")] = QStringLiteral("Example"); - m_variables["PROJECTDIR"] = QDir::homePath() + "/projects/ExampleProjectDir"; - m_variables["PROJECTDIRNAME"] = "ExampleProjectDir"; - m_variables["VERSIONCONTROLPLUGIN"] = "kdevgit"; + m_variables[QStringLiteral("PROJECTDIR")] = QDir::homePath() + "/projects/ExampleProjectDir"; + m_variables[QStringLiteral("PROJECTDIRNAME")] = QStringLiteral("ExampleProjectDir"); + m_variables[QStringLiteral("VERSIONCONTROLPLUGIN")] = QStringLiteral("kdevgit"); KTextEditor::Document* doc = KTextEditor::Editor::instance()->createDocument(this); m_preview.reset(doc); m_preview->setReadWrite(false); QVBoxLayout* layout = new QVBoxLayout; layout->setContentsMargins(0, 0, 0, 0); setLayout(layout); m_view = m_preview->createView(this); m_view->setStatusBarEnabled(false); if (KTextEditor::ConfigInterface* config = qobject_cast(m_view)) { - config->setConfigValue("icon-bar", false); - config->setConfigValue("folding-bar", false); - config->setConfigValue("line-numbers", false); - config->setConfigValue("dynamic-word-wrap", true); + config->setConfigValue(QStringLiteral("icon-bar"), false); + config->setConfigValue(QStringLiteral("folding-bar"), false); + config->setConfigValue(QStringLiteral("line-numbers"), false); + config->setConfigValue(QStringLiteral("dynamic-word-wrap"), true); } layout->addWidget(m_view); } TemplatePreview::~TemplatePreview() { } QString TemplatePreview::setText(const QString& text, bool isProject, TemplateRenderer::EmptyLinesPolicy policy) { QString rendered; QString errorString; if (!text.isEmpty()) { if (isProject) { rendered = KMacroExpander::expandMacros(text, m_variables); } else { TemplatePreviewRenderer renderer; renderer.setEmptyLinesPolicy(policy); rendered = renderer.render(text); errorString = renderer.errorString(); } } m_preview->setReadWrite(true); m_preview->setText(rendered); m_view->setCursorPosition(KTextEditor::Cursor(0, 0)); m_preview->setReadWrite(false); return errorString; } KTextEditor::Document* TemplatePreview::document() const { return m_preview.data(); } diff --git a/plugins/filetemplates/templateselectionpage.cpp b/plugins/filetemplates/templateselectionpage.cpp index 70db5e208..ac90116e5 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_) : 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()); 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 = 0; 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("kdevfiletemplates.knsrc", ui->view).exec(); + 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("kdevfiletemplates", 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("get-hot-new-stuff")); + 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("application-x-archive"), i18n("Load Template From File"), d->ui->view); + 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/filetemplates/tests/test_generationtest.cpp b/plugins/filetemplates/tests/test_generationtest.cpp index 04cb4d665..2c9b8fb52 100644 --- a/plugins/filetemplates/tests/test_generationtest.cpp +++ b/plugins/filetemplates/tests/test_generationtest.cpp @@ -1,126 +1,126 @@ /* * This file is part of KDevelop * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "test_generationtest.h" #include "tests_config.h" #include #include #include #include #include #include #include using namespace KDevelop; #define COMPARE_FILES(name) \ do { \ -QFile actualFile(Path(Path(baseUrl), name).toLocalFile()); \ +QFile actualFile(Path(Path(baseUrl), QStringLiteral(name)).toLocalFile()); \ QVERIFY(actualFile.open(QIODevice::ReadOnly)); \ -QFile expectedFile(TESTS_EXPECTED_DIR "/" name); \ +QFile expectedFile(QStringLiteral(TESTS_EXPECTED_DIR "/" name)); \ QVERIFY(expectedFile.open(QIODevice::ReadOnly)); \ QCOMPARE(actualFile.size(), expectedFile.size()); \ QCOMPARE(QString(actualFile.readAll()), QString(expectedFile.readAll())); \ } while(0) void TestGenerationTest::initTestCase() { QByteArray xdgData = qgetenv("XDG_DATA_DIRS"); xdgData.prepend(TESTS_DATA_DIR ":"); bool addedDir = qputenv("XDG_DATA_DIRS", xdgData); QVERIFY(addedDir); AutoTestShell::init(); TestCore::initialize (Core::NoUi); - TemplatesModel model("testgenerationtest"); + TemplatesModel model(QStringLiteral("testgenerationtest")); model.refresh(); renderer = new TemplateRenderer; renderer->setEmptyLinesPolicy(TemplateRenderer::TrimEmptyLines); - renderer->addVariable("name", "TestName"); - renderer->addVariable("license", "Test license header\nIn two lines"); + renderer->addVariable(QStringLiteral("name"), "TestName"); + renderer->addVariable(QStringLiteral("license"), "Test license header\nIn two lines"); QStringList testCases; - testCases << "firstTestCase"; - testCases << "secondTestCase"; - testCases << "thirdTestCase"; - renderer->addVariable("testCases", testCases); + testCases << QStringLiteral("firstTestCase"); + testCases << QStringLiteral("secondTestCase"); + testCases << QStringLiteral("thirdTestCase"); + renderer->addVariable(QStringLiteral("testCases"), testCases); } void TestGenerationTest::cleanupTestCase() { delete renderer; TestCore::shutdown(); } void TestGenerationTest::init() { dir.reset(new QTemporaryDir); baseUrl = QUrl::fromLocalFile(dir->path()); } void TestGenerationTest::yamlTemplate() { - QString description = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "testgenerationtest/template_descriptions/test_yaml2.desktop"); + QString description = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("testgenerationtest/template_descriptions/test_yaml2.desktop")); QVERIFY(!description.isEmpty()); SourceFileTemplate file; - file.addAdditionalSearchLocation(TESTS_DATA_DIR "/testgenerationtest/templates"); + file.addAdditionalSearchLocation(QStringLiteral(TESTS_DATA_DIR "/testgenerationtest/templates")); file.setTemplateDescription(description); QCOMPARE(file.name(), QStringLiteral("Testing YAML Template")); DocumentChangeSet changes = renderer->renderFileTemplate(file, baseUrl, urls(file)); changes.applyAllChanges(); COMPARE_FILES("testname.yaml"); } void TestGenerationTest::cppTemplate() { - QString description = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "testgenerationtest/template_descriptions/test_qtestlib.desktop"); + QString description = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("testgenerationtest/template_descriptions/test_qtestlib.desktop")); QVERIFY(!description.isEmpty()); SourceFileTemplate file; - file.addAdditionalSearchLocation(TESTS_DATA_DIR "/testgenerationtest/templates"); + file.addAdditionalSearchLocation(QStringLiteral(TESTS_DATA_DIR "/testgenerationtest/templates")); file.setTemplateDescription(description); QCOMPARE(file.name(), QStringLiteral("Testing C++ Template")); DocumentChangeSet changes = renderer->renderFileTemplate(file, baseUrl, urls(file)); changes.applyAllChanges(); COMPARE_FILES("testname.h"); COMPARE_FILES("testname.cpp"); } QHash< QString, QUrl > TestGenerationTest::urls (const SourceFileTemplate& file) { QHash ret; foreach (const SourceFileTemplate::OutputFile& output, file.outputFiles()) { QUrl url = Path(Path(baseUrl), renderer->render(output.outputName).toLower()).toUrl(); ret.insert(output.identifier, url); } return ret; } QTEST_GUILESS_MAIN(TestGenerationTest); diff --git a/plugins/genericprojectmanager/test/test_projectload.cpp b/plugins/genericprojectmanager/test/test_projectload.cpp index 65a47829c..4b5cb020a 100644 --- a/plugins/genericprojectmanager/test/test_projectload.cpp +++ b/plugins/genericprojectmanager/test/test_projectload.cpp @@ -1,382 +1,383 @@ /* This file is part of KDevelop Copyright 2010 Niko Sams This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "test_projectload.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include QTEST_MAIN(TestProjectLoad) Q_DECLARE_METATYPE(KDevelop::IProject*) using namespace KDevelop; ///FIXME: get rid of this, use temporary dir+file classes! void exec(const QString &cmd) { QProcess proc; proc.setProcessChannelMode(QProcess::ForwardedChannels); proc.start(cmd); proc.waitForFinished(); Q_ASSERT(proc.exitStatus() == QProcess::NormalExit); Q_ASSERT(proc.exitStatus() == 0); } void TestProjectLoad::initTestCase() { AutoTestShell::init(); TestCore::initialize(); ICore::self()->languageController()->backgroundParser()->disableProcessing(); qRegisterMetaType(); foreach(IProject* p, ICore::self()->projectController()->projects()) { ICore::self()->projectController()->closeProject(p); } } void TestProjectLoad::cleanupTestCase() { TestCore::shutdown(); } void TestProjectLoad::init() { foreach(IProject* p, ICore::self()->projectController()->projects()) { ICore::self()->projectController()->closeProject(p); } QCOMPARE(ICore::self()->projectController()->projects().size(), 0); } struct TestProject { // temp directory of project QTemporaryDir* dir; // name of the project (random) QString name; // project file (*.kdev4) QUrl file; ~TestProject() { IProject* p = ICore::self()->projectController()->findProjectByName(name); if (p) { ICore::self()->projectController()->closeProject(p); } delete dir; } }; TestProject makeProject() { TestProject ret; ret.dir = new QTemporaryDir(); QFileInfo dir(ret.dir->path()); Q_ASSERT(dir.exists()); ret.name = dir.fileName(); QStringList projectFileContents; projectFileContents - << "[Project]" + << QStringLiteral("[Project]") << QStringLiteral("Name=") + ret.name - << "Manager=KDevGenericManager"; + << QStringLiteral("Manager=KDevGenericManager"); QUrl projecturl = QUrl::fromLocalFile( dir.absoluteFilePath() + "/simpleproject.kdev4" ); QFile projectFile(projecturl.toLocalFile()); projectFile.open(QIODevice::WriteOnly); - projectFile.write(projectFileContents.join("\n").toLatin1()); + projectFile.write(projectFileContents.join(QStringLiteral("\n")).toLatin1()); projectFile.close(); ret.file = projecturl; Q_ASSERT(ret.dir->isValid()); Q_ASSERT(projecturl.adjusted(QUrl::RemoveFilename).toLocalFile() == ret.dir->path() + '/'); return ret; } void TestProjectLoad::addRemoveFiles() { const TestProject p = makeProject(); QFile f(p.dir->path()+"/sdf"); f.open(QIODevice::WriteOnly); f.close(); ICore::self()->projectController()->openProject(p.file); QSignalSpy spy(KDevelop::ICore::self()->projectController(), SIGNAL(projectOpened(KDevelop::IProject*))); QVERIFY(spy.wait(2000)); IProject* project = ICore::self()->projectController()->projects().first(); QCOMPARE(project->projectFile().toUrl(), p.file); //KDirWatch adds/removes the file automatically for (int i=0; i<100; ++i) { QFile f2(p.dir->path()+"/blub"+QString::number(i)); f2.open(QIODevice::WriteOnly); f2.close(); } for (int i=0; i<50; ++i) { QFile f2(p.dir->path()+"/blub"+QString::number(i)); QVERIFY(f2.exists()); f2.remove(); } QTest::qWait(500); QUrl url = QUrl::fromLocalFile(p.dir->path()+"/blub"+QString::number(50)).adjusted(QUrl::NormalizePathSegments); QCOMPARE(project->filesForPath(IndexedString(url)).count(), 1); - ProjectFileItem* file = project->filesForPath(IndexedString(url)).first(); + ProjectFileItem* file = project->filesForPath(IndexedString(url)).at(0); project->projectFileManager()->removeFilesAndFolders(QList() << file ); //message box has to be accepted manually :( for (int i=51; i<100; ++i) { QFile f2(p.dir->path()+"/blub"+QString::number(i)); f2.remove(); } QTest::qWait(2000); QCOMPARE(project->projectItem()->fileList().count(), 1); } void TestProjectLoad::removeDirRecursive() { const TestProject p = makeProject(); { QFile f(p.dir->path()+"/sdf"); f.open(QIODevice::WriteOnly); f.close(); } { - QDir(p.dir->path()).mkdir("blub"); + QDir(p.dir->path()).mkdir(QStringLiteral("blub")); for (int i=0; i<10; ++i) { QFile f(p.dir->path()+"/blub/file"+QString::number(i)); f.open(QIODevice::WriteOnly); f.close(); } } //close previously opened projects QTest::qWait(1000); //wait for projects to load foreach ( IProject* p, ICore::self()->projectController()->projects()) { ICore::self()->projectController()->closeProject(p); QTest::qWait(100); } QVERIFY(ICore::self()->projectController()->projects().isEmpty()); ICore::self()->projectController()->openProject(p.file); QSignalSpy spy(KDevelop::ICore::self()->projectController(), SIGNAL(projectOpened(KDevelop::IProject*))); QVERIFY(spy.wait(20000)); IProject* project = ICore::self()->projectController()->projects().first(); QCOMPARE(project->projectFile().toUrl(), p.file); for (int i=0; i<1; ++i) { QUrl url = QUrl::fromLocalFile(p.dir->path()+"/blub").adjusted(QUrl::NormalizePathSegments); QCOMPARE(project->foldersForPath(IndexedString(url)).count(), 1); - ProjectFolderItem* file = project->foldersForPath(IndexedString(url)).first(); + ProjectFolderItem* file = project->foldersForPath(IndexedString(url)).at(0); project->projectFileManager()->removeFilesAndFolders(QList() << file ); } QTest::qWait(2000); QCOMPARE(project->projectItem()->fileList().count(), 1); } void createFile(const QString& path) { QFile f(path); f.open(QIODevice::WriteOnly); f.write(QByteArray::number(qrand())); f.write(QByteArray::number(qrand())); f.write(QByteArray::number(qrand())); f.write(QByteArray::number(qrand())); f.close(); } void _writeRandomStructure(QString path, int files) { QDir p(path); QString name = QString::number(qrand()); if (qrand() < RAND_MAX / 5) { p.mkdir(name); path += '/' + name; // qDebug() << "wrote path" << path; } else { createFile(path+'/'+name); // qDebug() << "wrote file" << path+"/"+name; } files--; if (files > 0) { _writeRandomStructure(path, files); } } void fillProject(int filesPerDir, int dirs, const TestProject& project, bool wait) { for(int i=0; i < dirs; ++i) { const QString name = "foox" + QString::number(i); QDir(project.dir->path()).mkdir(name); _writeRandomStructure(project.dir->path() + name, filesPerDir); if (wait) { QTest::qWait(100); } } } void TestProjectLoad::addLotsOfFiles() { TestProject p = makeProject(); ICore::self()->projectController()->openProject(p.file); QVERIFY(QSignalSpy(KDevelop::ICore::self()->projectController(), SIGNAL(projectOpened(KDevelop::IProject*))).wait(2000)); QCOMPARE(ICore::self()->projectController()->projects().size(), 1); IProject* project = ICore::self()->projectController()->projects().first(); QCOMPARE(project->projectFile().toUrl(), p.file); fillProject(50, 25, p, true); QTest::qWait(2000); } void TestProjectLoad::addMultipleJobs() { const TestProject p1 = makeProject(); fillProject(10, 25, p1, false); const TestProject p2 = makeProject(); fillProject(10, 25, p2, false); QSignalSpy spy(ICore::self()->projectController(), SIGNAL(projectOpened(KDevelop::IProject*))); ICore::self()->projectController()->openProject(p1.file); ICore::self()->projectController()->openProject(p2.file); const int wait = 25; const int maxWait = 2000; int waited = 0; while(waited < maxWait && spy.count() != 2) { QTest::qWait(wait); waited += wait; } QCOMPARE(ICore::self()->projectController()->projects().size(), 2); } void TestProjectLoad::raceJob() { // our goal here is to try to reproduce https://bugs.kde.org/show_bug.cgi?id=260741 // my idea is that this can be triggered by the following: // - list dir foo/bar containing lots of files // - remove dir foo while listjob is still running TestProject p = makeProject(); QDir dir(p.dir->path()); - QVERIFY(dir.mkpath("test/zzzzz")); + QVERIFY(dir.mkpath(QStringLiteral("test/zzzzz"))); for(int i = 0; i < 1000; ++i) { createFile(QString(p.dir->path() + "/test/zzzzz/%1").arg(i)); createFile(QString(p.dir->path() + "/test/%1").arg(i)); } ICore::self()->projectController()->openProject(p.file); QVERIFY(QSignalSpy(KDevelop::ICore::self()->projectController(), SIGNAL(projectOpened(KDevelop::IProject*))).wait(2000)); QCOMPARE(ICore::self()->projectController()->projectCount(), 1); IProject *project = ICore::self()->projectController()->projectAt(0); QCOMPARE(project->projectFile().toUrl(), p.file); ProjectFolderItem* root = project->projectItem(); QCOMPARE(root->rowCount(), 1); ProjectBaseItem* testItem = root->child(0); QVERIFY(testItem->folder()); QCOMPARE(testItem->baseName(), QStringLiteral("test")); QCOMPARE(testItem->rowCount(), 1001); - ProjectBaseItem* asdfItem = testItem->children().last(); + int last = testItem->children().size() - 1; + ProjectBaseItem* asdfItem = testItem->children().at(last); QVERIFY(asdfItem->folder()); // move dir - dir.rename("test", "test2"); + dir.rename(QStringLiteral("test"), QStringLiteral("test2")); // move sub dir - dir.rename("test2/zzzzz", "test2/bla"); + dir.rename(QStringLiteral("test2/zzzzz"), QStringLiteral("test2/bla")); QTest::qWait(500); QCOMPARE(root->rowCount(), 1); testItem = root->child(0); QVERIFY(testItem->folder()); QCOMPARE(testItem->baseName(), QStringLiteral("test2")); } void TestProjectLoad::addDuringImport() { // our goal here is to try to reproduce an issue in the optimized filesForPath implementation // which requires the project to be associated to the model to function properly // to trigger this we create a big project, import it and then call filesForPath during // the import action TestProject p = makeProject(); QDir dir(p.dir->path()); - QVERIFY(dir.mkpath("test/zzzzz")); + QVERIFY(dir.mkpath(QStringLiteral("test/zzzzz"))); for(int i = 0; i < 1000; ++i) { createFile(QString(p.dir->path() + "/test/zzzzz/%1").arg(i)); createFile(QString(p.dir->path() + "/test/%1").arg(i)); } QSignalSpy spy(ICore::self()->projectController(), SIGNAL(projectAboutToBeOpened(KDevelop::IProject*))); ICore::self()->projectController()->openProject(p.file); // not yet ready QCOMPARE(ICore::self()->projectController()->projectCount(), 0); // but about to be opened QCOMPARE(spy.count(), 1); - IProject* project = spy.value(0).first().value(); + IProject* project = spy.value(0).at(0).value(); QVERIFY(project); QCOMPARE(project->path(), Path(KIO::upUrl(p.file))); - QUrl file = p.file.resolved(QUrl("test/zzzzz/999")); + QUrl file = p.file.resolved(QUrl(QStringLiteral("test/zzzzz/999"))); QVERIFY(QFile::exists(file.toLocalFile())); // this most probably is not yet loaded // and this should not crash QCOMPARE(project->itemsForPath(IndexedString(file)).size(), 0); // now delete that file and don't crash QFile::remove(file.toLocalFile()); // now create another file QUrl file2 = file.adjusted(QUrl::RemoveFilename); file2.setPath(file2.path() + "999v2"); createFile(file2.toLocalFile()); QVERIFY(!project->isReady()); // now wait for finish QVERIFY(QSignalSpy(KDevelop::ICore::self()->projectController(),\ SIGNAL(projectOpened(KDevelop::IProject*))).wait(2000)); QVERIFY(project->isReady()); // make sure our file removal + addition was properly tracked QCOMPARE(project->filesForPath(IndexedString(file)).size(), 0); QCOMPARE(project->filesForPath(IndexedString(file2)).size(), 1); //NOTE: this test is probabably incomplete, I bet there are some race conditions left, // esp. when adding a file at a point where the parent folder was already imported // or removing a file that was already imported } diff --git a/plugins/git/gitjob.h b/plugins/git/gitjob.h index 22fd44484..ff6d8fe32 100644 --- a/plugins/git/gitjob.h +++ b/plugins/git/gitjob.h @@ -1,34 +1,35 @@ /* * This file is part of KDevelop * Copyright 2010 Aleix Pol Gonzalez * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_PLUGIN_GITJOB_H #define KDEVPLATFORM_PLUGIN_GITJOB_H #include class GitJob : public KDevelop::DVcsJob { + Q_OBJECT public: explicit GitJob(const QDir& workingDir, KDevelop::IPlugin* parent = 0, KDevelop::OutputJob::OutputJobVerbosity verbosity = KDevelop::OutputJob::Verbose); }; #endif // KDEVPLATFORM_PLUGIN_GITJOB_H diff --git a/plugins/git/gitplugin.cpp b/plugins/git/gitplugin.cpp index f06ef4ec0..7a58b510b 100644 --- a/plugins/git/gitplugin.cpp +++ b/plugins/git/gitplugin.cpp @@ -1,1462 +1,1465 @@ /*************************************************************************** * 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 "gitclonejob.h" #include #include #include "stashmanagerdialog.h" #include #include #include #include #include #include #include "gitjob.h" #include "gitmessagehighlighter.h" #include "gitplugincheckinrepositoryjob.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 "^HEAD"; + return QStringLiteral("^HEAD"); case VcsRevision::Base: - return ""; + return QString(); case VcsRevision::Working: - return ""; + return QString(); case VcsRevision::Previous: Q_ASSERT(!currentRevision.isEmpty()); return currentRevision + "^1"; case VcsRevision::Start: - return ""; + 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()) { m_hasError = true; m_errorDescription = i18n("git is not installed"); return; } KDEV_USE_EXTENSION_INTERFACE( KDevelop::IBasicVersionControl ) KDEV_USE_EXTENSION_INTERFACE( KDevelop::IDistributedVersionControl ) KDEV_USE_EXTENSION_INTERFACE( KDevelop::IBranchingVersionControl ) m_hasError = false; 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, 0); 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 QLatin1String("Git"); + 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.cd(QStringLiteral(".git")) && dir.exists(QStringLiteral("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(srcRevision.revisionType()==VcsRevision::Special && dstRevision.revisionType()==VcsRevision::Special && srcRevision.specialType()==VcsRevision::Base && dstRevision.specialType()==VcsRevision::Working) *job << "HEAD"; 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")); QDir dir = dotGitDirectory(localLocations.front()); 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; } 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 0; 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 = 0; QStringList lines = job->output().split('\n'); bool skipNext=false; QMap definedRevisions; for(QStringList::const_iterator it=lines.constBegin(), itEnd=lines.constEnd(); it!=itEnd; ++it) { if(skipNext) { skipNext=false; results += qVariantFromValue(*annotation); continue; } if(it->isEmpty()) continue; QString name = it->left(it->indexOf(' ')); QString value = it->right(it->size()-name.size()-1); - if(name=="author") + if(name==QLatin1String("author")) annotation->setAuthor(value); - else if(name=="author-mail") {} //TODO: do smth with the e-mail? - else if(name=="author-tz") {} //TODO: does it really matter? - else if(name=="author-time") + 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=="summary") + else if(name==QLatin1String("summary")) annotation->setCommitMessage(value); else if(name.startsWith(QStringLiteral("committer"))) {} //We will just store the authors - else if(name=="previous") {} //We don't need that either - else if(name=="filename") { skipNext=true; } - else if(name=="boundary") { + 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 { QStringList values = value.split(' '); VcsRevision rev; rev.setRevisionValue(name.left(8), KDevelop::VcsRevision::GlobalNumber); skipNext = definedRevisions.contains(name); if(!skipNext) definedRevisions.insert(name, VcsAnnotationLine()); annotation = &definedRevisions[name]; 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(0, 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::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) { QStringList branchListDirty = job->output().split('\n', QString::SkipEmptyParts); QStringList branchList; foreach(QString 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; if (branch.startsWith('*')) branch = branch.right(branch.size()-2); branchList<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 QRegExp rx_com( "commit \\w{1,40}" ); QStringList lines = job->output().split('\n', QString::SkipEmptyParts); DVcsEvent item; QString commitLog; for (int i=0; i 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 == "Author") { + if (cap1 == QLatin1String("Author")) { item.setAuthor(infoRegex.cap(2).trimmed()); - } else if (cap1 == "Date") { + } 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)[0].toLatin1()); + 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(" ")) { + } 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) { QStringList outputLines = job->output().split('\n', QString::SkipEmptyParts); QDir workingDir = job->directory(); QDir dotGit = dotGitDirectory(QUrl::fromLocalFile(workingDir.absolutePath())); QVariantList statuses; QList processedFiles; foreach(const QString& line, outputLines) { //every line is 2 chars for the status, 1 space then the file desc QString curr=line.right(line.size()-3); QString state = line.left(2); int arrow = curr.indexOf(QStringLiteral(" -> ")); if(arrow>=0) { VcsStatusInfo status; status.setUrl(QUrl::fromLocalFile(dotGit.absoluteFilePath(curr.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))); status.setState(messageToState(state)); processedFiles.append(status.url()); qCDebug(PLUGIN_GIT) << "Checking git status for " << line << curr << messageToState(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) { QStringList versionString = job->output().trimmed().split(' ').last().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) { QString 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 QString& msg) { Q_ASSERT(msg.size()==1 || msg.size()==2); VcsStatusInfo::State ret = VcsStatusInfo::ItemUnknown; - if(msg.contains('U') || msg == "AA" || msg == "DD") + if(msg.contains('U') || msg == QLatin1String("AA") || msg == QLatin1String("DD")) ret = VcsStatusInfo::ItemHasConflicts; else switch(msg[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[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" << "-c" << "color.diff=false" << "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(".git/MERGE_MSG")); + 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 = 0, Qt::WindowFlags f = 0) : StandardVcsLocationWidget(parent, f) {} bool isCorrect() const override { return !url().isEmpty(); } }; KDevelop::VcsLocationWidget* GitPlugin::vcsLocation(QWidget* parent) const { return new GitVcsLocationWidget(parent); } bool GitPlugin::hasError() const { return m_hasError; } QString GitPlugin::errorDescription() const { return m_errorDescription; } 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("HEAD")); + 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) { auto job = new DVcsJob(urlDir(repository), this); *job << "git" << "config" << key << value; return job; } + +#include "gitplugin.moc" diff --git a/plugins/git/stashmanagerdialog.cpp b/plugins/git/stashmanagerdialog.cpp index 308d36c7c..2f6ca1e37 100644 --- a/plugins/git/stashmanagerdialog.cpp +++ b/plugins/git/stashmanagerdialog.cpp @@ -1,153 +1,153 @@ /* * This file is part of KDevelop * Copyright 2010 Aleix Pol Gonzalez * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "stashmanagerdialog.h" #include "ui_stashmanagerdialog.h" #include "gitplugin.h" #include "stashpatchsource.h" #include #include #include #include #include #include #include #include #include using namespace KDevelop; StashManagerDialog::StashManagerDialog(const QDir& stashed, GitPlugin* plugin, QWidget* parent) : QDialog(parent), m_plugin(plugin), m_dir(stashed) { setWindowTitle(i18n("Stash Manager")); m_mainWidget = new QWidget(this); m_ui = new Ui::StashManager; m_ui->setupUi(m_mainWidget); StashModel* m = new StashModel(stashed, plugin, this); m_ui->stashView->setModel(m); connect(m_ui->show, &QPushButton::clicked, this, &StashManagerDialog::showStash); connect(m_ui->apply, &QPushButton::clicked, this, &StashManagerDialog::applyClicked); connect(m_ui->branch, &QPushButton::clicked, this, &StashManagerDialog::branchClicked); connect(m_ui->pop, &QPushButton::clicked, this, &StashManagerDialog::popClicked); connect(m_ui->drop, &QPushButton::clicked, this, &StashManagerDialog::dropClicked); connect(m, &StashModel::rowsInserted, this, &StashManagerDialog::stashesFound); connect(m_ui->buttonBox, &QDialogButtonBox::rejected, this, &StashManagerDialog::reject); m_mainWidget->setEnabled(false); //we won't enable it until we have the model with data and selection } StashManagerDialog::~StashManagerDialog() { delete m_ui; } void StashManagerDialog::stashesFound() { QModelIndex firstIdx=m_ui->stashView->model()->index(0, 0); m_ui->stashView->setCurrentIndex(firstIdx); m_mainWidget->setEnabled(true); } QString StashManagerDialog::selection() const { QModelIndex idx = m_ui->stashView->currentIndex(); Q_ASSERT(idx.isValid()); return idx.data().toString(); } void StashManagerDialog::runStash(const QStringList& arguments) { VcsJob* job = m_plugin->gitStash(m_dir, arguments, OutputJob::Verbose); connect(job, &VcsJob::result, this, &StashManagerDialog::accept); m_mainWidget->setEnabled(false); ICore::self()->runController()->registerJob(job); } void StashManagerDialog::showStash() { IPatchReview * review = ICore::self()->pluginController()->extensionForPlugin(); IPatchSource::Ptr stashPatch(new StashPatchSource(selection(), m_plugin, m_dir)); review->startReview(stashPatch); accept(); } void StashManagerDialog::applyClicked() { runStash(QStringList(QStringLiteral("apply")) << selection()); } void StashManagerDialog::popClicked() { runStash(QStringList(QStringLiteral("pop")) << selection()); } void StashManagerDialog::dropClicked() { QString sel = selection(); int ret = KMessageBox::questionYesNo(this, i18n("Are you sure you want to drop the stash '%1'?", sel)); if(ret == KMessageBox::Yes) - runStash(QStringList("drop") << sel); + runStash(QStringList(QStringLiteral("drop")) << sel); } void StashManagerDialog::branchClicked() { QString branchName = QInputDialog::getText(this, i18n("KDevelop - Git Stash"), i18n("Select a name for the new branch:")); if(!branchName.isEmpty()) - runStash(QStringList("branch") << branchName << selection()); + runStash(QStringList(QStringLiteral("branch")) << branchName << selection()); } //////////////////StashModel StashModel::StashModel(const QDir& dir, GitPlugin* git, QObject* parent) : QStandardItemModel(parent) { VcsJob* job=git->gitStash(dir, QStringList(QStringLiteral("list")), OutputJob::Silent); connect(job, &VcsJob::finished, this, &StashModel::stashListReady); ICore::self()->runController()->registerJob(job); } void StashModel::stashListReady(KJob* _job) { DVcsJob* job = qobject_cast(_job); QList< QByteArray > output = job->rawOutput().split('\n'); foreach(const QByteArray& line, output) { QList< QByteArray > fields = line.split(':'); QList elements; foreach(const QByteArray& field, fields) elements += new QStandardItem(QString(field.trimmed())); appendRow(elements); } } diff --git a/plugins/git/tests/test_git.cpp b/plugins/git/tests/test_git.cpp index c80a1d3a7..14dd9bcd4 100644 --- a/plugins/git/tests/test_git.cpp +++ b/plugins/git/tests/test_git.cpp @@ -1,452 +1,452 @@ /*************************************************************************** * This file was partly taken from KDevelop's cvs plugin * * Copyright 2007 Robert Gruber * * * * Adapted for Git * * Copyright 2008 Evgeniy Ivanov * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "test_git.h" #include #include #include #include #include #include #include #include "../gitplugin.h" #define VERIFYJOB(j) \ do { QVERIFY(j); QVERIFY(j->exec()); QVERIFY((j)->status() == KDevelop::VcsJob::JobSucceeded); } while(0) -const QString tempDir = QDir::tempPath(); -const QString gitTest_BaseDir(tempDir + "/kdevGit_testdir/"); -const QString gitTest_BaseDir2(tempDir + "/kdevGit_testdir2/"); -const QString gitRepo(gitTest_BaseDir + ".git"); -const QString gitSrcDir(gitTest_BaseDir + "src/"); -const QString gitTest_FileName("testfile"); -const QString gitTest_FileName2("foo"); -const QString gitTest_FileName3("bar"); +inline QString tempDir() { return QDir::tempPath(); } +inline QString gitTest_BaseDir() { return tempDir() + "/kdevGit_testdir/"; } +inline QString gitTest_BaseDir2() { return tempDir() + "/kdevGit_testdir2/"; } +inline QString gitRepo() { return gitTest_BaseDir() + ".git"; } +inline QString gitSrcDir() { return gitTest_BaseDir() + "src/"; } +inline QString gitTest_FileName() { return QStringLiteral("testfile"); } +inline QString gitTest_FileName2() { return QStringLiteral("foo"); } +inline QString gitTest_FileName3() { return QStringLiteral("bar"); } using namespace KDevelop; bool writeFile(const QString &path, const QString& content, QIODevice::OpenModeFlag mode = QIODevice::WriteOnly) { QFile f(path); if (!f.open(mode)) { return false; } QTextStream input(&f); input << content; return true; } void GitInitTest::initTestCase() { - AutoTestShell::init({"kdevgit"}); + AutoTestShell::init({QStringLiteral("kdevgit")}); TestCore::initialize(); m_plugin = new GitPlugin(TestCore::self()); } void GitInitTest::cleanupTestCase() { delete m_plugin; TestCore::shutdown(); } void GitInitTest::init() { // Now create the basic directory structure - QDir tmpdir(tempDir); - tmpdir.mkdir(gitTest_BaseDir); - tmpdir.mkdir(gitSrcDir); - tmpdir.mkdir(gitTest_BaseDir2); + QDir tmpdir(tempDir()); + tmpdir.mkdir(gitTest_BaseDir()); + tmpdir.mkdir(gitSrcDir()); + tmpdir.mkdir(gitTest_BaseDir2()); } void GitInitTest::cleanup() { removeTempDirs(); } void GitInitTest::repoInit() { qDebug() << "Trying to init repo"; // make job that creates the local repository - VcsJob* j = m_plugin->init(QUrl::fromLocalFile(gitTest_BaseDir)); + VcsJob* j = m_plugin->init(QUrl::fromLocalFile(gitTest_BaseDir())); VERIFYJOB(j); //check if the CVSROOT directory in the new local repository exists now - QVERIFY(QFileInfo(gitRepo).exists()); + QVERIFY(QFileInfo(gitRepo()).exists()); //check if isValidDirectory works - QVERIFY(m_plugin->isValidDirectory(QUrl::fromLocalFile(gitTest_BaseDir))); + QVERIFY(m_plugin->isValidDirectory(QUrl::fromLocalFile(gitTest_BaseDir()))); //and for non-git dir, I hope nobody has /tmp under git - QVERIFY(!m_plugin->isValidDirectory(QUrl::fromLocalFile("/tmp"))); + QVERIFY(!m_plugin->isValidDirectory(QUrl::fromLocalFile(QStringLiteral("/tmp")))); //we have nothing, so output should be empty - DVcsJob * j2 = m_plugin->gitRevParse(gitRepo, QStringList(QStringLiteral("--branches"))); + DVcsJob * j2 = m_plugin->gitRevParse(gitRepo(), QStringList(QStringLiteral("--branches"))); QVERIFY(j2); QVERIFY(j2->exec()); QString out = j2->output(); QVERIFY(j2->output().isEmpty()); // Make sure to set the Git identity so unit tests don't depend on that - auto j3 = m_plugin->setConfigOption(QUrl::fromLocalFile(gitTest_BaseDir), + auto j3 = m_plugin->setConfigOption(QUrl::fromLocalFile(gitTest_BaseDir()), QStringLiteral("user.email"), QStringLiteral("me@example.com")); VERIFYJOB(j3); - auto j4 = m_plugin->setConfigOption(QUrl::fromLocalFile(gitTest_BaseDir), + auto j4 = m_plugin->setConfigOption(QUrl::fromLocalFile(gitTest_BaseDir()), QStringLiteral("user.name"), QStringLiteral("My Name")); VERIFYJOB(j4); } void GitInitTest::addFiles() { qDebug() << "Adding files to the repo"; //we start it after repoInit, so we still have empty git repo - QVERIFY(writeFile(gitTest_BaseDir + gitTest_FileName, "HELLO WORLD")); - QVERIFY(writeFile(gitTest_BaseDir + gitTest_FileName2, "No, bar()!")); + QVERIFY(writeFile(gitTest_BaseDir() + gitTest_FileName(), QStringLiteral("HELLO WORLD"))); + QVERIFY(writeFile(gitTest_BaseDir() + gitTest_FileName2(), QStringLiteral("No, bar()!"))); //test git-status exitCode (see DVcsJob::setExitCode). - VcsJob* j = m_plugin->status(QList() << QUrl::fromLocalFile(gitTest_BaseDir)); + VcsJob* j = m_plugin->status(QList() << QUrl::fromLocalFile(gitTest_BaseDir())); VERIFYJOB(j); // /tmp/kdevGit_testdir/ and testfile - j = m_plugin->add(QList() << QUrl::fromLocalFile(gitTest_BaseDir + gitTest_FileName)); + j = m_plugin->add(QList() << QUrl::fromLocalFile(gitTest_BaseDir() + gitTest_FileName())); VERIFYJOB(j); - QVERIFY(writeFile(gitSrcDir + gitTest_FileName3, "No, foo()! It's bar()!")); + QVERIFY(writeFile(gitSrcDir() + gitTest_FileName3(), QStringLiteral("No, foo()! It's bar()!"))); //test git-status exitCode again - j = m_plugin->status(QList() << QUrl::fromLocalFile(gitTest_BaseDir)); + j = m_plugin->status(QList() << QUrl::fromLocalFile(gitTest_BaseDir())); VERIFYJOB(j); //repository path without trailing slash and a file in a parent directory // /tmp/repo and /tmp/repo/src/bar - j = m_plugin->add(QList() << QUrl::fromLocalFile(gitSrcDir + gitTest_FileName3)); + j = m_plugin->add(QList() << QUrl::fromLocalFile(gitSrcDir() + gitTest_FileName3())); VERIFYJOB(j); //let's use absolute path, because it's used in ContextMenus - j = m_plugin->add(QList() << QUrl::fromLocalFile(gitTest_BaseDir + gitTest_FileName2)); + j = m_plugin->add(QList() << QUrl::fromLocalFile(gitTest_BaseDir() + gitTest_FileName2())); VERIFYJOB(j); //Now let's create several files and try "git add file1 file2 file3" - QStringList files = QStringList() << "file1" << "file2" << "la la"; + QStringList files = QStringList() << QStringLiteral("file1") << QStringLiteral("file2") << QStringLiteral("la la"); QList multipleFiles; foreach(const QString& file, files) { - QVERIFY(writeFile(gitTest_BaseDir + file, file)); - multipleFiles << QUrl::fromLocalFile(gitTest_BaseDir + file); + QVERIFY(writeFile(gitTest_BaseDir() + file, file)); + multipleFiles << QUrl::fromLocalFile(gitTest_BaseDir() + file); } j = m_plugin->add(multipleFiles); VERIFYJOB(j); } void GitInitTest::commitFiles() { qDebug() << "Committing..."; //we start it after addFiles, so we just have to commit - VcsJob* j = m_plugin->commit(QStringLiteral("Test commit"), QList() << QUrl::fromLocalFile(gitTest_BaseDir)); + VcsJob* j = m_plugin->commit(QStringLiteral("Test commit"), QList() << QUrl::fromLocalFile(gitTest_BaseDir())); VERIFYJOB(j); //test git-status exitCode one more time. - j = m_plugin->status(QList() << QUrl::fromLocalFile(gitTest_BaseDir)); + j = m_plugin->status(QList() << QUrl::fromLocalFile(gitTest_BaseDir())); VERIFYJOB(j); //since we committed the file to the "pure" repository, .git/refs/heads/master should exist //TODO: maybe other method should be used - QString headRefName(gitRepo + "/refs/heads/master"); + QString headRefName(gitRepo() + "/refs/heads/master"); QVERIFY(QFileInfo(headRefName).exists()); //Test the results of the "git add" - DVcsJob* jobLs = new DVcsJob(gitTest_BaseDir, m_plugin); + DVcsJob* jobLs = new DVcsJob(gitTest_BaseDir(), m_plugin); *jobLs << "git" << "ls-tree" << "--name-only" << "-r" << "HEAD"; if (jobLs->exec() && jobLs->status() == KDevelop::VcsJob::JobSucceeded) { QStringList files = jobLs->output().split('\n'); - QVERIFY(files.contains(gitTest_FileName)); - QVERIFY(files.contains(gitTest_FileName2)); - QVERIFY(files.contains("src/" + gitTest_FileName3)); + QVERIFY(files.contains(gitTest_FileName())); + QVERIFY(files.contains(gitTest_FileName2())); + QVERIFY(files.contains("src/" + gitTest_FileName3())); } QString firstCommit; QFile headRef(headRefName); if (headRef.open(QIODevice::ReadOnly)) { QTextStream output(&headRef); output >> firstCommit; } headRef.close(); QVERIFY(!firstCommit.isEmpty()); qDebug() << "Committing one more time"; //let's try to change the file and test "git commit -a" - QVERIFY(writeFile(gitTest_BaseDir + gitTest_FileName, "Just another HELLO WORLD\n")); + QVERIFY(writeFile(gitTest_BaseDir() + gitTest_FileName(), QStringLiteral("Just another HELLO WORLD\n"))); //add changes - j = m_plugin->add(QList() << QUrl::fromLocalFile(gitTest_BaseDir + gitTest_FileName)); + j = m_plugin->add(QList() << QUrl::fromLocalFile(gitTest_BaseDir() + gitTest_FileName())); VERIFYJOB(j); - j = m_plugin->commit(QStringLiteral("KDevelop's Test commit2"), QList() << QUrl::fromLocalFile(gitTest_BaseDir)); + j = m_plugin->commit(QStringLiteral("KDevelop's Test commit2"), QList() << QUrl::fromLocalFile(gitTest_BaseDir())); VERIFYJOB(j); QString secondCommit; if (headRef.open(QIODevice::ReadOnly)) { QTextStream output(&headRef); output >> secondCommit; } headRef.close(); QVERIFY(!secondCommit.isEmpty()); QVERIFY(firstCommit != secondCommit); } void GitInitTest::testInit() { repoInit(); } void GitInitTest::testAdd() { repoInit(); addFiles(); } void GitInitTest::testCommit() { repoInit(); addFiles(); commitFiles(); } void GitInitTest::testBranch(const QString& newBranch) { //Already tested, so I assume that it works - const QUrl baseUrl = QUrl::fromLocalFile(gitTest_BaseDir); + const QUrl baseUrl = QUrl::fromLocalFile(gitTest_BaseDir()); QString oldBranch = runSynchronously(m_plugin->currentBranch(baseUrl)).toString(); VcsRevision rev; rev.setRevisionValue(oldBranch, KDevelop::VcsRevision::GlobalNumber); VcsJob* j = m_plugin->branch(baseUrl, rev, newBranch); VERIFYJOB(j); QVERIFY(runSynchronously(m_plugin->branches(baseUrl)).toStringList().contains(newBranch)); // switch branch j = m_plugin->switchBranch(baseUrl, newBranch); VERIFYJOB(j); QCOMPARE(runSynchronously(m_plugin->currentBranch(baseUrl)).toString(), newBranch); // get into detached head state - j = m_plugin->switchBranch(baseUrl, "HEAD~1"); + j = m_plugin->switchBranch(baseUrl, QStringLiteral("HEAD~1")); VERIFYJOB(j); QCOMPARE(runSynchronously(m_plugin->currentBranch(baseUrl)).toString(), QString()); // switch back j = m_plugin->switchBranch(baseUrl, newBranch); VERIFYJOB(j); QCOMPARE(runSynchronously(m_plugin->currentBranch(baseUrl)).toString(), newBranch); j = m_plugin->deleteBranch(baseUrl, oldBranch); VERIFYJOB(j); QVERIFY(!runSynchronously(m_plugin->branches(baseUrl)).toStringList().contains(oldBranch)); } void GitInitTest::testBranching() { repoInit(); addFiles(); commitFiles(); - const QUrl baseUrl = QUrl::fromLocalFile(gitTest_BaseDir); + const QUrl baseUrl = QUrl::fromLocalFile(gitTest_BaseDir()); VcsJob* j = m_plugin->branches(baseUrl); VERIFYJOB(j); QString curBranch = runSynchronously(m_plugin->currentBranch(baseUrl)).toString(); QCOMPARE(curBranch, QStringLiteral("master")); - testBranch("new"); - testBranch("averylongbranchnamejusttotestlongnames"); - testBranch("KDE/4.10"); + testBranch(QStringLiteral("new")); + testBranch(QStringLiteral("averylongbranchnamejusttotestlongnames")); + testBranch(QStringLiteral("KDE/4.10")); } void GitInitTest::revHistory() { repoInit(); addFiles(); commitFiles(); - QList commits = m_plugin->getAllCommits(gitTest_BaseDir); + QList commits = m_plugin->getAllCommits(gitTest_BaseDir()); QVERIFY(!commits.isEmpty()); QStringList logMessages; for (int i = 0; i < commits.count(); ++i) logMessages << commits[i].getLog(); QCOMPARE(commits.count(), 2); QCOMPARE(logMessages[0], QStringLiteral("KDevelop's Test commit2")); //0 is later than 1! QCOMPARE(logMessages[1], QStringLiteral("Test commit")); QVERIFY(commits[1].getParents().isEmpty()); //0 is later than 1! QVERIFY(!commits[0].getParents().isEmpty()); //initial commit is on the top QVERIFY(commits[1].getCommit().contains(QRegExp("^\\w{,40}$"))); QVERIFY(commits[0].getCommit().contains(QRegExp("^\\w{,40}$"))); QVERIFY(commits[0].getParents()[0].contains(QRegExp("^\\w{,40}$"))); } void GitInitTest::testAnnotation() { repoInit(); addFiles(); commitFiles(); // called after commitFiles - QVERIFY(writeFile(gitTest_BaseDir + gitTest_FileName, "An appended line", QIODevice::Append)); + QVERIFY(writeFile(gitTest_BaseDir() + gitTest_FileName(), QStringLiteral("An appended line"), QIODevice::Append)); - VcsJob* j = m_plugin->commit(QStringLiteral("KDevelop's Test commit3"), QList() << QUrl::fromLocalFile(gitTest_BaseDir)); + VcsJob* j = m_plugin->commit(QStringLiteral("KDevelop's Test commit3"), QList() << QUrl::fromLocalFile(gitTest_BaseDir())); VERIFYJOB(j); - j = m_plugin->annotate(QUrl::fromLocalFile(gitTest_BaseDir + gitTest_FileName), VcsRevision::createSpecialRevision(VcsRevision::Head)); + j = m_plugin->annotate(QUrl::fromLocalFile(gitTest_BaseDir() + gitTest_FileName()), VcsRevision::createSpecialRevision(VcsRevision::Head)); VERIFYJOB(j); QList results = j->fetchResults().toList(); QCOMPARE(results.size(), 2); QVERIFY(results.at(0).canConvert()); VcsAnnotationLine annotation = results.at(0).value(); QCOMPARE(annotation.lineNumber(), 0); QCOMPARE(annotation.commitMessage(), QStringLiteral("KDevelop's Test commit2")); QVERIFY(results.at(1).canConvert()); annotation = results.at(1).value(); QCOMPARE(annotation.lineNumber(), 1); QCOMPARE(annotation.commitMessage(), QStringLiteral("KDevelop's Test commit3")); } void GitInitTest::testRemoveEmptyFolder() { repoInit(); - QDir d(gitTest_BaseDir); - d.mkdir("emptydir"); + QDir d(gitTest_BaseDir()); + d.mkdir(QStringLiteral("emptydir")); - VcsJob* j = m_plugin->remove(QList() << QUrl::fromLocalFile(gitTest_BaseDir+"emptydir/")); + VcsJob* j = m_plugin->remove(QList() << QUrl::fromLocalFile(gitTest_BaseDir()+"emptydir/")); if (j) VERIFYJOB(j); - QVERIFY(!d.exists("emptydir")); + QVERIFY(!d.exists(QStringLiteral("emptydir"))); } void GitInitTest::testRemoveEmptyFolderInFolder() { repoInit(); - QDir d(gitTest_BaseDir); - d.mkdir("dir"); + QDir d(gitTest_BaseDir()); + d.mkdir(QStringLiteral("dir")); - QDir d2(gitTest_BaseDir+"dir"); - d2.mkdir("emptydir"); + QDir d2(gitTest_BaseDir()+"dir"); + d2.mkdir(QStringLiteral("emptydir")); - VcsJob* j = m_plugin->remove(QList() << QUrl::fromLocalFile(gitTest_BaseDir+"dir/")); + VcsJob* j = m_plugin->remove(QList() << QUrl::fromLocalFile(gitTest_BaseDir()+"dir/")); if (j) VERIFYJOB(j); - QVERIFY(!d.exists("dir")); + QVERIFY(!d.exists(QStringLiteral("dir"))); } void GitInitTest::testRemoveUnindexedFile() { repoInit(); - QVERIFY(writeFile(gitTest_BaseDir + gitTest_FileName, "An appended line", QIODevice::Append)); + QVERIFY(writeFile(gitTest_BaseDir() + gitTest_FileName(), QStringLiteral("An appended line"), QIODevice::Append)); - VcsJob* j = m_plugin->remove(QList() << QUrl::fromLocalFile(gitTest_BaseDir + gitTest_FileName)); + VcsJob* j = m_plugin->remove(QList() << QUrl::fromLocalFile(gitTest_BaseDir() + gitTest_FileName())); if (j) VERIFYJOB(j); - QVERIFY(!QFile::exists(gitTest_BaseDir + gitTest_FileName)); + QVERIFY(!QFile::exists(gitTest_BaseDir() + gitTest_FileName())); } void GitInitTest::testRemoveFolderContainingUnversionedFiles() { repoInit(); - QDir d(gitTest_BaseDir); - d.mkdir("dir"); + QDir d(gitTest_BaseDir()); + d.mkdir(QStringLiteral("dir")); - QVERIFY(writeFile(gitTest_BaseDir + "dir/foo", "An appended line", QIODevice::Append)); - VcsJob* j = m_plugin->add(QList() << QUrl::fromLocalFile(gitTest_BaseDir+"dir")); + QVERIFY(writeFile(gitTest_BaseDir() + "dir/foo", QStringLiteral("An appended line"), QIODevice::Append)); + VcsJob* j = m_plugin->add(QList() << QUrl::fromLocalFile(gitTest_BaseDir()+"dir")); VERIFYJOB(j); - j = m_plugin->commit("initial commit", QList() << QUrl::fromLocalFile(gitTest_BaseDir)); + j = m_plugin->commit(QStringLiteral("initial commit"), QList() << QUrl::fromLocalFile(gitTest_BaseDir())); VERIFYJOB(j); - QVERIFY(writeFile(gitTest_BaseDir + "dir/bar", "An appended line", QIODevice::Append)); + QVERIFY(writeFile(gitTest_BaseDir() + "dir/bar", QStringLiteral("An appended line"), QIODevice::Append)); - j = m_plugin->remove(QList() << QUrl::fromLocalFile(gitTest_BaseDir + "dir")); + j = m_plugin->remove(QList() << QUrl::fromLocalFile(gitTest_BaseDir() + "dir")); if (j) VERIFYJOB(j); - QVERIFY(!QFile::exists(gitTest_BaseDir + "dir")); + QVERIFY(!QFile::exists(gitTest_BaseDir() + "dir")); } void GitInitTest::removeTempDirs() { - for (const auto& dirPath : {gitTest_BaseDir, gitTest_BaseDir2}) { + for (const auto& dirPath : {gitTest_BaseDir(), gitTest_BaseDir2()}) { QDir dir(dirPath); if (dir.exists() && !dir.removeRecursively()) { qDebug() << "QDir::removeRecursively(" << dirPath << ") returned false"; } } } void GitInitTest::testDiff() { repoInit(); addFiles(); commitFiles(); - QVERIFY(writeFile(gitTest_BaseDir + gitTest_FileName, "something else")); + QVERIFY(writeFile(gitTest_BaseDir() + gitTest_FileName(), QStringLiteral("something else"))); VcsRevision srcrev = VcsRevision::createSpecialRevision(VcsRevision::Base); VcsRevision dstrev = VcsRevision::createSpecialRevision(VcsRevision::Working); - VcsJob* j = m_plugin->diff(QUrl::fromLocalFile(gitTest_BaseDir), srcrev, dstrev, VcsDiff::DiffUnified, IBasicVersionControl::Recursive); + VcsJob* j = m_plugin->diff(QUrl::fromLocalFile(gitTest_BaseDir()), srcrev, dstrev, VcsDiff::DiffUnified, IBasicVersionControl::Recursive); VERIFYJOB(j); KDevelop::VcsDiff d = j->fetchResults().value(); QVERIFY(d.baseDiff().isLocalFile()); QString path = d.baseDiff().toLocalFile(); QVERIFY(QDir().exists(path+"/.git")); } QTEST_MAIN(GitInitTest) // #include "gittest.moc" diff --git a/plugins/grepview/grepdialog.cpp b/plugins/grepview/grepdialog.cpp index ee212f124..13867bd3f 100644 --- a/plugins/grepview/grepdialog.cpp +++ b/plugins/grepview/grepdialog.cpp @@ -1,490 +1,495 @@ /*************************************************************************** * Copyright 1999-2001 Bernd Gehrmann and the KDevelop Team * * bernd@kdevelop.org * * Copyright 2007 Dukju Ahn * * Copyright 2010 Julien Desgats * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "grepdialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "grepviewplugin.h" #include "grepjob.h" #include "grepoutputview.h" #include "grepfindthread.h" #include "greputil.h" using namespace KDevelop; namespace { -QString allOpenFilesString = i18n("All Open Files"); -QString allOpenProjectsString = i18n("All Open Projects"); - -const QStringList template_desc = QStringList() - << "verbatim" - << "word" - << "assignment" - << "->MEMBER(" - << "class::MEMBER(" - << "OBJECT->member("; - -const QStringList template_str = QStringList() - << "%s" - << "\\b%s\\b" - << "\\b%s\\b\\s*=[^=]" - << "\\->\\s*\\b%s\\b\\s*\\(" - << "([a-z0-9_$]+)\\s*::\\s*\\b%s\\b\\s*\\(" - << "\\b%s\\b\\s*\\->\\s*([a-z0-9_$]+)\\s*\\("; - -const QStringList repl_template = QStringList() - << "%s" - << "%s" - << "%s = " - << "->%s(" - << "\\1::%s(" - << "%s->\\1("; - -const QStringList filepatterns = QStringList() - << "*.h,*.hxx,*.hpp,*.hh,*.h++,*.H,*.tlh,*.cpp,*.cc,*.C,*.c++,*.cxx,*.ocl,*.inl,*.idl,*.c,*.m,*.mm,*.M,*.y,*.ypp,*.yxx,*.y++,*.l" - << "*.cpp,*.cc,*.C,*.c++,*.cxx,*.ocl,*.inl,*.c,*.m,*.mm,*.M" - << "*.h,*.hxx,*.hpp,*.hh,*.h++,*.H,*.tlh,*.idl" - << "*.adb" - << "*.cs" - << "*.f" - << "*.html,*.htm" - << "*.hs" - << "*.java" - << "*.js" - << "*.php,*.php3,*.php4" - << "*.pl" - << "*.pp,*.pas" - << "*.py" - << "*.js,*.css,*.yml,*.rb,*.rhtml,*.html.erb,*.rjs,*.js.rjs,*.rxml,*.xml.builder" - << "CMakeLists.txt,*.cmake" - << "*"; - -const QStringList excludepatterns = QStringList() - << "/CVS/,/SCCS/,/.svn/,/_darcs/,/build/,/.git/" - << ""; +inline QString allOpenFilesString() { return i18n("All Open Files"); } +inline QString allOpenProjectsString() { return i18n("All Open Projects"); } + +inline QStringList template_desc() { return QStringList() + << QStringLiteral("verbatim") + << QStringLiteral("word") + << QStringLiteral("assignment") + << QStringLiteral("->MEMBER(") + << QStringLiteral("class::MEMBER(") + << QStringLiteral("OBJECT->member("); +} + +inline QStringList template_str() { return QStringList() + << QStringLiteral("%s") + << QStringLiteral("\\b%s\\b") + << QStringLiteral("\\b%s\\b\\s*=[^=]") + << QStringLiteral("\\->\\s*\\b%s\\b\\s*\\(") + << QStringLiteral("([a-z0-9_$]+)\\s*::\\s*\\b%s\\b\\s*\\(") + << QStringLiteral("\\b%s\\b\\s*\\->\\s*([a-z0-9_$]+)\\s*\\("); +} + +inline QStringList repl_template() { return QStringList() + << QStringLiteral("%s") + << QStringLiteral("%s") + << QStringLiteral("%s = ") + << QStringLiteral("->%s(") + << QStringLiteral("\\1::%s(") + << QStringLiteral("%s->\\1("); +} + +inline QStringList filepatterns() { return QStringList() + << QStringLiteral("*.h,*.hxx,*.hpp,*.hh,*.h++,*.H,*.tlh,*.cpp,*.cc,*.C,*.c++,*.cxx,*.ocl,*.inl,*.idl,*.c,*.m,*.mm,*.M,*.y,*.ypp,*.yxx,*.y++,*.l") + << QStringLiteral("*.cpp,*.cc,*.C,*.c++,*.cxx,*.ocl,*.inl,*.c,*.m,*.mm,*.M") + << QStringLiteral("*.h,*.hxx,*.hpp,*.hh,*.h++,*.H,*.tlh,*.idl") + << QStringLiteral("*.adb") + << QStringLiteral("*.cs") + << QStringLiteral("*.f") + << QStringLiteral("*.html,*.htm") + << QStringLiteral("*.hs") + << QStringLiteral("*.java") + << QStringLiteral("*.js") + << QStringLiteral("*.php,*.php3,*.php4") + << QStringLiteral("*.pl") + << QStringLiteral("*.pp,*.pas") + << QStringLiteral("*.py") + << QStringLiteral("*.js,*.css,*.yml,*.rb,*.rhtml,*.html.erb,*.rjs,*.js.rjs,*.rxml,*.xml.builder") + << QStringLiteral("CMakeLists.txt,*.cmake") + << QStringLiteral("*"); +} + +inline QStringList excludepatterns() { return QStringList() + << QStringLiteral("/CVS/,/SCCS/,/.svn/,/_darcs/,/build/,/.git/") + << QLatin1String(""); +} ///Separator used to separate search paths. -const QString pathsSeparator(";"); +inline QString pathsSeparator() { return (QStringLiteral(";")); } ///Max number of items in paths combo box. const int pathsMaxCount = 25; } GrepDialog::GrepDialog( GrepViewPlugin * plugin, QWidget *parent ) : QDialog(parent), Ui::GrepWidget(), m_plugin( plugin ) { setAttribute(Qt::WA_DeleteOnClose); setWindowTitle( i18n("Find/Replace in Files") ); setupUi(this); auto searchButton = buttonBox->button(QDialogButtonBox::Ok); Q_ASSERT(searchButton); searchButton->setText(tr("Search...")); - searchButton->setIcon(QIcon::fromTheme("edit-find")); + searchButton->setIcon(QIcon::fromTheme(QStringLiteral("edit-find"))); connect(searchButton, &QPushButton::clicked, this, &GrepDialog::startSearch); connect(buttonBox, &QDialogButtonBox::rejected, this, &GrepDialog::reject); KConfigGroup cg = ICore::self()->activeSession()->config()->group( "GrepDialog" ); patternCombo->addItems( cg.readEntry("LastSearchItems", QStringList()) ); patternCombo->setInsertPolicy(QComboBox::InsertAtTop); - templateTypeCombo->addItems(template_desc); + templateTypeCombo->addItems(template_desc()); templateTypeCombo->setCurrentIndex( cg.readEntry("LastUsedTemplateIndex", 0) ); - templateEdit->addItems( cg.readEntry("LastUsedTemplateString", template_str) ); + templateEdit->addItems( cg.readEntry("LastUsedTemplateString", template_str()) ); templateEdit->setEditable(true); templateEdit->setCompletionMode(KCompletion::CompletionPopup); KCompletion* comp = templateEdit->completionObject(); connect(templateEdit, static_cast(&KComboBox::returnPressed), comp, static_cast(&KCompletion::addItem)); for(int i=0; icount(); i++) comp->addItem(templateEdit->itemText(i)); - replacementTemplateEdit->addItems( cg.readEntry("LastUsedReplacementTemplateString", repl_template) ); + replacementTemplateEdit->addItems( cg.readEntry("LastUsedReplacementTemplateString", repl_template()) ); replacementTemplateEdit->setEditable(true); replacementTemplateEdit->setCompletionMode(KCompletion::CompletionPopup); comp = replacementTemplateEdit->completionObject(); connect(replacementTemplateEdit, static_cast(&KComboBox::returnPressed), comp, static_cast(&KCompletion::addItem)); for(int i=0; icount(); i++) comp->addItem(replacementTemplateEdit->itemText(i)); regexCheck->setChecked(cg.readEntry("regexp", false )); caseSensitiveCheck->setChecked(cg.readEntry("case_sens", true)); searchPaths->setCompletionObject(new KUrlCompletion()); searchPaths->setAutoDeleteCompletionObject(true); QList projects = m_plugin->core()->projectController()->projects(); - searchPaths->addItems(cg.readEntry("SearchPaths", QStringList(!projects.isEmpty() ? allOpenProjectsString : QDir::homePath() ) )); + searchPaths->addItems(cg.readEntry("SearchPaths", QStringList(!projects.isEmpty() ? allOpenProjectsString() : QDir::homePath() ) )); searchPaths->setInsertPolicy(QComboBox::InsertAtTop); - syncButton->setIcon(QIcon::fromTheme("dirsync")); + syncButton->setIcon(QIcon::fromTheme(QStringLiteral("dirsync"))); syncButton->setMenu(createSyncButtonMenu()); depthSpin->setValue(cg.readEntry("depth", -1)); limitToProjectCheck->setChecked(cg.readEntry("search_project_files", true)); - filesCombo->addItems(cg.readEntry("file_patterns", filepatterns)); - excludeCombo->addItems(cg.readEntry("exclude_patterns", excludepatterns) ); + filesCombo->addItems(cg.readEntry("file_patterns", filepatterns())); + excludeCombo->addItems(cg.readEntry("exclude_patterns", excludepatterns()) ); connect(templateTypeCombo, static_cast(&KComboBox::activated), this, &GrepDialog::templateTypeComboActivated); connect(patternCombo, &QComboBox::editTextChanged, this, &GrepDialog::patternComboEditTextChanged); patternComboEditTextChanged( patternCombo->currentText() ); patternCombo->setFocus(); connect(searchPaths, static_cast(&KComboBox::activated), this, &GrepDialog::setSearchLocations); - directorySelector->setIcon(QIcon::fromTheme("document-open")); + directorySelector->setIcon(QIcon::fromTheme(QStringLiteral("document-open"))); connect(directorySelector, &QPushButton::clicked, this, &GrepDialog::selectDirectoryDialog ); directoryChanged(directorySelector->text()); } void GrepDialog::selectDirectoryDialog() { const QString dirName = QFileDialog::getExistingDirectory(this, tr("Select directory to search in"), searchPaths->lineEdit()->text()); if (!dirName.isEmpty()) { setSearchLocations(dirName); } } void GrepDialog::addUrlToMenu(QMenu* menu, const QUrl& url) { QAction* action = menu->addAction(m_plugin->core()->projectController()->prettyFileName(url, KDevelop::IProjectController::FormatPlain)); action->setData(QVariant(url.toString(QUrl::PreferLocalFile))); connect(action, &QAction::triggered, this, &GrepDialog::synchronizeDirActionTriggered); } void GrepDialog::addStringToMenu(QMenu* menu, QString string) { QAction* action = menu->addAction(string); action->setData(QVariant(string)); connect(action, &QAction::triggered, this, &GrepDialog::synchronizeDirActionTriggered); } void GrepDialog::synchronizeDirActionTriggered(bool) { QAction* action = qobject_cast(sender()); Q_ASSERT(action); - setSearchLocations(action->data().value()); + setSearchLocations(action->data().toString()); } QMenu* GrepDialog::createSyncButtonMenu() { QMenu* ret = new QMenu; QSet hadUrls; IDocument *doc = m_plugin->core()->documentController()->activeDocument(); if ( doc ) { Path url = Path(doc->url()).parent(); // always add the current file's parent directory hadUrls.insert(url); addUrlToMenu(ret, url.toUrl()); url = url.parent(); while(m_plugin->core()->projectController()->findProjectForUrl(url.toUrl())) { if(hadUrls.contains(url)) break; hadUrls.insert(url); addUrlToMenu(ret, url.toUrl()); url = url.parent(); } } foreach(IProject* project, m_plugin->core()->projectController()->projects()) { if (!hadUrls.contains(project->path())) { addUrlToMenu(ret, project->path().toUrl()); } } - addStringToMenu(ret, allOpenFilesString); - addStringToMenu(ret, allOpenProjectsString); + addStringToMenu(ret, allOpenFilesString()); + addStringToMenu(ret, allOpenProjectsString()); return ret; } void GrepDialog::directoryChanged(const QString& dir) { QUrl currentUrl = QUrl::fromLocalFile(dir); if( !currentUrl.isValid() ) { setEnableProjectBox(false); return; } bool projectAvailable = true; foreach(const QUrl& url, getDirectoryChoice()) { IProject *proj = ICore::self()->projectController()->findProjectForUrl( url ); if( !proj || !proj->path().toUrl().isLocalFile() ) projectAvailable = false; } setEnableProjectBox(projectAvailable); } GrepDialog::~GrepDialog() { KConfigGroup cg = ICore::self()->activeSession()->config()->group( "GrepDialog" ); // memorize the last patterns and paths cg.writeEntry("LastSearchItems", qCombo2StringList(patternCombo)); cg.writeEntry("regexp", regexCheck->isChecked()); cg.writeEntry("depth", depthSpin->value()); cg.writeEntry("search_project_files", limitToProjectCheck->isChecked()); cg.writeEntry("case_sens", caseSensitiveCheck->isChecked()); cg.writeEntry("exclude_patterns", qCombo2StringList(excludeCombo)); cg.writeEntry("file_patterns", qCombo2StringList(filesCombo)); cg.writeEntry("LastUsedTemplateIndex", templateTypeCombo->currentIndex()); cg.writeEntry("LastUsedTemplateString", qCombo2StringList(templateEdit)); cg.writeEntry("LastUsedReplacementTemplateString", qCombo2StringList(replacementTemplateEdit)); cg.writeEntry("SearchPaths", qCombo2StringList(searchPaths)); cg.sync(); } void GrepDialog::templateTypeComboActivated(int index) { - templateEdit->setCurrentItem( template_str[index], true ); - replacementTemplateEdit->setCurrentItem( repl_template[index], true ); + templateEdit->setCurrentItem( template_str().at(index), true ); + replacementTemplateEdit->setCurrentItem( repl_template().at(index), true ); } void GrepDialog::setEnableProjectBox(bool enable) { limitToProjectCheck->setEnabled(enable); limitToProjectLabel->setEnabled(enable); } void GrepDialog::setPattern(const QString &pattern) { patternCombo->setEditText(pattern); patternComboEditTextChanged(pattern); } void GrepDialog::setSearchLocations(const QString &dir) { if(!dir.isEmpty()) { if(QDir::isAbsolutePath(dir)) { static_cast(searchPaths->completionObject())->setDir( QUrl::fromLocalFile(dir) ); } if (searchPaths->contains(dir)) { searchPaths->removeItem(searchPaths->findText(dir)); } searchPaths->insertItem(0, dir); searchPaths->setCurrentItem(dir); if (searchPaths->count() > pathsMaxCount) { searchPaths->removeItem(searchPaths->count() - 1); } } directoryChanged(dir); } QString GrepDialog::patternString() const { return patternCombo->currentText(); } QString GrepDialog::templateString() const { - return templateEdit->currentText().isEmpty() ? "%s" : templateEdit->currentText(); + return templateEdit->currentText().isEmpty() ? QStringLiteral("%s") : templateEdit->currentText(); } QString GrepDialog::replacementTemplateString() const { return replacementTemplateEdit->currentText(); } QString GrepDialog::filesString() const { return filesCombo->currentText(); } QString GrepDialog::excludeString() const { return excludeCombo->currentText(); } bool GrepDialog::useProjectFilesFlag() const { if (!limitToProjectCheck->isEnabled()) return false; return limitToProjectCheck->isChecked(); } bool GrepDialog::regexpFlag() const { return regexCheck->isChecked(); } int GrepDialog::depthValue() const { return depthSpin->value(); } bool GrepDialog::caseSensitiveFlag() const { return caseSensitiveCheck->isChecked(); } void GrepDialog::patternComboEditTextChanged( const QString& text) { buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!text.isEmpty()); } QList< QUrl > GrepDialog::getDirectoryChoice() const { QList< QUrl > ret; QString text = searchPaths->currentText(); - if(text == allOpenFilesString) + if(text == allOpenFilesString()) { foreach(IDocument* doc, ICore::self()->documentController()->openDocuments()) ret << doc->url(); - }else if(text == allOpenProjectsString) + }else if(text == allOpenProjectsString()) { foreach(IProject* project, ICore::self()->projectController()->projects()) ret << project->path().toUrl(); }else{ - QStringList semicolonSeparatedFileList = text.split(pathsSeparator); + QStringList semicolonSeparatedFileList = text.split(pathsSeparator()); if(!semicolonSeparatedFileList.isEmpty() && QFileInfo(semicolonSeparatedFileList[0]).exists()) { // We use QFileInfo to make sure this is really a semicolon-separated file list, not a file containing // a semicolon in the name. foreach(const QString& file, semicolonSeparatedFileList) ret << QUrl::fromLocalFile(file); }else{ ret << QUrl::fromUserInput(searchPaths->currentText()); } } return ret; } bool GrepDialog::isPartOfChoice(QUrl url) const { foreach(const QUrl& choice, getDirectoryChoice()) if(choice.isParentOf(url) || choice == url) return true; return false; } void GrepDialog::startSearch() { // search for unsaved documents QList unsavedFiles; QStringList include = GrepFindFilesThread::parseInclude(filesString()); QStringList exclude = GrepFindFilesThread::parseExclude(excludeString()); foreach(IDocument* doc, ICore::self()->documentController()->openDocuments()) { QUrl docUrl = doc->url(); if(doc->state() != IDocument::Clean && isPartOfChoice(docUrl) && QDir::match(include, docUrl.fileName()) && !QDir::match(exclude, docUrl.toLocalFile())) { unsavedFiles << doc; } } if(!ICore::self()->documentController()->saveSomeDocuments(unsavedFiles)) { close(); return; } QList choice = getDirectoryChoice(); GrepJob* job = m_plugin->newGrepJob(); const QString descriptionOrUrl(searchPaths->currentText()); QString description = descriptionOrUrl; // Shorten the description - if(descriptionOrUrl != allOpenFilesString && descriptionOrUrl != allOpenProjectsString) { + if(descriptionOrUrl != allOpenFilesString() && descriptionOrUrl != allOpenProjectsString()) { auto prettyFileName = [](const QUrl& url) { return ICore::self()->projectController()->prettyFileName(url, KDevelop::IProjectController::FormatPlain); }; if (choice.size() > 1) { description = i18np("%2, and %1 more item", "%2, and %1 more items", choice.size() - 1, prettyFileName(choice[0])); } else if (!choice.isEmpty()) { description = prettyFileName(choice[0]); } } GrepOutputView *toolView = (GrepOutputView*)ICore::self()->uiController()-> findToolView(i18n("Find/Replace in Files"), m_plugin->toolViewFactory(), IUiController::CreateAndRaise); GrepOutputModel* outputModel = toolView->renewModel(patternString(), description); connect(job, &GrepJob::showErrorMessage, toolView, &GrepOutputView::showErrorMessage); //the GrepOutputModel gets the 'showMessage' signal to store it and forward //it to toolView connect(job, &GrepJob::showMessage, outputModel, &GrepOutputModel::showMessageSlot); connect(outputModel, &GrepOutputModel::showMessage, toolView, &GrepOutputView::showMessage); connect(toolView, &GrepOutputView::outputViewIsClosed, job, [=]() {job->kill();}); job->setOutputModel(outputModel); job->setPatternString(patternString()); job->setReplacementTemplateString(replacementTemplateString()); job->setTemplateString(templateString()); job->setFilesString(filesString()); job->setExcludeString(excludeString()); job->setDirectoryChoice(choice); job->setProjectFilesFlag( useProjectFilesFlag() ); job->setRegexpFlag( regexpFlag() ); job->setDepth( depthValue() ); job->setCaseSensitive( caseSensitiveFlag() ); ICore::self()->runController()->registerJob(job); m_plugin->rememberSearchDirectory(descriptionOrUrl); close(); } diff --git a/plugins/grepview/grepoutputdelegate.h b/plugins/grepview/grepoutputdelegate.h index f9a1a64e3..ac1721944 100644 --- a/plugins/grepview/grepoutputdelegate.h +++ b/plugins/grepview/grepoutputdelegate.h @@ -1,42 +1,43 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright (C) 2007 Andreas Pakulat * * Copyright 2010 Julien Desgats * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef KDEVPLATFORM_PLUGIN_GREPOUTPUTDELEGATE_H #define KDEVPLATFORM_PLUGIN_GREPOUTPUTDELEGATE_H #include class GrepOutputDelegate : public QStyledItemDelegate { + Q_OBJECT public: explicit GrepOutputDelegate(QObject* parent); virtual ~GrepOutputDelegate(); static GrepOutputDelegate* self(); void paint(QPainter*, const QStyleOptionViewItem&, const QModelIndex&) const override; virtual QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override; private: static GrepOutputDelegate* m_self; QColor blendColor(QColor color1, QColor color2, double blend) const; }; #endif diff --git a/plugins/grepview/grepoutputmodel.cpp b/plugins/grepview/grepoutputmodel.cpp index 47bac973c..ae021ee79 100644 --- a/plugins/grepview/grepoutputmodel.cpp +++ b/plugins/grepview/grepoutputmodel.cpp @@ -1,480 +1,480 @@ /*************************************************************************** * Copyright 1999-2001 Bernd Gehrmann and the KDevelop Team * * bernd@kdevelop.org * * Copyright 2007 Dukju Ahn * * Copyright 2010 Silvère Lestang * * Copyright 2010 Julien Desgats * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "grepoutputmodel.h" #include "grepviewplugin.h" #include "greputil.h" #include #include #include #include #include #include #include #include #include using namespace KDevelop; GrepOutputItem::GrepOutputItem(DocumentChangePointer change, const QString &text, bool checkable) : QStandardItem(), m_change(change) { setText(text); setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); setCheckable(checkable); if(checkable) setCheckState(Qt::Checked); } GrepOutputItem::GrepOutputItem(const QString& filename, const QString& text, bool checkable) : QStandardItem(), m_change(new DocumentChange(IndexedString(filename), KTextEditor::Range::invalid(), QString(), QString())) { setText(text); setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); setCheckable(checkable); if(checkable) { setTristate(true); setCheckState(Qt::Checked); } } int GrepOutputItem::lineNumber() const { // line starts at 0 for cursor but we want to start at 1 return m_change->m_range.start().line() + 1; } QString GrepOutputItem::filename() const { return m_change->m_document.str(); } DocumentChangePointer GrepOutputItem::change() const { return m_change; } bool GrepOutputItem::isText() const { return m_change->m_range.isValid(); } void GrepOutputItem::propagateState() { for(int i = 0; i < rowCount(); i++) { GrepOutputItem *item = static_cast(child(i)); if(item->isEnabled()) { item->setCheckState(checkState()); item->propagateState(); } } } void GrepOutputItem::refreshState() { if(rowCount() > 0) { int checked = 0; int unchecked = 0; int enabled = 0; //only enabled items are relevants for(int i = 0; i < rowCount(); i++) { QStandardItem *item = child(i); if(item->isEnabled()) { enabled += 1; switch(child(i)->checkState()) { case Qt::Checked: checked += 1; break; case Qt::Unchecked: unchecked += 1; break; default: break; } } } if(enabled == 0) { setCheckState(Qt::Unchecked); setEnabled(false); } else if(checked == enabled) { setCheckState(Qt::Checked); } else if (unchecked == enabled) { setCheckState(Qt::Unchecked); } else { setCheckState(Qt::PartiallyChecked); } } if(GrepOutputItem *p = static_cast(parent())) { p->refreshState(); } } QVariant GrepOutputItem::data ( int role ) const { GrepOutputModel *grepModel = static_cast(model()); if(role == Qt::ToolTipRole && grepModel && isText()) { QString start = text().left(m_change->m_range.start().column()).toHtmlEscaped(); QString repl = "" + grepModel->replacementFor(m_change->m_oldText).toHtmlEscaped() + ""; QString end = text().right(text().length() - m_change->m_range.end().column()).toHtmlEscaped(); return QVariant(QString(start + repl + end).trimmed()); } else if (role == Qt::FontRole) { return QFontDatabase::systemFont(QFontDatabase::FixedFont); } else { return QStandardItem::data(role); } } GrepOutputItem::~GrepOutputItem() {} /////////////////////////////////////////////////////////////// GrepOutputModel::GrepOutputModel( QObject *parent ) : QStandardItemModel( parent ), m_regExp(), m_replacement(), m_replacementTemplate(), m_finalReplacement(), m_finalUpToDate(false), m_rootItem(0), m_fileCount(0), m_matchCount(0), m_itemsCheckable(false) { connect(this, &GrepOutputModel::itemChanged, this, &GrepOutputModel::updateCheckState); } GrepOutputModel::~GrepOutputModel() {} void GrepOutputModel::clear() { QStandardItemModel::clear(); // the above clear() also destroys the root item, so invalidate the pointer m_rootItem = 0; m_fileCount = 0; m_matchCount = 0; } void GrepOutputModel::setRegExp(const QRegExp& re) { m_regExp = re; m_finalUpToDate = false; } void GrepOutputModel::setReplacement(const QString& repl) { m_replacement = repl; m_finalUpToDate = false; } void GrepOutputModel::setReplacementTemplate(const QString& tmpl) { m_replacementTemplate = tmpl; m_finalUpToDate = false; } QString GrepOutputModel::replacementFor(const QString &text) { if(!m_finalUpToDate) { m_finalReplacement = substitudePattern(m_replacementTemplate, m_replacement); m_finalUpToDate = true; } return QString(text).replace(m_regExp, m_finalReplacement); } void GrepOutputModel::activate( const QModelIndex &idx ) { QStandardItem *stditem = itemFromIndex(idx); GrepOutputItem *grepitem = dynamic_cast(stditem); if( !grepitem || !grepitem->isText() ) return; QUrl url = QUrl::fromLocalFile(grepitem->filename()); int line = grepitem->lineNumber() - 1; KTextEditor::Range range( line, 0, line+1, 0); // Try to find the actual text range we found during the grep IDocument* doc = ICore::self()->documentController()->documentForUrl( url ); if(!doc) doc = ICore::self()->documentController()->openDocument( url, range ); if(!doc) return; if (KTextEditor::Document* tdoc = doc->textDocument()) { KTextEditor::Range matchRange = grepitem->change()->m_range; QString actualText = tdoc->text(matchRange); QString expectedText = grepitem->change()->m_oldText; if (actualText == expectedText) { range = matchRange; } } ICore::self()->documentController()->activateDocument( doc, range ); } QModelIndex GrepOutputModel::previousItemIndex(const QModelIndex ¤tIdx) const { GrepOutputItem* current_item = 0; if (!currentIdx.isValid()) { // no item selected, search recursively for the last item in search results QStandardItem *it = item(0,0); while (it) { QStandardItem *child = it->child( it->rowCount() - 1 ); if (!child) return it->index(); it = child; } return QModelIndex(); } else current_item = dynamic_cast(itemFromIndex(currentIdx)); if (current_item->parent() != 0) { int row = currentIdx.row(); if(!current_item->isText()) // the item is a file { int item_row = current_item->row(); if(item_row > 0) { int idx_last_item = current_item->parent()->child(item_row - 1)->rowCount() - 1; return current_item->parent()->child(item_row - 1)->child(idx_last_item)->index(); } } else // the item is a match { if(row > 0) return current_item->parent()->child(row - 1)->index(); else // we return the index of the last item of the previous file { int parrent_row = current_item->parent()->row(); if(parrent_row > 0) { int idx_last_item = current_item->parent()->parent()->child(parrent_row - 1)->rowCount() - 1; return current_item->parent()->parent()->child(parrent_row - 1)->child(idx_last_item)->index(); } } } } return currentIdx; } QModelIndex GrepOutputModel::nextItemIndex(const QModelIndex ¤tIdx) const { GrepOutputItem* current_item = 0; if (!currentIdx.isValid()) { QStandardItem *it = item(0,0); if (!it) return QModelIndex(); current_item = dynamic_cast(it); } else current_item = dynamic_cast(itemFromIndex(currentIdx)); if (current_item->parent() == 0) { // root item with overview of search results if (current_item->rowCount() > 0) return nextItemIndex(current_item->child(0)->index()); else return QModelIndex(); } else { int row = currentIdx.row(); if(!current_item->isText()) // the item is a file { int item_row = current_item->row(); if(item_row < current_item->parent()->rowCount()) { return current_item->parent()->child(item_row)->child(0)->index(); } } else // the item is a match { if(row < current_item->parent()->rowCount() - 1) return current_item->parent()->child(row + 1)->index(); else // we return the index of the first item of the next file { int parrent_row = current_item->parent()->row(); if(parrent_row < current_item->parent()->parent()->rowCount() - 1) { return current_item->parent()->parent()->child(parrent_row + 1)->child(0)->index(); } } } } return currentIdx; } const GrepOutputItem *GrepOutputModel::getRootItem() const { return m_rootItem; } bool GrepOutputModel::itemsCheckable() const { return m_itemsCheckable; } void GrepOutputModel::makeItemsCheckable(bool checkable) { if(m_itemsCheckable == checkable) return; if(m_rootItem) makeItemsCheckable(checkable, m_rootItem); m_itemsCheckable = checkable; } void GrepOutputModel::makeItemsCheckable(bool checkable, GrepOutputItem* item) { item->setCheckable(checkable); if(checkable) { item->setCheckState(Qt::Checked); if(item->rowCount() && checkable) item->setTristate(true); } for(int row = 0; row < item->rowCount(); ++row) makeItemsCheckable(checkable, static_cast(item->child(row, 0))); } void GrepOutputModel::appendOutputs( const QString &filename, const GrepOutputItem::List &items ) { if(items.isEmpty()) return; if(rowCount() == 0) { - m_rootItem = new GrepOutputItem("", "", m_itemsCheckable); + m_rootItem = new GrepOutputItem(QString(), QString(), m_itemsCheckable); appendRow(m_rootItem); } m_fileCount += 1; m_matchCount += items.length(); const QString matchText = i18np("1 match", "%1 matches", m_matchCount); const QString fileText = i18np("1 file", "%1 files", m_fileCount); m_rootItem->setText(i18nc("%1 is e.g. '4 matches', %2 is e.g. '1 file'", "

%1 in %2

", matchText, fileText)); QString fnString = i18np("%2 (one match)", "%2 (%1 matches)", items.length(), ICore::self()->projectController()->prettyFileName(QUrl::fromLocalFile(filename))); GrepOutputItem *fileItem = new GrepOutputItem(filename, fnString, m_itemsCheckable); m_rootItem->appendRow(fileItem); foreach( const GrepOutputItem& item, items ) { GrepOutputItem* copy = new GrepOutputItem(item); copy->setCheckable(m_itemsCheckable); if(m_itemsCheckable) { copy->setCheckState(Qt::Checked); if(copy->rowCount()) copy->setTristate(true); } fileItem->appendRow(copy); } } void GrepOutputModel::updateCheckState(QStandardItem* item) { // if we don't disconnect the SIGNAL, the setCheckState will call it in loop disconnect(this, &GrepOutputModel::itemChanged, nullptr, nullptr); // try to update checkstate on non checkable items would make a checkbox appear if(item->isCheckable()) { GrepOutputItem *it = static_cast(item); it->propagateState(); it->refreshState(); } connect(this, &GrepOutputModel::itemChanged, this, &GrepOutputModel::updateCheckState); } void GrepOutputModel::doReplacements() { Q_ASSERT(m_rootItem); if (!m_rootItem) return; // nothing to do, abort DocumentChangeSet changeSet; changeSet.setFormatPolicy(DocumentChangeSet::NoAutoFormat); for(int fileRow = 0; fileRow < m_rootItem->rowCount(); fileRow++) { GrepOutputItem *file = static_cast(m_rootItem->child(fileRow)); for(int matchRow = 0; matchRow < file->rowCount(); matchRow++) { GrepOutputItem *match = static_cast(file->child(matchRow)); if(match->checkState() == Qt::Checked) { DocumentChangePointer change = match->change(); // setting replacement text based on current replace value change->m_newText = replacementFor(change->m_oldText); changeSet.addChange(change); // this item cannot be checked anymore match->setCheckState(Qt::Unchecked); match->setEnabled(false); } } } DocumentChangeSet::ChangeResult result = changeSet.applyAllChanges(); if(!result.m_success) { DocumentChangePointer ch = result.m_reasonChange; if(ch) emit showErrorMessage(i18nc("%1 is the old text, %2 is the new text, %3 is the file path, %4 and %5 are its row and column", "Failed to replace %1 by %2 in %3:%4:%5", ch->m_oldText.toHtmlEscaped(), ch->m_newText.toHtmlEscaped(), ch->m_document.toUrl().toLocalFile(), ch->m_range.start().line() + 1, ch->m_range.start().column() + 1)); } } void GrepOutputModel::showMessageSlot(IStatus* status, const QString& message) { m_savedMessage = message; m_savedIStatus = status; showMessageEmit(); } void GrepOutputModel::showMessageEmit() { emit showMessage(m_savedIStatus, m_savedMessage); } bool GrepOutputModel::hasResults() { return(m_matchCount > 0); } diff --git a/plugins/grepview/grepoutputview.cpp b/plugins/grepview/grepoutputview.cpp index cc25669a3..84aebada2 100644 --- a/plugins/grepview/grepoutputview.cpp +++ b/plugins/grepview/grepoutputview.cpp @@ -1,384 +1,384 @@ /************************************************************************** * Copyright 2010 Silvère Lestang * * Copyright 2010 Julien Desgats * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "grepoutputview.h" #include "grepoutputmodel.h" #include "grepoutputdelegate.h" #include "ui_grepoutputview.h" #include "grepviewplugin.h" #include "grepdialog.h" #include "greputil.h" #include "grepjob.h" #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; GrepOutputViewFactory::GrepOutputViewFactory(GrepViewPlugin* plugin) : m_plugin(plugin) {} QWidget* GrepOutputViewFactory::create(QWidget* parent) { return new GrepOutputView(parent, m_plugin); } Qt::DockWidgetArea GrepOutputViewFactory::defaultPosition() { return Qt::BottomDockWidgetArea; } QString GrepOutputViewFactory::id() const { - return "org.kdevelop.GrepOutputView"; + return QStringLiteral("org.kdevelop.GrepOutputView"); } const int GrepOutputView::HISTORY_SIZE = 5; GrepOutputView::GrepOutputView(QWidget* parent, GrepViewPlugin* plugin) : QWidget(parent) , m_next(0) , m_prev(0) , m_collapseAll(0) , m_expandAll(0) , m_clearSearchHistory(0) , m_statusLabel(0) , m_plugin(plugin) { Ui::GrepOutputView::setupUi(this); setWindowTitle(i18nc("@title:window", "Find/Replace Output View")); - setWindowIcon(QIcon::fromTheme("edit-find", windowIcon())); + setWindowIcon(QIcon::fromTheme(QStringLiteral("edit-find"), windowIcon())); - m_prev = new QAction(QIcon::fromTheme("go-previous"), i18n("&Previous Item"), this); + m_prev = new QAction(QIcon::fromTheme(QStringLiteral("go-previous")), i18n("&Previous Item"), this); m_prev->setEnabled(false); - m_next = new QAction(QIcon::fromTheme("go-next"), i18n("&Next Item"), this); + m_next = new QAction(QIcon::fromTheme(QStringLiteral("go-next")), i18n("&Next Item"), this); m_next->setEnabled(false); - m_collapseAll = new QAction(QIcon::fromTheme("arrow-left-double"), i18n("C&ollapse All"), this); // TODO change icon + m_collapseAll = new QAction(QIcon::fromTheme(QStringLiteral("arrow-left-double")), i18n("C&ollapse All"), this); // TODO change icon m_collapseAll->setEnabled(false); - m_expandAll = new QAction(QIcon::fromTheme("arrow-right-double"), i18n("&Expand All"), this); // TODO change icon + m_expandAll = new QAction(QIcon::fromTheme(QStringLiteral("arrow-right-double")), i18n("&Expand All"), this); // TODO change icon m_expandAll->setEnabled(false); QAction *separator = new QAction(this); separator->setSeparator(true); - QAction *newSearchAction = new QAction(QIcon::fromTheme("edit-find"), i18n("New &Search"), this); - m_clearSearchHistory = new QAction(QIcon::fromTheme("edit-clear-list"), i18n("Clear Search History"), this); + QAction *newSearchAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-find")), i18n("New &Search"), this); + m_clearSearchHistory = new QAction(QIcon::fromTheme(QStringLiteral("edit-clear-list")), i18n("Clear Search History"), this); addAction(m_prev); addAction(m_next); addAction(m_collapseAll); addAction(m_expandAll); addAction(separator); addAction(newSearchAction); addAction(m_clearSearchHistory); separator = new QAction(this); separator->setSeparator(true); addAction(separator); QWidgetAction *statusWidget = new QWidgetAction(this); m_statusLabel = new QLabel(this); statusWidget->setDefaultWidget(m_statusLabel); addAction(statusWidget); modelSelector->setEditable(false); modelSelector->setContextMenuPolicy(Qt::CustomContextMenu); connect(modelSelector, &KComboBox::customContextMenuRequested, this, &GrepOutputView::modelSelectorContextMenu); connect(modelSelector, static_cast(&KComboBox::currentIndexChanged), this, &GrepOutputView::changeModel); resultsTreeView->setItemDelegate(GrepOutputDelegate::self()); resultsTreeView->setHeaderHidden(true); resultsTreeView->setUniformRowHeights(false); resultsTreeView->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); connect(m_prev, &QAction::triggered, this, &GrepOutputView::selectPreviousItem); connect(m_next, &QAction::triggered, this, &GrepOutputView::selectNextItem); connect(m_collapseAll, &QAction::triggered, this, &GrepOutputView::collapseAllItems); connect(m_expandAll, &QAction::triggered, this, &GrepOutputView::expandAllItems); connect(applyButton, &QPushButton::clicked, this, &GrepOutputView::onApply); connect(m_clearSearchHistory, &QAction::triggered, this, &GrepOutputView::clearSearchHistory); KConfigGroup cg = ICore::self()->activeSession()->config()->group( "GrepDialog" ); replacementCombo->addItems( cg.readEntry("LastReplacementItems", QStringList()) ); replacementCombo->setInsertPolicy(QComboBox::InsertAtTop); - applyButton->setIcon(QIcon::fromTheme("dialog-ok-apply")); + applyButton->setIcon(QIcon::fromTheme(QStringLiteral("dialog-ok-apply"))); connect(replacementCombo, &KComboBox::editTextChanged, this, &GrepOutputView::replacementTextChanged); connect(replacementCombo, static_cast(&KComboBox::returnPressed), this, &GrepOutputView::onApply); connect(newSearchAction, &QAction::triggered, this, &GrepOutputView::showDialog); resultsTreeView->header()->setStretchLastSection(true); updateCheckable(); } void GrepOutputView::replacementTextChanged(QString) { updateCheckable(); if (model()) { // see https://bugs.kde.org/show_bug.cgi?id=274902 - renewModel can trigger a call here without an active model updateApplyState(model()->index(0, 0), model()->index(0, 0)); } } GrepOutputView::~GrepOutputView() { KConfigGroup cg = ICore::self()->activeSession()->config()->group( "GrepDialog" ); cg.writeEntry("LastReplacementItems", qCombo2StringList(replacementCombo, true)); emit outputViewIsClosed(); } GrepOutputModel* GrepOutputView::renewModel(const QString& name, const QString& description) { // Crear oldest model while(modelSelector->count() > GrepOutputView::HISTORY_SIZE) { QVariant var = modelSelector->itemData(GrepOutputView::HISTORY_SIZE - 1); qvariant_cast(var)->deleteLater(); modelSelector->removeItem(GrepOutputView::HISTORY_SIZE - 1); } replacementCombo->clearEditText(); GrepOutputModel* newModel = new GrepOutputModel(resultsTreeView); applyButton->setEnabled(false); // text may be already present newModel->setReplacement(replacementCombo->currentText()); connect(newModel, &GrepOutputModel::rowsRemoved, this, &GrepOutputView::rowsRemoved); connect(resultsTreeView, &QTreeView::activated, newModel, &GrepOutputModel::activate); connect(replacementCombo, &KComboBox::editTextChanged, newModel, &GrepOutputModel::setReplacement); connect(newModel, &GrepOutputModel::rowsInserted, this, &GrepOutputView::expandElements); connect(newModel, &GrepOutputModel::showErrorMessage, this, &GrepOutputView::showErrorMessage); connect(m_plugin, &GrepViewPlugin::grepJobFinished, this, &GrepOutputView::updateScrollArea); // appends new model to history - const QString displayName = i18n("Search \"%1\" in %2 (at time %3)", name, description, QTime::currentTime().toString("hh:mm")); + const QString displayName = i18n("Search \"%1\" in %2 (at time %3)", name, description, QTime::currentTime().toString(QStringLiteral("hh:mm"))); modelSelector->insertItem(0, displayName, qVariantFromValue(newModel)); modelSelector->setCurrentIndex(0);//setCurrentItem(displayName); updateCheckable(); return newModel; } GrepOutputModel* GrepOutputView::model() { return static_cast(resultsTreeView->model()); } void GrepOutputView::changeModel(int index) { if (model()) { disconnect(model(), &GrepOutputModel::showMessage, this, &GrepOutputView::showMessage); disconnect(model(), &GrepOutputModel::dataChanged, this, &GrepOutputView::updateApplyState); } replacementCombo->clearEditText(); //after deleting the whole search history, index is -1 if(index >= 0) { QVariant var = modelSelector->itemData(index); GrepOutputModel *resultModel = static_cast(qvariant_cast(var)); resultsTreeView->setModel(resultModel); connect(model(), &GrepOutputModel::showMessage, this, &GrepOutputView::showMessage); connect(model(), &GrepOutputModel::dataChanged, this, &GrepOutputView::updateApplyState); model()->showMessageEmit(); applyButton->setEnabled(model()->hasResults() && model()->getRootItem() && model()->getRootItem()->checkState() != Qt::Unchecked && !replacementCombo->currentText().isEmpty()); if(model()->hasResults()) expandElements(QModelIndex()); } updateCheckable(); updateApplyState(model()->index(0, 0), model()->index(0, 0)); } void GrepOutputView::setMessage(const QString& msg, MessageType type) { if (type == Error) { QPalette palette = m_statusLabel->palette(); KColorScheme::adjustForeground(palette, KColorScheme::NegativeText, QPalette::WindowText); m_statusLabel->setPalette(palette); } else { m_statusLabel->setPalette(QPalette()); } m_statusLabel->setText(msg); } void GrepOutputView::showErrorMessage( const QString& errorMessage ) { setMessage(errorMessage, Error); } void GrepOutputView::showMessage( KDevelop::IStatus* , const QString& message ) { setMessage(message, Information); } void GrepOutputView::onApply() { if(model()) { Q_ASSERT(model()->rowCount()); // ask a confirmation before an empty string replacement if(replacementCombo->currentText().length() == 0 && KMessageBox::questionYesNo(this, i18n("Do you want to replace with an empty string?"), i18n("Start replacement")) == KMessageBox::No) { return; } setEnabled(false); model()->doReplacements(); setEnabled(true); } } void GrepOutputView::showDialog() { m_plugin->showDialog(true); } void GrepOutputView::expandElements(const QModelIndex& index) { m_prev->setEnabled(true); m_next->setEnabled(true); m_collapseAll->setEnabled(true); m_expandAll->setEnabled(true); resultsTreeView->expand(index); } void GrepOutputView::selectPreviousItem() { if (!model()) { return; } QModelIndex prev_idx = model()->previousItemIndex(resultsTreeView->currentIndex()); if (prev_idx.isValid()) { resultsTreeView->setCurrentIndex(prev_idx); model()->activate(prev_idx); } } void GrepOutputView::selectNextItem() { if (!model()) { return; } QModelIndex next_idx = model()->nextItemIndex(resultsTreeView->currentIndex()); if (next_idx.isValid()) { resultsTreeView->setCurrentIndex(next_idx); model()->activate(next_idx); } } void GrepOutputView::collapseAllItems() { // Collapse everything resultsTreeView->collapseAll(); // Now reopen the first children, which correspond to the files. resultsTreeView->expand(resultsTreeView->model()->index(0, 0)); } void GrepOutputView::expandAllItems() { resultsTreeView->expandAll(); } void GrepOutputView::rowsRemoved() { m_prev->setEnabled(model()->rowCount()); m_next->setEnabled(model()->rowCount()); } void GrepOutputView::updateApplyState(const QModelIndex& topLeft, const QModelIndex& bottomRight) { Q_UNUSED(bottomRight); if (!model() || !model()->hasResults()) { applyButton->setEnabled(false); return; } // we only care about the root item if(!topLeft.parent().isValid()) { applyButton->setEnabled(topLeft.data(Qt::CheckStateRole) != Qt::Unchecked && model()->itemsCheckable()); } } void GrepOutputView::updateCheckable() { if(model()) model()->makeItemsCheckable(!replacementCombo->currentText().isEmpty() || model()->itemsCheckable()); } void GrepOutputView::clearSearchHistory() { GrepJob *runningJob = m_plugin->grepJob(); if(runningJob) { runningJob->kill(); } while(modelSelector->count() > 0) { QVariant var = modelSelector->itemData(0); qvariant_cast(var)->deleteLater(); modelSelector->removeItem(0); } applyButton->setEnabled(false); m_statusLabel->setText(QString()); } void GrepOutputView::modelSelectorContextMenu(const QPoint& pos) { QPoint globalPos = modelSelector->mapToGlobal(pos); QMenu myMenu; myMenu.addAction(m_clearSearchHistory); myMenu.exec(globalPos); } void GrepOutputView::updateScrollArea() { for (int col = 0; col < model()->columnCount(); ++col) resultsTreeView->resizeColumnToContents(col); } diff --git a/plugins/grepview/greputil.cpp b/plugins/grepview/greputil.cpp index 0447a2655..e70556b76 100644 --- a/plugins/grepview/greputil.cpp +++ b/plugins/grepview/greputil.cpp @@ -1,64 +1,64 @@ /*************************************************************************** * Copyright 2010 Julien Desgats * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "greputil.h" #include #include #include static int const MAX_LAST_SEARCH_ITEMS_COUNT = 15; QString substitudePattern(const QString& pattern, const QString& searchString) { QString subst = searchString; QString result; bool expectEscape = false; - foreach(const QChar &ch, pattern) + foreach(const QChar ch, pattern) { if(expectEscape) { expectEscape = false; if(ch == '%') result.append('%'); else if(ch == 's') result.append(subst); else result.append('%').append(ch); } else if(ch == '%') expectEscape = true; else result.append(ch); } return result; } QStringList qCombo2StringList( QComboBox* combo, bool allowEmpty ) { QStringList list; if (!combo) { return list; } int skippedItem = -1; if (!combo->currentText().isEmpty() || allowEmpty) { list << combo->currentText(); } if (combo->currentIndex() != -1 && !combo->itemText(combo->currentIndex()).isEmpty()) { skippedItem = combo->currentIndex(); } for (int i = 0; i < std::min(MAX_LAST_SEARCH_ITEMS_COUNT, combo->count()); ++i) { if (i != skippedItem && !combo->itemText(i).isEmpty()) { list << combo->itemText(i); } } return list; } diff --git a/plugins/grepview/grepviewplugin.cpp b/plugins/grepview/grepviewplugin.cpp index b59217ad7..f87bc6782 100644 --- a/plugins/grepview/grepviewplugin.cpp +++ b/plugins/grepview/grepviewplugin.cpp @@ -1,232 +1,232 @@ /*************************************************************************** * Copyright 1999-2001 by Bernd Gehrmann * * bernd@kdevelop.org * * Copyright 2007 Dukju Ahn * * Copyright 2010 Benjamin Port * * Copyright 2010 Julien Desgats * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "grepviewplugin.h" #include "grepdialog.h" #include "grepoutputmodel.h" #include "grepoutputdelegate.h" #include "grepjob.h" #include "grepoutputview.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include Q_LOGGING_CATEGORY(PLUGIN_GREPVIEW, "kdevplatform.plugins.grepview") static QString patternFromSelection(const KDevelop::IDocument* doc) { if (!doc) return QString(); QString pattern; KTextEditor::Range range = doc->textSelection(); if( range.isValid() ) { pattern = doc->textDocument()->text( range ); } if( pattern.isEmpty() ) { pattern = doc->textWord(); } // Before anything, this removes line feeds from the // beginning and the end. int len = pattern.length(); if (len > 0 && pattern[0] == '\n') { pattern.remove(0, 1); len--; } if (len > 0 && pattern[len-1] == '\n') pattern.truncate(len-1); return pattern; } GrepViewPlugin::GrepViewPlugin( QObject *parent, const QVariantList & ) - : KDevelop::IPlugin( "kdevgrepview", parent ), m_currentJob(0) + : KDevelop::IPlugin( QStringLiteral("kdevgrepview"), parent ), m_currentJob(0) { - setXMLFile("kdevgrepview.rc"); + setXMLFile(QStringLiteral("kdevgrepview.rc")); - QDBusConnection::sessionBus().registerObject( "/org/kdevelop/GrepViewPlugin", + QDBusConnection::sessionBus().registerObject( QStringLiteral("/org/kdevelop/GrepViewPlugin"), this, QDBusConnection::ExportScriptableSlots ); - QAction*action = actionCollection()->addAction("edit_grep"); + QAction*action = actionCollection()->addAction(QStringLiteral("edit_grep")); action->setText(i18n("Find/Replace in Fi&les...")); - actionCollection()->setDefaultShortcut( action, QKeySequence("Ctrl+Alt+F") ); + actionCollection()->setDefaultShortcut( action, QKeySequence(QStringLiteral("Ctrl+Alt+F")) ); connect(action, &QAction::triggered, this, &GrepViewPlugin::showDialogFromMenu); action->setToolTip( i18n("Search for expressions over several files") ); action->setWhatsThis( i18n("Opens the 'Find/Replace in files' dialog. There you " "can enter a regular expression which is then " "searched for within all files in the directories " "you specify. Matches will be displayed, you " "can switch to a match directly. You can also do replacement.") ); - action->setIcon(QIcon::fromTheme("edit-find")); + action->setIcon(QIcon::fromTheme(QStringLiteral("edit-find"))); // instantiate delegate, it's supposed to be deleted via QObject inheritance new GrepOutputDelegate(this); m_factory = new GrepOutputViewFactory(this); core()->uiController()->addToolView(i18n("Find/Replace in Files"), m_factory); } GrepOutputViewFactory* GrepViewPlugin::toolViewFactory() const { return m_factory; } GrepViewPlugin::~GrepViewPlugin() { } void GrepViewPlugin::unload() { core()->uiController()->removeToolView(m_factory); } void GrepViewPlugin::startSearch(QString pattern, QString directory, bool showOptions) { m_directory = directory; showDialog(false, pattern, showOptions); } KDevelop::ContextMenuExtension GrepViewPlugin::contextMenuExtension(KDevelop::Context* context) { KDevelop::ContextMenuExtension extension = KDevelop::IPlugin::contextMenuExtension(context); if( context->type() == KDevelop::Context::ProjectItemContext ) { KDevelop::ProjectItemContext* ctx = dynamic_cast( context ); QList items = ctx->items(); // verify if there is only one folder selected if ((items.count() == 1) && (items.first()->folder())) { QAction* action = new QAction( i18n( "Find/Replace in This Folder" ), this ); - action->setIcon(QIcon::fromTheme("edit-find")); + action->setIcon(QIcon::fromTheme(QStringLiteral("edit-find"))); m_contextMenuDirectory = items.at(0)->folder()->path().toLocalFile(); connect( action, &QAction::triggered, this, &GrepViewPlugin::showDialogFromProject); extension.addAction( KDevelop::ContextMenuExtension::ExtensionGroup, action ); } } if ( context->type() == KDevelop::Context::EditorContext ) { KDevelop::EditorContext *econtext = dynamic_cast(context); if ( econtext->view()->selection() ) { - QAction* action = new QAction(QIcon::fromTheme("edit-find"), i18n("&Find/Replace in Files"), this); + QAction* action = new QAction(QIcon::fromTheme(QStringLiteral("edit-find")), i18n("&Find/Replace in Files"), this); connect(action, &QAction::triggered, this, &GrepViewPlugin::showDialogFromMenu); extension.addAction(KDevelop::ContextMenuExtension::ExtensionGroup, action); } } if(context->type() == KDevelop::Context::FileContext) { KDevelop::FileContext *fcontext = dynamic_cast(context); // TODO: just stat() or QFileInfo().isDir() for local files? should be faster than mime type checking - QMimeType mimetype = QMimeDatabase().mimeTypeForUrl(fcontext->urls().first()); - static const QMimeType directoryMime = QMimeDatabase().mimeTypeForName("inode/directory"); + QMimeType mimetype = QMimeDatabase().mimeTypeForUrl(fcontext->urls().at(0)); + static const QMimeType directoryMime = QMimeDatabase().mimeTypeForName(QStringLiteral("inode/directory")); if (mimetype == directoryMime) { QAction* action = new QAction( i18n( "Find/Replace in This Folder" ), this ); - action->setIcon(QIcon::fromTheme("edit-find")); - m_contextMenuDirectory = fcontext->urls().first().toLocalFile(); + action->setIcon(QIcon::fromTheme(QStringLiteral("edit-find"))); + m_contextMenuDirectory = fcontext->urls().at(0).toLocalFile(); connect( action, &QAction::triggered, this, &GrepViewPlugin::showDialogFromProject); extension.addAction( KDevelop::ContextMenuExtension::ExtensionGroup, action ); } } return extension; } void GrepViewPlugin::showDialog(bool setLastUsed, QString pattern, bool showOptions) { GrepDialog* dlg = new GrepDialog( this, core()->uiController()->activeMainWindow() ); KDevelop::IDocument* doc = core()->documentController()->activeDocument(); if(!pattern.isEmpty()) { dlg->setPattern(pattern); } else if(!setLastUsed) { QString pattern = patternFromSelection(doc); if (!pattern.isEmpty()) { dlg->setPattern( pattern ); } } //if directory is empty then use a default value from the config file. if (!m_directory.isEmpty()) { dlg->setSearchLocations(m_directory); } if(showOptions) dlg->show(); else{ dlg->startSearch(); dlg->deleteLater(); } } void GrepViewPlugin::showDialogFromMenu() { showDialog(); } void GrepViewPlugin::showDialogFromProject() { rememberSearchDirectory(m_contextMenuDirectory); showDialog(); } void GrepViewPlugin::rememberSearchDirectory(QString const & directory) { m_directory = directory; } GrepJob* GrepViewPlugin::newGrepJob() { if(m_currentJob != 0) { m_currentJob->kill(); } m_currentJob = new GrepJob(); connect(m_currentJob, &GrepJob::finished, this, &GrepViewPlugin::jobFinished); return m_currentJob; } GrepJob* GrepViewPlugin::grepJob() { return m_currentJob; } void GrepViewPlugin::jobFinished(KJob* job) { if(job == m_currentJob) { emit grepJobFinished(); m_currentJob = 0; } } diff --git a/plugins/grepview/tests/test_findreplace.cpp b/plugins/grepview/tests/test_findreplace.cpp index 3b080d403..73227fb21 100644 --- a/plugins/grepview/tests/test_findreplace.cpp +++ b/plugins/grepview/tests/test_findreplace.cpp @@ -1,195 +1,195 @@ /*************************************************************************** * Copyright 1999-2001 Bernd Gehrmann and the KDevelop Team * * bernd@kdevelop.org * * Copyright 2010 Julien Desgats * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include #include #include #include #include #include #include "test_findreplace.h" #include "../grepjob.h" #include "../grepviewplugin.h" #include "../grepoutputmodel.h" void FindReplaceTest::initTestCase() { KDevelop::AutoTestShell::init(); KDevelop::TestCore::initialize(KDevelop::Core::NoUi); } void FindReplaceTest::cleanupTestCase() { KDevelop::TestCore::shutdown(); } void FindReplaceTest::testFind_data() { QTest::addColumn("subject"); QTest::addColumn("search"); QTest::addColumn("matches"); QTest::newRow("Basic") << "foobar" << QRegExp("foo") << (MatchList() << Match(0, 0, 3)); QTest::newRow("Multiple matches") << "foobar\nbar\nbarfoo" << QRegExp("foo") << (MatchList() << Match(0, 0, 3) << Match(2, 3, 6)); QTest::newRow("Multiple on same line") << "foobarbaz" << QRegExp("ba") << (MatchList() << Match(0, 3, 5) << Match(0, 6, 8)); QTest::newRow("Multiple sticked together") << "foofoobar" << QRegExp("foo") << (MatchList() << Match(0, 0, 3) << Match(0, 3, 6)); QTest::newRow("RegExp (member call)") << "foo->bar ();\nbar();" << QRegExp("\\->\\s*\\b(bar)\\b\\s*\\(") << (MatchList() << Match(0, 3, 10)); // the matching must be started after the last previous match QTest::newRow("RegExp (greedy match)") << "foofooo" << QRegExp("[o]+") << (MatchList() << Match(0, 1, 3) << Match(0, 4, 7)); QTest::newRow("Matching EOL") << "foobar\nfoobar" << QRegExp("foo.*") << (MatchList() << Match(0, 0, 6) << Match(1, 0, 6)); QTest::newRow("Matching EOL (Windows style)") << "foobar\r\nfoobar" << QRegExp("foo.*") << (MatchList() << Match(0, 0, 6) << Match(1, 0, 6)); QTest::newRow("Empty lines handling") << "foo\n\n\n" << QRegExp("bar") << (MatchList()); QTest::newRow("Can match empty string (at EOL)") << "foobar\n" << QRegExp(".*") << (MatchList() << Match(0, 0, 6)); QTest::newRow("Matching empty string anywhere") << "foobar\n" << QRegExp("") << (MatchList()); } void FindReplaceTest::testFind() { QFETCH(QString, subject); QFETCH(QRegExp, search); QFETCH(MatchList, matches); QTemporaryFile file; QVERIFY(file.open()); file.write(subject.toUtf8()); file.close(); GrepOutputItem::List actualMatches = grepFile(file.fileName(), search); QCOMPARE(actualMatches.length(), matches.length()); for(int i=0; im_range.start().line(), matches[i].line); QCOMPARE(actualMatches[i].change()->m_range.start().column(), matches[i].start); QCOMPARE(actualMatches[i].change()->m_range.end().column(), matches[i].end); } // check that file has not been altered by grepFile QVERIFY(file.open()); QCOMPARE(QString(file.readAll()), subject); } void FindReplaceTest::testReplace_data() { QTest::addColumn("subject"); QTest::addColumn("searchPattern"); QTest::addColumn("searchTemplate"); QTest::addColumn("replace"); QTest::addColumn("replaceTemplate"); QTest::addColumn("result"); QTest::newRow("Raw replace") - << (FileList() << File("myfile.txt", "some text\nreplacement\nsome other test\n") - << File("otherfile.txt", "some replacement text\n\n")) + << (FileList() << File(QStringLiteral("myfile.txt"), QStringLiteral("some text\nreplacement\nsome other test\n")) + << File(QStringLiteral("otherfile.txt"), QStringLiteral("some replacement text\n\n"))) << "replacement" << "%s" << "dummy" << "%s" - << (FileList() << File("myfile.txt", "some text\ndummy\nsome other test\n") - << File("otherfile.txt", "some dummy text\n\n")); + << (FileList() << File(QStringLiteral("myfile.txt"), QStringLiteral("some text\ndummy\nsome other test\n")) + << File(QStringLiteral("otherfile.txt"), QStringLiteral("some dummy text\n\n"))); // see bug: https://bugs.kde.org/show_bug.cgi?id=301362 QTest::newRow("LF character replace") - << (FileList() << File("somefile.txt", "hello world\\n")) + << (FileList() << File(QStringLiteral("somefile.txt"), QStringLiteral("hello world\\n"))) << "\\\\n" << "%s" << "\\n\\n" << "%s" - << (FileList() << File("somefile.txt", "hello world\\n\\n")); + << (FileList() << File(QStringLiteral("somefile.txt"), QStringLiteral("hello world\\n\\n"))); QTest::newRow("Template replace") - << (FileList() << File("somefile.h", "struct Foo {\n void setFoo(int foo);\n};") - << File("somefile.cpp", "instance->setFoo(0);\n setFoo(0); /*not replaced*/")) + << (FileList() << File(QStringLiteral("somefile.h"), QStringLiteral("struct Foo {\n void setFoo(int foo);\n};")) + << File(QStringLiteral("somefile.cpp"), QStringLiteral("instance->setFoo(0);\n setFoo(0); /*not replaced*/"))) << "setFoo" << "\\->\\s*\\b%s\\b\\s*\\(" << "setBar" << "->%s(" - << (FileList() << File("somefile.h", "struct Foo {\n void setFoo(int foo);\n};") - << File("somefile.cpp", "instance->setBar(0);\n setFoo(0); /*not replaced*/")); + << (FileList() << File(QStringLiteral("somefile.h"), QStringLiteral("struct Foo {\n void setFoo(int foo);\n};")) + << File(QStringLiteral("somefile.cpp"), QStringLiteral("instance->setBar(0);\n setFoo(0); /*not replaced*/"))); QTest::newRow("Template with captures") - << (FileList() << File("somefile.cpp", "inst::func(1, 2)\n otherInst :: func (\"foo\")\n func()")) + << (FileList() << File(QStringLiteral("somefile.cpp"), QStringLiteral("inst::func(1, 2)\n otherInst :: func (\"foo\")\n func()"))) << "func" << "([a-z0-9_$]+)\\s*::\\s*\\b%s\\b\\s*\\(" << "REPL" << "\\1::%s(" - << (FileList() << File("somefile.cpp", "inst::REPL(1, 2)\n otherInst::REPL(\"foo\")\n func()")); + << (FileList() << File(QStringLiteral("somefile.cpp"), QStringLiteral("inst::REPL(1, 2)\n otherInst::REPL(\"foo\")\n func()"))); QTest::newRow("Regexp pattern") - << (FileList() << File("somefile.txt", "foobar\n foooobar\n fake")) + << (FileList() << File(QStringLiteral("somefile.txt"), QStringLiteral("foobar\n foooobar\n fake"))) << "f\\w*o" << "%s" << "FOO" << "%s" - << (FileList() << File("somefile.txt", "FOObar\n FOObar\n fake")); + << (FileList() << File(QStringLiteral("somefile.txt"), QStringLiteral("FOObar\n FOObar\n fake"))); } void FindReplaceTest::testReplace() { QFETCH(FileList, subject); QFETCH(QString, searchPattern); QFETCH(QString, searchTemplate); QFETCH(QString, replace); QFETCH(QString, replaceTemplate); QFETCH(FileList, result); QTemporaryDir tempDir; QDir dir(tempDir.path()); // we need some convenience functions that are not in QTemporaryDir foreach(const File& fileData, subject) { QFile file(dir.filePath(fileData.first)); QVERIFY(file.open(QIODevice::WriteOnly)); QVERIFY(file.write(fileData.second.toUtf8()) != -1); file.close(); } GrepJob *job = new GrepJob(this); GrepOutputModel *model = new GrepOutputModel(job); job->setOutputModel(model); job->setPatternString(searchPattern); job->setTemplateString(searchTemplate); job->setReplacementTemplateString(replaceTemplate); - job->setFilesString("*"); - job->setExcludeString(""); + job->setFilesString(QStringLiteral("*")); + job->setExcludeString(QString()); job->setDirectoryChoice(QList() << QUrl::fromLocalFile(dir.path())); job->setDepth(-1); // fully recursive job->setRegexpFlag(true); job->setCaseSensitive(true); job->setProjectFilesFlag(false); QVERIFY(job->exec()); QVERIFY(model->hasResults()); model->setReplacement(replace); model->makeItemsCheckable(true); model->doReplacements(); foreach(const File& fileData, result) { QFile file(dir.filePath(fileData.first)); QVERIFY(file.open(QIODevice::ReadOnly)); QCOMPARE(QString(file.readAll()), fileData.second); file.close(); } tempDir.remove(); } QTEST_MAIN(FindReplaceTest); diff --git a/plugins/konsole/kdevkonsoleviewplugin.cpp b/plugins/konsole/kdevkonsoleviewplugin.cpp index 6908a7be7..f9bf74613 100644 --- a/plugins/konsole/kdevkonsoleviewplugin.cpp +++ b/plugins/konsole/kdevkonsoleviewplugin.cpp @@ -1,97 +1,97 @@ /*************************************************************************** * 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): mplugin(plugin) {} QWidget* create(QWidget *parent = 0) override { return new KDevKonsoleView(mplugin, parent); } Qt::DockWidgetArea defaultPosition() override { return Qt::BottomDockWidgetArea; } QString id() const override { - return "org.kdevelop.KonsoleView"; + 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) { core()->uiController()->addToolView(QStringLiteral("Konsole"), m_viewFactory); } } void KDevKonsoleViewPlugin::unload() { if (m_viewFactory) { core()->uiController()->removeToolView(m_viewFactory); } } bool KDevKonsoleViewPlugin::hasError() const { return !m_viewFactory; } QString KDevKonsoleViewPlugin::errorDescription() const { return !m_viewFactory ? i18n("Failed to load 'konsolepart' plugin") : QString(); } KPluginFactory* KDevKonsoleViewPlugin::konsoleFactory() const { return m_konsoleFactory; } KDevKonsoleViewPlugin::~KDevKonsoleViewPlugin() { } #include "kdevkonsoleviewplugin.moc" diff --git a/plugins/openwith/openwithplugin.cpp b/plugins/openwith/openwithplugin.cpp index 7603b5ae6..59451c71c 100644 --- a/plugins/openwith/openwithplugin.cpp +++ b/plugins/openwith/openwithplugin.cpp @@ -1,291 +1,291 @@ /* * This file is part of KDevelop * Copyright 2009 Andreas Pakulat * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "openwithplugin.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; K_PLUGIN_FACTORY_WITH_JSON(KDevOpenWithFactory, "kdevopenwith.json", registerPlugin();) namespace { bool sortActions(QAction* left, QAction* right) { return left->text() < right->text(); } bool isTextEditor(const KService::Ptr& service) { return service->serviceTypes().contains( QStringLiteral("KTextEditor/Document") ); } QString defaultForMimeType(const QString& mimeType) { KConfigGroup config = KSharedConfig::openConfig()->group("Open With Defaults"); if (config.hasKey(mimeType)) { QString storageId = config.readEntry(mimeType, QString()); if (!storageId.isEmpty() && KService::serviceByStorageId(storageId)) { return storageId; } } return QString(); } bool canOpenDefault(const QString& mimeType) { if (defaultForMimeType(mimeType).isEmpty() && mimeType == QLatin1String("inode/directory")) { // potentially happens in non-kde environments apparently, see https://git.reviewboard.kde.org/r/122373 return KMimeTypeTrader::self()->preferredService(mimeType); } else { return true; } } } OpenWithPlugin::OpenWithPlugin ( QObject* parent, const QVariantList& ) : IPlugin ( QStringLiteral("kdevopenwith"), parent ), m_actionMap( 0 ) { KDEV_USE_EXTENSION_INTERFACE( IOpenWith ) } OpenWithPlugin::~OpenWithPlugin() { } KDevelop::ContextMenuExtension OpenWithPlugin::contextMenuExtension( KDevelop::Context* context ) { // do not recurse if (context->hasType(KDevelop::Context::OpenWithContext)) { return ContextMenuExtension(); } m_urls.clear(); m_actionMap.reset(); m_services.clear(); FileContext* filectx = dynamic_cast( context ); ProjectItemContext* projctx = dynamic_cast( context ); if ( filectx && filectx->urls().count() > 0 ) { m_urls = filectx->urls(); } else if ( projctx && projctx->items().count() > 0 ) { // For now, let's handle *either* files only *or* directories only const int wantedType = projctx->items().at(0)->type(); foreach( ProjectBaseItem* item, projctx->items() ) { if (wantedType == ProjectBaseItem::File && item->file()) { m_urls << item->file()->path().toUrl(); } else if ((wantedType == ProjectBaseItem::Folder || wantedType == ProjectBaseItem::BuildFolder) && item->folder()) { m_urls << item->folder()->path().toUrl(); } } } if (m_urls.isEmpty()) { return KDevelop::ContextMenuExtension(); } m_actionMap.reset(new QSignalMapper( this )); connect( m_actionMap.data(), static_cast(&QSignalMapper::mapped), this, &OpenWithPlugin::open ); // Ok, lets fetch the mimetype for the !!first!! url and the relevant services // TODO: Think about possible alternatives to using the mimetype of the first url. QMimeType mimetype = QMimeDatabase().mimeTypeForUrl(m_urls.first()); m_mimeType = mimetype.name(); QList partActions = actionsForServiceType(QStringLiteral("KParts/ReadOnlyPart")); QList appActions = actionsForServiceType(QStringLiteral("Application")); OpenWithContext subContext(m_urls, mimetype); QList extensions = ICore::self()->pluginController()->queryPluginsForContextMenuExtensions( &subContext ); foreach( const ContextMenuExtension& ext, extensions ) { appActions += ext.actions(ContextMenuExtension::OpenExternalGroup); partActions += ext.actions(ContextMenuExtension::OpenEmbeddedGroup); } { auto other = new QAction(i18n("other..."), this); connect(other, &QAction::triggered, this, [this] { auto dialog = new KOpenWithDialog(m_urls, ICore::self()->uiController()->activeMainWindow()); if (dialog->exec() == QDialog::Accepted && dialog->service()) { openService(dialog->service()); } }); appActions << other; } // Now setup a menu with actions for each part and app QMenu* menu = new QMenu( i18n("Open With" ) ); auto documentOpenIcon = QIcon::fromTheme( QStringLiteral("document-open") ); menu->setIcon( documentOpenIcon ); if (!partActions.isEmpty()) { menu->addSection(i18n("Embedded Editors")); menu->addActions( partActions ); } if (!appActions.isEmpty()) { menu->addSection(i18n("External Applications")); menu->addActions( appActions ); } KDevelop::ContextMenuExtension ext; if (canOpenDefault(m_mimeType)) { QAction* openAction = new QAction( i18n( "Open" ), this ); openAction->setIcon( documentOpenIcon ); connect( openAction, SIGNAL(triggered()), SLOT(openDefault()) ); ext.addAction( KDevelop::ContextMenuExtension::FileGroup, openAction ); } ext.addAction(KDevelop::ContextMenuExtension::FileGroup, menu->menuAction()); return ext; } QList OpenWithPlugin::actionsForServiceType( const QString& serviceType ) { KService::List list = KMimeTypeTrader::self()->query( m_mimeType, serviceType ); KService::Ptr pref = KMimeTypeTrader::self()->preferredService( m_mimeType, serviceType ); m_services += list; QList actions; QAction* standardAction = 0; const QString defaultId = defaultForMimeType(m_mimeType); foreach( KService::Ptr svc, list ) { QAction* act = new QAction( isTextEditor(svc) ? i18n("Default Editor") : svc->name(), this ); act->setIcon( QIcon::fromTheme( svc->icon() ) ); if (svc->storageId() == defaultId || (defaultId.isEmpty() && isTextEditor(svc))) { QFont font = act->font(); font.setBold(true); act->setFont(font); } connect(act, &QAction::triggered, m_actionMap.data(), static_cast(&QSignalMapper::map)); m_actionMap->setMapping( act, svc->storageId() ); actions << act; if ( isTextEditor(svc) ) { standardAction = act; } else if ( svc->storageId() == pref->storageId() ) { standardAction = act; } } std::sort(actions.begin(), actions.end(), sortActions); if (standardAction) { actions.removeOne(standardAction); actions.prepend(standardAction); } return actions; } void OpenWithPlugin::openDefault() { // check preferred handler const QString defaultId = defaultForMimeType(m_mimeType); if (!defaultId.isEmpty()) { open(defaultId); return; } // default handlers if (m_mimeType == QLatin1String("inode/directory")) { KService::Ptr service = KMimeTypeTrader::self()->preferredService(m_mimeType); KRun::runService(*service, m_urls, ICore::self()->uiController()->activeMainWindow()); } else { foreach( const QUrl& u, m_urls ) { ICore::self()->documentController()->openDocument( u ); } } } void OpenWithPlugin::open( const QString& storageid ) { openService(KService::serviceByStorageId( storageid )); } void OpenWithPlugin::openService(const KService::Ptr& service) { if (service->isApplication()) { KRun::runService( *service, m_urls, ICore::self()->uiController()->activeMainWindow() ); } else { QString prefName = service->desktopEntryName(); if (isTextEditor(service)) { // If the user chose a KTE part, lets make sure we're creating a TextDocument instead of // a PartDocument by passing no preferredpart to the documentcontroller // TODO: Solve this rather inside DocumentController - prefName = ""; + prefName.clear(); } foreach( const QUrl& u, m_urls ) { ICore::self()->documentController()->openDocument( u, prefName ); } } KConfigGroup config = KSharedConfig::openConfig()->group("Open With Defaults"); if (service->storageId() != config.readEntry(m_mimeType, QString())) { int setDefault = KMessageBox::questionYesNo( qApp->activeWindow(), i18nc("%1: mime type name, %2: app/part name", "Do you want to open all '%1' files by default with %2?", m_mimeType, service->name() ), i18n("Set as default?"), KStandardGuiItem::yes(), KStandardGuiItem::no(), QStringLiteral("OpenWith-%1").arg(m_mimeType) ); if (setDefault == KMessageBox::Yes) { config.writeEntry(m_mimeType, service->storageId()); } } } void OpenWithPlugin::openFilesInternal( const QList& files ) { if (files.isEmpty()) { return; } m_urls = files; m_mimeType = QMimeDatabase().mimeTypeForUrl(m_urls.first()).name(); openDefault(); } #include "openwithplugin.moc" diff --git a/plugins/outlineview/outlinenode.cpp b/plugins/outlineview/outlinenode.cpp index 4e722c9b4..474518772 100644 --- a/plugins/outlineview/outlinenode.cpp +++ b/plugins/outlineview/outlinenode.cpp @@ -1,299 +1,299 @@ /* * KDevelop outline view * Copyright 2010, 2015 Alex Richardson * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "outlinenode.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "debug_outline.h" using namespace KDevelop; OutlineNode::OutlineNode(const QString& text, OutlineNode* parent) : m_cachedText(text) , m_parent(parent) { } OutlineNode::OutlineNode(DUContext* ctx, const QString& name, OutlineNode* parent) : m_cachedText(name) , m_declOrContext(ctx) , m_parent(parent) { KTextEditor::CodeCompletionModel::CompletionProperties prop; switch (ctx->type()) { case KDevelop::DUContext::Class: prop |= KTextEditor::CodeCompletionModel::Class; break; case KDevelop::DUContext::Enum: prop |= KTextEditor::CodeCompletionModel::Enum; break; case KDevelop::DUContext::Function: prop |= KTextEditor::CodeCompletionModel::Function; break; case KDevelop::DUContext::Namespace: prop |= KTextEditor::CodeCompletionModel::Namespace; break; case KDevelop::DUContext::Template: prop |= KTextEditor::CodeCompletionModel::Template; break; default: break; } m_cachedIcon = DUChainUtils::iconForProperties(prop); appendContext(ctx, ctx->topContext()); } OutlineNode::OutlineNode(Declaration* decl, OutlineNode* parent) : m_declOrContext(decl) , m_parent(parent) { // qCDebug(PLUGIN_OUTLINE) << "Adding:" << decl->qualifiedIdentifier().toString() << ": " <identifier().toString(); m_cachedIcon = DUChainUtils::iconForDeclaration(decl); if (NamespaceAliasDeclaration* alias = dynamic_cast(decl)) { //e.g. C++ using namespace statement m_cachedText = alias->importIdentifier().toString(); } else if (ClassMemberDeclaration* member = dynamic_cast(decl)) { if (member->isFriend()) { m_cachedText = "friend " + m_cachedText; } } if (AbstractType::Ptr type = decl->abstractType()) { //add the (function return) type at the end (after a colon - like UML) //so that the first thing seen is the name of the function/variable //and not the (function return) type AbstractType::WhichType typeEnum = type->whichType(); switch (typeEnum) { case AbstractType::TypeFunction: { FunctionType::Ptr func = type.cast(); // func->partToString() does not add the argument names -> do it manually if (DUContext* fCtx = DUChainUtils::getFunctionContext(decl)) { m_cachedText += '('; bool first = true; foreach (Declaration* childDecl, fCtx->localDeclarations(decl->topContext())) { if (first) { first = false; } else { m_cachedText += QStringLiteral(", "); } if (childDecl->abstractType()) { m_cachedText += childDecl->abstractType()->toString(); } auto ident = childDecl->identifier(); if (!ident.isEmpty()) { m_cachedText += ' ' + ident.toString(); } } m_cachedText += ')'; } else { qCWarning(PLUGIN_OUTLINE) << "Missing function context:" << decl->qualifiedIdentifier().toString(); m_cachedText += func->partToString(FunctionType::SignatureArguments); } //constructors/destructors have no return type, a trailing semicolon would look stupid if (func->returnType()) { m_cachedText += " : " + func->partToString(FunctionType::SignatureReturn); } return; // don't append any children here! } case AbstractType::TypeEnumeration: //no need to append the fully qualified type break; case AbstractType::TypeEnumerator: //no need to append the fully qualified type Q_ASSERT(decl->type()); m_cachedText += " = " + decl->type()->valueAsString(); break; case AbstractType::TypeStructure: { //this seems to be the way it has to be done (after grepping through source code) //TODO shouldn't there be some kind of isFriend() functionality? - static IndexedIdentifier friendIdentifier(Identifier("friend")); + static IndexedIdentifier friendIdentifier(Identifier(QStringLiteral("friend"))); const bool isFriend = decl->indexedIdentifier() == friendIdentifier; if (isFriend) { //FIXME There seems to be no way of finding out whether the friend is class/struct/etc m_cachedText += ' ' + type->toString(); } break; } case AbstractType::TypeAlias: { //append the type it aliases TypeAliasType::Ptr alias = type.cast(); if (AbstractType::Ptr targetType = alias->type()) { m_cachedText += " : " + targetType->toString(); } } break; default: QString typeStr = type->toString(); if (!typeStr.isEmpty()) { m_cachedText += " : " + typeStr; } } } //these two don't seem to be hit if (decl->isAutoDeclaration()) { m_cachedText = "Implicit: " + m_cachedText; } if (decl->isAnonymous()) { m_cachedText = "" + m_cachedText; } if (DUContext* ctx = decl->internalContext()) { appendContext(ctx, decl->topContext()); } if (m_cachedText.isEmpty()) { m_cachedText = i18nc("An anonymous declaration (class, function, etc.)", ""); } } std::unique_ptr OutlineNode::dummyNode() { return std::unique_ptr(new OutlineNode(QStringLiteral(""), nullptr)); } std::unique_ptr OutlineNode::fromTopContext(TopDUContext* ctx) { auto result = dummyNode(); result->appendContext(ctx, ctx); return result; } void OutlineNode::appendContext(DUContext* ctx, TopDUContext* top) { // qDebug() << ctx->scopeIdentifier().toString() << "context type=" << ctx->type(); foreach (Declaration* childDecl, ctx->localDeclarations(top)) { if (childDecl) { m_children.emplace_back(childDecl, this); } } bool certainlyRequiresSorting = false; foreach (DUContext* childContext, ctx->childContexts()) { if (childContext->owner()) { // if there is a onwner, this will already have been handled by the loop above // TODO: is this always true? With my testing so far it seems to be // qDebug() << childContext->scopeIdentifier(true).toString() // << " has an owner declaration: " << childContext->owner()->toString() << "-> skip"; continue; } QVector decls = childContext->localDeclarations(top); if (decls.isEmpty()) { continue; } // we now know that we will have o sort since we appended a node in the wrong order certainlyRequiresSorting = true; QString ctxName = childContext->scopeIdentifier(true).toString(); // if child context is a template context or if name is empty append to current list, // otherwise create a new context node if (childContext->type() == DUContext::Template || ctxName.isEmpty()) { //append all subcontexts to this node appendContext(childContext, top); } else { // context without matching declaration, for example the definition of // "class Foo::Bar if it was forward declared in a namespace before: // namespace Foo { class Bar; } // class Foo::Bar { ... }; // TODO: icon and location for the namespace if (childContext->type() == DUContext::ContextType::Helper) { // This context could be for a definition of an existing class method. // If we don't merge all those context end up with a tree like this: // +-+- FooClass // | \-- method1() // +-+- FooClass // | \-- method2() // \ OtherStuff auto it = std::find_if(m_children.begin(), m_children.end(), [childContext](const OutlineNode& node) { if (DUContext* ctx = dynamic_cast(node.duChainObject())) { return ctx->equalScopeIdentifier(childContext); } return false; }); if (it != m_children.end()) { it->appendContext(childContext, top); } else { // TODO: get the correct icon for the context m_children.emplace_back(childContext, ctxName, this); } } else { // just add the context m_children.emplace_back(childContext, ctxName, this); } } } // we now need to sort since sometimes the elements from ctx->localDeclarations(top) // are not in the order they appear in the source. Additionally, if we had any child // contexts that were added, they will be at the end of the list // and need to be moved to the correct location. In that case certainlyRequiresSorting // will be true and we can pass it to sortByLocation() to skip the std::is_sorted() call sortByLocation(certainlyRequiresSorting); } void OutlineNode::sortByLocation(bool requiresSorting) { if (m_children.size() <= 1) { return; } // TODO: does it make sense to cache m_declOrContext->range().start? // adds 8 bytes to each node, but save a lot of pointer lookups when sorting // qDebug("sorting children of %s (%p) by location", qPrintable(m_cachedText), this); auto compare = [](const OutlineNode& n1, const OutlineNode& n2) -> bool { // nodes without decl always go at the end if (!n1.m_declOrContext) { return false; } else if (!n2.m_declOrContext) { return true; } return n1.m_declOrContext->range().start < n2.m_declOrContext->range().start; }; // since most nodes will be correctly sorted we check that before calling std::sort(). // This saves a lot of move ctor/assingnment calls in the common case. // If we appended a context without a Declaration* we know that it will be unsorted // so we can pass requiresSorting = true to skip the useless std::is_sorted() call. // uncomment the following qDebug() lines to see whether this optimization really makes sense if (requiresSorting || !std::is_sorted(m_children.begin(), m_children.end(), compare)) { // qDebug("Need to sort node %s(%p)", qPrintable(m_cachedText), this); std::sort(m_children.begin(), m_children.end(), compare); } else { // qDebug("Node %s(%p) was sorted!", qPrintable(m_cachedText), this); } } OutlineNode::~OutlineNode() { } diff --git a/plugins/outlineview/outlinewidget.cpp b/plugins/outlineview/outlinewidget.cpp index 2bc82c451..100d848cc 100644 --- a/plugins/outlineview/outlinewidget.cpp +++ b/plugins/outlineview/outlinewidget.cpp @@ -1,105 +1,105 @@ /* * 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 "outlinewidget.h" #include #include #include #include #include #include #include #include #include #include #include #include "outlineviewplugin.h" #include "outlinemodel.h" using namespace KDevelop; OutlineWidget::OutlineWidget(QWidget* parent, OutlineViewPlugin* plugin) : QWidget(parent) , m_plugin(plugin) , m_model(new OutlineModel(this)) , m_tree(new QTreeView(this)) , m_proxy(new KRecursiveFilterProxyModel(this)) , m_filter(new QLineEdit(this)) { - setObjectName("Outline View"); + setObjectName(QStringLiteral("Outline View")); setWindowTitle(i18n("Outline")); setWhatsThis(i18n("Outline View")); - setWindowIcon(QIcon::fromTheme("code-class", windowIcon())); //TODO: better icon? + setWindowIcon(QIcon::fromTheme(QStringLiteral("code-class"), windowIcon())); //TODO: better icon? m_proxy->setSourceModel(m_model); m_proxy->setFilterCaseSensitivity(Qt::CaseInsensitive); m_proxy->setSortCaseSensitivity(Qt::CaseInsensitive); m_proxy->setDynamicSortFilter(false); m_tree->setModel(m_proxy); m_tree->setHeaderHidden(true); //filter connect(m_filter, &QLineEdit::textChanged, m_proxy, &KRecursiveFilterProxyModel::setFilterFixedString); connect(m_tree, &QTreeView::activated, this, &OutlineWidget::activated); QHBoxLayout* filterLayout = new QHBoxLayout(); m_filter->setPlaceholderText(i18n("Filter...")); - m_sortAlphabetically = new QPushButton(QIcon::fromTheme("view-sort-ascending"), QString(), this); + m_sortAlphabetically = new QPushButton(QIcon::fromTheme(QStringLiteral("view-sort-ascending")), QString(), this); m_sortAlphabetically->setToolTip(i18n("Sort alphabetically")); m_sortAlphabetically->setCheckable(true); connect(m_sortAlphabetically, &QPushButton::toggled, this, [this](bool sort) { // calling sort with -1 will restore the original order m_proxy->sort(sort ? 0 : -1, Qt::AscendingOrder); m_sortAlphabetically->setChecked(sort); }); filterLayout->addWidget(m_sortAlphabetically); filterLayout->addWidget(m_filter); setFocusProxy(m_filter); QVBoxLayout* vbox = new QVBoxLayout(this); vbox->setMargin(0); vbox->addLayout(filterLayout); vbox->addWidget(m_tree); setLayout(vbox); expandFirstLevel(); connect(m_model, &QAbstractItemModel::modelReset, this, &OutlineWidget::expandFirstLevel); } void OutlineWidget::activated(QModelIndex index) { QModelIndex realIndex = m_proxy->mapToSource(index); m_model->activate(realIndex); } OutlineWidget::~OutlineWidget() { } void OutlineWidget::expandFirstLevel() { for (int i = 0; i < m_proxy->rowCount(); i++) { m_tree->expand(m_proxy->index(i, 0)); } } diff --git a/plugins/patchreview/localpatchsource.cpp b/plugins/patchreview/localpatchsource.cpp index 006aab20e..527cc4ba3 100644 --- a/plugins/patchreview/localpatchsource.cpp +++ b/plugins/patchreview/localpatchsource.cpp @@ -1,136 +1,136 @@ /*************************************************************************** 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 "localpatchsource.h" #include #include #include #include #include #include #include #include "ui_localpatchwidget.h" #include "debug.h" LocalPatchSource::LocalPatchSource() : m_applied(false) , m_depth(0) , m_widget(new LocalPatchWidget(this, 0)) { } LocalPatchSource::~LocalPatchSource() { if ( !m_command.isEmpty() && !m_filename.isEmpty() ) { QFile::remove( m_filename.toLocalFile() ); } } QString LocalPatchSource::name() const { return i18n( "Custom Patch" ); } QIcon LocalPatchSource::icon() const { - return QIcon::fromTheme("text-x-patch"); + return QIcon::fromTheme(QStringLiteral("text-x-patch")); } void LocalPatchSource::update() { if( !m_command.isEmpty() ) { QTemporaryFile temp(QDir::tempPath() + QLatin1String("/patchreview_XXXXXX.diff")); if( temp.open() ) { temp.setAutoRemove( false ); QString filename = temp.fileName(); qCDebug(PLUGIN_PATCHREVIEW) << "temp file: " << filename; temp.close(); KProcess proc; proc.setWorkingDirectory( m_baseDir.toLocalFile() ); proc.setOutputChannelMode( KProcess::OnlyStdoutChannel ); proc.setStandardOutputFile( filename ); ///Try to apply, if it works, the patch is not applied proc << KShell::splitArgs( m_command ); qCDebug(PLUGIN_PATCHREVIEW) << "calling " << m_command; if ( proc.execute() ) { qWarning() << "returned with bad exit code"; return; } if ( !m_filename.isEmpty() ) { QFile::remove( m_filename.toLocalFile() ); } m_filename = QUrl::fromLocalFile( filename ); qCDebug(PLUGIN_PATCHREVIEW) << "success, diff: " << m_filename; }else{ qWarning() << "PROBLEM"; } emit patchChanged(); } } QWidget* LocalPatchSource::customWidget() const { return m_widget; } LocalPatchWidget::LocalPatchWidget(LocalPatchSource* lpatch, QWidget* parent) : QWidget(parent) , m_lpatch(lpatch) , m_ui(new Ui::LocalPatchWidget) { m_ui->setupUi(this); connect( m_ui->applied, &QCheckBox::stateChanged, this, &LocalPatchWidget::updatePatchFromEdit ); connect( m_ui->filename, &KUrlRequester::textChanged, this, &LocalPatchWidget::updatePatchFromEdit ); m_ui->baseDir->setMode( KFile::Directory ); connect( m_ui->command, &QLineEdit::textChanged, this, &LocalPatchWidget::updatePatchFromEdit ); // connect( commandToFile, SIGNAL(clicked(bool)), this, SLOT(slotToFile()) ); connect( m_ui->filename->lineEdit(), &KLineEdit::returnPressed, this, &LocalPatchWidget::updatePatchFromEdit ); connect( m_ui->filename->lineEdit(), &KLineEdit::editingFinished, this, &LocalPatchWidget::updatePatchFromEdit ); connect( m_ui->filename, &KUrlRequester::urlSelected, this, &LocalPatchWidget::updatePatchFromEdit ); connect( m_ui->command, &QLineEdit::textChanged, this, &LocalPatchWidget::updatePatchFromEdit ); // connect( commandToFile, SIGNAL(clicked(bool)), m_plugin, SLOT(commandToFile()) ); connect(m_lpatch, &LocalPatchSource::patchChanged, this, &LocalPatchWidget::syncPatch); } void LocalPatchWidget::syncPatch() { m_ui->command->setText( m_lpatch->command()); m_ui->filename->setUrl( m_lpatch->file() ); m_ui->baseDir->setUrl( m_lpatch->baseDir() ); m_ui->applied->setCheckState( m_lpatch->isAlreadyApplied() ? Qt::Checked : Qt::Unchecked ); if ( m_lpatch->command().isEmpty() ) m_ui->tabWidget->setCurrentIndex( m_ui->tabWidget->indexOf( m_ui->fileTab ) ); else m_ui->tabWidget->setCurrentIndex( m_ui->tabWidget->indexOf( m_ui->commandTab ) ); } void LocalPatchWidget::updatePatchFromEdit() { m_lpatch->setCommand(m_ui->command->text()); m_lpatch->setFilename(m_ui->filename->url()); m_lpatch->setBaseDir(m_ui->baseDir->url()); m_lpatch->setAlreadyApplied(m_ui->applied->checkState() == Qt::Checked ); emit m_lpatch->patchChanged(); } diff --git a/plugins/patchreview/patchhighlighter.cpp b/plugins/patchreview/patchhighlighter.cpp index 0d3a75767..98dcc3803 100644 --- a/plugins/patchreview/patchhighlighter.cpp +++ b/plugins/patchreview/patchhighlighter.cpp @@ -1,624 +1,624 @@ /*************************************************************************** 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. * * * ***************************************************************************/ #include "patchhighlighter.h" #include #include #include "patchreview.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; namespace { QPointer currentTooltip; KTextEditor::MovingRange* currentTooltipMark; QSize sizeHintForHtml( QString html, QSize maxSize ) { QTextDocument doc; doc.setHtml( html ); QSize ret; if( doc.idealWidth() > maxSize.width() ) { doc.setPageSize( QSize( maxSize.width(), 30 ) ); ret.setWidth( maxSize.width() ); }else{ ret.setWidth( doc.idealWidth() ); } ret.setHeight( doc.size().height() ); if( ret.height() > maxSize.height() ) ret.setHeight( maxSize.height() ); return ret; } } void PatchHighlighter::showToolTipForMark( QPoint pos, KTextEditor::MovingRange* markRange) { if( currentTooltipMark == markRange && currentTooltip ) return; delete currentTooltip; //Got the difference Diff2::Difference* diff = m_differencesForRanges[markRange]; QString html; #if 0 if( diff->hasConflict() ) html += i18n( "Conflict
" ); #endif Diff2::DifferenceStringList lines; - html += ""; + html += QLatin1String(""); if( diff->applied() ) { if( !m_plugin->patch()->isAlreadyApplied() ) html += i18n( "Applied.
" ); if( isInsertion( diff ) ) { html += i18n( "Insertion
" ); } else { if( isRemoval( diff ) ) html += i18n( "Removal
" ); html += i18n( "Previous:
" ); lines = diff->sourceLines(); } } else { if( m_plugin->patch()->isAlreadyApplied() ) html += i18n( "Reverted.
" ); if( isRemoval( diff ) ) { html += i18n( "Removal
" ); } else { if( isInsertion( diff ) ) html += i18n( "Insertion
" ); html += i18n( "Alternative:
" ); lines = diff->destinationLines(); } } - html += "
"; + html += QLatin1String("
"); for( int a = 0; a < lines.size(); ++a ) { Diff2::DifferenceString* line = lines[a]; uint currentPos = 0; QString string = line->string(); Diff2::MarkerList markers = line->markerList(); for( int b = 0; b < markers.size(); ++b ) { QString spanText = string.mid( currentPos, markers[b]->offset() - currentPos ).toHtmlEscaped(); if( markers[b]->type() == Diff2::Marker::End && ( currentPos != 0 || markers[b]->offset() != static_cast( string.size() ) ) ) { html += "" + spanText + ""; }else{ html += spanText; } currentPos = markers[b]->offset(); } html += string.mid( currentPos, string.length()-currentPos ).toHtmlEscaped(); - html += "
"; + html += QLatin1String("
"); } auto browser = new QTextBrowser; browser->setPalette( QApplication::palette() ); browser->setHtml( html ); int maxHeight = 500; browser->setMinimumSize( sizeHintForHtml( html, QSize( ( ICore::self()->uiController()->activeMainWindow()->width()*2 )/3, maxHeight ) ) ); browser->setMaximumSize( browser->minimumSize() + QSize( 10, 10 ) ); if( browser->minimumHeight() != maxHeight ) browser->setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); QVBoxLayout* layout = new QVBoxLayout; layout->setMargin( 0 ); layout->addWidget( browser ); KDevelop::ActiveToolTip* tooltip = new KDevelop::ActiveToolTip( ICore::self()->uiController()->activeMainWindow(), pos + QPoint( 5, -browser->sizeHint().height() - 30 ) ); tooltip->setLayout( layout ); tooltip->resize( tooltip->sizeHint() + QSize( 10, 10 ) ); tooltip->move( pos - QPoint( 0, 20 + tooltip->height() ) ); tooltip->setHandleRect( QRect( pos - QPoint( 15, 15 ), pos + QPoint( 15, 15 ) ) ); currentTooltip = tooltip; currentTooltipMark = markRange; ActiveToolTip::showToolTip( tooltip ); } void PatchHighlighter::markClicked( KTextEditor::Document* doc, const KTextEditor::Mark& mark, bool& handled ) { m_applying = true; if( handled ) return; handled = true; // TODO: reconsider workaround // if( doc->activeView() ) ///This is a workaround, if the cursor is somewhere else, the editor will always jump there when a mark was clicked // doc->activeView()->setCursorPosition( KTextEditor::Cursor( mark.line, 0 ) ); KTextEditor::MovingRange* range = rangeForMark( mark ); if( range ) { QString currentText = doc->text( range->toRange() ); Diff2::Difference* diff = m_differencesForRanges[range]; removeLineMarker( range ); QString sourceText; QString targetText; for( int a = 0; a < diff->sourceLineCount(); ++a ) { sourceText += diff->sourceLineAt( a )->string(); if( !sourceText.endsWith( '\n' ) ) sourceText += '\n'; } for( int a = 0; a < diff->destinationLineCount(); ++a ) { targetText += diff->destinationLineAt( a )->string(); if( !targetText.endsWith( '\n' ) ) targetText += '\n'; } QString replace; QString replaceWith; if( !diff->applied() ) { replace = sourceText; replaceWith = targetText; }else { replace = targetText; replaceWith = sourceText; } if( currentText.simplified() != replace.simplified() ) { KMessageBox::error( ICore::self()->uiController()->activeMainWindow(), i18n( "Could not apply the change: Text should be \"%1\", but is \"%2\".", replace, currentText ) ); return; } diff->apply( !diff->applied() ); KTextEditor::Cursor start = range->start().toCursor(); range->document()->replaceText( range->toRange(), replaceWith ); uint replaceWithLines = replaceWith.count( '\n' ); KTextEditor::Range newRange( start, KTextEditor::Cursor(start.line() + replaceWithLines, start.column()) ); range->setRange( newRange ); addLineMarker( range, diff ); } { // After applying the change, show the tooltip again, mainly to update an old tooltip delete currentTooltip; bool h = false; markToolTipRequested( doc, mark, QCursor::pos(), h ); } m_applying = false; } KTextEditor::MovingRange* PatchHighlighter::rangeForMark( const KTextEditor::Mark& mark ) { for( QMap::const_iterator it = m_differencesForRanges.constBegin(); it != m_differencesForRanges.constEnd(); ++it ) { if( it.key()->start().line() == mark.line ) { return it.key(); } } return 0; } void PatchHighlighter::markToolTipRequested( KTextEditor::Document*, const KTextEditor::Mark& mark, QPoint pos, bool& handled ) { if( handled ) return; handled = true; int myMarksPattern = KTextEditor::MarkInterface::markType22 | KTextEditor::MarkInterface::markType23 | KTextEditor::MarkInterface::markType24 | KTextEditor::MarkInterface::markType25 | KTextEditor::MarkInterface::markType26 | KTextEditor::MarkInterface::markType27; if( mark.type & myMarksPattern ) { //There is a mark in this line. Show the old text. KTextEditor::MovingRange* range = rangeForMark( mark ); if( range ) showToolTipForMark( pos, range ); } } bool PatchHighlighter::isInsertion( Diff2::Difference* diff ) { return diff->sourceLineCount() == 0; } bool PatchHighlighter::isRemoval( Diff2::Difference* diff ) { return diff->destinationLineCount() == 0; } QStringList PatchHighlighter::splitAndAddNewlines( const QString& text ) const { QStringList result = text.split( '\n', QString::KeepEmptyParts ); for( QStringList::iterator iter = result.begin(); iter != result.end(); ++iter ) { iter->append( '\n' ); } if ( !result.isEmpty() ) { QString & last = result.last(); last.remove( last.size() - 1, 1 ); } return result; } void PatchHighlighter::performContentChange( KTextEditor::Document* doc, const QStringList& oldLines, const QStringList& newLines, int editLineNumber ) { QPair, QList > diffChange = m_model->linesChanged( oldLines, newLines, editLineNumber ); QList inserted = diffChange.first; QList removed = diffChange.second; // Remove all ranges that are in the same line (the line markers) foreach( KTextEditor::MovingRange* r, m_differencesForRanges.keys() ) { Diff2::Difference* diff = m_differencesForRanges[r]; if ( removed.contains( diff ) ) { removeLineMarker( r ); m_ranges.remove( r ); m_differencesForRanges.remove( r ); delete r; delete diff; } } KTextEditor::MovingInterface* moving = dynamic_cast( doc ); if ( !moving ) return; foreach( Diff2::Difference* diff, inserted ) { int lineStart = diff->destinationLineNumber(); if ( lineStart > 0 ) { --lineStart; } int lineEnd = diff->destinationLineEnd(); if ( lineEnd > 0 ) { --lineEnd; } KTextEditor::Range newRange( lineStart, 0, lineEnd, 0 ); KTextEditor::MovingRange * r = moving->newMovingRange( newRange ); m_differencesForRanges[r] = diff; m_ranges.insert( r ); addLineMarker( r, diff ); } } void PatchHighlighter::textRemoved( KTextEditor::Document* doc, const KTextEditor::Range& range, const QString& oldText ) { if ( m_applying ) { // Do not interfere with patch application return; } qCDebug(PLUGIN_PATCHREVIEW) << "removal range" << range; qCDebug(PLUGIN_PATCHREVIEW) << "removed text" << oldText; QStringList removedLines = splitAndAddNewlines( oldText ); int startLine = range.start().line(); QString remainingLine = doc->line( startLine ); remainingLine += '\n'; QString prefix = remainingLine.mid( 0, range.start().column() ); QString suffix = remainingLine.mid( range.start().column() ); if ( !removedLines.empty() ) { removedLines.first() = prefix + removedLines.first(); removedLines.last() = removedLines.last() + suffix; } performContentChange( doc, removedLines, QStringList() << remainingLine, startLine + 1 ); } void PatchHighlighter::highlightFromScratch(KTextEditor::Document* doc) { qCDebug(PLUGIN_PATCHREVIEW) << "re-doing"; //The document was loaded / reloaded if ( !m_model->differences() ) return; KTextEditor::MovingInterface* moving = dynamic_cast( doc ); if ( !moving ) return; KTextEditor::MarkInterface* markIface = dynamic_cast( doc ); if( !markIface ) return; clear(); KColorScheme scheme( QPalette::Active ); - QImage tintedInsertion = QIcon::fromTheme( "insert-text" ).pixmap( 16, 16 ).toImage(); + QImage tintedInsertion = QIcon::fromTheme( QStringLiteral("insert-text") ).pixmap( 16, 16 ).toImage(); KIconEffect::colorize( tintedInsertion, scheme.foreground( KColorScheme::NegativeText ).color(), 1.0 ); - QImage tintedRemoval = QIcon::fromTheme( "edit-delete" ).pixmap( 16, 16 ).toImage(); + QImage tintedRemoval = QIcon::fromTheme( QStringLiteral("edit-delete") ).pixmap( 16, 16 ).toImage(); KIconEffect::colorize( tintedRemoval, scheme.foreground( KColorScheme::NegativeText ).color(), 1.0 ); - QImage tintedChange = QIcon::fromTheme( "text-field" ).pixmap( 16, 16 ).toImage(); + QImage tintedChange = QIcon::fromTheme( QStringLiteral("text-field") ).pixmap( 16, 16 ).toImage(); KIconEffect::colorize( tintedChange, scheme.foreground( KColorScheme::NegativeText ).color(), 1.0 ); markIface->setMarkDescription( KTextEditor::MarkInterface::markType22, i18n( "Insertion" ) ); markIface->setMarkPixmap( KTextEditor::MarkInterface::markType22, QPixmap::fromImage( tintedInsertion ) ); markIface->setMarkDescription( KTextEditor::MarkInterface::markType23, i18n( "Removal" ) ); markIface->setMarkPixmap( KTextEditor::MarkInterface::markType23, QPixmap::fromImage( tintedRemoval ) ); markIface->setMarkDescription( KTextEditor::MarkInterface::markType24, i18n( "Change" ) ); markIface->setMarkPixmap( KTextEditor::MarkInterface::markType24, QPixmap::fromImage( tintedChange ) ); markIface->setMarkDescription( KTextEditor::MarkInterface::markType25, i18n( "Insertion" ) ); - markIface->setMarkPixmap( KTextEditor::MarkInterface::markType25, QIcon::fromTheme( "insert-text" ).pixmap( 16, 16 ) ); + markIface->setMarkPixmap( KTextEditor::MarkInterface::markType25, QIcon::fromTheme( QStringLiteral("insert-text") ).pixmap( 16, 16 ) ); markIface->setMarkDescription( KTextEditor::MarkInterface::markType26, i18n( "Removal" ) ); - markIface->setMarkPixmap( KTextEditor::MarkInterface::markType26, QIcon::fromTheme( "edit-delete" ).pixmap( 16, 16 ) ); + markIface->setMarkPixmap( KTextEditor::MarkInterface::markType26, QIcon::fromTheme( QStringLiteral("edit-delete") ).pixmap( 16, 16 ) ); markIface->setMarkDescription( KTextEditor::MarkInterface::markType27, i18n( "Change" ) ); - markIface->setMarkPixmap( KTextEditor::MarkInterface::markType27, QIcon::fromTheme( "text-field" ).pixmap( 16, 16 ) ); + markIface->setMarkPixmap( KTextEditor::MarkInterface::markType27, QIcon::fromTheme( QStringLiteral("text-field") ).pixmap( 16, 16 ) ); for ( Diff2::DifferenceList::const_iterator it = m_model->differences()->constBegin(); it != m_model->differences()->constEnd(); ++it ) { Diff2::Difference* diff = *it; int line, lineCount; Diff2::DifferenceStringList lines; if( diff->applied() ) { line = diff->destinationLineNumber(); lineCount = diff->destinationLineCount(); lines = diff->destinationLines(); } else { line = diff->sourceLineNumber(); lineCount = diff->sourceLineCount(); lines = diff->sourceLines(); } if ( line > 0 ) line -= 1; KTextEditor::Cursor c( line, 0 ); KTextEditor::Cursor endC( line + lineCount, 0 ); if ( doc->lines() <= c.line() ) c.setLine( doc->lines() - 1 ); if ( doc->lines() <= endC.line() ) endC.setLine( doc->lines() ); if ( endC.isValid() && c.isValid() ) { KTextEditor::MovingRange * r = moving->newMovingRange( KTextEditor::Range( c, endC ) ); m_ranges << r; m_differencesForRanges[r] = *it; addLineMarker( r, diff ); } } } void PatchHighlighter::textInserted(KTextEditor::Document* doc, const KTextEditor::Cursor& cursor, const QString& text) { KTextEditor::Range range(cursor, KTextEditor::Cursor(text.count('\n'), text.size()-text.lastIndexOf('\n')+1)); if( range == doc->documentRange() ) { highlightFromScratch(doc); } else { if ( m_applying ) { // Do not interfere with patch application return; } qCDebug(PLUGIN_PATCHREVIEW) << "insertion range" << range; QString text = doc->text( range ); qCDebug(PLUGIN_PATCHREVIEW) << "inserted text" << text; QStringList insertedLines = splitAndAddNewlines( text ); int startLine = range.start().line(); int endLine = range.end().line(); QString prefix = doc->line( startLine ).mid( 0, range.start().column() ); QString suffix = doc->line( endLine ).mid( range.end().column() ); suffix += '\n'; QString removedLine = prefix + suffix; if ( !insertedLines.empty() ) { insertedLines.first() = prefix + insertedLines.first(); insertedLines.last() = insertedLines.last() + suffix; } performContentChange( doc, QStringList() << removedLine, insertedLines, startLine + 1 ); } } PatchHighlighter::PatchHighlighter( Diff2::DiffModel* model, IDocument* kdoc, PatchReviewPlugin* plugin ) throw( QString ) : m_doc( kdoc ), m_plugin( plugin ), m_model( model ), m_applying( false ) { KTextEditor::Document* doc = kdoc->textDocument(); // connect( kdoc, SIGNAL(destroyed(QObject*)), this, SLOT(documentDestroyed()) ); connect(doc, &KTextEditor::Document::textInserted, this, &PatchHighlighter::textInserted); connect(doc, &KTextEditor::Document::textRemoved, this, &PatchHighlighter::textRemoved); connect(doc, &KTextEditor::Document::destroyed, this, &PatchHighlighter::documentDestroyed); if ( doc->lines() == 0 ) return; if (qobject_cast(doc)) { //can't use new signal/slot syntax here, MarkInterface is not a QObject connect(doc, SIGNAL(markToolTipRequested(KTextEditor::Document*,KTextEditor::Mark,QPoint,bool&)), this, SLOT(markToolTipRequested(KTextEditor::Document*,KTextEditor::Mark,QPoint,bool &))); connect(doc, SIGNAL(markClicked(KTextEditor::Document*,KTextEditor::Mark,bool&)), this, SLOT(markClicked(KTextEditor::Document*,KTextEditor::Mark,bool&))); } if (qobject_cast(doc)) { //can't use new signal/slot syntax here, MovingInterface is not a QObject connect(doc, SIGNAL(aboutToDeleteMovingInterfaceContent(KTextEditor::Document*)), this, SLOT( aboutToDeleteMovingInterfaceContent(KTextEditor::Document*))); connect(doc, SIGNAL(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document*)), this, SLOT(aboutToDeleteMovingInterfaceContent(KTextEditor::Document*))); } highlightFromScratch(doc); } void PatchHighlighter::removeLineMarker( KTextEditor::MovingRange* range ) { KTextEditor::MovingInterface* moving = dynamic_cast( range->document() ); if ( !moving ) return; KTextEditor::MarkInterface* markIface = dynamic_cast( range->document() ); if( !markIface ) return; markIface->removeMark( range->start().line(), KTextEditor::MarkInterface::markType22 ); markIface->removeMark( range->start().line(), KTextEditor::MarkInterface::markType23 ); markIface->removeMark( range->start().line(), KTextEditor::MarkInterface::markType24 ); markIface->removeMark( range->start().line(), KTextEditor::MarkInterface::markType25 ); markIface->removeMark( range->start().line(), KTextEditor::MarkInterface::markType26 ); markIface->removeMark( range->start().line(), KTextEditor::MarkInterface::markType27 ); // Remove all ranges that are in the same line (the line markers) foreach( KTextEditor::MovingRange* r, m_ranges ) { if( r != range && range->contains( r->toRange() ) ) { delete r; m_ranges.remove( r ); m_differencesForRanges.remove( r ); } } } void PatchHighlighter::addLineMarker( KTextEditor::MovingRange* range, Diff2::Difference* diff ) { KTextEditor::MovingInterface* moving = dynamic_cast( range->document() ); if ( !moving ) return; KTextEditor::MarkInterface* markIface = dynamic_cast( range->document() ); if( !markIface ) return; KTextEditor::Attribute::Ptr t( new KTextEditor::Attribute() ); bool isOriginalState = diff->applied() == m_plugin->patch()->isAlreadyApplied(); if( isOriginalState ) { t->setProperty( QTextFormat::BackgroundBrush, QBrush( ColorCache::self()->blendBackground( QColor( 0, 255, 255 ), 20 ) ) ); }else{ t->setProperty( QTextFormat::BackgroundBrush, QBrush( ColorCache::self()->blendBackground( QColor( 255, 0, 255 ), 20 ) ) ); } range->setAttribute( t ); range->setZDepth( -500 ); KTextEditor::MarkInterface::MarkTypes mark; if( isOriginalState ) { mark = KTextEditor::MarkInterface::markType27; if( isInsertion( diff ) ) mark = KTextEditor::MarkInterface::markType25; if( isRemoval( diff ) ) mark = KTextEditor::MarkInterface::markType26; }else{ mark = KTextEditor::MarkInterface::markType24; if( isInsertion( diff ) ) mark = KTextEditor::MarkInterface::markType22; if( isRemoval( diff ) ) mark = KTextEditor::MarkInterface::markType23; } markIface->addMark( range->start().line(), mark ); Diff2::DifferenceStringList lines; if( diff->applied() ) lines = diff->destinationLines(); else lines = diff->sourceLines(); for( int a = 0; a < lines.size(); ++a ) { Diff2::DifferenceString* line = lines[a]; int currentPos = 0; QString string = line->string(); Diff2::MarkerList markers = line->markerList(); for( int b = 0; b < markers.size(); ++b ) { if( markers[b]->type() == Diff2::Marker::End ) { if( currentPos != 0 || markers[b]->offset() != static_cast( string.size() ) ) { KTextEditor::MovingRange * r2 = moving->newMovingRange( KTextEditor::Range( KTextEditor::Cursor( a + range->start().line(), currentPos ), KTextEditor::Cursor( a + range->start().line(), markers[b]->offset() ) ) ); m_ranges << r2; KTextEditor::Attribute::Ptr t( new KTextEditor::Attribute() ); t->setProperty( QTextFormat::BackgroundBrush, QBrush( ColorCache::self()->blendBackground( QColor( 255, 0, 0 ), 70 ) ) ); r2->setAttribute( t ); r2->setZDepth( -600 ); } } currentPos = markers[b]->offset(); } } } void PatchHighlighter::clear() { if( m_ranges.empty() ) return; KTextEditor::MovingInterface* moving = dynamic_cast( m_doc->textDocument() ); if ( !moving ) return; KTextEditor::MarkInterface* markIface = dynamic_cast( m_doc->textDocument() ); if( !markIface ) return; QHash marks = markIface->marks(); foreach( int line, marks.keys() ) { markIface->removeMark( line, KTextEditor::MarkInterface::markType22 ); markIface->removeMark( line, KTextEditor::MarkInterface::markType23 ); markIface->removeMark( line, KTextEditor::MarkInterface::markType24 ); markIface->removeMark( line, KTextEditor::MarkInterface::markType25 ); markIface->removeMark( line, KTextEditor::MarkInterface::markType26 ); markIface->removeMark( line, KTextEditor::MarkInterface::markType27 ); } qDeleteAll( m_ranges ); m_ranges.clear(); m_differencesForRanges.clear(); } PatchHighlighter::~PatchHighlighter() { clear(); } IDocument* PatchHighlighter::doc() { return m_doc; } void PatchHighlighter::documentDestroyed() { qCDebug(PLUGIN_PATCHREVIEW) << "document destroyed"; m_ranges.clear(); m_differencesForRanges.clear(); } void PatchHighlighter::aboutToDeleteMovingInterfaceContent( KTextEditor::Document* ) { qCDebug(PLUGIN_PATCHREVIEW) << "about to delete"; clear(); } QList< KTextEditor::MovingRange* > PatchHighlighter::ranges() const { return m_differencesForRanges.keys(); } diff --git a/plugins/patchreview/patchreview.cpp b/plugins/patchreview/patchreview.cpp index ef13c0d07..7f39a57cb 100644 --- a/plugins/patchreview/patchreview.cpp +++ b/plugins/patchreview/patchreview.cpp @@ -1,540 +1,540 @@ /*************************************************************************** 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 ///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); 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 ); } } 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 ); 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( 0 ); 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." ); } 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( 0, str, i18n( "Kompare Model Update" ) ); } catch ( const char * str ) { KMessageBox::error( 0, str, i18n( "Kompare Model Update" ) ); } removeHighlighting(); m_modelList.reset( nullptr ); 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 ) {} QWidget* create( QWidget *parent = 0 ) override { return m_plugin->createToolView( parent ); } Qt::DockWidgetArea defaultPosition() override { return Qt::BottomDockWidgetArea; } QString id() const override { - return "org.kdevelop.PatchReview"; + 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(0); } 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 ) { removeHighlighting(); m_modelList.reset( 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::MainWindow* w = dynamic_cast( ICore::self()->uiController()->activeMainWindow() ); - if( w->area()->objectName() == "review" ) { + if( w->area()->objectName() == QLatin1String("review") ) { if( ICore::self()->documentController()->saveAllDocuments() ) - ICore::self()->uiController()->switchToArea( "code", KDevelop::IUiController::ThisWindow ); + 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() == "review") { + if (area->objectName() == QLatin1String("review")) { emit area->clearDocuments(); } } - if ( ICore::self()->uiController()->activeArea()->objectName() != "review" ) - ICore::self()->uiController()->switchToArea( "review", KDevelop::IUiController::ThisWindow ); + if ( ICore::self()->uiController()->activeArea()->objectName() != QLatin1String("review") ) + ICore::self()->uiController()->switchToArea( QStringLiteral("review"), KDevelop::IUiController::ThisWindow ); } QUrl PatchReviewPlugin::urlForFileModel( const Diff2::DiffModel* model ) { QUrl file = m_patch->baseDir(); file.setPath(file.toLocalFile() + '/' + model->destinationPath() + '/' + model->destinationFile()); return file; } void PatchReviewPlugin::updateReview() { if( !m_patch ) return; m_updateKompareTimer->stop(); switchToEmptyReviewArea(); IDocument* futureActiveDoc = ICore::self()->documentController()->openDocument( m_patch->file() ); 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" ) ); IDocument* buddyDoc = futureActiveDoc; KTextEditor::ModificationInterface* modif = dynamic_cast( futureActiveDoc->textDocument() ); modif->setModifiedOnDiskWarning( false ); if( m_modelList->modelCount() < maximumFilesToOpenDirectly ) { //Open all relates files for( int a = 0; a < m_modelList->modelCount(); ++a ) { QUrl absoluteUrl = urlForFileModel( m_modelList->modelAt( a ) ); - if( QFileInfo( absoluteUrl.toLocalFile() ).exists() && absoluteUrl.toLocalFile() != "/dev/null" ) + if( QFileInfo( absoluteUrl.toLocalFile() ).exists() && absoluteUrl.toLocalFile() != QLatin1String("/dev/null") ) { - buddyDoc = ICore::self()->documentController()->openDocument( absoluteUrl, KTextEditor::Range::invalid(), IDocumentController::DoNotActivate, "", buddyDoc ); + buddyDoc = ICore::self()->documentController()->openDocument( absoluteUrl, KTextEditor::Range::invalid(), IDocumentController::DoNotActivate, QLatin1String(""), buddyDoc ); seekHunk( true, absoluteUrl ); //Jump to the first changed position }else{ // Maybe the file was deleted qCDebug(PLUGIN_PATCHREVIEW) << "could not open" << absoluteUrl << "because it doesn't exist"; } } } Q_ASSERT( futureActiveDoc ); ICore::self()->documentController()->activateDocument( futureActiveDoc ); bool b = ICore::self()->uiController()->findToolView( i18n( "Patch Review" ), m_factory ); Q_ASSERT( b ); Q_UNUSED( b ); } 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 ); notifyPatchChanged(); } PatchReviewPlugin::PatchReviewPlugin( QObject *parent, const QVariantList & ) - : KDevelop::IPlugin( "kdevpatchreview", parent ), + : KDevelop::IPlugin( QStringLiteral("kdevpatchreview"), parent ), m_patch( 0 ), m_factory( new PatchReviewToolViewFactory( this ) ) { KDEV_USE_EXTENSION_INTERFACE( KDevelop::IPatchReview ) qRegisterMetaType( "const Diff2::DiffModel*" ); - setXMLFile( "kdevpatchreview.rc" ); + 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( "dialog-ok" ) ); + m_finishReview->setIcon( QIcon::fromTheme( QStringLiteral("dialog-ok") ) ); actionCollection()->setDefaultShortcut( m_finishReview, Qt::CTRL|Qt::Key_Return ); - actionCollection()->addAction("commit_or_finish_review", m_finishReview); + actionCollection()->addAction(QStringLiteral("commit_or_finish_review"), m_finishReview); foreach(Sublime::Area* area, ICore::self()->uiController()->allAreas()) { - if (area->objectName() == "review") + if (area->objectName() == QLatin1String("review")) area->addAction(m_finishReview); } core()->uiController()->addToolView( i18n( "Patch Review" ), m_factory ); 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. if( m_patch && doc->url() != m_patch->file() ) forceUpdate(); } void PatchReviewPlugin::textDocumentCreated( IDocument* doc ) { if (m_patch) { addHighlighting( doc->url(), doc ); } } void PatchReviewPlugin::unload() { core()->uiController()->removeToolView( m_factory ); KDevelop::IPlugin::unload(); } QWidget* PatchReviewPlugin::createToolView( QWidget* parent ) { return new PatchReviewToolView( parent, this ); } void PatchReviewPlugin::areaChanged(Sublime::Area* area) { - bool reviewing = area->objectName() == "review"; + bool reviewing = area->objectName() == QLatin1String("review"); m_finishReview->setEnabled(reviewing); if(!reviewing) { closeReview(); } } #include "patchreview.moc" // kate: space-indent on; indent-width 4; tab-width 4; replace-tabs on diff --git a/plugins/patchreview/patchreviewtoolview.cpp b/plugins/patchreview/patchreviewtoolview.cpp index cbf8320b3..8258a6aa7 100644 --- a/plugins/patchreview/patchreviewtoolview.cpp +++ b/plugins/patchreview/patchreviewtoolview.cpp @@ -1,569 +1,572 @@ /*************************************************************************** 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 "patchreviewtoolview.h" #include "localpatchsource.h" #include "patchreview.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 #ifdef WITH_PURPOSE #include #include #endif using namespace KDevelop; class PatchFilesModel : public VcsFileChangesModel { + Q_OBJECT public: PatchFilesModel( QObject *parent, bool allowSelection ) : VcsFileChangesModel( parent, allowSelection ) { }; enum ItemRoles { HunksNumberRole = LastItemRole+1 }; public slots: void updateState( const KDevelop::VcsStatusInfo &status, unsigned hunksNum ) { int row = VcsFileChangesModel::updateState( invisibleRootItem(), status ); if ( row == -1 ) return; QStandardItem *item = invisibleRootItem()->child( row, 0 ); setFileInfo( item, hunksNum ); item->setData( QVariant( hunksNum ), HunksNumberRole ); } void updateState( const KDevelop::VcsStatusInfo &status ) { int row = VcsFileChangesModel::updateState( invisibleRootItem(), status ); if ( row == -1 ) return; QStandardItem *item = invisibleRootItem()->child( row, 0 ); setFileInfo( invisibleRootItem()->child( row, 0 ), item->data( HunksNumberRole ).toUInt() ); } private: void setFileInfo( QStandardItem *item, unsigned int hunksNum ) { QString newText = i18ncp( "%1: number of changed hunks, %2: file name", "%2 (1 hunk)", "%2 (%1 hunks)", hunksNum, item->index().data(VcsFileChangesModel::UrlRole).toUrl().toDisplayString(QUrl::PreferLocalFile) ); item->setText( newText ); } }; PatchReviewToolView::PatchReviewToolView( QWidget* parent, PatchReviewPlugin* plugin ) : QWidget( parent ), m_resetCheckedUrls( true ), m_plugin( plugin ) { connect( m_plugin->finishReviewAction(), &QAction::triggered, this, &PatchReviewToolView::finishReview ); connect( plugin, &PatchReviewPlugin::patchChanged, this, &PatchReviewToolView::patchChanged ); connect( plugin, &PatchReviewPlugin::startingNewReview, this, &PatchReviewToolView::startingNewReview ); connect( ICore::self()->documentController(), &IDocumentController::documentActivated, this, &PatchReviewToolView::documentActivated ); Sublime::MainWindow* w = dynamic_cast( ICore::self()->uiController()->activeMainWindow() ); connect(w, &Sublime::MainWindow::areaChanged, m_plugin, &PatchReviewPlugin::areaChanged); showEditDialog(); patchChanged(); } void PatchReviewToolView::resizeEvent(QResizeEvent* ev) { bool vertical = (width() < height()); m_editPatch.buttonsLayout->setDirection(vertical ? QBoxLayout::TopToBottom : QBoxLayout::LeftToRight); m_editPatch.contentLayout->setDirection(vertical ? QBoxLayout::TopToBottom : QBoxLayout::LeftToRight); m_editPatch.buttonsSpacer->changeSize(vertical ? 0 : 40, 0, QSizePolicy::Fixed, QSizePolicy::Fixed); QWidget::resizeEvent(ev); if(m_customWidget) { m_editPatch.contentLayout->removeWidget( m_customWidget ); m_editPatch.contentLayout->insertWidget(0, m_customWidget ); } } void PatchReviewToolView::startingNewReview() { m_resetCheckedUrls = true; } void PatchReviewToolView::patchChanged() { fillEditFromPatch(); kompareModelChanged(); #ifdef WITH_PURPOSE IPatchSource::Ptr p = m_plugin->patch(); if (p) { m_exportMenu->model()->setInputData(QJsonObject { { QStringLiteral("urls"), QJsonArray { p->file().toString() } }, { QStringLiteral("mimeType"), { QStringLiteral("text/x-patch") } }, { QStringLiteral("localBaseDir"), { p->baseDir().toString() } } }); } #endif } PatchReviewToolView::~PatchReviewToolView() { } LocalPatchSource* PatchReviewToolView::GetLocalPatchSource() { IPatchSource::Ptr ips = m_plugin->patch(); if ( !ips ) return 0; return dynamic_cast( ips.data() ); } void PatchReviewToolView::fillEditFromPatch() { IPatchSource::Ptr ipatch = m_plugin->patch(); if ( !ipatch ) return; m_editPatch.cancelReview->setVisible( ipatch->canCancel() ); m_fileModel->setIsCheckbable( m_plugin->patch()->canSelectFiles() ); if( m_customWidget ) { qCDebug(PLUGIN_PATCHREVIEW) << "removing custom widget"; m_customWidget->hide(); m_editPatch.contentLayout->removeWidget( m_customWidget ); } m_customWidget = ipatch->customWidget(); if( m_customWidget ) { m_editPatch.contentLayout->insertWidget( 0, m_customWidget ); m_customWidget->show(); qCDebug(PLUGIN_PATCHREVIEW) << "got custom widget"; } bool showTests = false; IProject* project = 0; QMap files = ipatch->additionalSelectableFiles(); QMap::const_iterator it = files.constBegin(); for (; it != files.constEnd(); ++it) { project = ICore::self()->projectController()->findProjectForUrl(it.key()); if (project && !ICore::self()->testController()->testSuitesForProject(project).isEmpty()) { showTests = true; break; } } m_editPatch.testsButton->setVisible(showTests); m_editPatch.testProgressBar->hide(); } void PatchReviewToolView::slotAppliedChanged( int newState ) { if ( LocalPatchSource* lpatch = GetLocalPatchSource() ) { lpatch->setAlreadyApplied( newState == Qt::Checked ); m_plugin->notifyPatchChanged(); } } void PatchReviewToolView::showEditDialog() { m_editPatch.setupUi( this ); bool allowSelection = m_plugin->patch() && m_plugin->patch()->canSelectFiles(); m_fileModel = new PatchFilesModel( this, allowSelection ); m_editPatch.filesList->setModel( m_fileModel ); m_editPatch.filesList->header()->hide(); m_editPatch.filesList->setRootIsDecorated( false ); m_editPatch.filesList->setContextMenuPolicy(Qt::CustomContextMenu); connect(m_editPatch.filesList, &QTreeView::customContextMenuRequested, this, &PatchReviewToolView::customContextMenuRequested); connect(m_fileModel, &PatchFilesModel::itemChanged, this, &PatchReviewToolView::fileItemChanged); - m_editPatch.previousFile->setIcon( QIcon::fromTheme( "arrow-left" ) ); - m_editPatch.previousHunk->setIcon( QIcon::fromTheme( "arrow-up" ) ); - m_editPatch.nextHunk->setIcon( QIcon::fromTheme( "arrow-down" ) ); - m_editPatch.nextFile->setIcon( QIcon::fromTheme( "arrow-right" ) ); - m_editPatch.cancelReview->setIcon( QIcon::fromTheme( "dialog-cancel" ) ); - m_editPatch.updateButton->setIcon( QIcon::fromTheme( "view-refresh" ) ); - m_editPatch.testsButton->setIcon( QIcon::fromTheme( "preflight-verifier" ) ); + m_editPatch.previousFile->setIcon( QIcon::fromTheme( QStringLiteral("arrow-left") ) ); + m_editPatch.previousHunk->setIcon( QIcon::fromTheme( QStringLiteral("arrow-up") ) ); + m_editPatch.nextHunk->setIcon( QIcon::fromTheme( QStringLiteral("arrow-down") ) ); + m_editPatch.nextFile->setIcon( QIcon::fromTheme( QStringLiteral("arrow-right") ) ); + m_editPatch.cancelReview->setIcon( QIcon::fromTheme( QStringLiteral("dialog-cancel") ) ); + m_editPatch.updateButton->setIcon( QIcon::fromTheme( QStringLiteral("view-refresh") ) ); + m_editPatch.testsButton->setIcon( QIcon::fromTheme( QStringLiteral("preflight-verifier") ) ); m_editPatch.finishReview->setDefaultAction(m_plugin->finishReviewAction()); #ifdef WITH_PURPOSE m_exportMenu = new Purpose::Menu(this); connect(m_exportMenu, &Purpose::Menu::finished, this, [](const QJsonObject &output, int error, const QString &message) { if (error==0) { KMessageBox::information(0, i18n("You can find the new request at:
%1
", output["url"].toString()), QString(), QString(), KMessageBox::AllowLink); } else { QMessageBox::warning(nullptr, i18n("Error exporting"), i18n("Couldn't export the patch.\n%1", message)); } }); m_exportMenu->model()->setPluginType("Export"); m_editPatch.exportReview->setMenu( m_exportMenu ); #else m_editPatch.exportReview->setEnabled(false); #endif connect( m_editPatch.previousHunk, &QToolButton::clicked, this, &PatchReviewToolView::prevHunk ); connect( m_editPatch.nextHunk, &QToolButton::clicked, this, &PatchReviewToolView::nextHunk ); connect( m_editPatch.previousFile, &QToolButton::clicked, this, &PatchReviewToolView::prevFile ); connect( m_editPatch.nextFile, &QToolButton::clicked, this, &PatchReviewToolView::nextFile ); connect( m_editPatch.filesList, &QTreeView::activated , this, &PatchReviewToolView::fileDoubleClicked ); connect( m_editPatch.cancelReview, &QPushButton::clicked, m_plugin, &PatchReviewPlugin::cancelReview ); //connect( m_editPatch.cancelButton, SIGNAL(pressed()), this, SLOT(slotEditCancel()) ); //connect( this, SIGNAL(finished(int)), this, SLOT(slotEditDialogFinished(int)) ); connect( m_editPatch.updateButton, &QPushButton::clicked, m_plugin, &PatchReviewPlugin::forceUpdate ); connect( m_editPatch.testsButton, &QPushButton::clicked, this, &PatchReviewToolView::runTests ); - m_selectAllAction = new QAction(QIcon::fromTheme("edit-select-all"), i18n("Select All"), this ); + m_selectAllAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-select-all")), i18n("Select All"), this ); connect( m_selectAllAction, &QAction::triggered, this, &PatchReviewToolView::selectAll ); m_deselectAllAction = new QAction( i18n("Deselect All"), this ); connect( m_deselectAllAction, &QAction::triggered, this, &PatchReviewToolView::deselectAll ); } void PatchReviewToolView::customContextMenuRequested(const QPoint& ) { QList urls; QModelIndexList selectionIdxs = m_editPatch.filesList->selectionModel()->selectedIndexes(); foreach(const QModelIndex& idx, selectionIdxs) { urls += idx.data(KDevelop::VcsFileChangesModel::UrlRole).toUrl(); } QPointer menu = new QMenu(m_editPatch.filesList); QList extensions; if(!urls.isEmpty()) { KDevelop::FileContext context(urls); extensions = ICore::self()->pluginController()->queryPluginsForContextMenuExtensions( &context ); } QList vcsActions; foreach( const ContextMenuExtension& ext, extensions ) { vcsActions += ext.actions(ContextMenuExtension::VcsGroup); } menu->addAction(m_selectAllAction); menu->addAction(m_deselectAllAction); menu->addActions(vcsActions); if ( !menu->isEmpty() ) { menu->exec(QCursor::pos()); } delete menu; } void PatchReviewToolView::nextHunk() { IDocument* current = ICore::self()->documentController()->activeDocument(); if(current && current->textDocument()) m_plugin->seekHunk( true, current->textDocument()->url() ); } void PatchReviewToolView::prevHunk() { IDocument* current = ICore::self()->documentController()->activeDocument(); if(current && current->textDocument()) m_plugin->seekHunk( false, current->textDocument()->url() ); } void PatchReviewToolView::seekFile(bool forwards) { if(!m_plugin->patch()) return; QList checkedUrls = m_fileModel->checkedUrls(); QList allUrls = m_fileModel->urls(); IDocument* current = ICore::self()->documentController()->activeDocument(); if(!current || checkedUrls.empty()) return; qCDebug(PLUGIN_PATCHREVIEW) << "seeking direction" << forwards; int currentIndex = allUrls.indexOf(current->url()); QUrl newUrl; if((forwards && current->url() == checkedUrls.back()) || (!forwards && current->url() == checkedUrls[0])) { newUrl = m_plugin->patch()->file(); qCDebug(PLUGIN_PATCHREVIEW) << "jumping to patch"; } else if(current->url() == m_plugin->patch()->file() || currentIndex == -1) { if(forwards) newUrl = checkedUrls[0]; else newUrl = checkedUrls.back(); qCDebug(PLUGIN_PATCHREVIEW) << "jumping from patch"; } else { QSet checkedUrlsSet( checkedUrls.toSet() ); for(int offset = 1; offset < allUrls.size(); ++offset) { int pos; if(forwards) { pos = (currentIndex + offset) % allUrls.size(); }else{ pos = currentIndex - offset; if(pos < 0) pos += allUrls.size(); } if(checkedUrlsSet.contains(allUrls[pos])) { newUrl = allUrls[pos]; break; } } } if(newUrl.isValid()) { activate( newUrl, forwards ? current : 0 ); }else{ qCDebug(PLUGIN_PATCHREVIEW) << "found no valid target url"; } } void PatchReviewToolView::activate( const QUrl& url, IDocument* buddy ) const { qCDebug(PLUGIN_PATCHREVIEW) << "activating url" << url; // If the document is already open in this area, just re-activate it if(KDevelop::IDocument* doc = ICore::self()->documentController()->documentForUrl(url)) { foreach(Sublime::View* view, ICore::self()->uiController()->activeArea()->views()) { if(view->document() == dynamic_cast(doc)) { ICore::self()->documentController()->activateDocument(doc); return; } } } // If the document is not open yet, open it in the correct order - IDocument* newDoc = ICore::self()->documentController()->openDocument(url, KTextEditor::Range(), IDocumentController::DefaultMode, "", buddy); + IDocument* newDoc = ICore::self()->documentController()->openDocument(url, KTextEditor::Range(), IDocumentController::DefaultMode, QString(), buddy); KTextEditor::View* view = 0; if(newDoc) view= newDoc->activeTextView(); if(view && view->cursorPosition().line() == 0) m_plugin->seekHunk( true, url ); } void PatchReviewToolView::fileItemChanged( QStandardItem* item ) { if (item->column()!=0) return; QUrl url = m_fileModel->statusInfo(item->index()).url(); if (url.isEmpty()) return; KDevelop::IDocument* doc = ICore::self()->documentController()->documentForUrl(url); if(m_fileModel->isCheckable() && item->checkState() != Qt::Checked) { // Eventually close the document if(doc && doc->state() == IDocument::Clean) { foreach(Sublime::View* view, ICore::self()->uiController()->activeArea()->views()) { if(view->document() == dynamic_cast(doc)) { ICore::self()->uiController()->activeArea()->closeView(view); return; } } } } else if (!doc) { ICore::self()->documentController()->openDocument(url, KTextEditor::Range::invalid(), IDocumentController::DoNotActivate); } } void PatchReviewToolView::nextFile() { seekFile(true); } void PatchReviewToolView::prevFile() { seekFile(false); } void PatchReviewToolView::deselectAll() { m_fileModel->setAllChecked(false); } void PatchReviewToolView::selectAll() { m_fileModel->setAllChecked(true); } void PatchReviewToolView::finishReview() { QList selectedUrls = m_fileModel->checkedUrls(); qCDebug(PLUGIN_PATCHREVIEW) << "finishing review with" << selectedUrls; m_plugin->finishReview( selectedUrls ); } void PatchReviewToolView::fileDoubleClicked( const QModelIndex& idx ) { QModelIndex i = idx.sibling(idx.row(), 0); QUrl file = m_fileModel->statusInfo( i ).url(); activate( file ); } void PatchReviewToolView::kompareModelChanged() { QList oldCheckedUrls = m_fileModel->checkedUrls(); m_fileModel->clear(); if ( !m_plugin->modelList() ) return; QMap additionalUrls = m_plugin->patch()->additionalSelectableFiles(); const Diff2::DiffModelList* models = m_plugin->modelList()->models(); if( models ) { Diff2::DiffModelList::const_iterator it = models->constBegin(); for(; it != models->constEnd(); ++it ) { Diff2::DifferenceList * diffs = ( *it )->differences(); int cnt = 0; if ( diffs ) cnt = diffs->count(); QUrl file = m_plugin->urlForFileModel( *it ); if( !QFileInfo( file.toLocalFile() ).isReadable() ) continue; VcsStatusInfo status; status.setUrl( file ); status.setState( cnt>0 ? VcsStatusInfo::ItemModified : VcsStatusInfo::ItemUpToDate ); m_fileModel->updateState( status, cnt ); } } for( QMap::const_iterator it = additionalUrls.constBegin(); it != additionalUrls.constEnd(); it++ ) { VcsStatusInfo status; status.setUrl( it.key() ); status.setState( it.value() ); m_fileModel->updateState( status ); } if(!m_resetCheckedUrls) m_fileModel->setCheckedUrls(oldCheckedUrls); else m_resetCheckedUrls = false; m_editPatch.filesList->resizeColumnToContents( 0 ); // Eventually select the active document documentActivated( ICore::self()->documentController()->activeDocument() ); } void PatchReviewToolView::documentActivated( IDocument* doc ) { if( !doc ) return; if ( !m_plugin->modelList() ) return; QModelIndex idx = m_fileModel->indexForUrl( doc->url() ); if ( idx.isValid() ) { m_editPatch.filesList->setCurrentIndex( idx ); } else { m_editPatch.filesList->setCurrentIndex( QModelIndex() ); } } void PatchReviewToolView::runTests() { IPatchSource::Ptr ipatch = m_plugin->patch(); if ( !ipatch ) { return; } IProject* project = 0; QMap files = ipatch->additionalSelectableFiles(); QMap::const_iterator it = files.constBegin(); for (; it != files.constEnd(); ++it) { project = ICore::self()->projectController()->findProjectForUrl(it.key()); if (project) { break; } } if (!project) { return; } m_editPatch.testProgressBar->setFormat(i18n("Running tests: %p%")); m_editPatch.testProgressBar->setValue(0); m_editPatch.testProgressBar->show(); ProjectTestJob* job = new ProjectTestJob(project, this); connect(job, &ProjectTestJob::finished, this, &PatchReviewToolView::testJobResult); connect(job, SIGNAL(percent(KJob*,ulong)), this, SLOT(testJobPercent(KJob*,ulong))); ICore::self()->runController()->registerJob(job); } void PatchReviewToolView::testJobPercent(KJob* job, ulong percent) { Q_UNUSED(job); m_editPatch.testProgressBar->setValue(percent); } void PatchReviewToolView::testJobResult(KJob* job) { ProjectTestJob* testJob = qobject_cast(job); if (!testJob) { return; } ProjectTestResult result = testJob->testResult(); QString format; if (result.passed > 0 && result.failed == 0 && result.error == 0) { format = i18np("Test passed", "All %1 tests passed", result.passed); } else { format = i18n("Test results: %1 passed, %2 failed, %3 errors", result.passed, result.failed, result.error); } m_editPatch.testProgressBar->setFormat(format); // Needed because some test jobs may raise their own output views ICore::self()->uiController()->raiseToolView(this); } + +#include "patchreviewtoolview.moc" diff --git a/plugins/problemreporter/problemhighlighter.cpp b/plugins/problemreporter/problemhighlighter.cpp index 65b3aee8b..9dd6ddabb 100644 --- a/plugins/problemreporter/problemhighlighter.cpp +++ b/plugins/problemreporter/problemhighlighter.cpp @@ -1,259 +1,259 @@ /* * KDevelop Problem Reporter * * Copyright 2008 Hamish Rodda * Copyright 2008-2009 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "problemhighlighter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "problemnavigationcontext.h" using namespace KTextEditor; using namespace KDevelop; namespace { QColor colorForSeverity(IProblem::Severity severity) { KColorScheme scheme(QPalette::Active); switch (severity) { case IProblem::Error: return scheme.foreground(KColorScheme::NegativeText).color(); case IProblem::Warning: return scheme.foreground(KColorScheme::NeutralText).color(); case IProblem::Hint: default: return scheme.foreground(KColorScheme::PositiveText).color(); } } } ProblemHighlighter::ProblemHighlighter(KTextEditor::Document* document) : m_document(document) { Q_ASSERT(m_document); foreach (KTextEditor::View* view, m_document->views()) viewCreated(document, view); connect(m_document.data(), &Document::viewCreated, this, &ProblemHighlighter::viewCreated); connect(ICore::self()->languageController()->completionSettings(), &ICompletionSettings::settingsChanged, this, &ProblemHighlighter::settingsChanged); connect(m_document.data(), &Document::aboutToReload, this, &ProblemHighlighter::clearProblems); if (qobject_cast(m_document)) { // can't use new signal/slot syntax here, MovingInterface is not a QObject connect(m_document, SIGNAL(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document*)), this, SLOT(clearProblems())); } connect(m_document, SIGNAL(aboutToRemoveText(KTextEditor::Range)), this, SLOT(aboutToRemoveText(KTextEditor::Range))); } void ProblemHighlighter::settingsChanged() { // Re-highlight setProblems(m_problems); } void ProblemHighlighter::viewCreated(Document*, View* view) { KTextEditor::TextHintInterface* iface = dynamic_cast(view); if (!iface) return; iface->registerTextHintProvider(new ProblemTextHintProvider(this)); } ProblemTextHintProvider::ProblemTextHintProvider(ProblemHighlighter* highlighter) : m_highlighter(highlighter) { } QString ProblemTextHintProvider::textHint(View* view, const Cursor& pos) { KTextEditor::MovingInterface* moving = dynamic_cast(view->document()); if (moving) { ///@todo Sort the ranges when writing them, and do binary search instead of linear foreach (MovingRange* range, m_highlighter->m_topHLRanges) { if (m_highlighter->m_problemsForRanges.contains(range) && range->contains(pos)) { // There is a problem which's range contains the cursor IProblem::Ptr problem = m_highlighter->m_problemsForRanges[range]; if (problem->source() == IProblem::ToDo) { continue; } if (m_currentHintRange == range->toRange()) { continue; } m_currentHintRange = range->toRange(); KDevelop::AbstractNavigationWidget* widget = new KDevelop::AbstractNavigationWidget; widget->setContext(NavigationContextPointer(new ProblemNavigationContext(problem))); KDevelop::NavigationToolTip* tooltip = new KDevelop::NavigationToolTip(view, QCursor::pos() + QPoint(20, 40), widget); tooltip->resize(widget->sizeHint() + QSize(10, 10)); tooltip->setHandleRect(getItemBoundingRect(view, m_currentHintRange)); tooltip->connect(tooltip, &ActiveToolTip::destroyed, [&] () { m_currentHintRange = {}; }); - ActiveToolTip::showToolTip(tooltip, 99, "problem-tooltip"); + ActiveToolTip::showToolTip(tooltip, 99, QStringLiteral("problem-tooltip")); return QString(); } } } return QString(); } ProblemHighlighter::~ProblemHighlighter() { if (m_topHLRanges.isEmpty() || !m_document) return; qDeleteAll(m_topHLRanges); } void ProblemHighlighter::setProblems(const QVector& problems) { if (!m_document) return; const bool hadProblems = !m_problems.isEmpty(); m_problems = problems; qDeleteAll(m_topHLRanges); m_topHLRanges.clear(); m_problemsForRanges.clear(); IndexedString url(m_document->url()); /// TODO: create a better MarkInterface that makes it possible to add the marks to the scrollbar /// but having no background. /// also make it nicer together with other plugins, this would currently fail with /// this method... const uint errorMarkType = KTextEditor::MarkInterface::Error; const uint warningMarkType = KTextEditor::MarkInterface::Warning; KTextEditor::MarkInterface* markIface = dynamic_cast(m_document.data()); if (markIface && hadProblems) { // clear previously added marks foreach (KTextEditor::Mark* mark, markIface->marks().values()) { if (mark->type == errorMarkType || mark->type == warningMarkType) { markIface->removeMark(mark->line, mark->type); } } } if (problems.isEmpty()) { return; } DUChainReadLocker lock; TopDUContext* top = DUChainUtils::standardContextForUrl(m_document->url()); KTextEditor::MovingInterface* iface = dynamic_cast(m_document.data()); Q_ASSERT(iface); - foreach (const IProblem::Ptr problem, problems) { + foreach (const IProblem::Ptr& problem, problems) { if (problem->finalLocation().document != url || !problem->finalLocation().isValid()) continue; KTextEditor::Range range; if (top) range = top->transformFromLocalRevision(RangeInRevision::castFromSimpleRange(problem->finalLocation())); else range = problem->finalLocation(); if (range.end().line() >= m_document->lines()) range.end() = KTextEditor::Cursor(m_document->endOfLine(m_document->lines() - 1)); if (range.isEmpty()) { range.setEnd(range.end() + KTextEditor::Cursor(0, 1)); } KTextEditor::MovingRange* problemRange = iface->newMovingRange(range); m_problemsForRanges.insert(problemRange, problem); m_topHLRanges.append(problemRange); if (problem->source() != IProblem::ToDo && (problem->severity() != IProblem::Hint || ICore::self()->languageController()->completionSettings()->highlightSemanticProblems())) { KTextEditor::Attribute::Ptr attribute(new KTextEditor::Attribute()); attribute->setUnderlineStyle(QTextCharFormat::WaveUnderline); attribute->setUnderlineColor(colorForSeverity(problem->severity())); problemRange->setAttribute(attribute); } if (markIface && ICore::self()->languageController()->completionSettings()->highlightProblematicLines()) { uint mark; if (problem->severity() == IProblem::Error) { mark = errorMarkType; } else if (problem->severity() == IProblem::Warning) { mark = warningMarkType; } else { continue; } markIface->addMark(problem->finalLocation().start().line(), mark); } } } void ProblemHighlighter::aboutToRemoveText(const KTextEditor::Range& range) { if (range.onSingleLine()) { // no need to optimize this return; } QList::iterator it = m_topHLRanges.begin(); while (it != m_topHLRanges.end()) { if (range.contains((*it)->toRange())) { m_problemsForRanges.remove(*it); delete (*it); it = m_topHLRanges.erase(it); } else { ++it; } } } void ProblemHighlighter::clearProblems() { setProblems({}); } diff --git a/plugins/problemreporter/problemnavigationcontext.cpp b/plugins/problemreporter/problemnavigationcontext.cpp index db38d4205..431c862a2 100644 --- a/plugins/problemreporter/problemnavigationcontext.cpp +++ b/plugins/problemreporter/problemnavigationcontext.cpp @@ -1,122 +1,122 @@ /* Copyright 2009 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "problemnavigationcontext.h" #include #include #include #include #include #include #include #include using namespace KDevelop; ProblemNavigationContext::ProblemNavigationContext(const IProblem::Ptr& problem) : m_problem(problem) { m_widget = 0; QExplicitlySharedDataPointer solution = problem->solutionAssistant(); if (solution && !solution->actions().isEmpty()) { m_widget = new QWidget; QHBoxLayout* layout = new QHBoxLayout(m_widget); RichTextPushButton* button = new RichTextPushButton; // button->setPopupMode(QToolButton::InstantPopup); if (!solution->title().isEmpty()) button->setHtml(i18n("Solve: %1", solution->title())); else button->setHtml(i18n("Solve")); QMenu* menu = new QMenu; menu->setFocusPolicy(Qt::NoFocus); foreach (IAssistantAction::Ptr action, solution->actions()) { menu->addAction(action->toKAction()); } button->setMenu(menu); layout->addWidget(button); layout->setAlignment(button, Qt::AlignLeft); m_widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); } } ProblemNavigationContext::~ProblemNavigationContext() { delete m_widget; } QWidget* ProblemNavigationContext::widget() const { return m_widget; } bool ProblemNavigationContext::isWidgetMaximized() const { return false; } QString ProblemNavigationContext::name() const { return i18n("Problem"); } QString ProblemNavigationContext::html(bool shorten) { clear(); m_shorten = shorten; - modifyHtml() += "

"; + modifyHtml() += QStringLiteral("

"); modifyHtml() += i18n("Problem in %1:
", m_problem->sourceString()); modifyHtml() += m_problem->description().toHtmlEscaped(); - modifyHtml() += "
"; + modifyHtml() += QStringLiteral("
"); modifyHtml() += "" + m_problem->explanation().toHtmlEscaped() + ""; const QVector& diagnostics = m_problem->diagnostics(); if (!diagnostics.isEmpty()) { - modifyHtml() += "
"; + modifyHtml() += QStringLiteral("
"); DUChainReadLocker lock; for (auto diagnostic : diagnostics) { const DocumentRange range = diagnostic->finalLocation(); Declaration* declaration = DUChainUtils::itemUnderCursor(range.document.toUrl(), range.start()); modifyHtml() += labelHighlight(QStringLiteral("%1: ").arg(diagnostic->severityString())); modifyHtml() += diagnostic->description(); if (declaration) { - modifyHtml() += "
"; + modifyHtml() += QStringLiteral("
"); makeLink(declaration->toString(), KDevelop::DeclarationPointer(declaration), NavigationAction::NavigateDeclaration); modifyHtml() += i18n(" in "); makeLink(QStringLiteral("%1 :%2") .arg(declaration->url().toUrl().fileName()) .arg(declaration->rangeInCurrentRevision().start().line() + 1), KDevelop::DeclarationPointer(declaration), NavigationAction::NavigateDeclaration); } - modifyHtml() += "
"; + modifyHtml() += QStringLiteral("
"); } } - modifyHtml() += "

"; + modifyHtml() += QStringLiteral("

"); return currentHtml(); } diff --git a/plugins/problemreporter/problemnavigationcontext.h b/plugins/problemreporter/problemnavigationcontext.h index 5c6a520ae..22add5dc6 100644 --- a/plugins/problemreporter/problemnavigationcontext.h +++ b/plugins/problemreporter/problemnavigationcontext.h @@ -1,47 +1,48 @@ /* 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_IPROBLEMNAVIGATIONCONTEXT_H #define KDEVPLATFORM_IPROBLEMNAVIGATIONCONTEXT_H #include #include #include namespace KDevelop { // Navigation context for IProblem class ProblemNavigationContext : public AbstractNavigationContext { + Q_OBJECT public: explicit ProblemNavigationContext(const IProblem::Ptr& problem); ~ProblemNavigationContext(); virtual QString name() const override; virtual QString html(bool shorten = false) override; virtual QWidget* widget() const override; virtual bool isWidgetMaximized() const override; private: QPointer m_widget; IProblem::Ptr m_problem; }; } #endif // KDEVPLATFORM_PROBLEMNAVIGATIONCONTEXT_H diff --git a/plugins/problemreporter/problemreporterplugin.cpp b/plugins/problemreporter/problemreporterplugin.cpp index c9ebbb302..f013b2717 100644 --- a/plugins/problemreporter/problemreporterplugin.cpp +++ b/plugins/problemreporter/problemreporterplugin.cpp @@ -1,206 +1,206 @@ /* * KDevelop Problem Reporter * * Copyright 2006 Adam Treat * Copyright 2006-2007 Hamish Rodda * Copyright 2007-2008 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "problemreporterplugin.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "problemhighlighter.h" #include "problemtreeview.h" #include "problemreportermodel.h" #include #include #include #include #include #include #include "shell/problemmodelset.h" #include "problemsview.h" #include Q_LOGGING_CATEGORY(PLUGIN_PROBLEMREPORTER, "kdevplatform.plugins.problemreporter") K_PLUGIN_FACTORY_WITH_JSON(KDevProblemReporterFactory, "kdevproblemreporter.json", registerPlugin();) using namespace KDevelop; class ProblemReporterFactory : public KDevelop::IToolViewFactory { public: QWidget* create(QWidget* parent = 0) override { Q_UNUSED(parent); ProblemsView* v = new ProblemsView(); v->load(); return v; } Qt::DockWidgetArea defaultPosition() override { return Qt::BottomDockWidgetArea; } - QString id() const override { return "org.kdevelop.ProblemReporterView"; } + QString id() const override { return QStringLiteral("org.kdevelop.ProblemReporterView"); } }; ProblemReporterPlugin::ProblemReporterPlugin(QObject* parent, const QVariantList&) - : KDevelop::IPlugin("kdevproblemreporter", parent) + : KDevelop::IPlugin(QStringLiteral("kdevproblemreporter"), parent) , m_factory(new ProblemReporterFactory) , m_model(new ProblemReporterModel(this)) { KDevelop::ProblemModelSet* pms = core()->languageController()->problemModelSet(); - pms->addModel("Parser", m_model); + pms->addModel(QStringLiteral("Parser"), m_model); core()->uiController()->addToolView(i18n("Problems"), m_factory); - setXMLFile("kdevproblemreporter.rc"); + setXMLFile(QStringLiteral("kdevproblemreporter.rc")); connect(ICore::self()->documentController(), &IDocumentController::documentClosed, this, &ProblemReporterPlugin::documentClosed); connect(ICore::self()->documentController(), &IDocumentController::textDocumentCreated, this, &ProblemReporterPlugin::textDocumentCreated); connect(ICore::self()->languageController()->backgroundParser(), &BackgroundParser::parseJobFinished, this, &ProblemReporterPlugin::parseJobFinished, Qt::DirectConnection); } ProblemReporterPlugin::~ProblemReporterPlugin() { qDeleteAll(m_highlighters); } ProblemReporterModel* ProblemReporterPlugin::model() const { return m_model; } void ProblemReporterPlugin::unload() { KDevelop::ProblemModelSet* pms = KDevelop::ICore::self()->languageController()->problemModelSet(); - pms->removeModel("Parser"); + pms->removeModel(QStringLiteral("Parser")); core()->uiController()->removeToolView(m_factory); } void ProblemReporterPlugin::documentClosed(IDocument* doc) { if (!doc->textDocument()) return; IndexedString url(doc->url()); delete m_highlighters.take(url); } void ProblemReporterPlugin::textDocumentCreated(KDevelop::IDocument* document) { Q_ASSERT(document->textDocument()); m_highlighters.insert(IndexedString(document->url()), new ProblemHighlighter(document->textDocument())); DUChainReadLocker lock(DUChain::lock()); DUChain::self()->updateContextForUrl(IndexedString(document->url()), KDevelop::TopDUContext::AllDeclarationsContextsAndUses, this); } void ProblemReporterPlugin::updateReady(const IndexedString& url, const KDevelop::ReferencedTopDUContext&) { m_model->problemsUpdated(url); ProblemHighlighter* ph = m_highlighters.value(url); if (ph) { QVector allProblems = m_model->problems(url, false); ph->setProblems(allProblems); } } void ProblemReporterPlugin::parseJobFinished(KDevelop::ParseJob* parseJob) { if (parseJob->duChain()) updateReady(parseJob->document()); } KDevelop::ContextMenuExtension ProblemReporterPlugin::contextMenuExtension(KDevelop::Context* context) { KDevelop::ContextMenuExtension extension; KDevelop::EditorContext* editorContext = dynamic_cast(context); if (editorContext) { DUChainReadLocker lock(DUChain::lock(), 1000); if (!lock.locked()) { qCDebug(PLUGIN_PROBLEMREPORTER) << "failed to lock duchain in time"; return extension; } QString title; QList actions; TopDUContext* top = DUChainUtils::standardContextForUrl(editorContext->url()); if (top) { foreach (KDevelop::ProblemPointer problem, top->problems()) { if (problem->range().contains( top->transformToLocalRevision(KTextEditor::Cursor(editorContext->position())))) { KDevelop::IAssistant::Ptr solution = problem->solutionAssistant(); if (solution) { title = solution->title(); foreach (KDevelop::IAssistantAction::Ptr action, solution->actions()) actions << action->toKAction(); } } } } if (!actions.isEmpty()) { QString text; if (title.isEmpty()) text = i18n("Solve Problem"); else { text = i18n("Solve: %1", KDevelop::htmlToPlainText(title)); } QAction* menuAction = new QAction(text, 0); QMenu* menu(new QMenu(text, 0)); menuAction->setMenu(menu); foreach (QAction* action, actions) menu->addAction(action); extension.addAction(ContextMenuExtension::ExtensionGroup, menuAction); } } return extension; } #include "problemreporterplugin.moc" // kate: space-indent on; indent-width 2; tab-width 4; replace-tabs on; auto-insert-doxygen on diff --git a/plugins/problemreporter/problemsview.cpp b/plugins/problemreporter/problemsview.cpp index 659ff6de0..f0b373dbc 100644 --- a/plugins/problemreporter/problemsview.cpp +++ b/plugins/problemreporter/problemsview.cpp @@ -1,239 +1,239 @@ /* * 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 "problemtreeview.h" #include "problemmodel.h" namespace KDevelop { /// TODO: Move to util? /// Note: Support for recursing into child indices would be nice class ItemViewWalker { public: 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) : QMainWindow(parent) { setWindowTitle(i18n("Problems")); - setWindowIcon(QIcon::fromTheme("script-error", windowIcon())); + setWindowIcon(QIcon::fromTheme(QStringLiteral("script-error"), windowIcon())); m_toolBar = new QToolBar(this); m_toolBar->setMovable(false); m_toolBar->setFloatable(false); addToolBar(m_toolBar); m_tabWidget = new QTabWidget(this); m_tabWidget->setTabPosition(QTabWidget::South); setCentralWidget(m_tabWidget); } 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) { updateToolBar(); return; } m_tabWidget->setCurrentIndex(0); } void ProblemsView::onModelAdded(const ModelData& data) { addModel(data); } /** * @brief Returns the name part of the label * * E.g.: Test (666) => Test */ QString nameFromLabel(const QString& label) { QString txt = label; int i = txt.lastIndexOf('('); if (i != -1) txt = txt.left(i - 1); /// ignore whitespace before '(' return txt; } void ProblemsView::onModelRemoved(const QString& name) { int c = m_tabWidget->count(); int idx = 0; for (idx = 0; idx < c; ++idx) { if (nameFromLabel(m_tabWidget->tabText(idx)) == name) break; } if (idx < c) { QWidget* w = m_tabWidget->widget(idx); m_tabWidget->removeTab(idx); delete w; } } void ProblemsView::onCurrentChanged(int idx) { m_toolBar->clear(); if (idx == -1) return; updateToolBar(); } 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& data) { ProblemTreeView* view = new ProblemTreeView(NULL, data.model); connect(view, &ProblemTreeView::changed, this, &ProblemsView::onViewChanged); int idx = m_tabWidget->addTab(view, data.name); int rows = view->model()->rowCount(); updateTab(idx, rows); } void ProblemsView::updateToolBar() { QWidget* w = m_tabWidget->currentWidget(); m_toolBar->addActions(w->actions()); } void ProblemsView::updateTab(int idx, int rows) { const QString name = nameFromLabel(m_tabWidget->tabText(idx)); 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(); } } } diff --git a/plugins/problemreporter/problemtreeview.cpp b/plugins/problemreporter/problemtreeview.cpp index 59f616638..f5df0b23c 100644 --- a/plugins/problemreporter/problemtreeview.cpp +++ b/plugins/problemreporter/problemtreeview.cpp @@ -1,388 +1,388 @@ /* * KDevelop Problem Reporter * * Copyright (c) 2006-2007 Hamish Rodda * 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 "problemtreeview.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "problemreporterplugin.h" #include #include #include //#include "modeltest.h" using namespace KDevelop; namespace KDevelop { class ProblemTreeViewItemDelegate : public QItemDelegate { Q_OBJECT public: explicit ProblemTreeViewItemDelegate(QObject* parent = nullptr); void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override; }; } ProblemTreeViewItemDelegate::ProblemTreeViewItemDelegate(QObject* parent) : QItemDelegate(parent) { } void ProblemTreeViewItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { QStyleOptionViewItem newOption(option); newOption.textElideMode = index.column() == ProblemModel::File ? Qt::ElideMiddle : Qt::ElideRight; QItemDelegate::paint(painter, newOption, index); } ProblemTreeView::ProblemTreeView(QWidget* parent, QAbstractItemModel* itemModel) : QTreeView(parent) , m_proxy(new QSortFilterProxyModel(this)) { - setObjectName("Problem Reporter Tree"); + setObjectName(QStringLiteral("Problem Reporter Tree")); setWhatsThis(i18n("Problems")); setItemDelegate(new ProblemTreeViewItemDelegate(this)); setSelectionBehavior(QAbstractItemView::SelectRows); m_proxy->setSortRole(ProblemModel::SeverityRole); m_proxy->setDynamicSortFilter(true); m_proxy->sort(0, Qt::AscendingOrder); ProblemModel* problemModel = dynamic_cast(itemModel); Q_ASSERT(problemModel); setModel(problemModel); header()->setStretchLastSection(false); if (problemModel->features().testFlag(ProblemModel::CanDoFullUpdate)) { QAction* fullUpdateAction = new QAction(this); fullUpdateAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); fullUpdateAction->setText(i18n("Force Full Update")); fullUpdateAction->setToolTip(i18nc("@info:tooltip", "Re-parse all watched documents")); - fullUpdateAction->setIcon(QIcon::fromTheme("view-refresh")); + fullUpdateAction->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); connect(fullUpdateAction, &QAction::triggered, model(), &ProblemModel::forceFullUpdate); addAction(fullUpdateAction); } if (problemModel->features().testFlag(ProblemModel::CanShowImports)) { QAction* showImportsAction = new QAction(this); addAction(showImportsAction); showImportsAction->setCheckable(true); showImportsAction->setChecked(false); showImportsAction->setText(i18n("Show Imports")); showImportsAction->setToolTip(i18nc("@info:tooltip", "Display problems in imported files")); this->model()->setShowImports(false); connect(showImportsAction, &QAction::triggered, model(), &ProblemModel::setShowImports); } if (problemModel->features().testFlag(ProblemModel::ScopeFilter)) { KActionMenu* scopeMenu = new KActionMenu(this); scopeMenu->setDelayed(false); scopeMenu->setToolTip(i18nc("@info:tooltip", "Which files to display the problems for")); scopeMenu->setObjectName(QStringLiteral("scopeMenu")); QActionGroup* scopeActions = new QActionGroup(this); QAction* currentDocumentAction = new QAction(this); currentDocumentAction->setText(i18n("Current Document")); 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(currentDocumentAction); actions.push_back(openDocumentsAction); actions.push_back(currentProjectAction); actions.push_back(allProjectAction); if (problemModel->features().testFlag(ProblemModel::CanByPassScopeFilter)) { QAction* showAllAction = new QAction(this); showAllAction->setText(i18n("Show All")); showAllAction->setToolTip(i18nc("@info:tooltip", "Display ALL problems")); actions.push_back(showAllAction); } foreach (QAction* action, actions) { action->setCheckable(true); scopeActions->addAction(action); scopeMenu->addAction(action); } addAction(scopeMenu); setScope(CurrentDocument); // 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); model()->setScope(BypassScopeFilter); } else { currentDocumentAction->setChecked(true); model()->setScope(CurrentDocument); } QSignalMapper* scopeMapper = new QSignalMapper(this); scopeMapper->setMapping(currentDocumentAction, CurrentDocument); scopeMapper->setMapping(openDocumentsAction, OpenDocuments); scopeMapper->setMapping(currentProjectAction, CurrentProject); scopeMapper->setMapping(allProjectAction, AllProjects); connect(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)); if (problemModel->features().testFlag(ProblemModel::CanByPassScopeFilter)) { scopeMapper->setMapping(actions.last(), BypassScopeFilter); connect(actions.last(), &QAction::triggered, scopeMapper, static_cast(&QSignalMapper::map)); } connect(scopeMapper, static_cast(&QSignalMapper::mapped), this, &ProblemTreeView::setScope); } if (problemModel->features().testFlag(ProblemModel::SeverityFilter)) { QActionGroup* severityActions = new QActionGroup(this); auto errorSeverityAction = new QAction(this); errorSeverityAction->setToolTip(i18nc("@info:tooltip", "Display only errors")); - errorSeverityAction->setIcon(QIcon::fromTheme("dialog-error")); + errorSeverityAction->setIcon(QIcon::fromTheme(QStringLiteral("dialog-error"))); auto warningSeverityAction = new QAction(this); warningSeverityAction->setToolTip(i18nc("@info:tooltip", "Display errors and warnings")); - warningSeverityAction->setIcon(QIcon::fromTheme("dialog-warning")); + warningSeverityAction->setIcon(QIcon::fromTheme(QStringLiteral("dialog-warning"))); auto hintSeverityAction = new QAction(this); hintSeverityAction->setToolTip(i18nc("@info:tooltip", "Display errors, warnings and hints")); - hintSeverityAction->setIcon(QIcon::fromTheme("dialog-information")); + hintSeverityAction->setIcon(QIcon::fromTheme(QStringLiteral("dialog-information"))); QAction* severityActionArray[] = { errorSeverityAction, warningSeverityAction, hintSeverityAction }; for (int i = 0; i < 3; ++i) { severityActionArray[i]->setCheckable(true); severityActions->addAction(severityActionArray[i]); addAction(severityActionArray[i]); } hintSeverityAction->setChecked(true); model()->setSeverity(IProblem::Hint); QSignalMapper* severityMapper = new QSignalMapper(this); severityMapper->setMapping(errorSeverityAction, IProblem::Error); severityMapper->setMapping(warningSeverityAction, IProblem::Warning); severityMapper->setMapping(hintSeverityAction, IProblem::Hint); connect(errorSeverityAction, &QAction::triggered, severityMapper, static_cast(&QSignalMapper::map)); connect(warningSeverityAction, &QAction::triggered, severityMapper, static_cast(&QSignalMapper::map)); connect(hintSeverityAction, &QAction::triggered, severityMapper, static_cast(&QSignalMapper::map)); connect(severityMapper, static_cast(&QSignalMapper::mapped), model(), &ProblemModel::setSeverity); } if (problemModel->features().testFlag(ProblemModel::Grouping)) { KActionMenu* groupingMenu = new KActionMenu(i18n("Grouping"), this); 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); groupingMenu->addAction(action); } addAction(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), model(), &ProblemModel::setGrouping); } connect(this, &ProblemTreeView::clicked, this, &ProblemTreeView::itemActivated); connect(model(), &QAbstractItemModel::rowsInserted, this, &ProblemTreeView::changed); connect(model(), &QAbstractItemModel::rowsRemoved, this, &ProblemTreeView::changed); connect(model(), &QAbstractItemModel::modelReset, this, &ProblemTreeView::changed); } ProblemTreeView::~ProblemTreeView() { } void ProblemTreeView::openDocumentForCurrentProblem() { itemActivated(currentIndex()); } void ProblemTreeView::itemActivated(const QModelIndex& index) { if (!index.isValid()) return; KTextEditor::Cursor start; QUrl url; { // TODO: is this really necessary? DUChainReadLocker lock(DUChain::lock()); const auto problem = index.data(ProblemModel::ProblemRole).value(); if (!problem) return; url = problem->finalLocation().document.toUrl(); start = problem->finalLocation().start(); } ICore::self()->documentController()->openDocument(url, start); } void ProblemTreeView::setScope(int scope) { foreach (auto action, actions()) { if (action->objectName() == QLatin1String("scopeMenu")) { action->setText(i18n("Scope: %1", action->menu()->actions().at(scope)->text())); } } model()->setScope(scope); } void ProblemTreeView::resizeColumns() { for (int i = 0; i < model()->columnCount(); ++i) resizeColumnToContents(i); } void ProblemTreeView::dataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector& roles) { QTreeView::dataChanged(topLeft, bottomRight, roles); resizeColumns(); } void ProblemTreeView::reset() { QTreeView::reset(); resizeColumns(); } ProblemModel* ProblemTreeView::model() const { return static_cast(m_proxy->sourceModel()); } void ProblemTreeView::setModel(QAbstractItemModel* model) { Q_ASSERT(qobject_cast(model)); m_proxy->setSourceModel(model); QTreeView::setModel(m_proxy); } void ProblemTreeView::contextMenuEvent(QContextMenuEvent* event) { QModelIndex index = indexAt(event->pos()); if (index.isValid()) { const auto problem = index.data(ProblemModel::ProblemRole).value(); if (!problem) { return; } QExplicitlySharedDataPointer solution = problem->solutionAssistant(); if (!solution) { return; } QList actions; foreach (KDevelop::IAssistantAction::Ptr action, solution->actions()) { actions << action->toKAction(); } if (!actions.isEmpty()) { QString title = solution->title(); title = KDevelop::htmlToPlainText(title); - title.replace("'", "\'"); + title.replace(QLatin1String("'"), QLatin1Char('\'')); QPointer m = new QMenu(this); m->addSection(title); m->addActions(actions); m->exec(event->globalPos()); delete m; } } } void ProblemTreeView::showEvent(QShowEvent* event) { Q_UNUSED(event) resizeColumns(); } #include "problemtreeview.moc" diff --git a/plugins/projectfilter/tests/test_projectfilter.cpp b/plugins/projectfilter/tests/test_projectfilter.cpp index 6da42221c..669830db8 100644 --- a/plugins/projectfilter/tests/test_projectfilter.cpp +++ b/plugins/projectfilter/tests/test_projectfilter.cpp @@ -1,396 +1,396 @@ /* * This file is part of KDevelop * Copyright 2013 Milian Wolff * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU 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_projectfilter.h" #include #include #include #include #include #include "../projectfilter.h" QTEST_GUILESS_MAIN(TestProjectFilter); using namespace KDevelop; typedef QSharedPointer TestFilter; Q_DECLARE_METATYPE(TestFilter) namespace { const bool Invalid = false; const bool Valid = true; const bool Folder = true; const bool File = false; struct MatchTest { QString path; bool isFolder; bool shouldMatch; }; void addTests(const QString& tag, const TestProject& project, const TestFilter& filter, MatchTest* tests, uint numTests) { for (uint i = 0; i < numTests; ++i) { const MatchTest& test = tests[i]; QTest::newRow(qstrdup(qPrintable(tag + ':' + test.path))) << filter << Path(project.path(), test.path) << test.isFolder << test.shouldMatch; if (test.isFolder) { // also test folder with trailing slash - should not make a difference QTest::newRow(qstrdup(qPrintable(tag + ':' + test.path + '/'))) << filter << Path(project.path(), test.path) << test.isFolder << test.shouldMatch; } } } ///FIXME: remove once we can use c++11 -#define ADD_TESTS(tag, project, filter, tests) addTests(tag, project, filter, tests, sizeof(tests) / sizeof(tests[0])) +#define ADD_TESTS(tag, project, filter, tests) addTests(QStringLiteral(tag), project, filter, tests, sizeof(tests) / sizeof(tests[0])) struct BenchData { BenchData(const Path &path = Path(), bool isFolder = false) : path(path) , isFolder(isFolder) {} Path path; bool isFolder; }; } Q_DECLARE_METATYPE(QVector) void TestProjectFilter::initTestCase() { AutoTestShell::init(); TestCore::initialize(Core::NoUi); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType >(); } void TestProjectFilter::cleanupTestCase() { TestCore::shutdown(); } void TestProjectFilter::match() { QFETCH(TestFilter, filter); QFETCH(KDevelop::Path, path); QFETCH(bool, isFolder); QFETCH(bool, expectedIsValid); QCOMPARE(filter->isValid(path, isFolder), expectedIsValid); } void TestProjectFilter::match_data() { QTest::addColumn("filter"); QTest::addColumn("path"); QTest::addColumn("isFolder"); QTest::addColumn("expectedIsValid"); { // test default filters const TestProject project; TestFilter filter(new ProjectFilter(&project, deserialize(defaultFilters()))); QTest::newRow("projectRoot") << filter << project.path() << Folder << Valid; QTest::newRow("project.kdev4") << filter << project.projectFile() << File << Invalid; MatchTest tests[] = { //{path, isFolder, isValid} - {".kdev4", Folder, Invalid}, - - {"folder", Folder, Valid}, - {"folder/folder", Folder, Valid}, - {"file", File, Valid}, - {"folder/file", File, Valid}, - {".file", File, Invalid}, - {".folder", Folder, Invalid}, - {"folder/.folder", Folder, Invalid}, - {"folder/.file", File, Invalid}, - - {".git", Folder, Invalid}, - {"_darcs", Folder, Invalid}, - {"_svn", Folder, Invalid}, - {".svn", Folder, Invalid}, - {"CVS", Folder, Invalid}, - {"SCCS", Folder, Invalid}, - {".hg", Folder, Invalid}, - {".bzr", Folder, Invalid}, - - {"foo.o", File, Invalid}, - {"foo.so", File, Invalid}, - {"foo.so.1", File, Invalid}, - {"foo.a", File, Invalid}, - {"moc_foo.cpp", File, Invalid}, - {"ui_foo.h", File, Invalid}, - {"qrc_foo.cpp", File, Invalid}, - {"foo.cpp~", File, Invalid}, - {".foo.cpp.kate-swp", File, Invalid}, - {".foo.cpp.swp", File, Invalid} + {QStringLiteral(".kdev4"), Folder, Invalid}, + + {QStringLiteral("folder"), Folder, Valid}, + {QStringLiteral("folder/folder"), Folder, Valid}, + {QStringLiteral("file"), File, Valid}, + {QStringLiteral("folder/file"), File, Valid}, + {QStringLiteral(".file"), File, Invalid}, + {QStringLiteral(".folder"), Folder, Invalid}, + {QStringLiteral("folder/.folder"), Folder, Invalid}, + {QStringLiteral("folder/.file"), File, Invalid}, + + {QStringLiteral(".git"), Folder, Invalid}, + {QStringLiteral("_darcs"), Folder, Invalid}, + {QStringLiteral("_svn"), Folder, Invalid}, + {QStringLiteral(".svn"), Folder, Invalid}, + {QStringLiteral("CVS"), Folder, Invalid}, + {QStringLiteral("SCCS"), Folder, Invalid}, + {QStringLiteral(".hg"), Folder, Invalid}, + {QStringLiteral(".bzr"), Folder, Invalid}, + + {QStringLiteral("foo.o"), File, Invalid}, + {QStringLiteral("foo.so"), File, Invalid}, + {QStringLiteral("foo.so.1"), File, Invalid}, + {QStringLiteral("foo.a"), File, Invalid}, + {QStringLiteral("moc_foo.cpp"), File, Invalid}, + {QStringLiteral("ui_foo.h"), File, Invalid}, + {QStringLiteral("qrc_foo.cpp"), File, Invalid}, + {QStringLiteral("foo.cpp~"), File, Invalid}, + {QStringLiteral(".foo.cpp.kate-swp"), File, Invalid}, + {QStringLiteral(".foo.cpp.swp"), File, Invalid} }; ADD_TESTS("default", project, filter, tests); } { // test exclude files, basename const TestProject project; const Filters filters = Filters() - << Filter(SerializedFilter("*.cpp", Filter::Files)); + << Filter(SerializedFilter(QStringLiteral("*.cpp"), Filter::Files)); TestFilter filter(new ProjectFilter(&project, filters)); QTest::newRow("projectRoot") << filter << project.path() << Folder << Valid; QTest::newRow("project.kdev4") << filter << project.projectFile() << File << Invalid; MatchTest tests[] = { //{path, isFolder, isValid} - {".kdev4", Folder, Invalid}, - - {"folder", Folder, Valid}, - {"file", File, Valid}, - {"file.cpp", File, Invalid}, - {"folder.cpp", Folder, Valid}, - {"folder/file.cpp", File, Invalid}, - {"folder/folder.cpp", Folder, Valid} + {QStringLiteral(".kdev4"), Folder, Invalid}, + + {QStringLiteral("folder"), Folder, Valid}, + {QStringLiteral("file"), File, Valid}, + {QStringLiteral("file.cpp"), File, Invalid}, + {QStringLiteral("folder.cpp"), Folder, Valid}, + {QStringLiteral("folder/file.cpp"), File, Invalid}, + {QStringLiteral("folder/folder.cpp"), Folder, Valid} }; ADD_TESTS("exclude:*.cpp", project, filter, tests); } { // test excludes on folders const TestProject project; const Filters filters = Filters() - << Filter(SerializedFilter("foo", Filter::Folders)); + << Filter(SerializedFilter(QStringLiteral("foo"), Filter::Folders)); TestFilter filter(new ProjectFilter(&project, filters)); QTest::newRow("projectRoot") << filter << project.path() << Folder << Valid; QTest::newRow("project.kdev4") << filter << project.projectFile() << File << Invalid; MatchTest tests[] = { //{path, isFolder, isValid} - {".kdev4", Folder, Invalid}, - - {"folder", Folder, Valid}, - {"file", File, Valid}, - {"foo", Folder, Invalid}, - {"folder/file", File, Valid}, - {"folder/foo", Folder, Invalid}, - {"folder/foo", File, Valid} + {QStringLiteral(".kdev4"), Folder, Invalid}, + + {QStringLiteral("folder"), Folder, Valid}, + {QStringLiteral("file"), File, Valid}, + {QStringLiteral("foo"), Folder, Invalid}, + {QStringLiteral("folder/file"), File, Valid}, + {QStringLiteral("folder/foo"), Folder, Invalid}, + {QStringLiteral("folder/foo"), File, Valid} }; ADD_TESTS("exclude:foo", project, filter, tests); } { // test includes const TestProject project; const Filters filters = Filters() - << Filter(SerializedFilter("*", Filter::Files)) - << Filter(SerializedFilter("*.cpp", Filter::Files, Filter::Inclusive)); + << Filter(SerializedFilter(QStringLiteral("*"), Filter::Files)) + << Filter(SerializedFilter(QStringLiteral("*.cpp"), Filter::Files, Filter::Inclusive)); TestFilter filter(new ProjectFilter(&project, filters)); QTest::newRow("projectRoot") << filter << project.path() << Folder << Valid; QTest::newRow("project.kdev4") << filter << project.projectFile() << File << Invalid; MatchTest tests[] = { //{path, isFolder, isValid} - {".kdev4", Folder, Invalid}, - - {"folder", Folder, Valid}, - {"file", File, Invalid}, - {"file.cpp", File, Valid}, - {".file.cpp", File, Valid}, - {"folder/file.cpp", File, Valid}, - {"folder/.file.cpp", File, Valid} + {QStringLiteral(".kdev4"), Folder, Invalid}, + + {QStringLiteral("folder"), Folder, Valid}, + {QStringLiteral("file"), File, Invalid}, + {QStringLiteral("file.cpp"), File, Valid}, + {QStringLiteral(".file.cpp"), File, Valid}, + {QStringLiteral("folder/file.cpp"), File, Valid}, + {QStringLiteral("folder/.file.cpp"), File, Valid} }; ADD_TESTS("include:*.cpp", project, filter, tests); project.projectConfiguration(); } { // test mixed stuff const TestProject project; const Filters filters = Filters() - << Filter(SerializedFilter("*", Filter::Files, Filter::Exclusive)) - << Filter(SerializedFilter("*.inc", Filter::Files, Filter::Inclusive)) - << Filter(SerializedFilter("*ex.inc", Filter::Files, Filter::Exclusive)) - << Filter(SerializedFilter("bar", Filter::Folders, Filter::Exclusive)); + << Filter(SerializedFilter(QStringLiteral("*"), Filter::Files, Filter::Exclusive)) + << Filter(SerializedFilter(QStringLiteral("*.inc"), Filter::Files, Filter::Inclusive)) + << Filter(SerializedFilter(QStringLiteral("*ex.inc"), Filter::Files, Filter::Exclusive)) + << Filter(SerializedFilter(QStringLiteral("bar"), Filter::Folders, Filter::Exclusive)); TestFilter filter(new ProjectFilter(&project, filters)); QTest::newRow("projectRoot") << filter << project.path() << Folder << Valid; QTest::newRow("project.kdev4") << filter << project.projectFile() << File << Invalid; MatchTest tests[] = { //{path, isFolder, isValid} - {".kdev4", Folder, Invalid}, - - {"folder", Folder, Valid}, - {"file", File, Invalid}, - {"file.inc", File, Valid}, - {"file.ex.inc", File, Invalid}, - {"folder/file", File, Invalid}, - {"folder/file.inc", File, Valid}, - {"folder/file.ex.inc", File, Invalid}, - {"bar", Folder, Invalid}, + {QStringLiteral(".kdev4"), Folder, Invalid}, + + {QStringLiteral("folder"), Folder, Valid}, + {QStringLiteral("file"), File, Invalid}, + {QStringLiteral("file.inc"), File, Valid}, + {QStringLiteral("file.ex.inc"), File, Invalid}, + {QStringLiteral("folder/file"), File, Invalid}, + {QStringLiteral("folder/file.inc"), File, Valid}, + {QStringLiteral("folder/file.ex.inc"), File, Invalid}, + {QStringLiteral("bar"), Folder, Invalid}, }; ADD_TESTS("mixed", project, filter, tests); } { // relative path const TestProject project; const Filters filters = Filters() - << Filter(SerializedFilter("/foo/*bar", Filter::Targets(Filter::Files | Filter::Folders))); + << Filter(SerializedFilter(QStringLiteral("/foo/*bar"), Filter::Targets(Filter::Files | Filter::Folders))); TestFilter filter(new ProjectFilter(&project, filters)); QTest::newRow("projectRoot") << filter << project.path() << Folder << Valid; QTest::newRow("project.kdev4") << filter << project.projectFile() << File << Invalid; MatchTest tests[] = { //{path, isFolder, isValid} - {".kdev4", Folder, Invalid}, - - {"foo", Folder, Valid}, - {"bar", File, Valid}, - {"foo/bar", Folder, Invalid}, - {"foo/bar", File, Invalid}, - {"foo/asdf/bar", Folder, Invalid}, - {"foo/asdf/bar", File, Invalid}, - {"foo/asdf_bar", Folder, Invalid}, - {"foo/asdf_bar", File, Invalid}, - {"asdf/bar", File, Valid}, - {"asdf/foo/bar", File, Valid}, + {QStringLiteral(".kdev4"), Folder, Invalid}, + + {QStringLiteral("foo"), Folder, Valid}, + {QStringLiteral("bar"), File, Valid}, + {QStringLiteral("foo/bar"), Folder, Invalid}, + {QStringLiteral("foo/bar"), File, Invalid}, + {QStringLiteral("foo/asdf/bar"), Folder, Invalid}, + {QStringLiteral("foo/asdf/bar"), File, Invalid}, + {QStringLiteral("foo/asdf_bar"), Folder, Invalid}, + {QStringLiteral("foo/asdf_bar"), File, Invalid}, + {QStringLiteral("asdf/bar"), File, Valid}, + {QStringLiteral("asdf/foo/bar"), File, Valid}, }; ADD_TESTS("relative", project, filter, tests); } { // trailing slash const TestProject project; const Filters filters = Filters() - << Filter(SerializedFilter("bar/", Filter::Targets(Filter::Files | Filter::Folders))); + << Filter(SerializedFilter(QStringLiteral("bar/"), Filter::Targets(Filter::Files | Filter::Folders))); TestFilter filter(new ProjectFilter(&project, filters)); QTest::newRow("projectRoot") << filter << project.path() << Folder << Valid; QTest::newRow("project.kdev4") << filter << project.projectFile() << File << Invalid; MatchTest tests[] = { //{path, isFolder, isValid} - {".kdev4", Folder, Invalid}, + {QStringLiteral(".kdev4"), Folder, Invalid}, - {"foo", Folder, Valid}, - {"bar", File, Valid}, - {"bar", Folder, Invalid}, - {"foo/bar", File, Valid}, - {"foo/bar", Folder, Invalid} + {QStringLiteral("foo"), Folder, Valid}, + {QStringLiteral("bar"), File, Valid}, + {QStringLiteral("bar"), Folder, Invalid}, + {QStringLiteral("foo/bar"), File, Valid}, + {QStringLiteral("foo/bar"), Folder, Invalid} }; ADD_TESTS("trailingslash", project, filter, tests); } { // escaping const TestProject project; const Filters filters = Filters() - << Filter(SerializedFilter("foo\\*bar", Filter::Files)); + << Filter(SerializedFilter(QStringLiteral("foo\\*bar"), Filter::Files)); TestFilter filter(new ProjectFilter(&project, filters)); QTest::newRow("projectRoot") << filter << project.path() << Folder << Valid; QTest::newRow("project.kdev4") << filter << project.projectFile() << File << Invalid; MatchTest tests[] = { //{path, isFolder, isValid} - {".kdev4", Folder, Invalid}, + {QStringLiteral(".kdev4"), Folder, Invalid}, - {"foobar", Folder, Valid}, - {"fooasdfbar", File, Valid}, - {"foo*bar", File, Invalid}, - {"foo/bar", Folder, Valid} + {QStringLiteral("foobar"), Folder, Valid}, + {QStringLiteral("fooasdfbar"), File, Valid}, + {QStringLiteral("foo*bar"), File, Invalid}, + {QStringLiteral("foo/bar"), Folder, Valid} }; ADD_TESTS("escaping", project, filter, tests); } } static QVector createBenchData(const Path& base, int folderDepth, int foldersPerFolder, int filesPerFolder) { QVector data; data << BenchData(base, true); for(int i = 0; i < filesPerFolder; ++i) { if (i % 2) { data << BenchData(Path(base, QStringLiteral("file%1.cpp").arg(i)), false); } else { data << BenchData(Path(base, QStringLiteral("file%1.h").arg(i)), true); } } for(int i = 0; i < foldersPerFolder && folderDepth > 0; ++i) { data += createBenchData(Path(base, QStringLiteral("folder%1").arg(i)), folderDepth - 1, foldersPerFolder, filesPerFolder); } return data; } void TestProjectFilter::bench() { QFETCH(TestFilter, filter); QFETCH(QVector, data); QBENCHMARK { foreach(const BenchData& bench, data) { filter->isValid(bench.path, bench.isFolder); } } } void TestProjectFilter::bench_data() { QTest::addColumn("filter"); QTest::addColumn >("data"); const TestProject project; QVector > dataSets = QVector >() << createBenchData(project.path(), 3, 5, 10) << createBenchData(project.path(), 3, 5, 20) << createBenchData(project.path(), 4, 5, 10) << createBenchData(project.path(), 3, 10, 10); { TestFilter filter(new ProjectFilter(&project, Filters())); foreach(const QVector& data, dataSets) { QTest::newRow(qstrdup(QByteArray("baseline-") + QByteArray::number(data.size()))) << filter << data; } } { TestFilter filter(new ProjectFilter(&project, deserialize(defaultFilters()))); foreach(const QVector& data, dataSets) { QTest::newRow(qstrdup(QByteArray("defaults-") + QByteArray::number(data.size()))) << filter << data; } } } diff --git a/plugins/projectmanagerview/projectbuildsetwidget.cpp b/plugins/projectmanagerview/projectbuildsetwidget.cpp index 7108a92f2..a97213261 100644 --- a/plugins/projectmanagerview/projectbuildsetwidget.cpp +++ b/plugins/projectmanagerview/projectbuildsetwidget.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 "projectbuildsetwidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "projectmanagerviewplugin.h" #include "projectmanagerview.h" #include "ui_projectbuildsetwidget.h" #include "debug.h" #include #include #include ProjectBuildSetWidget::ProjectBuildSetWidget( QWidget* parent ) : QWidget( parent ), m_view( 0 ), m_ui( new Ui::ProjectBuildSetWidget ) { m_ui->setupUi( this ); - m_ui->addItemButton->setIcon( QIcon::fromTheme( "list-add" ) ); + m_ui->addItemButton->setIcon( QIcon::fromTheme( QStringLiteral("list-add") ) ); connect( m_ui->addItemButton, &QToolButton::clicked, this, &ProjectBuildSetWidget::addItems ); - m_ui->removeItemButton->setIcon( QIcon::fromTheme( "list-remove" ) ); + m_ui->removeItemButton->setIcon( QIcon::fromTheme( QStringLiteral("list-remove") ) ); connect( m_ui->removeItemButton, &QToolButton::clicked, this, &ProjectBuildSetWidget::removeItems ); - m_ui->upButton->setIcon( QIcon::fromTheme( "go-up" ) ); + m_ui->upButton->setIcon( QIcon::fromTheme( QStringLiteral("go-up") ) ); connect( m_ui->upButton, &QToolButton::clicked, this, &ProjectBuildSetWidget::moveUp ); - m_ui->downButton->setIcon( QIcon::fromTheme( "go-down" ) ); + m_ui->downButton->setIcon( QIcon::fromTheme( QStringLiteral("go-down") ) ); connect( m_ui->downButton, &QToolButton::clicked, this, &ProjectBuildSetWidget::moveDown ); - m_ui->topButton->setIcon( QIcon::fromTheme( "go-top" ) ); + m_ui->topButton->setIcon( QIcon::fromTheme( QStringLiteral("go-top") ) ); connect( m_ui->topButton, &QToolButton::clicked, this, &ProjectBuildSetWidget::moveToTop ); - m_ui->bottomButton->setIcon( QIcon::fromTheme( "go-bottom" ) ); + m_ui->bottomButton->setIcon( QIcon::fromTheme( QStringLiteral("go-bottom") ) ); connect( m_ui->bottomButton, &QToolButton::clicked, this, &ProjectBuildSetWidget::moveToBottom ); m_ui->itemView->setContextMenuPolicy( Qt::CustomContextMenu ); connect( m_ui->itemView, &QTreeView::customContextMenuRequested, this, &ProjectBuildSetWidget::showContextMenu ); layout()->setMargin(0); } void ProjectBuildSetWidget::setProjectView( ProjectManagerView* view ) { m_view = view; m_ui->itemView->setModel( KDevelop::ICore::self()->projectController()->buildSetModel() ); connect( m_ui->itemView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &ProjectBuildSetWidget::selectionChanged ); } void ProjectBuildSetWidget::selectionChanged() { QModelIndexList selectedRows = m_ui->itemView->selectionModel()->selectedRows(); qCDebug(PLUGIN_PROJECTMANAGERVIEW) << "checking selectionmodel:" << selectedRows; m_ui->removeItemButton->setEnabled( !selectedRows.isEmpty() ); m_ui->addItemButton->setEnabled( !m_view->selectedItems().isEmpty() ); bool enableUp = selectedRows.count() > 0 && selectedRows.first().row() != 0; bool enableDown = selectedRows.count() > 0 && selectedRows.last().row() != m_ui->itemView->model()->rowCount() - 1; m_ui->upButton->setEnabled( enableUp ); m_ui->downButton->setEnabled( enableDown ); m_ui->bottomButton->setEnabled( enableDown ); m_ui->topButton->setEnabled( enableUp ); } ProjectBuildSetWidget::~ProjectBuildSetWidget() { delete m_ui; } //TODO test whether this could be replaced by projecttreeview.cpp::popupContextMenu_appendActions void showContextMenu_appendActions(QMenu& menu, const QList& actions) { menu.addSeparator(); foreach( QAction* act, actions ) { menu.addAction(act); } } void ProjectBuildSetWidget::showContextMenu( const QPoint& p ) { if( m_ui->itemView->selectionModel()->selectedRows().isEmpty() ) return; QList itemlist; if(m_ui->itemView->selectionModel()->selectedRows().count() == 1) { KDevelop::ProjectBuildSetModel* buildSet = KDevelop::ICore::self()->projectController()->buildSetModel(); int row = m_ui->itemView->selectionModel()->selectedRows()[0].row(); if(row < buildSet->items().size()) { - KDevelop::ProjectBaseItem* item = buildSet->items()[row].findItem(); + KDevelop::ProjectBaseItem* item = buildSet->items().at(row).findItem(); if(item) itemlist << item; } } QMenu m; m.setTitle( i18n("Build Set") ); - m.addAction( QIcon::fromTheme("list-remove"), i18n( "Remove From Build Set" ), this, SLOT(removeItems()) ); + m.addAction( QIcon::fromTheme(QStringLiteral("list-remove")), i18n( "Remove From Build Set" ), this, SLOT(removeItems()) ); if( !itemlist.isEmpty() ) { KDevelop::ProjectItemContextImpl context(itemlist); QList extensions = KDevelop::ICore::self()->pluginController()->queryPluginsForContextMenuExtensions( &context ); QList buildActions; QList vcsActions; QList extActions; QList projectActions; QList fileActions; QList runActions; foreach( const KDevelop::ContextMenuExtension& ext, extensions ) { buildActions += ext.actions(KDevelop::ContextMenuExtension::BuildGroup); fileActions += ext.actions(KDevelop::ContextMenuExtension::FileGroup); projectActions += ext.actions(KDevelop::ContextMenuExtension::ProjectGroup); vcsActions += ext.actions(KDevelop::ContextMenuExtension::VcsGroup); extActions += ext.actions(KDevelop::ContextMenuExtension::ExtensionGroup); runActions += ext.actions(KDevelop::ContextMenuExtension::RunGroup); } showContextMenu_appendActions(m, buildActions); showContextMenu_appendActions(m, runActions); showContextMenu_appendActions(m, fileActions); showContextMenu_appendActions(m, vcsActions); showContextMenu_appendActions(m, extActions); showContextMenu_appendActions(m, projectActions); } m.exec( m_ui->itemView->viewport()->mapToGlobal( p ) ); } void ProjectBuildSetWidget::addItems() { foreach( KDevelop::ProjectBaseItem* item, m_view->selectedItems() ) { KDevelop::ICore::self()->projectController()->buildSetModel()->addProjectItem( item ); } } void ProjectBuildSetWidget::removeItems() { // We only support contigous selection, so we only need to remove the first range QItemSelectionRange range = m_ui->itemView->selectionModel()->selection().first(); int top = range.top(); qCDebug(PLUGIN_PROJECTMANAGERVIEW) << "removing:" << range.top() << range.height(); KDevelop::ProjectBuildSetModel* buildSet = KDevelop::ICore::self()->projectController()->buildSetModel(); buildSet->removeRows( range.top(), range.height() ); top = qMin( top, buildSet->rowCount() - 1 ); QModelIndex sidx = buildSet->index( top, 0 ); QModelIndex eidx = buildSet->index( top, buildSet->columnCount() - 1 ); m_ui->itemView->selectionModel()->select( QItemSelection( sidx, eidx ), QItemSelectionModel::ClearAndSelect ); m_ui->itemView->selectionModel()->setCurrentIndex( sidx, QItemSelectionModel::Current ); } void ProjectBuildSetWidget::moveDown() { // We only support contigous selection, so we only need to remove the first range QItemSelectionRange range = m_ui->itemView->selectionModel()->selection().first(); int top = range.top(), height = range.height(); KDevelop::ProjectBuildSetModel* buildSet = KDevelop::ICore::self()->projectController()->buildSetModel(); buildSet->moveRowsDown( top, height ); int columnCount = buildSet->columnCount(); QItemSelection newrange( buildSet->index( top + 1, 0 ), buildSet->index( top + height, columnCount - 1 ) ); m_ui->itemView->selectionModel()->select( newrange, QItemSelectionModel::ClearAndSelect ); m_ui->itemView->selectionModel()->setCurrentIndex( newrange.first().topLeft(), QItemSelectionModel::Current ); } void ProjectBuildSetWidget::moveToBottom() { // We only support contigous selection, so we only need to remove the first range QItemSelectionRange range = m_ui->itemView->selectionModel()->selection().first(); int top = range.top(), height = range.height(); KDevelop::ProjectBuildSetModel* buildSet = KDevelop::ICore::self()->projectController()->buildSetModel(); buildSet->moveRowsToBottom( top, height ); int rowCount = buildSet->rowCount(); int columnCount = buildSet->columnCount(); QItemSelection newrange( buildSet->index( rowCount - height, 0 ), buildSet->index( rowCount - 1, columnCount - 1 ) ); m_ui->itemView->selectionModel()->select( newrange, QItemSelectionModel::ClearAndSelect ); m_ui->itemView->selectionModel()->setCurrentIndex( newrange.first().topLeft(), QItemSelectionModel::Current ); } void ProjectBuildSetWidget::moveUp() { // We only support contigous selection, so we only need to remove the first range QItemSelectionRange range = m_ui->itemView->selectionModel()->selection().first(); int top = range.top(), height = range.height(); KDevelop::ProjectBuildSetModel* buildSet = KDevelop::ICore::self()->projectController()->buildSetModel(); buildSet->moveRowsUp( top, height ); int columnCount = buildSet->columnCount(); QItemSelection newrange( buildSet->index( top - 1, 0 ), buildSet->index( top - 2 + height, columnCount - 1 ) ); m_ui->itemView->selectionModel()->select( newrange, QItemSelectionModel::ClearAndSelect ); m_ui->itemView->selectionModel()->setCurrentIndex( newrange.first().topLeft(), QItemSelectionModel::Current ); } void ProjectBuildSetWidget::moveToTop() { // We only support contigous selection, so we only need to remove the first range QItemSelectionRange range = m_ui->itemView->selectionModel()->selection().first(); int top = range.top(), height = range.height(); KDevelop::ProjectBuildSetModel* buildSet = KDevelop::ICore::self()->projectController()->buildSetModel(); buildSet->moveRowsToTop( top, height ); int columnCount = buildSet->columnCount(); QItemSelection newrange( buildSet->index( 0, 0 ), buildSet->index( height - 1, columnCount - 1 ) ); m_ui->itemView->selectionModel()->select( newrange, QItemSelectionModel::ClearAndSelect ); m_ui->itemView->selectionModel()->setCurrentIndex( newrange.first().topLeft(), QItemSelectionModel::Current ); } diff --git a/plugins/projectmanagerview/projectmanagerview.cpp b/plugins/projectmanagerview/projectmanagerview.cpp index 0a8f701c4..f08fe2399 100644 --- a/plugins/projectmanagerview/projectmanagerview.cpp +++ b/plugins/projectmanagerview/projectmanagerview.cpp @@ -1,283 +1,283 @@ /* This file is part of KDevelop Copyright 2005 Roberto Raggi Copyright 2007 Andreas Pakulat Copyright 2008 Aleix Pol This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "projectmanagerview.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../openwith/iopenwith.h" #include #include #include "projectmanagerviewplugin.h" #include "vcsoverlayproxymodel.h" #include "ui_projectmanagerview.h" #include "debug.h" using namespace KDevelop; ProjectManagerViewItemContext::ProjectManagerViewItemContext(const QList< ProjectBaseItem* >& items, ProjectManagerView* view) : ProjectItemContextImpl(items), m_view(view) { } ProjectManagerView *ProjectManagerViewItemContext::view() const { return m_view; } static const char sessionConfigGroup[] = "ProjectManagerView"; static const char splitterStateConfigKey[] = "splitterState"; static const char targetsVisibleConfigKey[] = "targetsVisible"; static const int projectTreeViewStrechFactor = 75; // % static const int projectBuildSetStrechFactor = 25; // % ProjectManagerView::ProjectManagerView( ProjectManagerViewPlugin* plugin, QWidget *parent ) : QWidget( parent ), m_ui(new Ui::ProjectManagerView), m_plugin(plugin) { m_ui->setupUi( this ); m_ui->projectTreeView->installEventFilter(this); - setWindowIcon( QIcon::fromTheme( "project-development", windowIcon() ) ); + setWindowIcon( QIcon::fromTheme( QStringLiteral("project-development"), windowIcon() ) ); KConfigGroup pmviewConfig(ICore::self()->activeSession()->config(), sessionConfigGroup); if (pmviewConfig.hasKey(splitterStateConfigKey)) { QByteArray geometry = pmviewConfig.readEntry(splitterStateConfigKey, QByteArray()); m_ui->splitter->restoreState(geometry); } else { m_ui->splitter->setStretchFactor(0, projectTreeViewStrechFactor); m_ui->splitter->setStretchFactor(1, projectBuildSetStrechFactor); } - m_syncAction = plugin->actionCollection()->action("locate_document"); + m_syncAction = plugin->actionCollection()->action(QStringLiteral("locate_document")); Q_ASSERT(m_syncAction); m_syncAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); m_syncAction->setText(i18n("Locate Current Document")); m_syncAction->setToolTip(i18n("Locates the current document in the project tree and selects it.")); - m_syncAction->setIcon(QIcon::fromTheme("dirsync")); + m_syncAction->setIcon(QIcon::fromTheme(QStringLiteral("dirsync"))); m_syncAction->setShortcut(Qt::ControlModifier + Qt::Key_Less); connect(m_syncAction, &QAction::triggered, this, &ProjectManagerView::locateCurrentDocument); addAction(m_syncAction); updateSyncAction(); m_toggleTargetsAction = new QAction(i18n("Show Targets"), this); m_toggleTargetsAction->setCheckable(true); m_toggleTargetsAction->setChecked(pmviewConfig.readEntry(targetsVisibleConfigKey, true)); - m_toggleTargetsAction->setIcon(QIcon::fromTheme("system-run")); + m_toggleTargetsAction->setIcon(QIcon::fromTheme(QStringLiteral("system-run"))); connect(m_toggleTargetsAction, &QAction::triggered, this, &ProjectManagerView::toggleHideTargets); addAction(m_toggleTargetsAction); - addAction(plugin->actionCollection()->action("project_build")); - addAction(plugin->actionCollection()->action("project_install")); - addAction(plugin->actionCollection()->action("project_clean")); + addAction(plugin->actionCollection()->action(QStringLiteral("project_build"))); + addAction(plugin->actionCollection()->action(QStringLiteral("project_install"))); + addAction(plugin->actionCollection()->action(QStringLiteral("project_clean"))); connect(m_ui->projectTreeView, &ProjectTreeView::activate, this, &ProjectManagerView::open); m_ui->buildSetView->setProjectView( this ); m_modelFilter = new ProjectProxyModel( this ); m_modelFilter->showTargets(m_toggleTargetsAction->isChecked()); m_modelFilter->setSourceModel(ICore::self()->projectController()->projectModel()); m_overlayProxy = new VcsOverlayProxyModel( this ); m_overlayProxy->setSourceModel(m_modelFilter); m_ui->projectTreeView->setModel( m_overlayProxy ); connect( m_ui->projectTreeView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &ProjectManagerView::selectionChanged ); connect( KDevelop::ICore::self()->documentController(), &IDocumentController::documentClosed, this, &ProjectManagerView::updateSyncAction); connect( KDevelop::ICore::self()->documentController(), &IDocumentController::documentActivated, this, &ProjectManagerView::updateSyncAction); connect( qobject_cast(KDevelop::ICore::self()->uiController()->activeMainWindow()), &Sublime::MainWindow::areaChanged, this, &ProjectManagerView::updateSyncAction); selectionChanged(); //Update the "sync" button after the initialization has completed, to see whether there already is some open documents QMetaObject::invokeMethod(this, "updateSyncAction", Qt::QueuedConnection); // Need to set this to get horizontal scrollbar. Also needs to be done after // the setModel call m_ui->projectTreeView->header()->setSectionResizeMode( QHeaderView::ResizeToContents ); } bool ProjectManagerView::eventFilter(QObject* obj, QEvent* event) { if (obj == m_ui->projectTreeView) { if (event->type() == QEvent::KeyRelease) { QKeyEvent* keyEvent = static_cast(event); if (keyEvent->key() == Qt::Key_Delete && keyEvent->modifiers() == Qt::NoModifier) { m_plugin->removeItems(selectedItems()); return true; } else if (keyEvent->key() == Qt::Key_F2 && keyEvent->modifiers() == Qt::NoModifier) { m_plugin->renameItems(selectedItems()); return true; } else if (keyEvent->key() == Qt::Key_C && keyEvent->modifiers() == Qt::ControlModifier) { m_plugin->copyFromContextMenu(); return true; } else if (keyEvent->key() == Qt::Key_V && keyEvent->modifiers() == Qt::ControlModifier) { m_plugin->pasteFromContextMenu(); return true; } } } return QObject::eventFilter(obj, event); } void ProjectManagerView::selectionChanged() { m_ui->buildSetView->selectionChanged(); QList selected; foreach( const QModelIndex& idx, m_ui->projectTreeView->selectionModel()->selectedRows() ) { selected << ICore::self()->projectController()->projectModel()->itemFromIndex(indexFromView( idx )); } selected.removeAll(0); KDevelop::ICore::self()->selectionController()->updateSelection( new ProjectManagerViewItemContext( selected, this ) ); } void ProjectManagerView::updateSyncAction() { m_syncAction->setEnabled( KDevelop::ICore::self()->documentController()->activeDocument() ); } ProjectManagerView::~ProjectManagerView() { KConfigGroup pmviewConfig(ICore::self()->activeSession()->config(), sessionConfigGroup); pmviewConfig.writeEntry(splitterStateConfigKey, m_ui->splitter->saveState()); pmviewConfig.sync(); delete m_ui; } QList ProjectManagerView::selectedItems() const { QList items; foreach( const QModelIndex &idx, m_ui->projectTreeView->selectionModel()->selectedIndexes() ) { KDevelop::ProjectBaseItem* item = ICore::self()->projectController()->projectModel()->itemFromIndex(indexFromView(idx)); if( item ) items << item; else qCDebug(PLUGIN_PROJECTMANAGERVIEW) << "adding an unknown item"; } return items; } void ProjectManagerView::selectItems(const QList< ProjectBaseItem* >& items) { QItemSelection selection; foreach (ProjectBaseItem *item, items) { QModelIndex indx = indexToView(item->index()); selection.append(QItemSelectionRange(indx, indx)); m_ui->projectTreeView->setCurrentIndex(indx); } m_ui->projectTreeView->selectionModel()->select(selection, QItemSelectionModel::ClearAndSelect); } void ProjectManagerView::expandItem(ProjectBaseItem* item) { m_ui->projectTreeView->expand( indexToView(item->index())); } void ProjectManagerView::toggleHideTargets(bool visible) { KConfigGroup pmviewConfig(ICore::self()->activeSession()->config(), sessionConfigGroup); pmviewConfig.writeEntry(targetsVisibleConfigKey, visible); m_modelFilter->showTargets(visible); } void ProjectManagerView::locateCurrentDocument() { ICore::self()->uiController()->raiseToolView(this); KDevelop::IDocument *doc = ICore::self()->documentController()->activeDocument(); if (!doc) { // in theory we should never get a null pointer as the action is only enabled // when there is an active document. // but: in practice it can happen that you close the last document and press // the shortcut to locate a doc or vice versa... so just do the failsafe thing here... return; } QModelIndex bestMatch; foreach (IProject* proj, ICore::self()->projectController()->projects()) { foreach (KDevelop::ProjectFileItem* item, proj->filesForPath(IndexedString(doc->url()))) { QModelIndex index = indexToView(item->index()); if (index.isValid()) { if (!bestMatch.isValid()) { bestMatch = index; } else if (KDevelop::ProjectBaseItem* parent = item->parent()) { // prefer files in their real folders over the 'copies' in the target folders if (!parent->target()) { bestMatch = index; break; } } } } } if (bestMatch.isValid()) { m_ui->projectTreeView->clearSelection(); m_ui->projectTreeView->setCurrentIndex(bestMatch); m_ui->projectTreeView->expand(bestMatch); m_ui->projectTreeView->scrollTo(bestMatch); } } void ProjectManagerView::open( const Path& path ) { IOpenWith::openFiles(QList() << path.toUrl()); } QModelIndex ProjectManagerView::indexFromView(const QModelIndex& index) const { return m_modelFilter->mapToSource( m_overlayProxy->mapToSource(index) ); } QModelIndex ProjectManagerView::indexToView(const QModelIndex& index) const { return m_overlayProxy->mapFromSource( m_modelFilter->mapFromSource(index) ); } diff --git a/plugins/projectmanagerview/projectmanagerviewplugin.cpp b/plugins/projectmanagerview/projectmanagerviewplugin.cpp index bdd5dd329..dddb53911 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 ) {} QWidget* create( QWidget *parent = 0 ) override { return new ProjectManagerView( mplugin, parent ); } Qt::DockWidgetArea defaultPosition() override { return Qt::LeftDockWidgetArea; } QString id() const override { - return "org.kdevelop.ProjectsView"; + 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( "kdevprojectmanagerview", parent ), d(new ProjectManagerViewPluginPrivate) + : IPlugin( QStringLiteral("kdevprojectmanagerview"), parent ), d(new ProjectManagerViewPluginPrivate) { d->m_buildAll = new QAction( i18n("Build all Projects"), this ); - d->m_buildAll->setIcon(QIcon::fromTheme("run-build")); + d->m_buildAll->setIcon(QIcon::fromTheme(QStringLiteral("run-build"))); connect( d->m_buildAll, &QAction::triggered, this, &ProjectManagerViewPlugin::buildAllProjects ); - actionCollection()->addAction( "project_buildall", d->m_buildAll ); + 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("run-build")); + 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( "project_build", d->m_build ); + 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("run-build-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( "project_install", d->m_install ); + 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("run-build-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( "project_clean", d->m_clean ); + 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("run-build-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( "project_configure", d->m_configure ); + 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("run-build-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( "project_prune", d->m_prune ); + 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( "locate_document" ); - setXMLFile( "kdevprojectmanagerview.rc" ); + 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("document-new")); + 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("folder-new")); + 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("run-build")); + 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("run-install")); + 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("run-clean")); + 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("list-add")); + 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("project-development-close")); + 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("view-refresh")); + 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("user-trash")); + 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("edit-rename")); + 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("user-trash")); + 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; - QMap< IProjectFileManager*, QList > filteredItems; + 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 - QMap< IProjectFileManager*, QList >::iterator it; + 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 ); - QMap< IBuildSystemManager*, QList > itemsByBuildSystem; + QHash< IBuildSystemManager*, QList > itemsByBuildSystem; foreach(ProjectBaseItem *item, items) itemsByBuildSystem[item->project()->buildSystemManager()].append(item->file()); - QMap< IBuildSystemManager*, QList >::iterator it; + 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 0; ProjectFileItem* ret = item->project()->projectFileManager()->addFile( Path(item->path(), name), item->folder() ); if (ret) { ICore::self()->documentController()->openDocument( ret->path().toUrl() ); } return ret; } void ProjectManagerViewPlugin::createFileFromContextMenu( ) { foreach( KDevelop::ProjectBaseItem* item, itemsFromIndexes( d->ctxProjectItemList ) ) { if ( item->folder() ) { createFile(item->folder()); } else if ( item->target() ) { ProjectFolderItem* folder=dynamic_cast(item->parent()); if(folder) { ProjectFileItem* f=createFile(folder); if(f) item->project()->buildSystemManager()->addFilesToTarget(QList() << f, item->target()); } } } } void ProjectManagerViewPlugin::copyFromContextMenu() { KDevelop::ProjectItemContext* ctx = dynamic_cast(ICore::self()->selectionController()->currentSelection()); 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().first(); + 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/projectmanagerview/projectmodelsaver.cpp b/plugins/projectmanagerview/projectmodelsaver.cpp index 3d74426aa..509c7daf8 100644 --- a/plugins/projectmanagerview/projectmodelsaver.cpp +++ b/plugins/projectmanagerview/projectmodelsaver.cpp @@ -1,73 +1,73 @@ /* This file is part of KDevelop Copyright 2012 Andrew Fuller 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 "projectmodelsaver.h" #include "projecttreeview.h" #include "project/projectmodel.h" #include #include #include #include namespace KDevelop { ProjectModelSaver::ProjectModelSaver() : m_project(0) { } void ProjectModelSaver::setProject(IProject* project) { m_project = project; } QModelIndex ProjectModelSaver::indexFromConfigString(const QAbstractItemModel *viewModel, const QString &key) const { const KDevelop::ProjectModel *projectModel = KDevelop::ICore::self()->projectController()->projectModel(); const QModelIndex sourceIndex = projectModel->pathToIndex(key.split('/')); if ( m_project && sourceIndex.isValid() ) { ProjectBaseItem* item = projectModel->itemFromIndex(sourceIndex); if ( item && item->project() == m_project ) { return ProjectTreeView::mapFromSource(qobject_cast(viewModel), sourceIndex); } } return QModelIndex(); } QString ProjectModelSaver::indexToConfigString(const QModelIndex& index) const { if( !index.isValid() || !m_project ) { return QString(); } ProjectBaseItem* item = index.data(ProjectModel::ProjectItemRole).value(); if ( !item || item->project() != m_project ) { return QString(); } - return ICore::self()->projectController()->projectModel()->pathFromIndex( item->index() ).join("/"); + return ICore::self()->projectController()->projectModel()->pathFromIndex( item->index() ).join(QLatin1Char('/')); } } diff --git a/plugins/projectmanagerview/projecttreeview.cpp b/plugins/projectmanagerview/projecttreeview.cpp index 81ba3ab80..653a69e7d 100644 --- a/plugins/projectmanagerview/projecttreeview.cpp +++ b/plugins/projectmanagerview/projecttreeview.cpp @@ -1,460 +1,460 @@ /* This file is part of KDevelop Copyright 2005 Roberto Raggi Copyright 2007 Andreas Pakulat Copyright 2009 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 "projecttreeview.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "projectmanagerviewplugin.h" #include "projectmodelsaver.h" #include "projectmodelitemdelegate.h" #include "debug.h" #include #include #include #include #include using namespace KDevelop; namespace { const char settingsConfigGroup[] = "ProjectTreeView"; QList fileItemsWithin(const QList& items) { QList fileItems; fileItems.reserve(items.size()); foreach(ProjectBaseItem* item, items) { if (ProjectFileItem *file = item->file()) fileItems.append(file); else if (item->folder()) fileItems.append(fileItemsWithin(item->children())); } return fileItems; } QList topLevelItemsWithin(QList items) { std::sort(items.begin(), items.end(), ProjectBaseItem::pathLessThan); Path lastFolder; for (int i = items.size() - 1; i >= 0; --i) { if (lastFolder.isParentOf(items[i]->path())) items.removeAt(i); else if (items[i]->folder()) lastFolder = items[i]->path(); } return items; } template void filterDroppedItems(QList &items, ProjectBaseItem* dest) { for (int i = items.size() - 1; i >= 0; --i) { //No drag and drop from and to same location if (items[i]->parent() == dest) items.removeAt(i); //No moving between projects (technically feasible if the projectmanager is the same though...) else if (items[i]->project() != dest->project()) items.removeAt(i); } } //TODO test whether this could be replaced by projectbuildsetwidget.cpp::showContextMenu_appendActions void popupContextMenu_appendActions(QMenu& menu, const QList& actions) { menu.addActions(actions); menu.addSeparator(); } } ProjectTreeView::ProjectTreeView( QWidget *parent ) : QTreeView( parent ), m_ctxProject( 0 ) { header()->hide(); setEditTriggers( QAbstractItemView::EditKeyPressed ); setContextMenuPolicy( Qt::CustomContextMenu ); setSelectionMode( QAbstractItemView::ExtendedSelection ); setIndentation(10); setDragEnabled(true); setDragDropMode(QAbstractItemView::InternalMove); setAutoScroll(true); setAutoExpandDelay(300); setItemDelegate(new ProjectModelItemDelegate(this)); connect( this, &ProjectTreeView::customContextMenuRequested, this, &ProjectTreeView::popupContextMenu ); connect( this, &ProjectTreeView::activated, this, &ProjectTreeView::slotActivated ); connect( ICore::self(), &ICore::aboutToShutdown, this, &ProjectTreeView::aboutToShutdown); connect( ICore::self()->projectController(), &IProjectController::projectOpened, this, &ProjectTreeView::restoreState ); connect( ICore::self()->projectController(), &IProjectController::projectClosing, this, &ProjectTreeView::saveState ); restoreState(); } ProjectTreeView::~ProjectTreeView() { } ProjectBaseItem* ProjectTreeView::itemAtPos(QPoint pos) { return indexAt(pos).data(ProjectModel::ProjectItemRole).value(); } void ProjectTreeView::dropEvent(QDropEvent* event) { ProjectItemContext* selectionCtxt = static_cast(KDevelop::ICore::self()->selectionController()->currentSelection()); ProjectBaseItem* destItem = itemAtPos(event->pos()); if (destItem && (dropIndicatorPosition() == AboveItem || dropIndicatorPosition() == BelowItem)) destItem = destItem->parent(); if (selectionCtxt && destItem) { if (ProjectFolderItem *folder = destItem->folder()) { QMenu dropMenu(this); QString seq = QKeySequence( Qt::ShiftModifier ).toString(); seq.chop(1); // chop superfluous '+' QAction* move = new QAction(i18n( "&Move Here" ) + '\t' + seq, &dropMenu); - move->setIcon(QIcon::fromTheme("go-jump")); + move->setIcon(QIcon::fromTheme(QStringLiteral("go-jump"))); dropMenu.addAction(move); seq = QKeySequence( Qt::ControlModifier ).toString(); seq.chop(1); QAction* copy = new QAction(i18n( "&Copy Here" ) + '\t' + seq, &dropMenu); - copy->setIcon(QIcon::fromTheme("edit-copy")); + copy->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy"))); dropMenu.addAction(copy); dropMenu.addSeparator(); QAction* cancel = new QAction(i18n( "C&ancel" ) + '\t' + QKeySequence( Qt::Key_Escape ).toString(), &dropMenu); - cancel->setIcon(QIcon::fromTheme("process-stop")); + cancel->setIcon(QIcon::fromTheme(QStringLiteral("process-stop"))); dropMenu.addAction(cancel); QAction *executedAction = 0; Qt::KeyboardModifiers modifiers = QApplication::keyboardModifiers(); if (modifiers == Qt::ControlModifier) { executedAction = copy; } else if (modifiers == Qt::ShiftModifier) { executedAction = move; } else { executedAction = dropMenu.exec(this->mapToGlobal(event->pos())); } QList usefulItems = topLevelItemsWithin(selectionCtxt->items()); filterDroppedItems(usefulItems, destItem); Path::List paths; foreach (ProjectBaseItem* i, usefulItems) { paths << i->path(); } bool success = false; if (executedAction == copy) { success = destItem->project()->projectFileManager()->copyFilesAndFolders(paths, folder); } else if (executedAction == move) { success = destItem->project()->projectFileManager()->moveFilesAndFolders(usefulItems, folder); } if (success) { //expand target folder expand( mapFromItem(folder)); //and select new items QItemSelection selection; foreach (const Path &path, paths) { const Path targetPath(folder->path(), path.lastPathSegment()); foreach (ProjectBaseItem *item, folder->children()) { if (item->path() == targetPath) { QModelIndex indx = mapFromItem( item ); selection.append(QItemSelectionRange(indx, indx)); setCurrentIndex(indx); } } } selectionModel()->select(selection, QItemSelectionModel::ClearAndSelect); } } else if (destItem->target() && destItem->project()->buildSystemManager()) { QMenu dropMenu(this); QString seq = QKeySequence( Qt::ControlModifier ).toString(); seq.chop(1); QAction* addToTarget = new QAction(i18n( "&Add to Target" ) + '\t' + seq, &dropMenu); - addToTarget->setIcon(QIcon::fromTheme("edit-link")); + addToTarget->setIcon(QIcon::fromTheme(QStringLiteral("edit-link"))); dropMenu.addAction(addToTarget); dropMenu.addSeparator(); QAction* cancel = new QAction(i18n( "C&ancel" ) + '\t' + QKeySequence( Qt::Key_Escape ).toString(), &dropMenu); - cancel->setIcon(QIcon::fromTheme("process-stop")); + cancel->setIcon(QIcon::fromTheme(QStringLiteral("process-stop"))); dropMenu.addAction(cancel); QAction *executedAction = 0; Qt::KeyboardModifiers modifiers = QApplication::keyboardModifiers(); if (modifiers == Qt::ControlModifier) { executedAction = addToTarget; } else { executedAction = dropMenu.exec(this->mapToGlobal(event->pos())); } if (executedAction == addToTarget) { QList usefulItems = fileItemsWithin(selectionCtxt->items()); filterDroppedItems(usefulItems, destItem); destItem->project()->buildSystemManager()->addFilesToTarget(usefulItems, destItem->target()); } } } event->accept(); } QModelIndex ProjectTreeView::mapFromSource(const QAbstractProxyModel* proxy, const QModelIndex& sourceIdx) { const QAbstractItemModel* next = proxy->sourceModel(); Q_ASSERT(next == sourceIdx.model() || qobject_cast(next)); if(next == sourceIdx.model()) return proxy->mapFromSource(sourceIdx); else { const QAbstractProxyModel* nextProxy = qobject_cast(next); QModelIndex idx = mapFromSource(nextProxy, sourceIdx); Q_ASSERT(idx.model() == nextProxy); return proxy->mapFromSource(idx); } } QModelIndex ProjectTreeView::mapFromItem(const ProjectBaseItem* item) { QModelIndex ret = mapFromSource(qobject_cast(model()), item->index()); Q_ASSERT(ret.model() == model()); return ret; } void ProjectTreeView::slotActivated( const QModelIndex &index ) { if ( QApplication::keyboardModifiers() & Qt::CTRL || QApplication::keyboardModifiers() & Qt::SHIFT ) { // Do not open file when Ctrl or Shift is pressed; that's for selection return; } KDevelop::ProjectBaseItem *item = index.data(ProjectModel::ProjectItemRole).value(); if ( item && item->file() ) { emit activate( item->file()->path() ); } } void ProjectTreeView::popupContextMenu( const QPoint &pos ) { QList itemlist; if ( indexAt(pos).isValid() ) { QModelIndexList indexes = selectionModel()->selectedRows(); foreach( const QModelIndex& index, indexes ) { if ( KDevelop::ProjectBaseItem *item = index.data(ProjectModel::ProjectItemRole).value() ) itemlist << item; } } if( !itemlist.isEmpty() ) { m_ctxProject = itemlist.at(0)->project(); } else { m_ctxProject = 0; } QMenu menu( this ); KDevelop::ProjectItemContextImpl context(itemlist); QList extensions = ICore::self()->pluginController()->queryPluginsForContextMenuExtensions( &context ); QList buildActions; QList vcsActions; QList extActions; QList projectActions; QList fileActions; QList runActions; foreach( const ContextMenuExtension& ext, extensions ) { buildActions += ext.actions(ContextMenuExtension::BuildGroup); fileActions += ext.actions(ContextMenuExtension::FileGroup); projectActions += ext.actions(ContextMenuExtension::ProjectGroup); vcsActions += ext.actions(ContextMenuExtension::VcsGroup); extActions += ext.actions(ContextMenuExtension::ExtensionGroup); runActions += ext.actions(ContextMenuExtension::RunGroup); } popupContextMenu_appendActions(menu, buildActions); popupContextMenu_appendActions(menu, runActions ); popupContextMenu_appendActions(menu, fileActions); popupContextMenu_appendActions(menu, vcsActions); popupContextMenu_appendActions(menu, extActions); if ( !itemlist.isEmpty() && itemlist.size() == 1 && itemlist[0]->folder() && !itemlist[0]->folder()->parent() ) { QAction* projectConfig = new QAction(i18n("Open Configuration..."), this); - projectConfig->setIcon(QIcon::fromTheme("configure")); + projectConfig->setIcon(QIcon::fromTheme(QStringLiteral("configure"))); connect( projectConfig, &QAction::triggered, this, &ProjectTreeView::openProjectConfig ); projectActions << projectConfig; } popupContextMenu_appendActions(menu, projectActions); if(!itemlist.isEmpty()) KDevelop::populateParentItemsMenu(itemlist.front(), &menu); if ( !menu.isEmpty() ) { menu.exec( mapToGlobal( pos ) ); } } void ProjectTreeView::openProjectConfig() { if( m_ctxProject ) { IProjectController* ip = ICore::self()->projectController(); ip->configureProject( m_ctxProject ); } } void ProjectTreeView::saveState() { KConfigGroup configGroup( ICore::self()->activeSession()->config(), settingsConfigGroup ); ProjectModelSaver saver; saver.setView( this ); saver.saveState( configGroup ); } void ProjectTreeView::restoreState(IProject* project) { KConfigGroup configGroup( ICore::self()->activeSession()->config(), settingsConfigGroup ); ProjectModelSaver saver; saver.setProject( project ); saver.setView( this ); saver.restoreState( configGroup ); } void ProjectTreeView::aboutToShutdown() { // save all projects, not just the last one that is closed disconnect( ICore::self()->projectController(), &IProjectController::projectClosing, this, &ProjectTreeView::saveState ); saveState(); } bool ProjectTreeView::event(QEvent* event) { if(event->type()==QEvent::ToolTip) { QPoint p = mapFromGlobal(QCursor::pos()); QModelIndex idxView = indexAt(p); ProjectBaseItem* it = idxView.data(ProjectModel::ProjectItemRole).value(); QModelIndex idx; if(it) idx = it->index(); if((m_idx!=idx || !m_tooltip) && it && it->file()) { m_idx=idx; ProjectFileItem* file=it->file(); KDevelop::DUChainReadLocker lock(KDevelop::DUChain::lock()); TopDUContext* top= DUChainUtils::standardContextForUrl(file->path().toUrl()); if(m_tooltip) m_tooltip->close(); if(top) { QWidget* navigationWidget = top->createNavigationWidget(); if( navigationWidget ) { m_tooltip = new KDevelop::NavigationToolTip(this, mapToGlobal(p) + QPoint(40, 0), navigationWidget); m_tooltip->resize( navigationWidget->sizeHint() + QSize(10, 10) ); qCDebug(PLUGIN_PROJECTMANAGERVIEW) << "tooltip size" << m_tooltip->size(); ActiveToolTip::showToolTip(m_tooltip); return true; } } } } return QAbstractItemView::event(event); } void ProjectTreeView::keyPressEvent(QKeyEvent* event) { if (event->key() == Qt::Key_Return && currentIndex().isValid() && state()!=QAbstractItemView::EditingState) { event->accept(); slotActivated(currentIndex()); } else QTreeView::keyPressEvent(event); } void ProjectTreeView::drawBranches(QPainter* painter, const QRect& rect, const QModelIndex& index) const { const bool colorizeByProject = KSharedConfig::openConfig()->group("UiSettings").readEntry("ColorizeByProject", false); if (colorizeByProject) { const auto projectPath = index.data(ProjectModel::ProjectRole).value()->path(); const QColor color = WidgetColorizer::colorForId(qHash(projectPath), palette()); WidgetColorizer::drawBranches(this, painter, rect, index, color); } QTreeView::drawBranches(painter, rect, index); } diff --git a/plugins/projectmanagerview/vcsoverlayproxymodel.cpp b/plugins/projectmanagerview/vcsoverlayproxymodel.cpp index 4ee99873e..53b7a8b30 100644 --- a/plugins/projectmanagerview/vcsoverlayproxymodel.cpp +++ b/plugins/projectmanagerview/vcsoverlayproxymodel.cpp @@ -1,133 +1,133 @@ /* This file is part of KDevelop Copyright 2013 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 "vcsoverlayproxymodel.h" #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; using SafeProjectPointer = QPointer; Q_DECLARE_METATYPE(SafeProjectPointer) VcsOverlayProxyModel::VcsOverlayProxyModel(QObject* parent): QIdentityProxyModel(parent) { connect(ICore::self()->projectController(), &IProjectController::projectOpened, this, &VcsOverlayProxyModel::addProject); connect(ICore::self()->projectController(), &IProjectController::projectClosing, this, &VcsOverlayProxyModel::removeProject); - foreach (const auto& project, ICore::self()->projectController()->projects()) { + foreach (const auto project, ICore::self()->projectController()->projects()) { addProject(project); } } QVariant VcsOverlayProxyModel::data(const QModelIndex& proxyIndex, int role) const { if(role == VcsStatusRole) { if (!proxyIndex.parent().isValid()) { IProject* p = qobject_cast(proxyIndex.data(ProjectModel::ProjectRole).value()); return m_branchName.value(p); } else { ProjectChangesModel* model = ICore::self()->projectController()->changesModel(); const QUrl url = proxyIndex.data(ProjectModel::UrlRole).toUrl(); const QModelIndex idx = model->indexForUrl(url); return idx.sibling(idx.row(), 1).data(Qt::DisplayRole); } } else return QAbstractProxyModel::data(proxyIndex, role); } void VcsOverlayProxyModel::addProject(IProject* p) { IPlugin* plugin = p->versionControlPlugin(); if(!plugin) return; // TODO: Show revision in case we're in "detached" state IBranchingVersionControl* branchingExtension = plugin->extension(); if(branchingExtension) { const QUrl url = p->path().toUrl(); branchingExtension->registerRepositoryForCurrentBranchChanges(url); //can't use new signal/slot syntax here, IBranchingVersionControl is not a QObject connect(plugin, SIGNAL(repositoryBranchChanged(QUrl)), SLOT(repositoryBranchChanged(QUrl))); repositoryBranchChanged(url); } } void VcsOverlayProxyModel::repositoryBranchChanged(const QUrl& url) { QList allProjects = ICore::self()->projectController()->projects(); foreach(IProject* project, allProjects) { if( url.isParentOf(project->path().toUrl()) || url.matches(project->path().toUrl(), QUrl::StripTrailingSlash)) { IPlugin* v = project->versionControlPlugin(); Q_ASSERT(v); IBranchingVersionControl* branching = v->extension(); Q_ASSERT(branching); VcsJob* job = branching->currentBranch(url); connect(job, &VcsJob::resultsReady, this, &VcsOverlayProxyModel::branchNameReady); job->setProperty("project", QVariant::fromValue(project)); ICore::self()->runController()->registerJob(job); } } } void VcsOverlayProxyModel::branchNameReady(KDevelop::VcsJob* job) { static const QString noBranchStr = i18nc("Version control: Currently not on a branch", "(no branch)"); if(job->status()==VcsJob::JobSucceeded) { SafeProjectPointer p = job->property("project").value(); QModelIndex index = indexFromProject(p); if(index.isValid()) { IProject* project = p.data(); const auto branchName = job->fetchResults().toString(); m_branchName[project] = branchName.isEmpty() ? noBranchStr : branchName; emit dataChanged(index, index); } } } void VcsOverlayProxyModel::removeProject(IProject* p) { m_branchName.remove(p); } QModelIndex VcsOverlayProxyModel::indexFromProject(QObject* project) { for(int i=0; i()==project) { return idx; } } return QModelIndex(); } diff --git a/plugins/quickopen/actionsquickopenprovider.cpp b/plugins/quickopen/actionsquickopenprovider.cpp index ec38c4de3..d1364a714 100644 --- a/plugins/quickopen/actionsquickopenprovider.cpp +++ b/plugins/quickopen/actionsquickopenprovider.cpp @@ -1,127 +1,127 @@ /* * This file is part of KDevelop * * Copyright 2015 Aleix Pol Gonzalez * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "actionsquickopenprovider.h" #include #include #include #include #include #include #include #include using namespace KDevelop; class ActionsQuickOpenItem : public QuickOpenDataBase { public: ActionsQuickOpenItem(const QString &display, QAction* action) : QuickOpenDataBase() , m_action(action) , m_display(display) {} QString text() const override { return m_display; } QString htmlDescription() const override { QString desc = m_action->toolTip(); const QKeySequence shortcut = m_action->shortcut(); if (!shortcut.isEmpty()) { desc = i18nc("description (shortcut)", "%1 (%2)", desc, shortcut.toString()); } return desc; } bool execute(QString&) override { m_action->trigger(); return true; } QIcon icon() const override { return m_action->icon(); } private: QAction* m_action; ///needed because it won't have the "E&xit" ampersand QString m_display; }; ActionsQuickOpenProvider::ActionsQuickOpenProvider() { } void ActionsQuickOpenProvider::setFilterText(const QString& text) { if (text.size() < 2) { return; } m_results.clear(); const QList collections = KActionCollection::allCollections(); - QRegularExpression mnemonicRx("^(.*)&(.+)$"); + QRegularExpression mnemonicRx(QStringLiteral("^(.*)&(.+)$")); for (KActionCollection* c : collections) { QList actions = c->actions(); - for(QAction* action : actions) { + foreach(QAction* action, actions) { if (!action->isEnabled()) continue; QString display = action->text(); QRegularExpressionMatch match = mnemonicRx.match(display); if (match.hasMatch()) { display = match.captured(1)+match.captured(2); } if (display.contains(text, Qt::CaseInsensitive)) { m_results += QuickOpenDataPointer(new ActionsQuickOpenItem(display, action)); } } } } uint ActionsQuickOpenProvider::unfilteredItemCount() const { uint ret = 0; QList collections = KActionCollection::allCollections(); foreach(KActionCollection* c, collections) { ret += c->count(); } return ret; } QuickOpenDataPointer ActionsQuickOpenProvider::data(uint row) const { return m_results.at(row); } uint ActionsQuickOpenProvider::itemCount() const { return m_results.count(); } void ActionsQuickOpenProvider::reset() { m_results.clear(); } diff --git a/plugins/quickopen/actionsquickopenprovider.h b/plugins/quickopen/actionsquickopenprovider.h index 85d5e9ecd..80da0c4ff 100644 --- a/plugins/quickopen/actionsquickopenprovider.h +++ b/plugins/quickopen/actionsquickopenprovider.h @@ -1,43 +1,44 @@ /* * This file is part of KDevelop * * Copyright 2015 Aleix Pol Gonzalez * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_PLUGIN_ACTIONSPROVIDER_H #define KDEVPLATFORM_PLUGIN_ACTIONSPROVIDER_H #include #include class ActionsQuickOpenProvider : public KDevelop::QuickOpenDataProviderBase { + Q_OBJECT public: ActionsQuickOpenProvider(); virtual KDevelop::QuickOpenDataPointer data(uint row) const override; virtual uint unfilteredItemCount() const override; virtual uint itemCount() const override; virtual void reset() override; virtual void setFilterText(const QString& text) override; private: QVector m_results; }; #endif // KDEVPLATFORM_PLUGIN_ACTIONSPROVIDER_H diff --git a/plugins/quickopen/declarationlistquickopen.h b/plugins/quickopen/declarationlistquickopen.h index edeab5633..7d45c52eb 100644 --- a/plugins/quickopen/declarationlistquickopen.h +++ b/plugins/quickopen/declarationlistquickopen.h @@ -1,43 +1,44 @@ /* This file is part of the KDE libraries Copyright (C) 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_PLUGIN_DECLARATION_LIST_QUICKOPEN_H #define KDEVPLATFORM_PLUGIN_DECLARATION_LIST_QUICKOPEN_H #include "duchainitemquickopen.h" namespace KDevelop { class IQuickOpen; } class DeclarationListDataProvider : public DUChainItemDataProvider { + Q_OBJECT public: DeclarationListDataProvider( KDevelop::IQuickOpen* quickopen, const QList& items, bool openDefinitions = false ); virtual void reset() override; private: QList m_items; }; #endif diff --git a/plugins/quickopen/documentationquickopenprovider.h b/plugins/quickopen/documentationquickopenprovider.h index cd962251d..50a782556 100644 --- a/plugins/quickopen/documentationquickopenprovider.h +++ b/plugins/quickopen/documentationquickopenprovider.h @@ -1,43 +1,44 @@ /* * This file is part of KDevelop * * Copyright 2013 Aleix Pol Gonzalez * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_PLUGIN_DOCUMENTATIONPROVIDER_H #define KDEVPLATFORM_PLUGIN_DOCUMENTATIONPROVIDER_H #include #include class DocumentationQuickOpenProvider : public KDevelop::QuickOpenDataProviderBase { + Q_OBJECT public: DocumentationQuickOpenProvider(); virtual KDevelop::QuickOpenDataPointer data(uint row) const override; virtual uint unfilteredItemCount() const override; virtual uint itemCount() const override; virtual void reset() override; virtual void setFilterText(const QString& text) override; private: QVector m_results; }; #endif // KDEVPLATFORM_PLUGIN_DOCUMENTATIONPROVIDER_H diff --git a/plugins/quickopen/duchainitemquickopen.cpp b/plugins/quickopen/duchainitemquickopen.cpp index f915f6f37..c6682d8c9 100644 --- a/plugins/quickopen/duchainitemquickopen.cpp +++ b/plugins/quickopen/duchainitemquickopen.cpp @@ -1,254 +1,254 @@ /* 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 #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; DUChainItemData::DUChainItemData( const DUChainItem& file, bool openDefinition ) : m_item(file) , m_openDefinition(openDefinition) { } QString DUChainItemData::text() const { DUChainReadLocker lock;; Declaration* decl = m_item.m_item.data(); if(!decl) return i18n("Not available any more: %1", m_item.m_text); if(FunctionDefinition* def = dynamic_cast(decl)) { if(def->declaration()) { decl = def->declaration(); } } QString text = decl->qualifiedIdentifier().toString(); if(!decl->abstractType()) { //With simplified representation, still mark functions as such by adding parens if(dynamic_cast(decl)) { - text += "(...)"; + text += QLatin1String("(...)"); } }else if(TypePtr function = decl->type()) { text += function->partToString( FunctionType::SignatureArguments ); } return text; } QList DUChainItemData::highlighting() const { DUChainReadLocker lock;; Declaration* decl = m_item.m_item.data(); if(!decl) { return QList(); } if(FunctionDefinition* def = dynamic_cast(decl)) { if(def->declaration()) { decl = def->declaration(); } } QTextCharFormat boldFormat; boldFormat.setFontWeight(QFont::Bold); QTextCharFormat normalFormat; int prefixLength = 0; QString signature; TypePtr function = decl->type(); if(function) { signature = function->partToString( FunctionType::SignatureArguments ); } //Only highlight the last part of the qualified identifier, so the scope doesn't distract too much QualifiedIdentifier id = decl->qualifiedIdentifier(); QString fullId = id.toString(); QString lastId; if(!id.isEmpty()) { lastId = id.last().toString(); } prefixLength += fullId.length() - lastId.length(); QList ret; ret << 0; ret << prefixLength; ret << QVariant(normalFormat); ret << prefixLength; ret << lastId.length(); ret << QVariant(boldFormat); if(!signature.isEmpty()) { ret << prefixLength + lastId.length(); ret << signature.length(); ret << QVariant(normalFormat); } return ret; } QString DUChainItemData::htmlDescription() const { if(m_item.m_noHtmlDestription) { return QString(); } DUChainReadLocker lock;; Declaration* decl = m_item.m_item.data(); if(!decl) { return i18n("Not available any more"); } TypePtr function = decl->type(); QString text; if( function && function->returnType() ) { text = i18nc("%1: function signature", "Return: %1", function->partToString(FunctionType::SignatureReturn)); } text += ' ' + i18nc("%1: file path", "File: %1", decl->url().str()); QString ret = "" + text + ""; if(!m_item.m_project.isEmpty()) { ret.prepend(i18n("Project %1", m_item.m_project) + (ret.isEmpty() ? ", " : "")); } return ret; } bool DUChainItemData::execute( QString& /*filterText*/ ) { DUChainReadLocker lock;; Declaration* decl = m_item.m_item.data(); if(!decl) { return false; } if(m_openDefinition && FunctionDefinition::definition(decl)) { decl = FunctionDefinition::definition(decl); } QUrl url = decl->url().toUrl(); KTextEditor::Cursor cursor = decl->rangeInCurrentRevision().start(); DUContext* internal = decl->internalContext(); if(internal && (internal->type() == DUContext::Other || internal->type() == DUContext::Class)) { //Move into the body if(internal->range().end.line > internal->range().start.line) { cursor = KTextEditor::Cursor(internal->range().start.line+1, 0); //Move into the body } } lock.unlock(); ICore::self()->documentController()->openDocument( url, cursor ); return true; } bool DUChainItemData::isExpandable() const { return true; } QWidget* DUChainItemData::expandingWidget() const { DUChainReadLocker lock;; Declaration* decl = dynamic_cast(m_item.m_item.data()); if( !decl || !decl->context() ) { return 0; } return decl->context()->createNavigationWidget( decl, decl->topContext(), m_item.m_project.isEmpty() ? QString() : ("" + i18n("Project %1", m_item.m_project) + "
") ); } QIcon DUChainItemData::icon() const { return QIcon(); } DUChainItemDataProvider::DUChainItemDataProvider( IQuickOpen* quickopen, bool openDefinitions ) : m_quickopen(quickopen) , m_openDefinitions(openDefinitions) { reset(); } void DUChainItemDataProvider::setFilterText( const QString& text ) { Base::setFilter( text ); } uint DUChainItemDataProvider::itemCount() const { return Base::filteredItems().count(); } uint DUChainItemDataProvider::unfilteredItemCount() const { return Base::items().count(); } QuickOpenDataPointer DUChainItemDataProvider::data( uint row ) const { return KDevelop::QuickOpenDataPointer( createData( Base::filteredItems()[row] ) ); } DUChainItemData* DUChainItemDataProvider::createData( const DUChainItem& item ) const { return new DUChainItemData( item, m_openDefinitions ); } QString DUChainItemDataProvider::itemText( const DUChainItem& data ) const { return data.m_text; } void DUChainItemDataProvider::reset() { } diff --git a/plugins/quickopen/duchainitemquickopen.h b/plugins/quickopen/duchainitemquickopen.h index 5de72aeb2..bf7a47209 100644 --- a/plugins/quickopen/duchainitemquickopen.h +++ b/plugins/quickopen/duchainitemquickopen.h @@ -1,102 +1,103 @@ /* 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. */ #ifndef DUCHAIN_ITEM_QUICKOPEN #define DUCHAIN_ITEM_QUICKOPEN #include #include #include #include namespace KDevelop { class IQuickOpen; } struct DUChainItem { DUChainItem() : m_noHtmlDestription(false) { } KDevelop::IndexedDeclaration m_item; QString m_text; QString m_project; bool m_noHtmlDestription; }; Q_DECLARE_TYPEINFO(DUChainItem, Q_MOVABLE_TYPE); class DUChainItemData : public KDevelop::QuickOpenDataBase { public: explicit DUChainItemData( const DUChainItem& item, bool openDefinition = false ); virtual QString text() const override; virtual QString htmlDescription() const override; virtual QList highlighting() const override; bool execute( QString& filterText ) override; virtual bool isExpandable() const override; virtual QWidget* expandingWidget() const override; virtual QIcon icon() const override; private: DUChainItem m_item; bool m_openDefinition; }; /** * A QuickOpenDataProvider that presents a list of declarations. * The declarations need to be set using setItems(..) in a re-implemented reset() function. * */ class DUChainItemDataProvider : public KDevelop::QuickOpenDataProviderBase, public KDevelop::Filter { + Q_OBJECT public: typedef KDevelop::Filter Base; /// When openDefinitions is true, the definitions will be opened if available on execute(). explicit DUChainItemDataProvider( KDevelop::IQuickOpen* quickopen, bool openDefinitions = false ); virtual void setFilterText( const QString& text ) override; virtual uint itemCount() const override; virtual uint unfilteredItemCount() const override; virtual KDevelop::QuickOpenDataPointer data( uint row ) const override; virtual void reset() override; protected: //Override to create own DUChainItemData derived classes DUChainItemData* createData( const DUChainItem& item ) const; //Reimplemented from Base<..> virtual QString itemText( const DUChainItem& data ) const override; KDevelop::IQuickOpen* m_quickopen; private: bool m_openDefinitions; }; #endif diff --git a/plugins/quickopen/expandingtree/expandingdelegate.cpp b/plugins/quickopen/expandingtree/expandingdelegate.cpp index 4be0b2618..56f461294 100644 --- a/plugins/quickopen/expandingtree/expandingdelegate.cpp +++ b/plugins/quickopen/expandingtree/expandingdelegate.cpp @@ -1,336 +1,336 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2006 Hamish Rodda * Copyright (C) 2007 David Nolden * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "expandingdelegate.h" #include #include #include #include #include #include #include "expandingwidgetmodel.h" #include "../debug.h" ExpandingDelegate::ExpandingDelegate(ExpandingWidgetModel* model, QObject* parent) : QItemDelegate(parent) , m_model(model) { } //Gets the background-color in the way QItemDelegate does it static QColor getUsedBackgroundColor(const QStyleOptionViewItem & option, const QModelIndex& index) { if (option.showDecorationSelected && (option.state & QStyle::State_Selected)) { QPalette::ColorGroup cg = option.state & QStyle::State_Enabled ? QPalette::Normal : QPalette::Disabled; if (cg == QPalette::Normal && !(option.state & QStyle::State_Active)) cg = QPalette::Inactive; return option.palette.brush(cg, QPalette::Highlight).color(); } else { QVariant value = index.data(Qt::BackgroundRole); if ((value).canConvert()) return qvariant_cast(value).color(); } return QApplication::palette().base().color(); } static void dampColors(QColor& col) { //Reduce the colors that are less visible to the eye, because they are closer to black when it comes to contrast //The most significant color to the eye is green. Then comes red, and then blue, with blue _much_ less significant. col.setBlue(0); col.setRed(col.red() / 2); } //A hack to compute more eye-focused contrast values static double readabilityContrast(QColor foreground, QColor background) { dampColors(foreground); dampColors(background); return abs(foreground.green()-background.green()) + abs(foreground.red()-background.red()) + abs(foreground.blue() - background.blue()); } void ExpandingDelegate::paint( QPainter * painter, const QStyleOptionViewItem & optionOld, const QModelIndex & index ) const { QStyleOptionViewItem option(optionOld); m_currentIndex = index; adjustStyle(index, option); if( index.column() == 0 ) model()->placeExpandingWidget(index); //Make sure the decorations are painted at the top, because the center of expanded items will be filled with the embedded widget. if( model()->isPartiallyExpanded(index) == ExpandingWidgetModel::ExpandUpwards ) m_cachedAlignment = Qt::AlignBottom; else m_cachedAlignment = Qt::AlignTop; option.decorationAlignment = m_cachedAlignment; option.displayAlignment = m_cachedAlignment; //qCDebug( PLUGIN_QUICKOPEN ) << "Painting row " << index.row() << ", column " << index.column() << ", internal " << index.internalPointer() << ", drawselected " << option.showDecorationSelected << ", selected " << (option.state & QStyle::State_Selected); m_cachedHighlights.clear(); m_backgroundColor = getUsedBackgroundColor(option, index); if (model()->indexIsItem(index) ) { m_currentColumnStart = 0; m_cachedHighlights = createHighlighting(index, option); } /*qCDebug( PLUGIN_QUICKOPEN ) << "Highlights for line:"; foreach (const QTextLayout::FormatRange& fr, m_cachedHighlights) qCDebug( PLUGIN_QUICKOPEN ) << fr.start << " len " << fr.length << " format ";*/ QItemDelegate::paint(painter, option, index); ///This is a bug workaround for the Qt raster paint engine: It paints over widgets embedded into the viewport when updating due to mouse events ///@todo report to Qt Software if( model()->isExpanded(index) && model()->expandingWidget( index ) ) model()->expandingWidget( index )->update(); } QList ExpandingDelegate::createHighlighting(const QModelIndex& index, QStyleOptionViewItem& option) const { Q_UNUSED( index ); Q_UNUSED( option ); return QList(); } QSize ExpandingDelegate::basicSizeHint( const QModelIndex& index ) const { return QItemDelegate::sizeHint( QStyleOptionViewItem(), index ); } QSize ExpandingDelegate::sizeHint ( const QStyleOptionViewItem & option, const QModelIndex & index ) const { QSize s = QItemDelegate::sizeHint( option, index ); if( model()->isExpanded(index) && model()->expandingWidget( index ) ) { QWidget* widget = model()->expandingWidget( index ); QSize widgetSize = widget->size(); s.setHeight( widgetSize.height() + s.height() + 10 ); //10 is the sum that must match exactly the offsets used in ExpandingWidgetModel::placeExpandingWidgets } else if( model()->isPartiallyExpanded( index ) ) { s.setHeight( s.height() + 30 + 10 ); } return s; } void ExpandingDelegate::adjustStyle( const QModelIndex& index, QStyleOptionViewItem & option ) const { Q_UNUSED(index) Q_UNUSED(option) } void ExpandingDelegate::adjustRect(QRect& rect) const { if (!model()->indexIsItem(m_currentIndex) /*&& m_currentIndex.column() == 0*/) { rect.setLeft(model()->treeView()->columnViewportPosition(0)); int columnCount = model()->columnCount(m_currentIndex.parent()); if(!columnCount) return; rect.setRight(model()->treeView()->columnViewportPosition(columnCount-1) + model()->treeView()->columnWidth(columnCount-1)); } } void ExpandingDelegate::drawDisplay( QPainter * painter, const QStyleOptionViewItem & option, const QRect & _rect, const QString & text ) const { QRect rect(_rect); adjustRect(rect); QTextLayout layout(text, option.font, painter->device()); QList additionalFormats; int missingFormats = text.length(); for (int i = 0; i < m_cachedHighlights.count(); ++i) { if (m_cachedHighlights[i].start + m_cachedHighlights[i].length <= m_currentColumnStart) continue; - if (!additionalFormats.count()) + if (additionalFormats.isEmpty()) if (i != 0 && m_cachedHighlights[i - 1].start + m_cachedHighlights[i - 1].length > m_currentColumnStart) { QTextLayout::FormatRange before; before.start = 0; before.length = m_cachedHighlights[i - 1].start + m_cachedHighlights[i - 1].length - m_currentColumnStart; before.format = m_cachedHighlights[i - 1].format; additionalFormats.append(before); } QTextLayout::FormatRange format; format.start = m_cachedHighlights[i].start - m_currentColumnStart; format.length = m_cachedHighlights[i].length; format.format = m_cachedHighlights[i].format; additionalFormats.append(format); } if(!additionalFormats.isEmpty()) missingFormats = text.length() - (additionalFormats.back().length + additionalFormats.back().start); if (missingFormats > 0) { QTextLayout::FormatRange format; format.start = text.length() - missingFormats; format.length = missingFormats; QTextCharFormat fm; fm.setForeground(option.palette.text()); format.format = fm; additionalFormats.append(format); } if(m_backgroundColor.isValid()) { QColor background = m_backgroundColor; // qCDebug(PLUGIN_QUICKOPEN) << text << "background:" << background.name(); //Now go through the formats, and make sure the contrast background/foreground is readable for(int a = 0; a < additionalFormats.size(); ++a) { QColor currentBackground = background; if(additionalFormats[a].format.hasProperty(QTextFormat::BackgroundBrush)) currentBackground = additionalFormats[a].format.background().color(); QColor currentColor = additionalFormats[a].format.foreground().color(); double currentContrast = readabilityContrast(currentColor, currentBackground); QColor invertedColor(0xffffffff-additionalFormats[a].format.foreground().color().rgb()); double invertedContrast = readabilityContrast(invertedColor, currentBackground); // qCDebug(PLUGIN_QUICKOPEN) << "values:" << invertedContrast << currentContrast << invertedColor.name() << currentColor.name(); if(invertedContrast > currentContrast) { // qCDebug(PLUGIN_QUICKOPEN) << text << additionalFormats[a].length << "switching from" << currentColor.name() << "to" << invertedColor.name(); QBrush b(additionalFormats[a].format.foreground()); b.setColor(invertedColor); additionalFormats[a].format.setForeground(b); } } } for(int a = additionalFormats.size()-1; a >= 0; --a) { if(additionalFormats[a].length == 0){ additionalFormats.removeAt(a); }else{ ///For some reason the text-formats seem to be invalid in some way, sometimes ///@todo Fix this properly, it sucks not copying everything over QTextCharFormat fm; fm.setForeground(QBrush(additionalFormats[a].format.foreground().color())); fm.setBackground(additionalFormats[a].format.background()); fm.setUnderlineStyle( additionalFormats[a].format.underlineStyle() ); fm.setUnderlineColor( additionalFormats[a].format.underlineColor() ); fm.setFontWeight( additionalFormats[a].format.fontWeight() ); additionalFormats[a].format = fm; } } // qCDebug( PLUGIN_QUICKOPEN ) << "Highlights for text [" << text << "] col start " << m_currentColumnStart << ":"; // foreach (const QTextLayout::FormatRange& fr, additionalFormats) // qCDebug( PLUGIN_QUICKOPEN ) << fr.start << " len " << fr.length << "foreground" << fr.format.foreground() << "background" << fr.format.background(); layout.setAdditionalFormats(additionalFormats); QTextOption to; to.setAlignment( m_cachedAlignment ); to.setWrapMode(QTextOption::WrapAnywhere); layout.setTextOption(to); layout.beginLayout(); QTextLine line = layout.createLine(); line.setLineWidth(rect.width()); layout.endLayout(); //We need to do some hand layouting here if( to.alignment() & Qt::AlignBottom) layout.draw(painter, QPoint(rect.left(), rect.bottom() - (int)line.height()) ); else layout.draw(painter, rect.topLeft() ); return; //if (painter->fontMetrics().width(text) > textRect.width() && !text.contains(QLatin1Char('\n'))) //str = elidedText(option.fontMetrics, textRect.width(), option.textElideMode, text); //qt_format_text(option.font, textRect, option.displayAlignment, str, 0, 0, 0, 0, painter); } void ExpandingDelegate::drawDecoration(QPainter* painter, const QStyleOptionViewItem& option, const QRect& rect, const QPixmap& pixmap) const { if (model()->indexIsItem(m_currentIndex) ) QItemDelegate::drawDecoration(painter, option, rect, pixmap); } void ExpandingDelegate::drawBackground ( QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index ) const { Q_UNUSED(index) QStyleOptionViewItemV4 opt = option; //initStyleOption(&opt, index); //Problem: This isn't called at all, because drawBrackground is not virtual :-/ QStyle *style = model()->treeView()->style() ? model()->treeView()->style() : QApplication::style(); style->drawControl(QStyle::CE_ItemViewItem, &opt, painter); } ExpandingWidgetModel* ExpandingDelegate::model() const { return m_model; } void ExpandingDelegate::heightChanged() const { } bool ExpandingDelegate::editorEvent ( QEvent * event, QAbstractItemModel * /*model*/, const QStyleOptionViewItem & /*option*/, const QModelIndex & index ) { if( event->type() == QEvent::MouseButtonRelease ) { event->accept(); model()->setExpanded(index, !model()->isExpanded( index )); heightChanged(); return true; } else { event->ignore(); } return false; } QList ExpandingDelegate::highlightingFromVariantList(const QList& customHighlights) const { QList ret; for (int i = 0; i + 2 < customHighlights.count(); i += 3) { if (!customHighlights[i].canConvert(QVariant::Int) || !customHighlights[i+1].canConvert(QVariant::Int) || !customHighlights[i+2].canConvert()) { qWarning() << "Unable to convert triple to custom formatting."; continue; } QTextLayout::FormatRange format; format.start = customHighlights[i].toInt(); format.length = customHighlights[i+1].toInt(); format.format = customHighlights[i+2].value().toCharFormat(); if(!format.format.isValid()) qWarning() << "Format is not valid"; ret << format; } return ret; } diff --git a/plugins/quickopen/expandingtree/expandingtree.h b/plugins/quickopen/expandingtree/expandingtree.h index 5bd7c97b7..df92444fd 100644 --- a/plugins/quickopen/expandingtree/expandingtree.h +++ b/plugins/quickopen/expandingtree/expandingtree.h @@ -1,38 +1,39 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2007 David Nolden * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_PLUGIN_EXPANDINGTREE_H #define KDEVPLATFORM_PLUGIN_EXPANDINGTREE_H #include #include //A tree that allows drawing additional information class ExpandingTree : public QTreeView { + Q_OBJECT public: explicit ExpandingTree(QWidget* parent); protected: virtual void drawRow ( QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index ) const override; virtual int sizeHintForColumn ( int column ) const override; private: mutable QTextDocument m_drawText; }; #endif // KDEVPLATFORM_PLUGIN_EXPANDINGTREE_H diff --git a/plugins/quickopen/expandingtree/expandingwidgetmodel.cpp b/plugins/quickopen/expandingtree/expandingwidgetmodel.cpp index 34be20b19..3b825e7c4 100644 --- a/plugins/quickopen/expandingtree/expandingwidgetmodel.cpp +++ b/plugins/quickopen/expandingtree/expandingwidgetmodel.cpp @@ -1,529 +1,529 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2007 David Nolden * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "expandingwidgetmodel.h" #include #include #include #include #include #include #include #include "expandingdelegate.h" #include #include "../debug.h" QIcon ExpandingWidgetModel::m_expandedIcon; QIcon ExpandingWidgetModel::m_collapsedIcon; using namespace KTextEditor; inline QModelIndex firstColumn( const QModelIndex& index ) { return index.sibling(index.row(), 0); } ExpandingWidgetModel::ExpandingWidgetModel( QWidget* parent ) : QAbstractTableModel(parent) { } ExpandingWidgetModel::~ExpandingWidgetModel() { clearExpanding(); } static QColor doAlternate(QColor color) { QColor background = QApplication::palette().background().color(); return KColorUtils::mix(color, background, 0.15); } uint ExpandingWidgetModel::matchColor(const QModelIndex& index) const { int matchQuality = contextMatchQuality( index.sibling(index.row(), 0) ); if( matchQuality > 0 ) { bool alternate = index.row() & 1; QColor badMatchColor(0xff00aa44); //Blue-ish green QColor goodMatchColor(0xff00ff00); //Green QColor background = treeView()->palette().light().color(); QColor totalColor = KColorUtils::mix(badMatchColor, goodMatchColor, ((float)matchQuality)/10.0); if(alternate) totalColor = doAlternate(totalColor); const float dynamicTint = 0.2f; const float minimumTint = 0.2f; double tintStrength = (dynamicTint*matchQuality)/10; if(tintStrength) tintStrength += minimumTint; //Some minimum tinting strength, else it's not visible any more return KColorUtils::tint(background, totalColor, tintStrength ).rgb(); }else{ return 0; } } QVariant ExpandingWidgetModel::data( const QModelIndex & index, int role ) const { switch( role ) { case Qt::BackgroundRole: { if( index.column() == 0 ) { //Highlight by match-quality uint color = matchColor(index); if( color ) return QBrush( color ); } //Use a special background-color for expanded items if( isExpanded(index) ) { if( index.row() & 1 ) { return doAlternate(treeView()->palette().toolTipBase().color()); } else { return treeView()->palette().toolTipBase(); } } } } return QVariant(); } void ExpandingWidgetModel::clearMatchQualities() { m_contextMatchQualities.clear(); } QModelIndex ExpandingWidgetModel::partiallyExpandedRow() const { if( m_partiallyExpanded.isEmpty() ) return QModelIndex(); else return m_partiallyExpanded.constBegin().key(); } void ExpandingWidgetModel::clearExpanding() { clearMatchQualities(); QMap oldExpandState = m_expandState; foreach( QPointer widget, m_expandingWidgets ) delete widget; m_expandingWidgets.clear(); m_expandState.clear(); m_partiallyExpanded.clear(); for( QMap::const_iterator it = oldExpandState.constBegin(); it != oldExpandState.constEnd(); ++it ) if(it.value() == Expanded) emit dataChanged(it.key(), it.key()); } ExpandingWidgetModel::ExpansionType ExpandingWidgetModel::isPartiallyExpanded(const QModelIndex& index) const { if( m_partiallyExpanded.contains(firstColumn(index)) ) return m_partiallyExpanded[firstColumn(index)]; else return NotExpanded; } void ExpandingWidgetModel::partiallyUnExpand(const QModelIndex& idx_) { QModelIndex index( firstColumn(idx_) ); m_partiallyExpanded.remove(index); m_partiallyExpanded.remove(idx_); } int ExpandingWidgetModel::partiallyExpandWidgetHeight() const { return 60; ///@todo use font-metrics text-height*2 for 2 lines } void ExpandingWidgetModel::rowSelected(const QModelIndex& idx_) { QModelIndex idx( firstColumn(idx_) ); if( !m_partiallyExpanded.contains( idx ) ) { QModelIndex oldIndex = partiallyExpandedRow(); //Unexpand the previous partially expanded row if( !m_partiallyExpanded.isEmpty() ) { ///@todo allow multiple partially expanded rows while( !m_partiallyExpanded.isEmpty() ) m_partiallyExpanded.erase(m_partiallyExpanded.begin()); //partiallyUnExpand( m_partiallyExpanded.begin().key() ); } //Notify the underlying models that the item was selected, and eventually get back the text for the expanding widget. if( !idx.isValid() ) { //All items have been unselected if( oldIndex.isValid() ) emit dataChanged(oldIndex, oldIndex); } else { QVariant variant = data(idx, CodeCompletionModel::ItemSelected); if( !isExpanded(idx) && variant.type() == QVariant::String) { //Either expand upwards or downwards, choose in a way that //the visible fields of the new selected entry are not moved. if( oldIndex.isValid() && (oldIndex < idx || (!(oldIndex < idx) && oldIndex.parent() < idx.parent()) ) ) m_partiallyExpanded.insert(idx, ExpandUpwards); else m_partiallyExpanded.insert(idx, ExpandDownwards); //Say that one row above until one row below has changed, so no items will need to be moved(the space that is taken from one item is given to the other) if( oldIndex.isValid() && oldIndex < idx ) { emit dataChanged(oldIndex, idx); if( treeView()->verticalScrollMode() == QAbstractItemView::ScrollPerItem ) { //Qt fails to correctly scroll in ScrollPerItem mode, so the selected index is completely visible, //so we do the scrolling by hand. QRect selectedRect = treeView()->visualRect(idx); QRect frameRect = treeView()->frameRect(); if( selectedRect.bottom() > frameRect.bottom() ) { int diff = selectedRect.bottom() - frameRect.bottom(); //We need to scroll down QModelIndex newTopIndex = idx; QModelIndex nextTopIndex = idx; QRect nextRect = treeView()->visualRect(nextTopIndex); while( nextTopIndex.isValid() && nextRect.isValid() && nextRect.top() >= diff ) { newTopIndex = nextTopIndex; nextTopIndex = treeView()->indexAbove(nextTopIndex); if( nextTopIndex.isValid() ) nextRect = treeView()->visualRect(nextTopIndex); } treeView()->scrollTo( newTopIndex, QAbstractItemView::PositionAtTop ); } } //This is needed to keep the item we are expanding completely visible. Qt does not scroll the view to keep the item visible. //But we must make sure that it isn't too expensive. //We need to make sure that scrolling is efficient, and the whole content is not repainted. //Since we are scrolling anyway, we can keep the next line visible, which might be a cool feature. //Since this also doesn't work smoothly, leave it for now //treeView()->scrollTo( nextLine, QAbstractItemView::EnsureVisible ); } else if( oldIndex.isValid() && idx < oldIndex ) { emit dataChanged(idx, oldIndex); //For consistency with the down-scrolling, we keep one additional line visible above the current visible. //Since this also doesn't work smoothly, leave it for now /* QModelIndex prevLine = idx.sibling(idx.row()-1, idx.column()); if( prevLine.isValid() ) treeView()->scrollTo( prevLine );*/ } else emit dataChanged(idx, idx); } else if( oldIndex.isValid() ) { //We are not partially expanding a new row, but we previously had a partially expanded row. So signalize that it has been unexpanded. emit dataChanged(oldIndex, oldIndex); } } }else{ qCDebug( PLUGIN_QUICKOPEN ) << "ExpandingWidgetModel::rowSelected: Row is already partially expanded"; } } QString ExpandingWidgetModel::partialExpandText(const QModelIndex& idx) const { if( !idx.isValid() ) return QString(); return data(firstColumn(idx), CodeCompletionModel::ItemSelected).toString(); } QRect ExpandingWidgetModel::partialExpandRect(const QModelIndex& idx_) const { QModelIndex idx(firstColumn(idx_)); if( !idx.isValid() ) return QRect(); ExpansionType expansion = ExpandDownwards; if( m_partiallyExpanded.find(idx) != m_partiallyExpanded.constEnd() ) expansion = m_partiallyExpanded[idx]; //Get the whole rectangle of the row: QModelIndex rightMostIndex = idx; QModelIndex tempIndex = idx; while( (tempIndex = rightMostIndex.sibling(rightMostIndex.row(), rightMostIndex.column()+1)).isValid() ) rightMostIndex = tempIndex; QRect rect = treeView()->visualRect(idx); QRect rightMostRect = treeView()->visualRect(rightMostIndex); rect.setLeft( rect.left() + 20 ); rect.setRight( rightMostRect.right() - 5 ); //These offsets must match exactly those used in ExpandingDelegate::sizeHint() int top = rect.top() + 5; int bottom = rightMostRect.bottom() - 5 ; if( expansion == ExpandDownwards ) top += basicRowHeight(idx); else bottom -= basicRowHeight(idx); rect.setTop( top ); rect.setBottom( bottom ); return rect; } bool ExpandingWidgetModel::isExpandable(const QModelIndex& idx_) const { QModelIndex idx(firstColumn(idx_)); if( !m_expandState.contains(idx) ) { m_expandState.insert(idx, NotExpandable); QVariant v = data(idx, CodeCompletionModel::IsExpandable); - if( v.canConvert() && v.value() ) + if( v.canConvert() && v.toBool() ) m_expandState[idx] = Expandable; } return m_expandState[idx] != NotExpandable; } bool ExpandingWidgetModel::isExpanded(const QModelIndex& idx_) const { QModelIndex idx(firstColumn(idx_)); return m_expandState.contains(idx) && m_expandState[idx] == Expanded; } void ExpandingWidgetModel::setExpanded(QModelIndex idx_, bool expanded) { QModelIndex idx(firstColumn(idx_)); //qCDebug( PLUGIN_QUICKOPEN ) << "Setting expand-state of row " << idx.row() << " to " << expanded; if( !idx.isValid() ) return; if( isExpandable(idx) ) { if( !expanded && m_expandingWidgets.contains(idx) && m_expandingWidgets[idx] ) { m_expandingWidgets[idx]->hide(); } m_expandState[idx] = expanded ? Expanded : Expandable; if( expanded ) partiallyUnExpand(idx); if( expanded && !m_expandingWidgets.contains(idx) ) { QVariant v = data(idx, CodeCompletionModel::ExpandingWidget); if( v.canConvert() ) { m_expandingWidgets[idx] = v.value(); } else if( v.canConvert() ) { //Create a html widget that shows the given string - KTextEdit* edit = new KTextEdit( v.value() ); + KTextEdit* edit = new KTextEdit( v.toString() ); edit->setReadOnly(true); edit->resize(200, 50); //Make the widget small so it embeds nicely. m_expandingWidgets[idx] = edit; } else { m_expandingWidgets[idx] = 0; } } //Eventually partially expand the row if( !expanded && firstColumn(treeView()->currentIndex()) == idx && !isPartiallyExpanded(idx) ) rowSelected(idx); //Partially expand the row. emit dataChanged(idx, idx); if(treeView()) treeView()->scrollTo(idx); } } int ExpandingWidgetModel::basicRowHeight( const QModelIndex& idx_ ) const { QModelIndex idx(firstColumn(idx_)); ExpandingDelegate* delegate = dynamic_cast( treeView()->itemDelegate(idx) ); if( !delegate || !idx.isValid() ) { qCDebug( PLUGIN_QUICKOPEN ) << "ExpandingWidgetModel::basicRowHeight: Could not get delegate"; return 15; } return delegate->basicSizeHint( idx ).height(); } void ExpandingWidgetModel::placeExpandingWidget(const QModelIndex& idx_) { QModelIndex idx(firstColumn(idx_)); QWidget* w = 0; if( m_expandingWidgets.contains(idx) ) w = m_expandingWidgets[idx]; if( w && isExpanded(idx) ) { if( !idx.isValid() ) return; QRect rect = treeView()->visualRect(idx); if( !rect.isValid() || rect.bottom() < 0 || rect.top() >= treeView()->height() ) { //The item is currently not visible w->hide(); return; } QModelIndex rightMostIndex = idx; QModelIndex tempIndex = idx; while( (tempIndex = rightMostIndex.sibling(rightMostIndex.row(), rightMostIndex.column()+1)).isValid() ) rightMostIndex = tempIndex; QRect rightMostRect = treeView()->visualRect(rightMostIndex); //Find out the basic height of the row rect.setLeft( rect.left() + 20 ); rect.setRight( rightMostRect.right() - 5 ); //These offsets must match exactly those used in KateCompletionDeleage::sizeHint() rect.setTop( rect.top() + basicRowHeight(idx) + 5 ); rect.setHeight( w->height() ); if( w->parent() != treeView()->viewport() || w->geometry() != rect || !w->isVisible() ) { w->setParent( treeView()->viewport() ); w->setGeometry(rect); w->show(); } } } void ExpandingWidgetModel::placeExpandingWidgets() { for( QMap >::const_iterator it = m_expandingWidgets.constBegin(); it != m_expandingWidgets.constEnd(); ++it ) { placeExpandingWidget(it.key()); } } int ExpandingWidgetModel::expandingWidgetsHeight() const { int sum = 0; for( QMap >::const_iterator it = m_expandingWidgets.constBegin(); it != m_expandingWidgets.constEnd(); ++it ) { if( isExpanded(it.key() ) && (*it) ) sum += (*it)->height(); } return sum; } QWidget* ExpandingWidgetModel::expandingWidget(const QModelIndex& idx_) const { QModelIndex idx(firstColumn(idx_)); if( m_expandingWidgets.contains(idx) ) return m_expandingWidgets[idx]; else return 0; } void ExpandingWidgetModel::cacheIcons() const { if( m_expandedIcon.isNull() ) - m_expandedIcon = KIconLoader::global()->loadIcon("arrow-down", KIconLoader::Small, 10); + m_expandedIcon = KIconLoader::global()->loadIcon(QStringLiteral("arrow-down"), KIconLoader::Small, 10); if( m_collapsedIcon.isNull() ) - m_collapsedIcon = KIconLoader::global()->loadIcon("arrow-right", KIconLoader::Small, 10); + m_collapsedIcon = KIconLoader::global()->loadIcon(QStringLiteral("arrow-right"), KIconLoader::Small, 10); } QList mergeCustomHighlighting( int leftSize, const QList& left, int rightSize, const QList& right ) { QList ret = left; if( left.isEmpty() ) { ret << QVariant(0); ret << QVariant(leftSize); ret << QTextFormat(QTextFormat::CharFormat); } if( right.isEmpty() ) { ret << QVariant(leftSize); ret << QVariant(rightSize); ret << QTextFormat(QTextFormat::CharFormat); } else { QList::const_iterator it = right.constBegin(); while( it != right.constEnd() ) { { QList::const_iterator testIt = it; for(int a = 0; a < 2; a++) { ++testIt; if(testIt == right.constEnd()) { qWarning() << "Length of input is not multiple of 3"; break; } } } ret << QVariant( (*it).toInt() + leftSize ); ++it; ret << QVariant( (*it).toInt() ); ++it; ret << *it; if(!(*it).value().isValid()) qCDebug( PLUGIN_QUICKOPEN ) << "Text-format is invalid"; ++it; } } return ret; } //It is assumed that between each two strings, one space is inserted QList mergeCustomHighlighting( QStringList strings, QList highlights, int grapBetweenStrings ) { if(strings.isEmpty()) { qWarning() << "List of strings is empty"; return QList(); } if(highlights.isEmpty()) { qWarning() << "List of highlightings is empty"; return QList(); } if(strings.count() != highlights.count()) { qWarning() << "Length of string-list is " << strings.count() << " while count of highlightings is " << highlights.count() << ", should be same"; return QList(); } //Merge them together QString totalString = strings[0]; QVariantList totalHighlighting = highlights[0]; strings.pop_front(); highlights.pop_front(); while( !strings.isEmpty() ) { totalHighlighting = mergeCustomHighlighting( totalString.length(), totalHighlighting, strings[0].length(), highlights[0] ); totalString += strings[0]; for(int a = 0; a < grapBetweenStrings; a++) totalString += ' '; strings.pop_front(); highlights.pop_front(); } //Combine the custom-highlightings return totalHighlighting; } diff --git a/plugins/quickopen/projectfilequickopen.cpp b/plugins/quickopen/projectfilequickopen.cpp index d9e14c2b0..b773faafb 100644 --- a/plugins/quickopen/projectfilequickopen.cpp +++ b/plugins/quickopen/projectfilequickopen.cpp @@ -1,362 +1,362 @@ /* 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 "projectfilequickopen.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../openwith/iopenwith.h" using namespace KDevelop; namespace { QSet openFiles() { QSet openFiles; const QList& docs = ICore::self()->documentController()->openDocuments(); openFiles.reserve(docs.size()); foreach( IDocument* doc, docs ) { openFiles << IndexedString(doc->url()); } return openFiles; } QString iconNameForUrl(const IndexedString& url) { if (url.isEmpty()) { return QStringLiteral("tab-duplicate"); } ProjectBaseItem* item = ICore::self()->projectController()->projectModel()->itemForPath(url); if (item) { return item->iconName(); } return QStringLiteral("unknown"); } } ProjectFileData::ProjectFileData( const ProjectFile& file ) : m_file(file) { } QString ProjectFileData::text() const { return m_file.projectPath.relativePath(m_file.path); } QString ProjectFileData::htmlDescription() const { return "" + i18nc("%1: project name", "Project %1", project()) + ""; } bool ProjectFileData::execute( QString& filterText ) { const QUrl url = m_file.path.toUrl(); IOpenWith::openFiles(QList() << url); QString path; uint lineNumber; if (extractLineNumber(filterText, path, lineNumber)) { IDocument* doc = ICore::self()->documentController()->documentForUrl(url); if (doc) { doc->setCursorPosition(KTextEditor::Cursor(lineNumber - 1, 0)); } } return true; } bool ProjectFileData::isExpandable() const { return true; } QList ProjectFileData::highlighting() const { QTextCharFormat boldFormat; boldFormat.setFontWeight(QFont::Bold); QTextCharFormat normalFormat; QString txt = text(); QList ret; int fileNameLength = m_file.path.lastPathSegment().length(); ret << 0; ret << txt.length() - fileNameLength; ret << QVariant(normalFormat); ret << txt.length() - fileNameLength; ret << fileNameLength; ret << QVariant(boldFormat); return ret; } QWidget* ProjectFileData::expandingWidget() const { const QUrl url = m_file.path.toUrl(); DUChainReadLocker lock; ///Find a du-chain for the document QList contexts = DUChain::self()->chainsForDocument(url); ///Pick a non-proxy context TopDUContext* chosen = 0; foreach( TopDUContext* ctx, contexts ) { if( !(ctx->parsingEnvironmentFile() && ctx->parsingEnvironmentFile()->isProxyContext()) ) { chosen = ctx; } } if( chosen ) { return chosen->createNavigationWidget(0, 0, "" + i18nc("%1: project name", "Project %1", project()) + ""); } else { QTextBrowser* ret = new QTextBrowser(); ret->resize(400, 100); ret->setText( "" + i18nc("%1: project name", "Project %1", project()) + "
" + i18n("Not parsed yet") + "
"); return ret; } return 0; } QIcon ProjectFileData::icon() const { const QString& iconName = iconNameForUrl(m_file.indexedPath); /** * FIXME: Move this cache into a more central place and reuse it elsewhere. * The project model e.g. could reuse this as well. * * Note: We cache here since otherwise displaying and esp. scrolling * in a large list of quickopen items becomes very slow. */ static QHash iconCache; QHash< QString, QPixmap >::const_iterator it = iconCache.constFind(iconName); if (it != iconCache.constEnd()) { return it.value(); } const QPixmap& pixmap = KIconLoader::global()->loadIcon(iconName, KIconLoader::Small); iconCache.insert(iconName, pixmap); return pixmap; } QString ProjectFileData::project() const { const IProject* project = ICore::self()->projectController()->findProjectForUrl(m_file.path.toUrl()); if (project) { return project->name(); } else { return i18n("none"); } } BaseFileDataProvider::BaseFileDataProvider() { } void BaseFileDataProvider::setFilterText( const QString& text ) { QString path(text); uint lineNumber; extractLineNumber(text, path, lineNumber); if ( path.startsWith(QLatin1String("./")) || path.startsWith(QLatin1String("../")) ) { // assume we want to filter relative to active document's url IDocument* doc = ICore::self()->documentController()->activeDocument(); if (doc) { path = Path(Path(doc->url()).parent(), path).pathOrUrl(); } } setFilter( path.split('/', QString::SkipEmptyParts) ); } uint BaseFileDataProvider::itemCount() const { return filteredItems().count(); } uint BaseFileDataProvider::unfilteredItemCount() const { return items().count(); } QuickOpenDataPointer BaseFileDataProvider::data(uint row) const { return QuickOpenDataPointer(new ProjectFileData( filteredItems().at(row) )); } ProjectFileDataProvider::ProjectFileDataProvider() { auto projectController = ICore::self()->projectController(); connect(projectController, &IProjectController::projectClosing, this, &ProjectFileDataProvider::projectClosing); connect(projectController, &IProjectController::projectOpened, this, &ProjectFileDataProvider::projectOpened); - foreach (const auto& project, projectController->projects()) { + foreach (const auto project, projectController->projects()) { projectOpened(project); } } void ProjectFileDataProvider::projectClosing( IProject* project ) { foreach(ProjectFileItem* file, KDevelop::allFiles(project->projectItem())) { fileRemovedFromSet(file); } } void ProjectFileDataProvider::projectOpened( IProject* project ) { const int processAfter = 1000; int processed = 0; foreach(ProjectFileItem* file, KDevelop::allFiles(project->projectItem())) { fileAddedToSet(file); if (++processed == processAfter) { // prevent UI-lockup when a huge project was imported QApplication::processEvents(); processed = 0; } } connect(project, &IProject::fileAddedToSet, this, &ProjectFileDataProvider::fileAddedToSet); connect(project, &IProject::fileRemovedFromSet, this, &ProjectFileDataProvider::fileRemovedFromSet); } void ProjectFileDataProvider::fileAddedToSet( ProjectFileItem* file ) { ProjectFile f; f.projectPath = file->project()->path(); f.path = file->path(); f.indexedPath = file->indexedPath(); f.outsideOfProject = !f.projectPath.isParentOf(f.path); auto it = std::lower_bound(m_projectFiles.begin(), m_projectFiles.end(), f); if (it == m_projectFiles.end() || it->path != f.path) { m_projectFiles.insert(it, f); } } void ProjectFileDataProvider::fileRemovedFromSet( ProjectFileItem* file ) { ProjectFile item; item.path = file->path(); // fast-path for non-generated files // NOTE: figuring out whether something is generated is expensive... and since // generated files are rare we apply this two-step algorithm here auto it = std::lower_bound(m_projectFiles.begin(), m_projectFiles.end(), item); if (it != m_projectFiles.end() && !(item < *it)) { m_projectFiles.erase(it); return; } // last try: maybe it was generated item.outsideOfProject = true; it = std::lower_bound(m_projectFiles.begin(), m_projectFiles.end(), item); if (it != m_projectFiles.end() && !(item < *it)) { m_projectFiles.erase(it); return; } } void ProjectFileDataProvider::reset() { clearFilter(); QList projectFiles = m_projectFiles; const auto& open = openFiles(); for(QList::iterator it = projectFiles.begin(); it != projectFiles.end();) { if (open.contains(it->indexedPath)) { it = projectFiles.erase(it); } else { ++it; } } setItems(projectFiles); } QSet ProjectFileDataProvider::files() const { QSet ret; foreach( IProject* project, ICore::self()->projectController()->projects() ) ret += project->fileSet(); return ret - openFiles(); } void OpenFilesDataProvider::reset() { clearFilter(); IProjectController* projCtrl = ICore::self()->projectController(); IDocumentController* docCtrl = ICore::self()->documentController(); const QList& docs = docCtrl->openDocuments(); QList currentFiles; currentFiles.reserve(docs.size()); foreach( IDocument* doc, docs ) { ProjectFile f; f.path = Path(doc->url()); IProject* project = projCtrl->findProjectForUrl(doc->url()); if (project) { f.projectPath = project->path(); } currentFiles << f; } std::sort(currentFiles.begin(), currentFiles.end()); setItems(currentFiles); } QSet OpenFilesDataProvider::files() const { return openFiles(); } diff --git a/plugins/quickopen/projectitemquickopen.cpp b/plugins/quickopen/projectitemquickopen.cpp index a9a57f501..1a5a17b58 100644 --- a/plugins/quickopen/projectitemquickopen.cpp +++ b/plugins/quickopen/projectitemquickopen.cpp @@ -1,363 +1,363 @@ /* 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 using namespace KDevelop; struct SubstringCache { 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 ) : 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; }; ProjectItemDataProvider::ProjectItemDataProvider( KDevelop::IQuickOpen* quickopen ) : m_quickopen(quickopen) { } void ProjectItemDataProvider::setFilterText( const QString& text ) { m_addedItems.clear(); - QStringList search(text.split("::", QString::SkipEmptyParts)); + 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(); } uint a = pos - filteredItemOffset; if(a > (uint)m_filteredItems.size()) { return KDevelop::QuickOpenDataPointer(); } QList ret; KDevelop::DUChainReadLocker lock( DUChain::lock() ); TopDUContext* ctx = DUChainUtils::standardContextForUrl(m_filteredItems[a].m_file.toUrl()); if(ctx) { QList decls = ctx->findDeclarations(m_filteredItems[a].m_id, CursorInRevision::invalid(), AbstractType::Ptr(), 0, 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() != m_filteredItems[a].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_project = .. @todo fill ret << QuickOpenDataPointer(new DUChainItemData(item)); } if(decls.isEmpty()) { DUChainItem item; item.m_text = m_filteredItems[a].m_id.toString(); //item.m_project = .. @todo fill 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/projectitemquickopen.h b/plugins/quickopen/projectitemquickopen.h index fa1a834e8..7da59c3f7 100644 --- a/plugins/quickopen/projectitemquickopen.h +++ b/plugins/quickopen/projectitemquickopen.h @@ -1,84 +1,85 @@ /* 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. */ #ifndef PROJECT_ITEM_QUICKOPEN #define PROJECT_ITEM_QUICKOPEN #include "duchainitemquickopen.h" #include #include struct CodeModelViewItem { CodeModelViewItem() { } CodeModelViewItem(const KDevelop::IndexedString& file, const KDevelop::QualifiedIdentifier& id) : m_file(file) , m_id(id) { } KDevelop::IndexedString m_file; KDevelop::QualifiedIdentifier m_id; }; Q_DECLARE_TYPEINFO(CodeModelViewItem, Q_MOVABLE_TYPE); typedef QMap > AddedItems; class ProjectItemDataProvider : public KDevelop::QuickOpenDataProviderBase { + Q_OBJECT public: enum ItemTypes { NoItems = 0, Classes = 1, Functions = 2, AllItemTypes = Classes + Functions }; explicit ProjectItemDataProvider( KDevelop::IQuickOpen* quickopen ); virtual void enableData( const QStringList& items, const QStringList& scopes ) override; virtual void setFilterText( const QString& text ) override; virtual QList data( uint start, uint end ) const; virtual void reset() override; virtual uint itemCount() const override; virtual uint unfilteredItemCount() const override; static QStringList supportedItemTypes(); private: KDevelop::QuickOpenDataPointer data( uint pos ) const override; ItemTypes m_itemTypes; KDevelop::IQuickOpen* m_quickopen; QSet m_files; QVector m_currentItems; QString m_currentFilter; QVector m_filteredItems; //Maps positions to the additional items behind those positions //Here additional inserted items are stored, that are not represented in m_filteredItems. //This is needed at least to also show overloaded function declarations mutable AddedItems m_addedItems; }; #endif diff --git a/plugins/quickopen/quickopenplugin.cpp b/plugins/quickopen/quickopenplugin.cpp index 509623df0..9c99006b3 100644 --- a/plugins/quickopen/quickopenplugin.cpp +++ b/plugins/quickopen/quickopenplugin.cpp @@ -1,1550 +1,1551 @@ /* * 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 #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 "expandingtree/expandingdelegate.h" #include "ui_quickopen.h" #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 "Quickopen"; + 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 QuickOpenDelegate : public ExpandingDelegate { + Q_OBJECT public: QuickOpenDelegate(ExpandingWidgetModel* model, QObject* parent = 0L) : 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 ); } }; class OutlineFilter : public DUChainUtils::DUChainItemFilter { public: enum OutlineMode { Functions, FunctionsAndClasses }; 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 0; KDevelop::DUChainReadLocker lock( DUChain::lock() ); return DUChainUtils::declarationForDefinition( DUChainUtils::itemUnderCursor( view->document()->url(), KTextEditor::Cursor(view->cursorPosition()) ) ); } ///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 0; KDevelop::DUChainReadLocker lock( DUChain::lock() ); TopDUContext* ctx = DUChainUtils::standardContextForUrl(view->document()->url()); if(!ctx) return 0; KTextEditor::Cursor cursor(view->cursorPosition()); DUContext* subCtx = ctx->findContext(ctx->transformToLocalRevision(cursor)); while(subCtx && !subCtx->owner()) subCtx = subCtx->parentContext(); Declaration* definition = 0; if(!subCtx || !subCtx->owner()) definition = DUChainUtils::declarationInLine(cursor, ctx); else definition = subCtx->owner(); if(!definition) return 0; 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())); } void QuickOpenWidget::showStandardButtons(bool show) { if(show) { o.okButton->show(); o.cancelButton->show(); }else{ o.okButton->hide(); o.cancelButton->hide(); } } QuickOpenWidget::QuickOpenWidget( QString title, QuickOpenModel* model, const QStringList& initialItems, const QStringList& initialScopes, bool listOnly, bool noSearchField ) : m_model(model), m_expandedTemporary(false) { m_filterTimer.setSingleShot(true); connect(&m_filterTimer, &QTimer::timeout, this, &QuickOpenWidget::applyFilter); Q_UNUSED( title ); o.setupUi( this ); o.list->header()->hide(); o.list->setRootIsDecorated( false ); o.list->setVerticalScrollMode( QAbstractItemView::ScrollPerItem ); connect(o.list->verticalScrollBar(), &QScrollBar::valueChanged, m_model, &QuickOpenModel::placeExpandingWidgets); o.searchLine->setFocus(); o.list->setItemDelegate( new QuickOpenDelegate( m_model, o.list ) ); if(!listOnly) { QStringList allTypes = m_model->allTypes(); QStringList allScopes = m_model->allScopes(); QMenu* itemsMenu = new QMenu; 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); } o.itemsButton->setMenu(itemsMenu); QMenu* scopesMenu = new QMenu; 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); } o.scopesButton->setMenu(scopesMenu); }else{ o.list->setFocusPolicy(Qt::StrongFocus); o.scopesButton->hide(); o.itemsButton->hide(); o.label->hide(); o.label_2->hide(); } showSearchField(!noSearchField); o.okButton->hide(); o.cancelButton->hide(); o.searchLine->installEventFilter( this ); o.list->installEventFilter( this ); o.list->setFocusPolicy(Qt::NoFocus); o.scopesButton->setFocusPolicy(Qt::NoFocus); o.itemsButton->setFocusPolicy(Qt::NoFocus); connect( o.searchLine, &QLineEdit::textChanged, this, &QuickOpenWidget::textChanged ); connect( o.list, &ExpandingTree::doubleClicked, this, &QuickOpenWidget::doubleClicked ); connect(o.okButton, &QPushButton::clicked, this, &QuickOpenWidget::accept); connect(o.okButton, &QPushButton::clicked, this, &QuickOpenWidget::ready); connect(o.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::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 o.list->setColumnWidth( 0, 20 ); } void QuickOpenWidget::setAlternativeSearchField(QLineEdit* alterantiveSearchField) { o.searchLine = alterantiveSearchField; o.searchLine->installEventFilter( this ); connect( o.searchLine, &QLineEdit::textChanged, this, &QuickOpenWidget::textChanged ); } void QuickOpenWidget::showSearchField(bool b) { if(b){ o.searchLine->show(); o.searchLabel->show(); }else{ o.searchLine->hide(); o.searchLabel->hide(); } } void QuickOpenWidget::prepareShow() { o.list->setModel( 0 ); o.list->setVerticalScrollMode(QAbstractItemView::ScrollPerItem); m_model->setTreeView( o.list ); o.list->setModel( m_model ); m_filterTimer.stop(); m_filter = QString(); if (!m_preselectedText.isEmpty()) { o.searchLine->setText(m_preselectedText); o.searchLine->selectAll(); } m_model->restart(false); connect( o.list->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &QuickOpenWidget::callRowSelected ); connect( o.list->selectionModel(), &QItemSelectionModel::selectionChanged, this, &QuickOpenWidget::callRowSelected ); } void QuickOpenWidgetDialog::run() { m_widget->prepareShow(); m_dialog->show(); } QuickOpenWidget::~QuickOpenWidget() { m_model->setTreeView( 0 ); } 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 = qobject_cast(sender())) { QMenu* menu = qobject_cast(action->parentWidget()); if(menu) { menu->show(); menu->setActiveAction(action); } } QStringList checkedItems; if(o.itemsButton->menu()) { foreach( QObject* obj, o.itemsButton->menu()->children() ) { QAction* box = qobject_cast( obj ); if( box ) { if( box->isChecked() ) checkedItems << box->text().remove('&'); } } - o.itemsButton->setText(checkedItems.join(", ")); + o.itemsButton->setText(checkedItems.join(QStringLiteral(", "))); } QStringList checkedScopes; if(o.scopesButton->menu()) { foreach( QObject* obj, o.scopesButton->menu()->children() ) { QAction* box = qobject_cast( obj ); if( box ) { if( box->isChecked() ) checkedScopes << box->text().remove('&'); } } - o.scopesButton->setText(checkedScopes.join(", ")); + o.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()); o.list->selectionModel()->setCurrentIndex( currentIndex, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows | QItemSelectionModel::Current ); callRowSelected(); } void QuickOpenWidget::callRowSelected() { QModelIndex currentIndex = o.list->selectionModel()->currentIndex(); if( currentIndex.isValid() ) m_model->rowSelected( currentIndex ); else qCDebug(PLUGIN_QUICKOPEN) << "current index is not valid"; } void QuickOpenWidget::accept() { QString filterText = o.searchLine->text(); m_model->execute( o.list->currentIndex(), filterText ); } void QuickOpenWidget::doubleClicked ( const QModelIndex & index ) { // crash guard: https://bugs.kde.org/show_bug.cgi?id=297178 o.list->setCurrentIndex(index); QMetaObject::invokeMethod(this, "accept", Qt::QueuedConnection); QMetaObject::invokeMethod(this, "ready", Qt::QueuedConnection); } 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 = o.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) { m_hadNoCommandSinceAlt = true; //Expand QModelIndex row = o.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(o.list, new QKeyEvent(QEvent::KeyPress, Qt::Key_Down, Qt::NoModifier)); QCoreApplication::sendEvent(o.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(o.list, new QKeyEvent(QEvent::KeyPress, Qt::Key_Up, Qt::NoModifier)); QCoreApplication::sendEvent(o.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(o.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 == o.list ) return false; QApplication::sendEvent( o.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(o.list->selectionModel()->currentIndex()); if( KDevelop::QuickOpenEmbeddedWidgetInterface* interface = dynamic_cast( w ) ){ interface->previous(); return true; } } else { QModelIndex row = o.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(o.list->selectionModel()->currentIndex()); if( KDevelop::QuickOpenEmbeddedWidgetInterface* interface = dynamic_cast( w ) ){ interface->next(); return true; } } else { QModelIndex row = o.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(o.list->selectionModel()->currentIndex()); if( KDevelop::QuickOpenEmbeddedWidgetInterface* interface = dynamic_cast( w ) ){ interface->accept(); return true; } } else { QString filterText = o.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( o.list->currentIndex(), filterText ) ) { if(!stillExists) return true; if(!(keyEvent->modifiers() & Qt::ShiftModifier)) emit ready(); } else { //Maybe the filter-text was changed: if( filterText != o.searchLine->text() ) { o.searchLine->setText( filterText ); } } } return true; } } } return false; } QuickOpenLineEdit* QuickOpenPlugin::quickOpenLine(QString name) { QList< QuickOpenLineEdit* > lines = ICore::self()->uiController()->activeMainWindow()->findChildren(name); foreach(QuickOpenLineEdit* line, lines) { if(line->isVisible()) { return line; } } return 0; } static QuickOpenPlugin* staticQuickOpenPlugin = 0; QuickOpenPlugin* QuickOpenPlugin::self() { return staticQuickOpenPlugin; } void QuickOpenPlugin::createActionsForMainWindow(Sublime::MainWindow* /*window*/, QString& xmlFile, KActionCollection& actions) { - xmlFile ="kdevquickopen.rc"; + xmlFile = QStringLiteral("kdevquickopen.rc"); - QAction* quickOpen = actions.addAction("quick_open"); + QAction* quickOpen = actions.addAction(QStringLiteral("quick_open")); quickOpen->setText( i18n("&Quick Open") ); - quickOpen->setIcon( QIcon::fromTheme("quickopen") ); + 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("quick_open_file"); + QAction* quickOpenFile = actions.addAction(QStringLiteral("quick_open_file")); quickOpenFile->setText( i18n("Quick Open &File") ); - quickOpenFile->setIcon( QIcon::fromTheme("quickopen-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("quick_open_class"); + QAction* quickOpenClass = actions.addAction(QStringLiteral("quick_open_class")); quickOpenClass->setText( i18n("Quick Open &Class") ); - quickOpenClass->setIcon( QIcon::fromTheme("quickopen-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("quick_open_function"); + QAction* quickOpenFunction = actions.addAction(QStringLiteral("quick_open_function")); quickOpenFunction->setText( i18n("Quick Open &Function") ); - quickOpenFunction->setIcon( QIcon::fromTheme("quickopen-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("quick_open_already_open"); + QAction* quickOpenAlreadyOpen = actions.addAction(QStringLiteral("quick_open_already_open")); quickOpenAlreadyOpen->setText( i18n("Quick Open &Already Open File") ); - quickOpenAlreadyOpen->setIcon( QIcon::fromTheme("quickopen-file") ); + quickOpenAlreadyOpen->setIcon( QIcon::fromTheme(QStringLiteral("quickopen-file")) ); connect(quickOpenAlreadyOpen, &QAction::triggered, this, &QuickOpenPlugin::quickOpenOpenFile); - QAction* quickOpenDocumentation = actions.addAction("quick_open_documentation"); + QAction* quickOpenDocumentation = actions.addAction(QStringLiteral("quick_open_documentation")); quickOpenDocumentation->setText( i18n("Quick Open &Documentation") ); - quickOpenDocumentation->setIcon( QIcon::fromTheme("quickopen-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("quick_open_actions"); + 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("quick_open_jump_declaration"); + m_quickOpenDeclaration = actions.addAction(QStringLiteral("quick_open_jump_declaration")); m_quickOpenDeclaration->setText( i18n("Jump to Declaration") ); - m_quickOpenDeclaration->setIcon( QIcon::fromTheme("go-jump-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("quick_open_jump_definition"); + m_quickOpenDefinition = actions.addAction(QStringLiteral("quick_open_jump_definition")); m_quickOpenDefinition->setText( i18n("Jump to Definition") ); - m_quickOpenDefinition->setIcon( QIcon::fromTheme("go-jump-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("quick_open_line", quickOpenLine); + actions.addAction(QStringLiteral("quick_open_line"), quickOpenLine); - QAction* quickOpenNextFunction = actions.addAction("quick_open_next_function"); + 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("quick_open_prev_function"); + 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("quick_open_outline"); + 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("kdevquickopen", parent) + : KDevelop::IPlugin(QStringLiteral("kdevquickopen"), parent) { staticQuickOpenPlugin = this; KDEV_USE_EXTENSION_INTERFACE( KDevelop::IQuickOpen ) m_model = new QuickOpenModel( 0 ); 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()->o.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 0; QUrl url = ICore::self()->documentController()->activeDocument()->url(); const auto languages = ICore::self()->languageController()->languagesForUrl(url); - foreach (const auto& language, languages) { + foreach (const auto language, languages) { QWidget* w = language->specialLanguageObjectNavigationWidget(url, KTextEditor::Cursor(view->cursorPosition()) ); if(w) return w; } return 0; } 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) { + 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 = 0; 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 = 0; int distanceBefore = INT_MIN; Declaration *nearestDeclAfter = 0; 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(0), cursorDecl(0), model(0) { } 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(0); 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) { dialog->widget()->o.list->setCurrentIndex( model->index(num,0,QModelIndex()) ); dialog->widget()->o.list->scrollTo( model->index(num,0,QModelIndex()), QAbstractItemView::PositionAtCenter ); } ++num; } } } QPointer dialog; Declaration* cursorDecl; QList items; QuickOpenModel* model; }; class OutlineQuickopenWidgetCreator : public QuickOpenWidgetCreator { public: OutlineQuickopenWidgetCreator(QStringList /*scopes*/, QStringList /*items*/) : m_creator(0) { } ~OutlineQuickopenWidgetCreator() override { delete m_creator; } QuickOpenWidget* createWidget() override { delete m_creator; m_creator = new CreateOutlineDialog; m_creator->start(); if(!m_creator->dialog) return 0; m_creator->dialog->deleteLater(); return m_creator->dialog->widget(); } void widgetShown() override { if(m_creator) { m_creator->finish(); delete m_creator; m_creator = 0; } } QString objectNameForLine() override { - return "Outline"; + return QStringLiteral("Outline"); } CreateOutlineDialog* m_creator; }; void QuickOpenPlugin::quickOpenNavigateFunctions() { CreateOutlineDialog create; create.start(); if(!create.dialog) return; m_currentWidgetHandler = create.dialog; - QuickOpenLineEdit* line = quickOpenLine("Outline"); + 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(0), 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(0, 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->o.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: qCDebug(PLUGIN_QUICKOPEN) << "closing because of window activation"; deactivate(); 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(); } break; default: break; } return false; } void QuickOpenLineEdit::activate() { qCDebug(PLUGIN_QUICKOPEN) << "activating"; - setText(""); - setStyleSheet(""); + 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 = 0; qApp->removeEventFilter(this); } void QuickOpenLineEdit::checkFocus() { qCDebug(PLUGIN_QUICKOPEN) << "checking focus" << m_widget; if(m_widget) { if(isVisible() && !isHidden()) setFocus(); else 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" // kate: space-indent on; indent-width 2; tab-width 4; replace-tabs on; auto-insert-doxygen on diff --git a/plugins/quickopen/quickopenplugin.h b/plugins/quickopen/quickopenplugin.h index 8818d0cae..12509f944 100644 --- a/plugins/quickopen/quickopenplugin.h +++ b/plugins/quickopen/quickopenplugin.h @@ -1,245 +1,245 @@ /* * 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_QUICKOPENPLUGIN_H #define KDEVPLATFORM_PLUGIN_QUICKOPENPLUGIN_H #include #include #include #include #include #include #include #include #include "ui_quickopen.h" class QAction; namespace KTextEditor { class Cursor; } class QuickOpenModel; class QuickOpenWidget; class QuickOpenLineEdit; class QuickOpenPlugin : public KDevelop::IPlugin, public KDevelop::IQuickOpen { Q_OBJECT Q_INTERFACES( KDevelop::IQuickOpen ) public: explicit QuickOpenPlugin( QObject *parent, const QVariantList & = QVariantList() ); ~QuickOpenPlugin() override; static QuickOpenPlugin* self(); // KDevelop::Plugin methods void unload() override; KDevelop::ContextMenuExtension contextMenuExtension(KDevelop::Context* context) override; enum ModelTypes { Files = 1, Functions = 2, Classes = 4, OpenFiles = 8, All = Files + Functions + Classes + OpenFiles }; /** * Shows the quickopen dialog with the specified Model-types * @param modes A combination of ModelTypes * */ void showQuickOpen( ModelTypes modes = All ); void showQuickOpen( const QStringList &items ) override; void registerProvider( const QStringList& scope, const QStringList& type, KDevelop::QuickOpenDataProviderBase* provider ) override; bool removeProvider( KDevelop::QuickOpenDataProviderBase* provider ) override; QSet fileSet() const override; //Frees the model by closing active quickopen dialoags, and retuns whether successful. bool freeModel(); void createActionsForMainWindow( Sublime::MainWindow* window, QString& xmlFile, KActionCollection& actions ) override; QuickOpenLineEdit* createQuickOpenLineWidget(); KDevelop::IQuickOpenLine* createQuickOpenLine(const QStringList& scopes, const QStringList& type, QuickOpenType kind) override; public slots: void quickOpen(); void quickOpenFile(); void quickOpenFunction(); void quickOpenClass(); void quickOpenDeclaration(); void quickOpenOpenFile(); void quickOpenDefinition(); void quickOpenNavigateFunctions(); void quickOpenDocumentation(); void quickOpenActions(); void previousFunction(); void nextFunction(); private slots: void storeScopes( const QStringList& ); void storeItems( const QStringList& ); private: friend class QuickOpenLineEdit; friend class StandardQuickOpenWidgetCreator; - QuickOpenLineEdit* quickOpenLine(QString name = "Quickopen"); + QuickOpenLineEdit* quickOpenLine(QString name = QStringLiteral("Quickopen")); enum FunctionJumpDirection { NextFunction, PreviousFunction }; void jumpToNearestFunction(FunctionJumpDirection direction); QPair specialObjectJumpPosition() const; QWidget* specialObjectNavigationWidget() const; bool jumpToSpecialObject(); void showQuickOpenWidget(const QStringList &items, const QStringList &scopes, bool preselectText); QuickOpenModel* m_model; class ProjectFileDataProvider* m_projectFileData; class ProjectItemDataProvider* m_projectItemData; class OpenFilesDataProvider* m_openFilesData; class DocumentationQuickOpenProvider* m_documentationItemData; class ActionsQuickOpenProvider* m_actionsItemData; QStringList lastUsedScopes; QStringList lastUsedItems; //We can only have one widget at a time, because we manipulate the model. QPointer m_currentWidgetHandler; QAction* m_quickOpenDeclaration; QAction* m_quickOpenDefinition; }; ///Will delete itself once the dialog is closed, so use QPointer when referencing it permanently class QuickOpenWidget : public QMenu { Q_OBJECT public: /** * @param initialItems List of items that should initially be enabled in the quickopen-list. If empty, all are enabled. * @param initialScopes List of scopes that should initially be enabled in the quickopen-list. If empty, all are enabled. * @param listOnly when this is true, the given items will be listed, but all filtering using checkboxes is disabled. * @param noSearchFied when this is true, no search-line is shown. * */ QuickOpenWidget( QString title, QuickOpenModel* model, const QStringList& initialItems, const QStringList& initialScopes, bool listOnly = false, bool noSearchField = false ); ~QuickOpenWidget() override; void setPreselectedText(const QString &text); void prepareShow(); void setAlternativeSearchField(QLineEdit* alterantiveSearchField); //Shows OK + Cancel. By default they are hidden void showStandardButtons(bool show); void showSearchField(bool show); signals: void scopesChanged( const QStringList& scopes ); void itemsChanged( const QStringList& scopes ); void ready(); private slots: void callRowSelected(); void updateTimerInterval( bool cheapFilterChange ); void accept(); void textChanged( const QString& str ); void updateProviders(); void doubleClicked ( const QModelIndex & index ); void applyFilter(); private: void showEvent(QShowEvent *) override; bool eventFilter ( QObject * watched, QEvent * event ) override; QuickOpenModel* m_model; bool m_expandedTemporary, m_hadNoCommandSinceAlt; QTime m_altDownTime; QString m_preselectedText; QTimer m_filterTimer; QString m_filter; public: Ui::QuickOpen o; friend class QuickOpenWidgetDialog; friend class QuickOpenPlugin; friend class QuickOpenLineEdit; }; class QuickOpenWidgetDialog : public QObject { Q_OBJECT public: QuickOpenWidgetDialog( QString title, QuickOpenModel* model, const QStringList& initialItems, const QStringList& initialScopes, bool listOnly = false, bool noSearchField = false ); ~QuickOpenWidgetDialog() override; ///Shows the dialog void run(); QuickOpenWidget* widget() const { return m_widget; } private: QDialog* m_dialog; //Warning: m_dialog is also the parent QuickOpenWidget* m_widget; }; class QuickOpenWidgetCreator; class QuickOpenLineEdit : public KDevelop::IQuickOpenLine { Q_OBJECT public: explicit QuickOpenLineEdit(QuickOpenWidgetCreator* creator) ; ~QuickOpenLineEdit() override ; bool insideThis(QObject* object); void showWithWidget(QuickOpenWidget* widget); void setDefaultText(const QString& text) override { m_defaultText = text; setPlaceholderText(m_defaultText); } private slots: void activate() ; void deactivate() ; void checkFocus(); void widgetDestroyed(QObject*); private: void focusInEvent(QFocusEvent* ev) override ; bool eventFilter(QObject* obj, QEvent* e) override ; void hideEvent(QHideEvent* ) override; QPointer m_widget; bool m_forceUpdate; QString m_defaultText; QuickOpenWidgetCreator* m_widgetCreator; }; #endif // KDEVPLATFORM_PLUGIN_QUICKOPENPLUGIN_H // kate: space-indent on; indent-width 2; tab-width 4; replace-tabs on; auto-insert-doxygen on diff --git a/plugins/quickopen/tests/quickopentestbase.cpp b/plugins/quickopen/tests/quickopentestbase.cpp index 0b0565ce6..e60ae84be 100644 --- a/plugins/quickopen/tests/quickopentestbase.cpp +++ b/plugins/quickopen/tests/quickopentestbase.cpp @@ -1,82 +1,82 @@ /* * Copyright Milian Wolff * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "quickopentestbase.h" #include #include #include #include #include #include using namespace KDevelop; QuickOpenTestBase::QuickOpenTestBase(Core::Setup setup_, QObject* parent) : QObject(parent) , core(0) , setup(setup_) , projectController(0) { qRegisterMetaType("QModelIndex"); } void QuickOpenTestBase::initTestCase() { AutoTestShell::init(); core = new TestCore; TestPluginController* pluginController = new TestPluginController(core); core->setPluginController(pluginController); TestCore::initialize(setup); projectController = new TestProjectController(core); delete core->projectController(); core->setProjectController(projectController); } void QuickOpenTestBase::cleanupTestCase() { TestCore::shutdown(); } void QuickOpenTestBase::cleanup() { projectController->closeAllProjects(); core->documentController()->closeAllDocuments(); } TestProject* getProjectWithFiles(int files) { TestProject* project = new TestProject; - ProjectFolderItem* foo = createChild(project->projectItem(), "foo"); - ProjectFolderItem* bar = createChild(foo, "bar"); + ProjectFolderItem* foo = createChild(project->projectItem(), QStringLiteral("foo")); + ProjectFolderItem* bar = createChild(foo, QStringLiteral("bar")); for(int i = 0; i < files; ++i) { createChild(bar, QString::number(i) + QLatin1String(".txt")); } return project; } QStringList items(const ProjectFileDataProvider& provider) { QStringList list; for(uint i = 0; i < provider.itemCount(); ++i) { list << provider.data(i)->text(); } return list; } diff --git a/plugins/quickopen/tests/test_quickopen.cpp b/plugins/quickopen/tests/test_quickopen.cpp index 333fbba25..a3e332d06 100644 --- a/plugins/quickopen/tests/test_quickopen.cpp +++ b/plugins/quickopen/tests/test_quickopen.cpp @@ -1,300 +1,300 @@ /* * Copyright Milian Wolff * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "test_quickopen.h" #include #include #include #include QTEST_MAIN(TestQuickOpen); using namespace KDevelop; TestQuickOpen::TestQuickOpen(QObject* parent) : QuickOpenTestBase(Core::Default, parent) { } void TestQuickOpen::testDuchainFilter() { using ItemList = QList; QFETCH(ItemList, items); QFETCH(QString, filter); QFETCH(ItemList, filtered); auto toStringList = [](const ItemList& items) { QStringList result; for ( const DUChainItem& item: items ) { result << item.m_text; } return result; }; TestFilter filterItems; filterItems.setItems(items); filterItems.setFilter(filter); QCOMPARE(toStringList(filterItems.filteredItems()), toStringList(filtered)); } void TestQuickOpen::testDuchainFilter_data() { using ItemList = QList; QTest::addColumn("items"); QTest::addColumn("filter"); QTest::addColumn("filtered"); auto i = [](const QString& text) { auto item = DUChainItem(); item.m_text = text; return item; }; auto items = ItemList() - << i("KTextEditor::Cursor") - << i("void KTextEditor::Cursor::explode()") - << i("QVector SomeNamespace::SomeClass::func(int)"); + << i(QStringLiteral("KTextEditor::Cursor")) + << i(QStringLiteral("void KTextEditor::Cursor::explode()")) + << i(QStringLiteral("QVector SomeNamespace::SomeClass::func(int)")); QTest::newRow("prefix") << items << "KTE" << ( ItemList() << items.at(0) << items.at(1) ); QTest::newRow("prefix_mismatch") << items << "KTEY" << ( ItemList() ); QTest::newRow("prefix_colon") << items << "KTE:" << ( ItemList() << items.at(0) << items.at(1) ); QTest::newRow("prefix_colon_mismatch") << items << "KTE:Y" << ( ItemList() ); QTest::newRow("prefix_colon_mismatch2") << items << "XKTE:" << ( ItemList() ); QTest::newRow("prefix_two_colon") << items << "KTE::" << ( ItemList() << items.at(0) << items.at(1) ); QTest::newRow("prefix_two_colon_mismatch") << items << "KTE::Y" << ( ItemList() ); QTest::newRow("prefix_two_colon_mismatch2") << items << "XKTE::" << ( ItemList() ); QTest::newRow("suffix") << items << "Curs" << ( ItemList() << items.at(0) << items.at(1) ); QTest::newRow("suffix2") << items << "curs" << ( ItemList() << items.at(0) << items.at(1) ); QTest::newRow("mid") << items << "SomeClass" << ( ItemList() << items.at(2) ); QTest::newRow("mid_abbrev") << items << "SClass" << ( ItemList() << items.at(2) ); } void TestQuickOpen::testAbbreviations() { QFETCH(QStringList, items); QFETCH(QString, filter); QFETCH(QStringList, filtered); PathTestFilter filterItems; filterItems.setItems(items); filterItems.setFilter(filter.split('/', QString::SkipEmptyParts)); QCOMPARE(QStringList(filterItems.filteredItems()), filtered); } void TestQuickOpen::testAbbreviations_data() { QTest::addColumn("items"); QTest::addColumn("filter"); QTest::addColumn("filtered"); const QStringList items = QStringList() - << "/foo/bar/caz/a.h" - << "/KateThing/CMakeLists.txt" - << "/FooBar/FooBar/Footestfoo.h"; + << QStringLiteral("/foo/bar/caz/a.h") + << QStringLiteral("/KateThing/CMakeLists.txt") + << QStringLiteral("/FooBar/FooBar/Footestfoo.h"); QTest::newRow("path_segments") << items << "fbc" << ( QStringList() ); QTest::newRow("path_segment_abbrev") << items << "cmli" << ( QStringList() << items.at(1) ); QTest::newRow("path_segment_old") << items << "kate/cmake" << ( QStringList() << items.at(1) ); QTest::newRow("path_segment_multi_mixed") << items << "ftfoo.h" << ( QStringList() << items.at(2) ); } void TestQuickOpen::testSorting() { QFETCH(QStringList, items); QFETCH(QString, filter); QFETCH(QStringList, filtered); PathTestFilter filterItems; filterItems.setItems(items); filterItems.setFilter(filter.split('/', QString::SkipEmptyParts)); QEXPECT_FAIL("bar7", "empty parts are skipped", Continue); QCOMPARE(QStringList(filterItems.filteredItems()), filtered); } void TestQuickOpen::testSorting_data() { QTest::addColumn("items"); QTest::addColumn("filter"); QTest::addColumn("filtered"); const QStringList items = QStringList() - << "/foo/a.h" - << "/foo/ab.h" - << "/foo/bc.h" - << "/bar/a.h"; + << QStringLiteral("/foo/a.h") + << QStringLiteral("/foo/ab.h") + << QStringLiteral("/foo/bc.h") + << QStringLiteral("/bar/a.h"); { QTest::newRow("no-filter") << items << QString() << items; } { - const QStringList filtered = QStringList() << "/bar/a.h"; + const QStringList filtered = QStringList() << QStringLiteral("/bar/a.h"); QTest::newRow("bar1") << items << QStringLiteral("bar") << filtered; QTest::newRow("bar2") << items << QStringLiteral("/bar") << filtered; QTest::newRow("bar3") << items << QStringLiteral("/bar/") << filtered; QTest::newRow("bar4") << items << QStringLiteral("bar/") << filtered; QTest::newRow("bar5") << items << QStringLiteral("ar/") << filtered; QTest::newRow("bar6") << items << QStringLiteral("r/") << filtered; QTest::newRow("bar7") << items << QStringLiteral("b/") << filtered; QTest::newRow("bar8") << items << QStringLiteral("b/a") << filtered; QTest::newRow("bar9") << items << QStringLiteral("b/a.h") << filtered; QTest::newRow("bar10") << items << QStringLiteral("b/a.") << filtered; } { - const QStringList filtered = QStringList() << "/foo/a.h" << "/foo/ab.h"; + const QStringList filtered = QStringList() << QStringLiteral("/foo/a.h") << QStringLiteral("/foo/ab.h"); QTest::newRow("foo_a1") << items << QStringLiteral("foo/a") << filtered; QTest::newRow("foo_a2") << items << QStringLiteral("/f/a") << filtered; } { // now matches ab.h too because of abbreviation matching, but should be sorted last - const QStringList filtered = QStringList() << "/foo/a.h" << "/bar/a.h" << "/foo/ab.h"; + const QStringList filtered = QStringList() << QStringLiteral("/foo/a.h") << QStringLiteral("/bar/a.h") << QStringLiteral("/foo/ab.h"); QTest::newRow("a_h") << items << QStringLiteral("a.h") << filtered; } { - const QStringList base = QStringList() << "/foo/a_test" << "/foo/test_b_1" << "/foo/test_b"; - const QStringList sorted = QStringList() << "/foo/test_b" << "/foo/test_b_1"; + const QStringList base = QStringList() << QStringLiteral("/foo/a_test") << QStringLiteral("/foo/test_b_1") << QStringLiteral("/foo/test_b"); + const QStringList sorted = QStringList() << QStringLiteral("/foo/test_b") << QStringLiteral("/foo/test_b_1"); QTest::newRow("prefer_exact") << base << QStringLiteral("test_b") << sorted; } { // from commit: 769491f06a4560a4798592ff060675ffb0d990a6 - const QString file = "/myProject/someStrangePath/anItem.cpp"; - const QStringList base = QStringList() << "/foo/a" << file; + const QString file = QStringLiteral("/myProject/someStrangePath/anItem.cpp"); + const QStringList base = QStringList() << QStringLiteral("/foo/a") << file; const QStringList filtered = QStringList() << file; QTest::newRow("strange") << base << QStringLiteral("strange/item") << filtered; } { - const QStringList base = QStringList() << "/foo/a_test" << "/foo/test_b_1" - << "/foo/test_b" << "/foo/test/a"; - const QStringList sorted = QStringList() << "/foo/test_b_1" << "/foo/test_b" - << "/foo/a_test" << "/foo/test/a"; + const QStringList base = QStringList() << QStringLiteral("/foo/a_test") << QStringLiteral("/foo/test_b_1") + << QStringLiteral("/foo/test_b") << QStringLiteral("/foo/test/a"); + const QStringList sorted = QStringList() << QStringLiteral("/foo/test_b_1") << QStringLiteral("/foo/test_b") + << QStringLiteral("/foo/a_test") << QStringLiteral("/foo/test/a"); QTest::newRow("prefer_start1") << base << QStringLiteral("test") << sorted; QTest::newRow("prefer_start2") << base << QStringLiteral("foo/test") << sorted; } { - const QStringList base = QStringList() << "/muh/kuh/asdf/foo" << "/muh/kuh/foo/asdf"; - const QStringList reverse = QStringList() << "/muh/kuh/foo/asdf" << "/muh/kuh/asdf/foo"; + const QStringList base = QStringList() << QStringLiteral("/muh/kuh/asdf/foo") << QStringLiteral("/muh/kuh/foo/asdf"); + const QStringList reverse = QStringList() << QStringLiteral("/muh/kuh/foo/asdf") << QStringLiteral("/muh/kuh/asdf/foo"); QTest::newRow("prefer_start3") << base << QStringLiteral("f") << base; QTest::newRow("prefer_start4") << base << QStringLiteral("/fo") << base; QTest::newRow("prefer_start5") << base << QStringLiteral("/foo") << base; QTest::newRow("prefer_start6") << base << QStringLiteral("a") << reverse; QTest::newRow("prefer_start7") << base << QStringLiteral("/a") << reverse; QTest::newRow("prefer_start8") << base << QStringLiteral("uh/as") << reverse; QTest::newRow("prefer_start9") << base << QStringLiteral("asdf") << reverse; } { - QTest::newRow("duplicate") << (QStringList() << "/muh/kuh/asdf/foo") << QStringLiteral("kuh/kuh") << QStringList(); + QTest::newRow("duplicate") << (QStringList() << QStringLiteral("/muh/kuh/asdf/foo")) << QStringLiteral("kuh/kuh") << QStringList(); } } void TestQuickOpen::testProjectFileFilter() { QTemporaryDir dir; TestProject* project = new TestProject(Path(dir.path())); - ProjectFolderItem* foo = createChild(project->projectItem(), "foo"); - createChild(foo, "bar"); - createChild(foo, "asdf"); - createChild(foo, "space bar"); - ProjectFolderItem* asdf = createChild(project->projectItem(), "asdf"); - createChild(asdf, "bar"); + ProjectFolderItem* foo = createChild(project->projectItem(), QStringLiteral("foo")); + createChild(foo, QStringLiteral("bar")); + createChild(foo, QStringLiteral("asdf")); + createChild(foo, QStringLiteral("space bar")); + ProjectFolderItem* asdf = createChild(project->projectItem(), QStringLiteral("asdf")); + createChild(asdf, QStringLiteral("bar")); QTemporaryFile tmpFile; tmpFile.setFileName(dir.path() + "/aaaa"); QVERIFY(tmpFile.open()); - ProjectFileItem* aaaa = new ProjectFileItem("aaaa", project->projectItem()); + ProjectFileItem* aaaa = new ProjectFileItem(QStringLiteral("aaaa"), project->projectItem()); QCOMPARE(project->fileSet().size(), 5); ProjectFileDataProvider provider; QCOMPARE(provider.itemCount(), 0u); projectController->addProject(project); const QStringList original = QStringList() - << "aaaa" << "asdf/bar" << "foo/asdf" << "foo/bar" << "foo/space bar"; + << QStringLiteral("aaaa") << QStringLiteral("asdf/bar") << QStringLiteral("foo/asdf") << QStringLiteral("foo/bar") << QStringLiteral("foo/space bar"); // lazy load QCOMPARE(provider.itemCount(), 0u); provider.reset(); QCOMPARE(items(provider), original); QCOMPARE(provider.itemPath(provider.items().first()), aaaa->path()); QCOMPARE(provider.data(0)->text(), QStringLiteral("aaaa")); // don't show opened file QVERIFY(core->documentController()->openDocument(QUrl::fromLocalFile(tmpFile.fileName()))); // lazy load again QCOMPARE(items(provider), original); provider.reset(); - QCOMPARE(items(provider), QStringList() << "asdf/bar" << "foo/asdf" << "foo/bar" << "foo/space bar"); + QCOMPARE(items(provider), QStringList() << QStringLiteral("asdf/bar") << QStringLiteral("foo/asdf") << QStringLiteral("foo/bar") << QStringLiteral("foo/space bar")); // prefer files starting with filter - provider.setFilterText("as"); + provider.setFilterText(QStringLiteral("as")); qDebug() << items(provider); - QCOMPARE(items(provider), QStringList() << "foo/asdf" << "asdf/bar"); + QCOMPARE(items(provider), QStringList() << QStringLiteral("foo/asdf") << QStringLiteral("asdf/bar")); // clear filter provider.reset(); - QCOMPARE(items(provider), QStringList() << "asdf/bar" << "foo/asdf" << "foo/bar" << "foo/space bar"); + QCOMPARE(items(provider), QStringList() << QStringLiteral("asdf/bar") << QStringLiteral("foo/asdf") << QStringLiteral("foo/bar") << QStringLiteral("foo/space bar")); // update on document close, lazy load again core->documentController()->closeAllDocuments(); - QCOMPARE(items(provider), QStringList() << "asdf/bar" << "foo/asdf" << "foo/bar" << "foo/space bar"); + QCOMPARE(items(provider), QStringList() << QStringLiteral("asdf/bar") << QStringLiteral("foo/asdf") << QStringLiteral("foo/bar") << QStringLiteral("foo/space bar")); provider.reset(); QCOMPARE(items(provider), original); - ProjectFileItem* blub = createChild(project->projectItem(), "blub"); + ProjectFileItem* blub = createChild(project->projectItem(), QStringLiteral("blub")); // lazy load QCOMPARE(provider.itemCount(), 5u); provider.reset(); QCOMPARE(provider.itemCount(), 6u); // ensure we don't add stuff multiple times QMetaObject::invokeMethod(&provider, "fileAddedToSet", Q_ARG(KDevelop::IProject*, project), Q_ARG(KDevelop::IndexedString, blub->indexedPath())); QCOMPARE(provider.itemCount(), 6u); provider.reset(); QCOMPARE(provider.itemCount(), 6u); // lazy load in this implementation delete blub; QCOMPARE(provider.itemCount(), 6u); provider.reset(); QCOMPARE(provider.itemCount(), 5u); QCOMPARE(items(provider), original); // allow filtering by path to project provider.setFilterText(dir.path()); QCOMPARE(items(provider), original); - Path buildFolderItem(project->path().parent(), ".build/generated.h"); + Path buildFolderItem(project->path().parent(), QStringLiteral(".build/generated.h")); new ProjectFileItem(project, buildFolderItem, project->projectItem()); // lazy load QCOMPARE(items(provider), original); provider.reset(); - QCOMPARE(items(provider), QStringList() << "aaaa" << "asdf/bar" << "foo/asdf" - << "foo/bar" << "foo/space bar" << "../.build/generated.h"); + QCOMPARE(items(provider), QStringList() << QStringLiteral("aaaa") << QStringLiteral("asdf/bar") << QStringLiteral("foo/asdf") + << QStringLiteral("foo/bar") << QStringLiteral("foo/space bar") << QStringLiteral("../.build/generated.h")); projectController->closeProject(project); provider.reset(); QVERIFY(!provider.itemCount()); } diff --git a/plugins/standardoutputview/outputwidget.cpp b/plugins/standardoutputview/outputwidget.cpp index 45d65e59a..3aae403d2 100644 --- a/plugins/standardoutputview/outputwidget.cpp +++ b/plugins/standardoutputview/outputwidget.cpp @@ -1,644 +1,644 @@ /* This file is part of KDevelop * * Copyright 2007 Andreas Pakulat * Copyright 2007 Dukju Ahn * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #include "outputwidget.h" #include "standardoutputview.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "toolviewdata.h" #include "debug.h" Q_LOGGING_CATEGORY(PLUGIN_STANDARDOUTPUTVIEW, "kdevplatform.plugins.standardoutputview") Q_DECLARE_METATYPE(QTreeView*) OutputWidget::OutputWidget(QWidget* parent, const ToolViewData* tvdata) : QWidget( parent ) , tabwidget(0) , stackwidget(0) , data(tvdata) , m_closeButton(0) , m_closeOthersAction(0) , nextAction(0) , previousAction(0) , activateOnSelect(0) , focusOnSelect(0) , filterInput(0) , filterAction(0) { setWindowTitle(i18n("Output View")); setWindowIcon(tvdata->icon); QVBoxLayout* layout = new QVBoxLayout(this); layout->setMargin(0); if( data->type & KDevelop::IOutputView::MultipleView ) { tabwidget = new QTabWidget(this); layout->addWidget( tabwidget ); m_closeButton = new QToolButton( this ); connect( m_closeButton, &QToolButton::clicked, this, &OutputWidget::closeActiveView ); m_closeButton->setIcon( QIcon::fromTheme( QStringLiteral( "tab-close") ) ); m_closeButton->setToolTip( i18n( "Close the currently active output view") ); m_closeOthersAction = new QAction( this ); connect(m_closeOthersAction, &QAction::triggered, this, &OutputWidget::closeOtherViews); m_closeOthersAction->setIcon(QIcon::fromTheme(QStringLiteral("tab-close-other"))); m_closeOthersAction->setToolTip( i18n( "Close all other output views" ) ); m_closeOthersAction->setText( m_closeOthersAction->toolTip() ); addAction(m_closeOthersAction); tabwidget->setCornerWidget(m_closeButton, Qt::TopRightCorner); } else if ( data->type == KDevelop::IOutputView::HistoryView ) { stackwidget = new QStackedWidget( this ); layout->addWidget( stackwidget ); previousAction = new QAction( QIcon::fromTheme( QStringLiteral( "arrow-left" ) ), i18n("Previous Output"), this ); connect(previousAction, &QAction::triggered, this, &OutputWidget::previousOutput); addAction(previousAction); nextAction = new QAction( QIcon::fromTheme( QStringLiteral( "arrow-right" ) ), i18n("Next Output"), this ); connect(nextAction, &QAction::triggered, this, &OutputWidget::nextOutput); addAction(nextAction); } addAction(dynamic_cast(data->plugin->actionCollection()->action(QStringLiteral("prev_error")))); addAction(dynamic_cast(data->plugin->actionCollection()->action(QStringLiteral("next_error")))); activateOnSelect = new KToggleAction( QIcon(), i18n("Select activated Item"), this ); activateOnSelect->setChecked( true ); focusOnSelect = new KToggleAction( QIcon(), i18n("Focus when selecting Item"), this ); focusOnSelect->setChecked( false ); if( data->option & KDevelop::IOutputView::ShowItemsButton ) { addAction(activateOnSelect); addAction(focusOnSelect); } QAction *separator = new QAction(this); separator->setSeparator(true); addAction(separator); QAction* action; - action = new QAction(QIcon::fromTheme("go-previous"), i18n("Previous Item"), this); + action = new QAction(QIcon::fromTheme(QStringLiteral("go-previous")), i18n("Previous Item"), this); connect(action, &QAction::triggered, this, &OutputWidget::selectPreviousItem); addAction(action); - action = new QAction(QIcon::fromTheme("go-next"), i18n("Next Item"), this); + action = new QAction(QIcon::fromTheme(QStringLiteral("go-next")), i18n("Next Item"), this); connect(action, &QAction::triggered, this, &OutputWidget::selectNextItem); addAction(action); QAction* selectAllAction = KStandardAction::selectAll(this, SLOT(selectAll()), this); selectAllAction->setShortcut(QKeySequence()); //FIXME: why does CTRL-A conflict with Katepart (while CTRL-Cbelow doesn't) ? selectAllAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); addAction(selectAllAction); QAction* copyAction = KStandardAction::copy(this, SLOT(copySelection()), this); copyAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); addAction(copyAction); if( data->option & KDevelop::IOutputView::AddFilterAction ) { QAction *separator = new QAction(this); separator->setSeparator(true); addAction(separator); filterInput = new QLineEdit(); filterInput->setMaximumWidth(150); filterInput->setMinimumWidth(100); filterInput->setPlaceholderText(i18n("Search...")); filterInput->setClearButtonEnabled(true); filterInput->setToolTip(i18n("Enter a wild card string to filter the output view")); filterAction = new QWidgetAction(this); filterAction->setDefaultWidget(filterInput); addAction(filterAction); connect(filterInput, &QLineEdit::textEdited, this, &OutputWidget::outputFilter ); if( data->type & KDevelop::IOutputView::MultipleView ) { connect(tabwidget, &QTabWidget::currentChanged, this, &OutputWidget::updateFilter); } else if ( data->type == KDevelop::IOutputView::HistoryView ) { connect(stackwidget, &QStackedWidget::currentChanged, this, &OutputWidget::updateFilter); } } addActions(data->actionList); connect( data, &ToolViewData::outputAdded, this, &OutputWidget::addOutput ); connect( this, &OutputWidget::outputRemoved, data->plugin, &StandardOutputView::outputRemoved ); foreach( int id, data->outputdata.keys() ) { changeModel( id ); changeDelegate( id ); } enableActions(); } void OutputWidget::addOutput( int id ) { QTreeView* listview = createListView(id); setCurrentWidget( listview ); connect( data->outputdata.value(id), &OutputData::modelChanged, this, &OutputWidget::changeModel); connect( data->outputdata.value(id), &OutputData::delegateChanged, this, &OutputWidget::changeDelegate); enableActions(); } void OutputWidget::setCurrentWidget( QTreeView* view ) { if( data->type & KDevelop::IOutputView::MultipleView ) { tabwidget->setCurrentWidget( view ); } else if( data->type & KDevelop::IOutputView::HistoryView ) { stackwidget->setCurrentWidget( view ); } } void OutputWidget::changeDelegate( int id ) { if( data->outputdata.contains( id ) && views.contains( id ) ) { views.value(id)->setItemDelegate(data->outputdata.value(id)->delegate); } else { addOutput(id); } } void OutputWidget::changeModel( int id ) { if( data->outputdata.contains( id ) && views.contains( id ) ) { OutputData* od = data->outputdata.value(id); views.value( id )->setModel(od->model); } else { addOutput( id ); } } void OutputWidget::removeOutput( int id ) { if( data->outputdata.contains( id ) && views.contains( id ) ) { QTreeView* view = views.value(id); if( data->type & KDevelop::IOutputView::MultipleView || data->type & KDevelop::IOutputView::HistoryView ) { if( data->type & KDevelop::IOutputView::MultipleView ) { int idx = tabwidget->indexOf( view ); if( idx != -1 ) { tabwidget->removeTab( idx ); if( proxyModels.contains( idx ) ) { delete proxyModels.take( idx ); filters.remove( idx ); } } } else { int idx = stackwidget->indexOf( view ); if( idx != -1 && proxyModels.contains( idx ) ) { delete proxyModels.take( idx ); filters.remove( idx ); } stackwidget->removeWidget( view ); } delete view; } else { views.value( id )->setModel( 0 ); views.value( id )->setItemDelegate( 0 ); if( proxyModels.contains( 0 ) ) { delete proxyModels.take( 0 ); filters.remove( 0 ); } } views.remove( id ); emit outputRemoved( data->toolViewId, id ); } enableActions(); } void OutputWidget::closeActiveView() { QWidget* widget = tabwidget->currentWidget(); if( !widget ) return; foreach( int id, views.keys() ) { if( views.value(id) == widget ) { OutputData* od = data->outputdata.value(id); if( od->behaviour & KDevelop::IOutputView::AllowUserClose ) { data->plugin->removeOutput( id ); } } } enableActions(); } void OutputWidget::closeOtherViews() { QWidget* widget = tabwidget->currentWidget(); if (!widget) return; foreach (int id, views.keys()) { if (views.value(id) == widget) { continue; // leave the active view open } OutputData* od = data->outputdata.value(id); if (od->behaviour & KDevelop::IOutputView::AllowUserClose) { data->plugin->removeOutput( id ); } } enableActions(); } QWidget* OutputWidget::currentWidget() const { QWidget* widget; if( data->type & KDevelop::IOutputView::MultipleView ) { widget = tabwidget->currentWidget(); } else if( data->type & KDevelop::IOutputView::HistoryView ) { widget = stackwidget->currentWidget(); } else { widget = views.begin().value(); } return widget; } KDevelop::IOutputViewModel *OutputWidget::outputViewModel() const { QWidget* widget = currentWidget(); if( !widget || !widget->isVisible() ) return nullptr; auto view = qobject_cast(widget); if( !view ) return nullptr; QAbstractItemModel *absmodel = view->model(); KDevelop::IOutputViewModel *iface = dynamic_cast(absmodel); if ( ! iface ) { // try if it's a proxy model? if ( QAbstractProxyModel* proxy = qobject_cast(absmodel) ) { iface = dynamic_cast(proxy->sourceModel()); } } return iface; } void OutputWidget::eventuallyDoFocus() { QWidget* widget = currentWidget(); if( focusOnSelect->isChecked() && !widget->hasFocus() ) { widget->setFocus( Qt::OtherFocusReason ); } } QAbstractItemView *OutputWidget::outputView() const { auto widget = currentWidget(); return qobject_cast(widget); } void OutputWidget::activateIndex(const QModelIndex &index, QAbstractItemView *view, KDevelop::IOutputViewModel *iface) { if( ! index.isValid() ) return; int tabIndex = currentOutputIndex(); QModelIndex sourceIndex = index; QModelIndex viewIndex = index; if( QAbstractProxyModel* proxy = proxyModels.value(tabIndex) ) { if ( index.model() == proxy ) { // index is from the proxy, map it to the source sourceIndex = proxy->mapToSource(index); } else if (proxy == view->model()) { // index is from the source, map it to the proxy viewIndex = proxy->mapFromSource(index); } } view->setCurrentIndex( viewIndex ); view->scrollTo( viewIndex ); if( activateOnSelect->isChecked() ) { iface->activate( sourceIndex ); } } void OutputWidget::selectNextItem() { selectItem(Next); } void OutputWidget::selectPreviousItem() { selectItem(Previous); } void OutputWidget::selectItem(Direction direction) { auto view = outputView(); auto iface = outputViewModel(); if ( ! view || ! iface ) return; eventuallyDoFocus(); auto index = view->currentIndex(); if (QAbstractProxyModel* proxy = proxyModels.value(currentOutputIndex())) { if ( index.model() == proxy ) { // index is from the proxy, map it to the source index = proxy->mapToSource(index); } } const auto newIndex = direction == Previous ? iface->previousHighlightIndex( index ) : iface->nextHighlightIndex( index ); qCDebug(PLUGIN_STANDARDOUTPUTVIEW) << "old:" << index << "- new:" << newIndex; activateIndex(newIndex, view, iface); } void OutputWidget::activate(const QModelIndex& index) { auto iface = outputViewModel(); auto view = outputView(); if( ! view || ! iface ) return; activateIndex(index, view, iface); } QTreeView* OutputWidget::createListView(int id) { auto createHelper = [&]() -> QTreeView* { KDevelop::FocusedTreeView* listview = new KDevelop::FocusedTreeView(this); listview->setEditTriggers( QAbstractItemView::NoEditTriggers ); listview->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); //Always enable the scrollbar, so it doesn't flash around listview->setHeaderHidden(true); listview->setUniformRowHeights(true); listview->setRootIsDecorated(false); listview->setSelectionMode( QAbstractItemView::ContiguousSelection ); if (data->outputdata.value(id)->behaviour & KDevelop::IOutputView::AutoScroll) { listview->setAutoScrollAtEnd(true); } connect(listview, &QTreeView::activated, this, &OutputWidget::activate); connect(listview, &QTreeView::clicked, this, &OutputWidget::activate); return listview; }; QTreeView* listview = 0; if( !views.contains(id) ) { bool newView = true; if( data->type & KDevelop::IOutputView::MultipleView || data->type & KDevelop::IOutputView::HistoryView ) { qCDebug(PLUGIN_STANDARDOUTPUTVIEW) << "creating listview"; listview = createHelper(); if( data->type & KDevelop::IOutputView::MultipleView ) { tabwidget->addTab( listview, data->outputdata.value(id)->title ); } else { stackwidget->addWidget( listview ); stackwidget->setCurrentWidget( listview ); } } else { if( views.isEmpty() ) { listview = createHelper(); layout()->addWidget( listview ); } else { listview = views.begin().value(); newView = false; } } views[id] = listview; changeModel( id ); changeDelegate( id ); if (newView) listview->scrollToBottom(); } else { listview = views.value(id); } enableActions(); return listview; } void OutputWidget::raiseOutput(int id) { if( views.contains(id) ) { if( data->type & KDevelop::IOutputView::MultipleView ) { int idx = tabwidget->indexOf( views.value(id) ); if( idx >= 0 ) { tabwidget->setCurrentIndex( idx ); } } else if( data->type & KDevelop::IOutputView::HistoryView ) { int idx = stackwidget->indexOf( views.value(id) ); if( idx >= 0 ) { stackwidget->setCurrentIndex( idx ); } } } enableActions(); } void OutputWidget::nextOutput() { if( stackwidget && stackwidget->currentIndex() < stackwidget->count()-1 ) { stackwidget->setCurrentIndex( stackwidget->currentIndex()+1 ); } enableActions(); } void OutputWidget::previousOutput() { if( stackwidget && stackwidget->currentIndex() > 0 ) { stackwidget->setCurrentIndex( stackwidget->currentIndex()-1 ); } enableActions(); } void OutputWidget::enableActions() { if( data->type == KDevelop::IOutputView::HistoryView ) { Q_ASSERT(stackwidget); Q_ASSERT(nextAction); Q_ASSERT(previousAction); previousAction->setEnabled( ( stackwidget->currentIndex() > 0 ) ); nextAction->setEnabled( ( stackwidget->currentIndex() < stackwidget->count() - 1 ) ); } } void OutputWidget::scrollToIndex( const QModelIndex& idx ) { QWidget* w = currentWidget(); if( !w ) return; QAbstractItemView *view = dynamic_cast(w); view->scrollTo( idx ); } void OutputWidget::copySelection() { QWidget* widget = currentWidget(); if( !widget ) return; QAbstractItemView *view = dynamic_cast(widget); if( !view ) return; QClipboard *cb = QApplication::clipboard(); QModelIndexList indexes = view->selectionModel()->selectedRows(); QString content; Q_FOREACH( const QModelIndex& index, indexes) { content += index.data().toString() + '\n'; } cb->setText(content); } void OutputWidget::selectAll() { QWidget* widget = currentWidget(); if( !widget ) return; QAbstractItemView *view = dynamic_cast(widget); if( !view ) return; view->selectAll(); } int OutputWidget::currentOutputIndex() { int index = 0; if( data->type & KDevelop::IOutputView::MultipleView ) { index = tabwidget->currentIndex(); } else if( data->type & KDevelop::IOutputView::HistoryView ) { index = stackwidget->currentIndex(); } return index; } void OutputWidget::outputFilter(const QString& filter) { QWidget* widget = currentWidget(); if( !widget ) return; QAbstractItemView *view = dynamic_cast(widget); if( !view ) return; int index = currentOutputIndex(); auto proxyModel = dynamic_cast(view->model()); if( !proxyModel ) { proxyModel = new QSortFilterProxyModel(view->model()); proxyModel->setDynamicSortFilter(true); proxyModel->setSourceModel(view->model()); proxyModels.insert(index, proxyModel); view->setModel(proxyModel); } QRegExp regExp(filter, Qt::CaseInsensitive); proxyModel->setFilterRegExp(regExp); filters[index] = filter; } void OutputWidget::updateFilter(int index) { if(filters.contains(index)) { filterInput->setText(filters[index]); } else { filterInput->clear(); } } void OutputWidget::setTitle(int outputId, const QString& title) { if( data->type & KDevelop::IOutputView::MultipleView ) { tabwidget->setTabText(outputId - 1, title); } } diff --git a/plugins/standardoutputview/tests/test_standardoutputview.cpp b/plugins/standardoutputview/tests/test_standardoutputview.cpp index 752290198..f06ff4b4f 100644 --- a/plugins/standardoutputview/tests/test_standardoutputview.cpp +++ b/plugins/standardoutputview/tests/test_standardoutputview.cpp @@ -1,234 +1,234 @@ /* Copyright (C) 2011 Silvère Lestang This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "test_standardoutputview.h" #include "../outputwidget.h" #include "../toolviewdata.h" namespace KDevelop { class IUiController; } class QAbstractItemDelegate; class QStandardItemModel; QTEST_MAIN(StandardOutputViewTest) -const QString StandardOutputViewTest::toolviewTitle = "my_toolview"; +const QString StandardOutputViewTest::toolviewTitle = QStringLiteral("my_toolview"); void StandardOutputViewTest::initTestCase() { KDevelop::AutoTestShell::init(); m_testCore = new KDevelop::TestCore(); m_testCore->initialize(KDevelop::Core::Default); m_controller = m_testCore->uiControllerInternal(); QTest::qWait(500); // makes sure that everything is loaded (don't know if it's required) m_stdOutputView = 0; KDevelop::IPluginController* plugin_controller = m_testCore->pluginController(); QList plugins = plugin_controller->loadedPlugins(); // make sure KDevStandardOutputView is loaded - KDevelop::IPlugin* plugin = plugin_controller->loadPlugin("KDevStandardOutputView"); + KDevelop::IPlugin* plugin = plugin_controller->loadPlugin(QStringLiteral("KDevStandardOutputView")); QVERIFY(plugin); m_stdOutputView = dynamic_cast(plugin); QVERIFY(m_stdOutputView); } void StandardOutputViewTest::cleanupTestCase() { m_testCore->cleanup(); delete m_testCore; } OutputWidget* StandardOutputViewTest::toolviewPointer(QString toolviewTitle) { QList< Sublime::View* > views = m_controller->activeArea()->toolViews(); foreach(Sublime::View* view, views) { Sublime::ToolDocument *doc = dynamic_cast(view->document()); if(doc) { if(doc->title() == toolviewTitle && view->hasWidget()) { return dynamic_cast(view->widget()); } } } return 0; } void StandardOutputViewTest::testRegisterAndRemoveToolView() { toolviewId = m_stdOutputView->registerToolView(toolviewTitle, KDevelop::IOutputView::HistoryView); QVERIFY(toolviewPointer(toolviewTitle)); // re-registering should return the same tool view instead of creating a new one QCOMPARE(toolviewId, m_stdOutputView->registerToolView(toolviewTitle, KDevelop::IOutputView::HistoryView)); m_stdOutputView->removeToolView(toolviewId); QVERIFY(!toolviewPointer(toolviewTitle)); } void StandardOutputViewTest::testActions() { toolviewId = m_stdOutputView->registerToolView(toolviewTitle, KDevelop::IOutputView::MultipleView, QIcon()); OutputWidget* outputWidget = toolviewPointer(toolviewTitle); QVERIFY(outputWidget); QList actions = outputWidget->actions(); QCOMPARE(actions.size(), 8); m_stdOutputView->removeToolView(toolviewId); QVERIFY(!toolviewPointer(toolviewTitle)); QList addedActions; - addedActions.append(new QAction("Action1", 0)); - addedActions.append(new QAction("Action2", 0)); + addedActions.append(new QAction(QStringLiteral("Action1"), 0)); + addedActions.append(new QAction(QStringLiteral("Action2"), 0)); toolviewId = m_stdOutputView->registerToolView(toolviewTitle, KDevelop::IOutputView::HistoryView, QIcon(), KDevelop::IOutputView::ShowItemsButton | KDevelop::IOutputView::AddFilterAction, addedActions); outputWidget = toolviewPointer(toolviewTitle); QVERIFY(outputWidget); actions = outputWidget->actions(); QCOMPARE(actions.size(), 13); QCOMPARE(actions[actions.size()-2]->text(), addedActions[0]->text()); QCOMPARE(actions[actions.size()-1]->text(), addedActions[1]->text()); m_stdOutputView->removeToolView(toolviewId); QVERIFY(!toolviewPointer(toolviewTitle)); } void StandardOutputViewTest::testRegisterAndRemoveOutput() { toolviewId = m_stdOutputView->registerToolView(toolviewTitle, KDevelop::IOutputView::MultipleView, QIcon()); OutputWidget* outputWidget = toolviewPointer(toolviewTitle); QVERIFY(outputWidget); for(int i = 0; i < 5; i++) { outputId[i] = m_stdOutputView->registerOutputInToolView(toolviewId, QStringLiteral("output%1").arg(i)); } for(int i = 0; i < 5; i++) { QCOMPARE(outputWidget->data->outputdata.value(outputId[i])->title, QStringLiteral("output%1").arg(i)); QCOMPARE(outputWidget->tabwidget->tabText(i), QStringLiteral("output%1").arg(i)); } for(int i = 0; i < 5; i++) { m_stdOutputView->removeOutput(outputId[i]); QVERIFY(!outputWidget->data->outputdata.contains(outputId[i])); } QCOMPARE(outputWidget->tabwidget->count(), 0); m_stdOutputView->removeToolView(toolviewId); QVERIFY(!toolviewPointer(toolviewTitle)); toolviewId = m_stdOutputView->registerToolView(toolviewTitle, KDevelop::IOutputView::HistoryView, QIcon(), KDevelop::IOutputView::ShowItemsButton | KDevelop::IOutputView::AddFilterAction); outputWidget = toolviewPointer(toolviewTitle); QVERIFY(outputWidget); for(int i = 0; i < 5; i++) { outputId[i] = m_stdOutputView->registerOutputInToolView(toolviewId, QStringLiteral("output%1").arg(i)); } for(int i = 0; i < 5; i++) { QCOMPARE(outputWidget->data->outputdata.value(outputId[i])->title, QStringLiteral("output%1").arg(i)); } for(int i = 0; i < 5; i++) { m_stdOutputView->removeOutput(outputId[i]); QVERIFY(!outputWidget->data->outputdata.contains(outputId[i])); } QCOMPARE(outputWidget->stackwidget->count(), 0); m_stdOutputView->removeToolView(toolviewId); QVERIFY(!toolviewPointer(toolviewTitle)); } void StandardOutputViewTest::testSetModelAndDelegate() { toolviewId = m_stdOutputView->registerToolView(toolviewTitle, KDevelop::IOutputView::MultipleView, QIcon()); OutputWidget* outputWidget = toolviewPointer(toolviewTitle); QVERIFY(outputWidget); QAbstractItemModel* model = new QStandardItemModel; QPointer checkModel(model); QAbstractItemDelegate* delegate = new QItemDelegate; QPointer checkDelegate(delegate); - outputId[0] = m_stdOutputView->registerOutputInToolView(toolviewId, "output"); + outputId[0] = m_stdOutputView->registerOutputInToolView(toolviewId, QStringLiteral("output")); m_stdOutputView->setModel(outputId[0], model); m_stdOutputView->setDelegate(outputId[0], delegate); QCOMPARE(outputWidget->views.value(outputId[0])->model(), model); QCOMPARE(outputWidget->views.value(outputId[0])->itemDelegate(), delegate); QVERIFY(model->parent()); // they have a parent (the outputdata), so parent() != 0x0 QVERIFY(delegate->parent()); m_stdOutputView->removeToolView(toolviewId); QVERIFY(!toolviewPointer(toolviewTitle)); // view deleted, hence model + delegate deleted QVERIFY(!checkModel.data()); QVERIFY(!checkDelegate.data()); } void StandardOutputViewTest::testStandardToolViews() { QFETCH(KDevelop::IOutputView::StandardToolView, view); int id = m_stdOutputView->standardToolView(view); QVERIFY(id); QCOMPARE(id, m_stdOutputView->standardToolView(view)); } void StandardOutputViewTest::testStandardToolViews_data() { QTest::addColumn("view"); QTest::newRow("build") << KDevelop::IOutputView::BuildView; QTest::newRow("run") << KDevelop::IOutputView::RunView; QTest::newRow("debug") << KDevelop::IOutputView::DebugView; QTest::newRow("test") << KDevelop::IOutputView::TestView; QTest::newRow("vcs") << KDevelop::IOutputView::VcsView; } diff --git a/plugins/subversion/kdevsvnplugin.cpp b/plugins/subversion/kdevsvnplugin.cpp index 0287bd7de..9d4cc3952 100644 --- a/plugins/subversion/kdevsvnplugin.cpp +++ b/plugins/subversion/kdevsvnplugin.cpp @@ -1,531 +1,531 @@ /*************************************************************************** * Copyright 2007 Dukju Ahn * * Copyright 2008 Andreas Pakulat * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "kdevsvnplugin.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kdevsvncpp/apr.hpp" #include "svncommitjob.h" #include "svnstatusjob.h" #include "svnaddjob.h" #include "svnrevertjob.h" #include "svnremovejob.h" #include "svnupdatejob.h" #include "svninfojob.h" #include "svndiffjob.h" #include "svncopyjob.h" #include "svnmovejob.h" #include "svnlogjob.h" #include "svnblamejob.h" #include "svnimportjob.h" #include "svncheckoutjob.h" #include "svnimportmetadatawidget.h" #include "svncheckoutmetadatawidget.h" #include #include #include "svnlocationwidget.h" #include "debug.h" Q_LOGGING_CATEGORY(PLUGIN_SVN, "kdevplatform.plugins.svn") K_PLUGIN_FACTORY_WITH_JSON(KDevSvnFactory, "kdevsubversion.json", registerPlugin();) KDevSvnPlugin::KDevSvnPlugin(QObject *parent, const QVariantList &) - : KDevelop::IPlugin("kdevsubversion", parent) + : KDevelop::IPlugin(QStringLiteral("kdevsubversion"), parent) , m_common(new KDevelop::VcsPluginHelper(this, this)) , copy_action( 0 ) , move_action( 0 ) , m_jobQueue(new ThreadWeaver::Queue(this)) { KDEV_USE_EXTENSION_INTERFACE(KDevelop::IBasicVersionControl) KDEV_USE_EXTENSION_INTERFACE(KDevelop::ICentralizedVersionControl) qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); } KDevSvnPlugin::~KDevSvnPlugin() { } bool KDevSvnPlugin::isVersionControlled(const QUrl &localLocation) { ///TODO: also check this in the other functions? if (!localLocation.isValid()) { return false; } SvnInfoJob* job = new SvnInfoJob(this); job->setLocation(localLocation); if (job->exec()) { QVariant result = job->fetchResults(); if (result.isValid()) { SvnInfoHolder h = result.value(); return !h.name.isEmpty(); } } else { qCDebug(PLUGIN_SVN) << "Couldn't execute job"; } return false; } KDevelop::VcsJob* KDevSvnPlugin::repositoryLocation(const QUrl &localLocation) { SvnInfoJob* job = new SvnInfoJob(this); job->setLocation(localLocation); job->setProvideInformation(SvnInfoJob::RepoUrlOnly); return job; } KDevelop::VcsJob* KDevSvnPlugin::status(const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode mode) { SvnStatusJob* job = new SvnStatusJob(this); job->setLocations(localLocations); job->setRecursive((mode == KDevelop::IBasicVersionControl::Recursive)); return job; } KDevelop::VcsJob* KDevSvnPlugin::add(const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion) { SvnAddJob* job = new SvnAddJob(this); job->setLocations(localLocations); job->setRecursive((recursion == KDevelop::IBasicVersionControl::Recursive)); return job; } KDevelop::VcsJob* KDevSvnPlugin::remove(const QList& localLocations) { SvnRemoveJob* job = new SvnRemoveJob(this); job->setLocations(localLocations); return job; } KDevelop::VcsJob* KDevSvnPlugin::edit(const QUrl& /*localLocation*/) { return 0; } KDevelop::VcsJob* KDevSvnPlugin::unedit(const QUrl& /*localLocation*/) { return 0; } KDevelop::VcsJob* KDevSvnPlugin::localRevision(const QUrl &localLocation, KDevelop::VcsRevision::RevisionType type) { SvnInfoJob* job = new SvnInfoJob(this); job->setLocation(localLocation); job->setProvideInformation(SvnInfoJob::RevisionOnly); job->setProvideRevisionType(type); return job; } KDevelop::VcsJob* KDevSvnPlugin::copy(const QUrl &localLocationSrc, const QUrl& localLocationDstn) { SvnCopyJob* job = new SvnCopyJob(this); job->setSourceLocation(localLocationSrc); job->setDestinationLocation(localLocationDstn); return job; } KDevelop::VcsJob* KDevSvnPlugin::move(const QUrl &localLocationSrc, const QUrl& localLocationDst) { SvnMoveJob* job = new SvnMoveJob(this); job->setSourceLocation(localLocationSrc); job->setDestinationLocation(localLocationDst); return job; } KDevelop::VcsJob* KDevSvnPlugin::revert(const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion) { SvnRevertJob* job = new SvnRevertJob(this); job->setLocations(localLocations); job->setRecursive((recursion == KDevelop::IBasicVersionControl::Recursive)); return job; } KDevelop::VcsJob* KDevSvnPlugin::update(const QList& localLocations, const KDevelop::VcsRevision& rev, KDevelop::IBasicVersionControl::RecursionMode recursion) { SvnUpdateJob* job = new SvnUpdateJob(this); job->setLocations(localLocations); job->setRevision(rev); job->setRecursive((recursion == KDevelop::IBasicVersionControl::Recursive)); return job; } KDevelop::VcsJob* KDevSvnPlugin::commit(const QString& message, const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion) { SvnCommitJob* job = new SvnCommitJob(this); qCDebug(PLUGIN_SVN) << "Committing locations:" << localLocations << endl; job->setUrls(localLocations); job->setCommitMessage(message) ; job->setRecursive((recursion == KDevelop::IBasicVersionControl::Recursive)); return job; } KDevelop::VcsJob* KDevSvnPlugin::diff(const QUrl &fileOrDirectory, const KDevelop::VcsRevision& srcRevision, const KDevelop::VcsRevision& dstRevision, KDevelop::VcsDiff::Type diffType, KDevelop::IBasicVersionControl::RecursionMode recurse) { KDevelop::VcsLocation loc(fileOrDirectory); return diff2(loc, loc, srcRevision, dstRevision, diffType, recurse); } KDevelop::VcsJob* KDevSvnPlugin::diff2(const KDevelop::VcsLocation& src, const KDevelop::VcsLocation& dst, const KDevelop::VcsRevision& srcRevision, const KDevelop::VcsRevision& dstRevision, KDevelop::VcsDiff::Type diffType, KDevelop::IBasicVersionControl::RecursionMode recurse) { SvnDiffJob* job = new SvnDiffJob(this); job->setSource(src); job->setDestination(dst); job->setSrcRevision(srcRevision); job->setDstRevision(dstRevision); job->setDiffType(diffType); job->setRecursive((recurse == KDevelop::IBasicVersionControl::Recursive)); return job; } KDevelop::VcsJob* KDevSvnPlugin::log(const QUrl &localLocation, const KDevelop::VcsRevision& rev, unsigned long limit) { SvnLogJob* job = new SvnLogJob(this); job->setLocation(localLocation); job->setStartRevision(rev); job->setLimit(limit); return job; } KDevelop::VcsJob* KDevSvnPlugin::log(const QUrl &localLocation, const KDevelop::VcsRevision& startRev, const KDevelop::VcsRevision& endRev) { SvnLogJob* job = new SvnLogJob(this); job->setLocation(localLocation); job->setStartRevision(startRev); job->setEndRevision(endRev); return job; } KDevelop::VcsJob* KDevSvnPlugin::annotate(const QUrl &localLocation, const KDevelop::VcsRevision& rev) { SvnBlameJob* job = new SvnBlameJob(this); job->setLocation(localLocation); job->setEndRevision(rev); return job; } KDevelop::VcsJob* KDevSvnPlugin::merge(const KDevelop::VcsLocation& localOrRepoLocationSrc, const KDevelop::VcsLocation& localOrRepoLocationDst, const KDevelop::VcsRevision& srcRevision, const KDevelop::VcsRevision& dstRevision, const QUrl &localLocation) { // TODO implement merge Q_UNUSED(localOrRepoLocationSrc) Q_UNUSED(localOrRepoLocationDst) Q_UNUSED(srcRevision) Q_UNUSED(dstRevision) Q_UNUSED(localLocation) return 0; } KDevelop::VcsJob* KDevSvnPlugin::resolve(const QList& /*localLocations*/, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/) { return 0; } KDevelop::VcsJob* KDevSvnPlugin::import(const QString & commitMessage, const QUrl &sourceDirectory, const KDevelop::VcsLocation & destinationRepository) { SvnImportJob* job = new SvnImportJob(this); job->setMapping(sourceDirectory, destinationRepository); job->setMessage(commitMessage); return job; } KDevelop::VcsJob* KDevSvnPlugin::createWorkingCopy(const KDevelop::VcsLocation & sourceRepository, const QUrl &destinationDirectory, KDevelop::IBasicVersionControl::RecursionMode recursion) { SvnCheckoutJob* job = new SvnCheckoutJob(this); job->setMapping(sourceRepository, destinationDirectory, recursion); return job; } KDevelop::ContextMenuExtension KDevSvnPlugin::contextMenuExtension(KDevelop::Context* context) { m_common->setupFromContext(context); const QList & ctxUrlList = m_common->contextUrlList(); bool hasVersionControlledEntries = false; foreach(const QUrl &url, ctxUrlList) { if (isVersionControlled(url) || isVersionControlled(KIO::upUrl(url))) { hasVersionControlledEntries = true; break; } } qCDebug(PLUGIN_SVN) << "version controlled?" << hasVersionControlledEntries; if (!hasVersionControlledEntries) return IPlugin::contextMenuExtension(context); QMenu* svnmenu= m_common->commonActions(); svnmenu->addSeparator(); if( !copy_action ) { copy_action = new QAction(i18n("Copy..."), this); connect(copy_action, SIGNAL(triggered()), this, SLOT(ctxCopy())); } svnmenu->addAction(copy_action); if( !move_action ) { move_action = new QAction(i18n("Move..."), this); connect(move_action, SIGNAL(triggered()), this, SLOT(ctxMove())); } svnmenu->addAction(move_action); KDevelop::ContextMenuExtension menuExt; menuExt.addAction(KDevelop::ContextMenuExtension::VcsGroup, svnmenu->menuAction()); return menuExt; } void KDevSvnPlugin::ctxInfo() { QList const & ctxUrlList = m_common->contextUrlList(); if (ctxUrlList.count() != 1) { KMessageBox::error(0, i18n("Please select only one item for this operation")); return; } } void KDevSvnPlugin::ctxStatus() { QList const & ctxUrlList = m_common->contextUrlList(); if (ctxUrlList.count() > 1) { KMessageBox::error(0, i18n("Please select only one item for this operation")); return; } } void KDevSvnPlugin::ctxCopy() { QList const & ctxUrlList = m_common->contextUrlList(); if (ctxUrlList.count() > 1) { KMessageBox::error(0, i18n("Please select only one item for this operation")); return; } QUrl source = ctxUrlList.first(); if (source.isLocalFile()) { QUrl dir = source; bool isFile = QFileInfo(source.toLocalFile()).isFile(); if (isFile) { dir = dir.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash); } KUrlRequesterDialog dlg(dir, i18n("Destination file/directory"), 0); if (isFile) { dlg.urlRequester()->setMode(KFile::File | KFile::Directory | KFile::LocalOnly); } else { dlg.urlRequester()->setMode(KFile::Directory | KFile::LocalOnly); } if (dlg.exec() == QDialog::Accepted) { KDevelop::ICore::self()->runController()->registerJob(copy(source, dlg.selectedUrl())); } } else { KMessageBox::error(0, i18n("Copying only works on local files")); return; } } void KDevSvnPlugin::ctxMove() { QList const & ctxUrlList = m_common->contextUrlList(); if (ctxUrlList.count() != 1) { KMessageBox::error(0, i18n("Please select only one item for this operation")); return; } QUrl source = ctxUrlList.first(); if (source.isLocalFile()) { QUrl dir = source; bool isFile = QFileInfo(source.toLocalFile()).isFile(); if (isFile) { dir = source.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash); } KUrlRequesterDialog dlg(dir, i18n("Destination file/directory"), 0); if (isFile) { dlg.urlRequester()->setMode(KFile::File | KFile::Directory | KFile::LocalOnly); } else { dlg.urlRequester()->setMode(KFile::Directory | KFile::LocalOnly); } if (dlg.exec() == QDialog::Accepted) { KDevelop::ICore::self()->runController()->registerJob(move(source, dlg.selectedUrl())); } } else { KMessageBox::error(0, i18n("Moving only works on local files/dirs")); return; } } void KDevSvnPlugin::ctxCat() { QList const & ctxUrlList = m_common->contextUrlList(); if (ctxUrlList.count() != 1) { KMessageBox::error(0, i18n("Please select only one item for this operation")); return; } } QString KDevSvnPlugin::name() const { return i18n("Subversion"); } KDevelop::VcsImportMetadataWidget* KDevSvnPlugin::createImportMetadataWidget(QWidget* parent) { return new SvnImportMetadataWidget(parent); } void KDevSvnPlugin::ctxImport() { QList const & ctxUrlList = m_common->contextUrlList(); if (ctxUrlList.count() != 1) { KMessageBox::error(0, i18n("Please select only one item for this operation")); return; } QDialog dlg; dlg.setWindowTitle(i18n("Import into Subversion repository")); SvnImportMetadataWidget* widget = new SvnImportMetadataWidget(&dlg); widget->setSourceLocation(KDevelop::VcsLocation(ctxUrlList.first())); widget->setSourceLocationEditable(false); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); auto layout = new QVBoxLayout(); dlg.setLayout(layout); layout->addWidget(widget); layout->addWidget(buttonBox); connect(buttonBox, &QDialogButtonBox::accepted, &dlg, &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, &dlg, &QDialog::reject); if (dlg.exec() == QDialog::Accepted) { KDevelop::ICore::self()->runController()->registerJob(import(widget->message(), widget->source(), widget->destination())); } } void KDevSvnPlugin::ctxCheckout() { QList const & ctxUrlList = m_common->contextUrlList(); if (ctxUrlList.count() != 1) { KMessageBox::error(0, i18n("Please select only one item for this operation")); return; } QDialog dlg; dlg.setWindowTitle(i18n("Checkout from Subversion repository")); SvnCheckoutMetadataWidget* widget = new SvnCheckoutMetadataWidget(&dlg); QUrl tmp = KIO::upUrl(ctxUrlList.first()); widget->setDestinationLocation(tmp); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); auto layout = new QVBoxLayout(); dlg.setLayout(layout); layout->addWidget(widget); layout->addWidget(buttonBox); connect(buttonBox, &QDialogButtonBox::accepted, &dlg, &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, &dlg, &QDialog::reject); if (dlg.exec() == QDialog::Accepted) { KDevelop::ICore::self()->runController()->registerJob(createWorkingCopy(widget->source(), widget->destination(), widget->recursionMode())); } } KDevelop::VcsLocationWidget* KDevSvnPlugin::vcsLocation(QWidget* parent) const { return new SvnLocationWidget(parent); } ThreadWeaver::Queue* KDevSvnPlugin::jobQueue() const { return m_jobQueue; } #include "kdevsvnplugin.moc" diff --git a/plugins/subversion/svnclient.cpp b/plugins/subversion/svnclient.cpp index d0905515a..1deba6d64 100644 --- a/plugins/subversion/svnclient.cpp +++ b/plugins/subversion/svnclient.cpp @@ -1,333 +1,335 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2007 Andreas Pakulat * * * * Parts of the file are copied from the RapidSvn C++ library * * Copyright (c) 2002-2006 The RapidSvn Group. All rights reserved. * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU 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 "svnclient.h" #include #include #include extern "C" { #include #include } #include "kdevsvncpp/targets.hpp" #include "kdevsvncpp/pool.hpp" #include #include void fail (apr_pool_t *pool, apr_status_t status, const char *fmt, ...) { va_list ap; char *msg; svn_error_t * error; va_start (ap, fmt); msg = apr_pvsprintf (pool, fmt, ap); va_end (ap); error = svn_error_create (status, NULL, msg); throw svn::ClientException (error); } void cleanup( apr_file_t* outfile, const char* outfileName, apr_file_t* errfile, const char* errfileName, const svn::Pool& pool ) { if( outfile != 0 ) { apr_file_close( outfile ); } if( errfile != 0 ) { apr_file_close( outfile ); } if( outfileName != 0 ) { svn_error_clear( svn_io_remove_file ( outfileName, pool ) ); } if( errfileName != 0 ) { svn_error_clear( svn_io_remove_file ( errfileName, pool ) ); } } SvnClient::SvnClient( svn::Context* ctx ) : QObject(0), svn::Client( ctx ), m_ctxt( ctx ) { } QString SvnClient::diff( const svn::Path& src, const svn::Revision& srcRev, const svn::Path& dst, const svn::Revision& dstRev, const bool recurse, const bool ignoreAncestry, const bool noDiffDeleted, const bool ignoreContentType ) throw (svn::ClientException) { svn::Pool pool; // null options apr_array_header_t *options = svn_cstring_split( "", "\t\r\n", false, pool ); svn_error_t* error; const char* outfileName = 0; apr_file_t* outfile = 0; const char* errfileName = 0; apr_file_t* errfile = 0; QByteArray ba = QString(QStandardPaths::writableLocation(QStandardPaths::TempLocation)+"/kdevelop_svn_diff" ).toUtf8(); error = svn_io_open_unique_file( &outfile, &outfileName, ba.data(), ".tmp", false, pool ); if( error != 0 ) { ::cleanup( outfile, outfileName, errfile, errfileName, pool ); throw svn::ClientException( error ); } error = svn_io_open_unique_file( &errfile, &errfileName, ba.data(), ".tmp", false, pool ); if( error != 0 ) { ::cleanup( outfile, outfileName, errfile, errfileName, pool ); throw svn::ClientException( error ); } error = svn_client_diff3( options, src.c_str(), srcRev.revision(), dst.c_str(), dstRev.revision(), recurse, ignoreAncestry, noDiffDeleted, ignoreContentType, "UTF-8", outfile, errfile, m_ctxt->ctx(), pool ); if ( error ) { ::cleanup( outfile, outfileName, errfile, errfileName, pool ); throw svn::ClientException(error); } // then we reopen outfile for reading apr_status_t aprstatus = apr_file_close (outfile); if (aprstatus) { ::cleanup (outfile, outfileName, errfile, errfileName, pool); ::fail (pool, aprstatus, "failed to close '%s'", outfileName); } aprstatus = apr_file_open (&outfile, outfileName, APR_READ, APR_OS_DEFAULT, pool); if (aprstatus) { ::cleanup (outfile, outfileName, errfile, errfileName, pool); ::fail (pool, aprstatus, "failed to open '%s'", outfileName); } svn_stringbuf_t* stringbuf; // now we can read the diff output from outfile and return that error = svn_stringbuf_from_aprfile (&stringbuf, outfile, pool); if (error != NULL) { ::cleanup (outfile, outfileName, errfile, errfileName, pool); throw svn::ClientException (error); } ::cleanup (outfile, outfileName, errfile, errfileName, pool); return QString::fromUtf8( stringbuf->data ); } QString SvnClient::diff( const svn::Path& src, const svn::Revision& pegRev, const svn::Revision& srcRev, const svn::Revision& dstRev, const bool recurse, const bool ignoreAncestry, const bool noDiffDeleted, const bool ignoreContentType ) throw (svn::ClientException) { svn::Pool pool; // null options apr_array_header_t *options = svn_cstring_split( "", "\t\r\n", false, pool ); svn_error_t* error; const char* outfileName = 0; apr_file_t* outfile = 0; const char* errfileName = 0; apr_file_t* errfile = 0; QByteArray ba = QStandardPaths::writableLocation(QStandardPaths::TempLocation).toUtf8(); error = svn_io_open_unique_file( &outfile, &outfileName, ba.data(), ".tmp", false, pool ); if( error != 0 ) { ::cleanup( outfile, outfileName, errfile, errfileName, pool ); throw svn::ClientException( error ); } error = svn_io_open_unique_file( &errfile, &errfileName, ba.data(), ".tmp", false, pool ); if( error != 0 ) { ::cleanup( outfile, outfileName, errfile, errfileName, pool ); throw svn::ClientException( error ); } error = svn_client_diff_peg3( options, src.c_str(), pegRev.revision(), srcRev.revision(), dstRev.revision(), recurse, ignoreAncestry, noDiffDeleted, ignoreContentType, "UTF-8", outfile, errfile, m_ctxt->ctx(), pool ); if ( error ) { ::cleanup( outfile, outfileName, errfile, errfileName, pool ); throw svn::ClientException(error); } // then we reopen outfile for reading apr_status_t aprstatus = apr_file_close (outfile); if (aprstatus) { ::cleanup (outfile, outfileName, errfile, errfileName, pool); ::fail (pool, aprstatus, "failed to close '%s'", outfileName); } aprstatus = apr_file_open (&outfile, outfileName, APR_READ, APR_OS_DEFAULT, pool); if (aprstatus) { ::cleanup (outfile, outfileName, errfile, errfileName, pool); ::fail (pool, aprstatus, "failed to open '%s'", outfileName); } svn_stringbuf_t* stringbuf; // now we can read the diff output from outfile and return that error = svn_stringbuf_from_aprfile (&stringbuf, outfile, pool); if (error != NULL) { ::cleanup (outfile, outfileName, errfile, errfileName, pool); throw svn::ClientException(error); } ::cleanup (outfile, outfileName, errfile, errfileName, pool); return QString::fromUtf8( stringbuf->data ); } static svn_error_t * kdev_logReceiver (void *baton, apr_hash_t * changedPaths, svn_revnum_t rev, const char *author, const char *date, const char *msg, apr_pool_t * pool) { SvnClient* client = (SvnClient *) baton; KDevelop::VcsEvent ev; ev.setAuthor( QString::fromUtf8( author ) ); ev.setDate( QDateTime::fromString( QString::fromUtf8( date ), Qt::ISODate ) ); ev.setMessage( QString::fromUtf8( msg ) ); KDevelop::VcsRevision vcsrev; vcsrev.setRevisionValue( QVariant( qlonglong( rev ) ), KDevelop::VcsRevision::GlobalNumber ); ev.setRevision( vcsrev ); if (changedPaths != NULL) { for (apr_hash_index_t *hi = apr_hash_first (pool, changedPaths); hi != NULL; hi = apr_hash_next (hi)) { char *path; void *val; apr_hash_this (hi, (const void **)&path, NULL, &val); svn_log_changed_path_t *log_item = reinterpret_cast (val); KDevelop::VcsItemEvent iev; iev.setRepositoryLocation( QString::fromUtf8( path ) ); iev.setRepositoryCopySourceLocation( QString::fromUtf8( log_item->copyfrom_path ) ); KDevelop::VcsRevision irev; irev.setRevisionValue( QVariant( qlonglong( log_item->copyfrom_rev ) ), KDevelop::VcsRevision::GlobalNumber ); iev.setRepositoryCopySourceRevision( irev ); switch( log_item->action ) { case 'A': iev.setActions( KDevelop::VcsItemEvent::Added ); break; case 'M': iev.setActions( KDevelop::VcsItemEvent::Modified ); break; case 'D': iev.setActions( KDevelop::VcsItemEvent::Deleted ); break; case 'R': iev.setActions( KDevelop::VcsItemEvent::Replaced ); break; } - ev.items().append( iev ); + auto items = ev.items(); + items.append( iev ); + ev.setItems( items ); } } client->emitLogEventReceived( ev ); return NULL; } void SvnClient::log( const char* path, const svn::Revision& start, const svn::Revision& end, int limit, bool discoverChangedPaths, bool strictNodeHistory ) throw (svn::ClientException) { svn::Pool pool; svn::Targets target(path); svn_error_t *error; error = svn_client_log2 ( target.array(pool), start.revision(), end.revision(), limit, discoverChangedPaths ? 1 : 0, strictNodeHistory ? 1 : 0, kdev_logReceiver, this, m_ctxt->ctx(), // client ctx pool); if (error != NULL) { throw svn::ClientException (error); } } void SvnClient::emitLogEventReceived( const KDevelop::VcsEvent& ev ) { emit logEventReceived( ev ); } diff --git a/plugins/subversion/svncommitjob.cpp b/plugins/subversion/svncommitjob.cpp index b595cd3a9..7ac3fdd45 100644 --- a/plugins/subversion/svncommitjob.cpp +++ b/plugins/subversion/svncommitjob.cpp @@ -1,175 +1,175 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2007 Andreas Pakulat * * Copyright 2007 Dukju Ahn * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU 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 "svncommitjob.h" #include "svncommitjob_p.h" #include #include #include #include #include #include #include #include "kdevsvncpp/client.hpp" #include "kdevsvncpp/path.hpp" #include "kdevsvncpp/targets.hpp" #include #include "svninternaljobbase.h" #include "kdevsvnplugin.h" SvnInternalCommitJob::SvnInternalCommitJob( SvnJobBase* parent ) : SvnInternalJobBase( parent ), m_recursive( true ), m_keepLock( false ) { } void SvnInternalCommitJob::setRecursive( bool recursive ) { QMutexLocker l( &m_mutex ); m_recursive = recursive; } void SvnInternalCommitJob::setCommitMessage( const QString& msg ) { QMutexLocker l( &m_mutex ); m_commitMessage = msg; } void SvnInternalCommitJob::setUrls( const QList& urls ) { QMutexLocker l( &m_mutex ); m_urls = urls; } void SvnInternalCommitJob::setKeepLock( bool lock ) { QMutexLocker l( &m_mutex ); m_keepLock = lock; } QList SvnInternalCommitJob::urls() const { QMutexLocker l( &m_mutex ); return m_urls; } QString SvnInternalCommitJob::commitMessage() const { QMutexLocker l( &m_mutex ); return m_commitMessage; } bool SvnInternalCommitJob::recursive() const { QMutexLocker l( &m_mutex ); return m_recursive; } bool SvnInternalCommitJob::keepLock() const { QMutexLocker l( &m_mutex ); return m_keepLock; } void SvnInternalCommitJob::run(ThreadWeaver::JobPointer /*self*/, ThreadWeaver::Thread* /*thread*/) { initBeforeRun(); svn::Client cli(m_ctxt); std::vector targets; QList l = urls(); foreach( const QUrl &u, l ) { QByteArray path = u.toString( QUrl::PreferLocalFile | QUrl::StripTrailingSlash ).toUtf8(); targets.push_back( svn::Path( path.data() ) ); } QByteArray ba = commitMessage().toUtf8(); try { cli.commit( svn::Targets(targets), ba.data(), recursive(), keepLock() ); }catch( svn::ClientException ce ) { qCDebug(PLUGIN_SVN) << "Couldn't commit:" << QString::fromUtf8( ce.message() ); setErrorMessage( QString::fromUtf8( ce.message() ) ); m_success = false; } } SvnCommitJob::SvnCommitJob( KDevSvnPlugin* parent ) : SvnJobBaseImpl(parent, KDevelop::OutputJob::Verbose ) { setType( KDevelop::VcsJob::Commit ); setObjectName(i18n("Subversion Commit")); } QVariant SvnCommitJob::fetchResults() { return QVariant(); } void SvnCommitJob::start() { - setTitle("commit"); + setTitle(QStringLiteral("commit")); setBehaviours( KDevelop::IOutputView::AllowUserClose | KDevelop::IOutputView::AutoScroll ); startOutput(); QStandardItemModel *m = qobject_cast(model()); m->setColumnCount(1); m->appendRow(new QStandardItem(i18n("Committing..."))); if( m_job->urls().isEmpty() ) { internalJobFailed(); setErrorText( i18n( "Not enough information to execute commit" ) ); m->appendRow(new QStandardItem(errorText())); } else { startInternalJob(); } } void SvnCommitJob::setCommitMessage( const QString& msg ) { if( status() == KDevelop::VcsJob::JobNotStarted ) m_job->setCommitMessage( msg ); } void SvnCommitJob::setKeepLock( bool keepLock ) { if( status() == KDevelop::VcsJob::JobNotStarted ) m_job->setKeepLock( keepLock ); } void SvnCommitJob::setUrls( const QList& urls ) { qCDebug(PLUGIN_SVN) << "Setting urls?" << status() << urls; if( status() == KDevelop::VcsJob::JobNotStarted ) m_job->setUrls( urls ); } void SvnCommitJob::setRecursive( bool recursive ) { if( status() == KDevelop::VcsJob::JobNotStarted ) m_job->setRecursive( recursive ); } diff --git a/plugins/subversion/svndiffjob.cpp b/plugins/subversion/svndiffjob.cpp index f2e5a5382..707be797b 100644 --- a/plugins/subversion/svndiffjob.cpp +++ b/plugins/subversion/svndiffjob.cpp @@ -1,441 +1,441 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2007 Andreas Pakulat * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "svndiffjob.h" #include "svndiffjob_p.h" #include #include #include #include #include #include #include #include "kdevsvncpp/path.hpp" #include "kdevsvncpp/revision.hpp" #include "icore.h" #include "iruncontroller.h" #include "svnclient.h" #include "svncatjob.h" ///@todo The subversion library returns borked diffs, where the headers are at the end. This function /// takes those headers, and moves them into the correct place to create a valid working diff. /// Find the source of this problem. QString repairDiff(QString diff) { qCDebug(PLUGIN_SVN) << "diff before repair:" << diff; QStringList lines = diff.split('\n'); QMap headers; for(int a = 0; a < lines.size()-1; ++a) { - if(lines[a].startsWith("Index: ") && lines[a+1].startsWith("=====")) { + if(lines[a].startsWith(QLatin1String("Index: ")) && lines[a+1].startsWith(QLatin1String("====="))) { QString fileName = lines[a].mid(strlen("Index: ")).trimmed(); headers[fileName] = lines[a]; qCDebug(PLUGIN_SVN) << "found header for" << fileName; lines[a] = QString(); - if(lines[a+1].startsWith("======")) { + if(lines[a+1].startsWith(QLatin1String("======"))) { headers[fileName] += '\n' + lines[a+1]; lines[a+1] = QString(); } } } QRegExp spaceRegExp("\\s"); for(int a = 0; a < lines.size()-1; ++a) { - if(lines[a].startsWith("--- ")) { + if(lines[a].startsWith(QLatin1String("--- "))) { QString tail = lines[a].mid(strlen("--- ")); if(tail.indexOf(spaceRegExp) != -1) { QString file = tail.left(tail.indexOf(spaceRegExp)); qCDebug(PLUGIN_SVN) << "checking for" << file; if(headers.contains(file)) { qCDebug(PLUGIN_SVN) << "adding header for" << file << ":" << headers[file]; lines[a] = headers[file] + '\n' + lines[a]; } } } } - QString ret = lines.join("\n"); + QString ret = lines.join(QLatin1Char('\n')); qCDebug(PLUGIN_SVN) << "repaired diff:" << ret; return ret; } //@TODO: Handle raw diffs by using SvnCatJob to fetch both files/revisions SvnInternalDiffJob::SvnInternalDiffJob( SvnJobBase* parent ) : SvnInternalJobBase( parent ), m_recursive( true ), m_ignoreAncestry( false ), m_ignoreContentType( false ), m_noDiffOnDelete( false ) { m_pegRevision.setRevisionValue( KDevelop::VcsRevision::Head, KDevelop::VcsRevision::Special ); } void SvnInternalDiffJob::run(ThreadWeaver::JobPointer /*self*/, ThreadWeaver::Thread* /*thread*/) { initBeforeRun(); SvnClient cli(m_ctxt); try { QString diff; if( destination().isValid() ) { QByteArray srcba; if( source().type() == KDevelop::VcsLocation::LocalLocation ) { srcba = source().localUrl().toString( QUrl::PreferLocalFile | QUrl::StripTrailingSlash ).toUtf8(); }else { srcba = source().repositoryServer().toUtf8(); } QByteArray dstba; if( destination().type() == KDevelop::VcsLocation::LocalLocation ) { dstba = destination().localUrl().toString( QUrl::PreferLocalFile | QUrl::StripTrailingSlash ).toUtf8(); }else { dstba = destination().repositoryServer().toUtf8(); } svn::Revision srcRev = createSvnCppRevisionFromVcsRevision( srcRevision() ); svn::Revision dstRev = createSvnCppRevisionFromVcsRevision( dstRevision() ); if( srcba.isEmpty() || ( dstba.isEmpty() && srcRev.kind() == svn_opt_revision_unspecified && dstRev.kind() == svn_opt_revision_unspecified ) ) { throw svn::ClientException( "Not enough information for a diff"); } diff = cli.diff( svn::Path( srcba.data() ), srcRev, svn::Path( dstba.data() ), dstRev, recursive(), ignoreAncestry(), noDiffOnDelete(), ignoreContentType() ); }else { QByteArray srcba; if( source().type() == KDevelop::VcsLocation::LocalLocation ) { srcba = source().localUrl().toString( QUrl::PreferLocalFile | QUrl::StripTrailingSlash ).toUtf8(); }else { srcba = source().repositoryServer().toUtf8(); } svn::Revision pegRev = createSvnCppRevisionFromVcsRevision( pegRevision() ); svn::Revision srcRev = createSvnCppRevisionFromVcsRevision( srcRevision() ); svn::Revision dstRev = createSvnCppRevisionFromVcsRevision( dstRevision() ); if( srcba.isEmpty() || pegRev.kind() == svn_opt_revision_unspecified || dstRev.kind() == svn_opt_revision_unspecified || srcRev.kind() == svn_opt_revision_unspecified) { throw svn::ClientException( "Not enough information for a diff"); } diff = cli.diff( svn::Path( srcba.data() ), pegRev, srcRev, dstRev, recursive(), ignoreAncestry(), noDiffOnDelete(), ignoreContentType() ); } diff = repairDiff(diff); emit gotDiff( diff ); }catch( svn::ClientException ce ) { qCDebug(PLUGIN_SVN) << "Exception while doing a diff: " << m_source.localUrl() << m_source.repositoryServer() << m_srcRevision.prettyValue() << m_destination.localUrl() << m_destination.repositoryServer() << m_dstRevision.prettyValue() << QString::fromUtf8( ce.message() ); setErrorMessage( QString::fromUtf8( ce.message() ) ); m_success = false; } } void SvnInternalDiffJob::setSource( const KDevelop::VcsLocation& src ) { QMutexLocker l( &m_mutex ); m_source = src; } void SvnInternalDiffJob::setDestination( const KDevelop::VcsLocation& dst ) { QMutexLocker l( &m_mutex ); m_destination = dst; } void SvnInternalDiffJob::setSrcRevision( const KDevelop::VcsRevision& srcRev ) { QMutexLocker l( &m_mutex ); m_srcRevision = srcRev; } void SvnInternalDiffJob::setDstRevision( const KDevelop::VcsRevision& dstRev ) { QMutexLocker l( &m_mutex ); m_dstRevision = dstRev; } void SvnInternalDiffJob::setPegRevision( const KDevelop::VcsRevision& pegRev ) { QMutexLocker l( &m_mutex ); m_pegRevision = pegRev; } void SvnInternalDiffJob::setRecursive( bool recursive ) { QMutexLocker l( &m_mutex ); m_recursive = recursive; } void SvnInternalDiffJob::setIgnoreAncestry( bool ignoreAncestry ) { QMutexLocker l( &m_mutex ); m_ignoreAncestry = ignoreAncestry; } void SvnInternalDiffJob::setIgnoreContentType( bool ignoreContentType ) { QMutexLocker l( &m_mutex ); m_ignoreContentType = ignoreContentType; } void SvnInternalDiffJob::setNoDiffOnDelete( bool noDiffOnDelete ) { QMutexLocker l( &m_mutex ); m_noDiffOnDelete = noDiffOnDelete; } bool SvnInternalDiffJob::recursive() const { QMutexLocker l( &m_mutex ); return m_recursive; } bool SvnInternalDiffJob::ignoreAncestry() const { QMutexLocker l( &m_mutex ); return m_ignoreAncestry; } bool SvnInternalDiffJob::ignoreContentType() const { QMutexLocker l( &m_mutex ); return m_ignoreContentType; } bool SvnInternalDiffJob::noDiffOnDelete() const { QMutexLocker l( &m_mutex ); return m_noDiffOnDelete; } KDevelop::VcsLocation SvnInternalDiffJob::source() const { QMutexLocker l( &m_mutex ); return m_source; } KDevelop::VcsLocation SvnInternalDiffJob::destination() const { QMutexLocker l( &m_mutex ); return m_destination; } KDevelop::VcsRevision SvnInternalDiffJob::srcRevision() const { QMutexLocker l( &m_mutex ); return m_srcRevision; } KDevelop::VcsRevision SvnInternalDiffJob::dstRevision() const { QMutexLocker l( &m_mutex ); return m_dstRevision; } KDevelop::VcsRevision SvnInternalDiffJob::pegRevision() const { QMutexLocker l( &m_mutex ); return m_pegRevision; } SvnDiffJob::SvnDiffJob( KDevSvnPlugin* parent ) : SvnJobBaseImpl( parent, KDevelop::OutputJob::Silent ) { setType( KDevelop::VcsJob::Add ); connect( m_job, &SvnInternalDiffJob::gotDiff, this, &SvnDiffJob::setDiff, Qt::QueuedConnection ); setObjectName(i18n("Subversion Diff")); } QVariant SvnDiffJob::fetchResults() { return qVariantFromValue( m_diff ); } void SvnDiffJob::start() { if( !m_job->source().isValid() || ( !m_job->destination().isValid() && ( m_job->srcRevision().revisionType() == KDevelop::VcsRevision::Invalid || m_job->dstRevision().revisionType() == KDevelop::VcsRevision::Invalid ) ) ) { internalJobFailed(); setErrorText( i18n( "Not enough information given to execute diff" ) ); } else { startInternalJob(); } } void SvnDiffJob::setSource( const KDevelop::VcsLocation& source ) { if( status() == KDevelop::VcsJob::JobNotStarted ) m_job->setSource( source ); } void SvnDiffJob::setDestination( const KDevelop::VcsLocation& destination ) { if( status() == KDevelop::VcsJob::JobNotStarted ) m_job->setDestination( destination ); } void SvnDiffJob::setPegRevision( const KDevelop::VcsRevision& pegRevision ) { if( status() == KDevelop::VcsJob::JobNotStarted ) m_job->setPegRevision( pegRevision ); } void SvnDiffJob::setSrcRevision( const KDevelop::VcsRevision& srcRevision ) { if( status() == KDevelop::VcsJob::JobNotStarted ) m_job->setSrcRevision( srcRevision ); } void SvnDiffJob::setDstRevision( const KDevelop::VcsRevision& dstRevision ) { if( status() == KDevelop::VcsJob::JobNotStarted ) m_job->setDstRevision( dstRevision ); } void SvnDiffJob::setRecursive( bool recursive ) { if( status() == KDevelop::VcsJob::JobNotStarted ) m_job->setRecursive( recursive ); } void SvnDiffJob::setIgnoreAncestry( bool ignoreAncestry ) { if( status() == KDevelop::VcsJob::JobNotStarted ) m_job->setIgnoreAncestry( ignoreAncestry ); } void SvnDiffJob::setIgnoreContentType( bool ignoreContentType ) { if( status() == KDevelop::VcsJob::JobNotStarted ) m_job->setIgnoreContentType( ignoreContentType ); } void SvnDiffJob::setNoDiffOnDelete( bool noDiffOnDelete ) { if( status() == KDevelop::VcsJob::JobNotStarted ) m_job->setNoDiffOnDelete( noDiffOnDelete ); } void SvnDiffJob::setDiff( const QString& diff ) { m_diff = KDevelop::VcsDiff(); - m_diff.setBaseDiff(QUrl::fromLocalFile("/")); + m_diff.setBaseDiff(QUrl::fromLocalFile(QStringLiteral("/"))); m_diff.setType( KDevelop::VcsDiff::DiffUnified ); m_diff.setContentType( KDevelop::VcsDiff::Text ); m_diff.setDiff( diff ); QRegExp fileRe("(?:^|\n)Index: ([^\n]+)\n"); QStringList paths; int pos = 0; while( ( pos = fileRe.indexIn( diff, pos ) ) != -1 ) { paths << fileRe.cap(1); pos += fileRe.matchedLength(); } if (paths.isEmpty()) { internalJobDone(); emit resultsReady( this ); return; } foreach( const QString &s, paths ) { if( !s.isEmpty() ) { SvnCatJob* job = new SvnCatJob( m_part ); KDevelop::VcsLocation l = m_job->source(); if( l.type() == KDevelop::VcsLocation::LocalLocation ) { l.setLocalUrl( QUrl::fromLocalFile( s ) ); }else { QString repoLocation = QUrl( l.repositoryServer() ).toString( QUrl::PreferLocalFile | QUrl::StripTrailingSlash ); QFileInfo fi( repoLocation ); if( s == fi.fileName() ) { l.setRepositoryServer( l.repositoryServer() ); }else { l.setRepositoryServer( l.repositoryServer() + '/' + s ); } } job->setSource( l ); job->setPegRevision( m_job->pegRevision() ); job->setSrcRevision( m_job->srcRevision() ); m_catJobMap[job] = l; connect( job, SIGNAL(resultsReady(KDevelop::VcsJob*)), this, SLOT(addLeftText(KDevelop::VcsJob*)) ); connect( job, SIGNAL(result(KJob*)), this, SLOT(removeJob(KJob*)) ); KDevelop::ICore::self()->runController()->registerJob(job); } } } void SvnDiffJob::addLeftText( KDevelop::VcsJob* job ) { if( m_catJobMap.contains( job ) ) { QVariant v = job->fetchResults(); m_diff.addLeftText( m_catJobMap[job], v.toString() ); m_catJobMap.remove(job); // KJobs delete themselves when finished } if( m_catJobMap.isEmpty() ) { internalJobDone(); emit resultsReady( this ); } } void SvnDiffJob::removeJob( KJob* job ) { if( job->error() != 0 ) { KDevelop::VcsJob* j = dynamic_cast( job ); if( j ) { if( m_catJobMap.contains( j ) ) { m_catJobMap.remove(j); // KJobs delete themselves when finished } } } if( m_catJobMap.isEmpty() ) { internalJobDone(); emit resultsReady( this ); } } void SvnDiffJob::setDiffType( KDevelop::VcsDiff::Type type ) { m_diffType = type; } diff --git a/plugins/subversion/svninternaljobbase.cpp b/plugins/subversion/svninternaljobbase.cpp index 575819473..6c59837c9 100644 --- a/plugins/subversion/svninternaljobbase.cpp +++ b/plugins/subversion/svninternaljobbase.cpp @@ -1,388 +1,388 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2007 Andreas Pakulat * * Copyright 2007 Dukju Ahn * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU 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 "svninternaljobbase.h" #include #include #include #include #include extern "C" { #include } #include #include #include "kdevsvncpp/context.hpp" #include "kdevsvncpp/apr.hpp" #include "kdevsvncpp/revision.hpp" SvnInternalJobBase::SvnInternalJobBase( SvnJobBase* parent ) : QObject(parent) , m_ctxt( new svn::Context() ) , m_guiSemaphore( 0 ) , m_mutex() , m_killMutex() , m_success( true ) , sendFirstDelta( false ) , killed( false ) { m_ctxt->setListener(this); } SvnInternalJobBase::~SvnInternalJobBase() { m_ctxt->setListener(0); delete m_ctxt; m_ctxt = 0; } void SvnInternalJobBase::defaultBegin(const ThreadWeaver::JobPointer& self, ThreadWeaver::Thread *thread) { emit started(); ThreadWeaver::Job::defaultBegin(self, thread); } void SvnInternalJobBase::defaultEnd(const ThreadWeaver::JobPointer& self, ThreadWeaver::Thread *thread) { ThreadWeaver::Job::defaultEnd(self, thread); if (!self->success()) { emit failed(); } emit done(); } bool SvnInternalJobBase::contextGetLogin( const std::string& realm, std::string& username, std::string& password, bool& maySave ) { emit needLogin( QString::fromUtf8( realm.c_str() ) ); m_guiSemaphore.acquire( 1 ); QMutexLocker l(&m_mutex); if( m_login_username.isEmpty() || m_login_password.isEmpty() ) return false; username = std::string( m_login_username.toUtf8() ); password = std::string( m_login_password.toUtf8() ); maySave = this->m_maySave; return true; } void SvnInternalJobBase::contextNotify( const char* path, svn_wc_notify_action_t action, svn_node_kind_t kind, const char* mimetype, svn_wc_notify_state_t contentState, svn_wc_notify_state_t propState, svn_revnum_t rev ) { QString notifyString; switch( action ){ case svn_wc_notify_add: notifyString = i18nc( "A file was marked to be added to svn", "Added %1", QString::fromUtf8( path ) ); break; case svn_wc_notify_delete: notifyString = i18nc( "A file was marked for deletion from svn", "Deleted %1", QString::fromUtf8( path ) ); break; // various update notifications case svn_wc_notify_update_delete: notifyString = i18nc( "A file was deleted during an svn update operation", "Deleted %1", QString::fromUtf8( path ) ); break; case svn_wc_notify_update_add: notifyString = i18nc( "A file was added during an svn update operation", "Added %1", QString::fromUtf8( path ) ); break; case svn_wc_notify_update_update: /* If this is an inoperative dir change, do no notification. An inoperative dir change is when a directory gets closed without any props having been changed. */ if (! ((kind == svn_node_dir) && ((propState == svn_wc_notify_state_inapplicable) || (propState == svn_wc_notify_state_unknown) || (propState == svn_wc_notify_state_unchanged)))) { if (kind == svn_node_file) { if (contentState == svn_wc_notify_state_conflicted) - notifyString = "Conflict On File"; + notifyString = QStringLiteral("Conflict On File"); else if (contentState == svn_wc_notify_state_merged) - notifyString = "File Merged"; + notifyString = QStringLiteral("File Merged"); else if (contentState == svn_wc_notify_state_changed) - notifyString = "File Updated"; + notifyString = QStringLiteral("File Updated"); } if (propState == svn_wc_notify_state_conflicted) - notifyString += " Conflict On Property"; + notifyString += QLatin1String(" Conflict On Property"); else if (propState == svn_wc_notify_state_merged) - notifyString += " Properties Merged"; + notifyString += QLatin1String(" Properties Merged"); else if (propState == svn_wc_notify_state_changed) - notifyString += " Properties Updated"; + notifyString += QLatin1String(" Properties Updated"); else notifyString += ' '; if (! ((contentState == svn_wc_notify_state_unchanged || contentState == svn_wc_notify_state_unknown) && (propState == svn_wc_notify_state_unchanged || propState == svn_wc_notify_state_unknown))) notifyString += QStringLiteral( " " ) + QString::fromUtf8( path ); } break; case svn_wc_notify_update_completed: // The last notification in an update (including updates of externals). notifyString = i18n("Revision %1", rev ); break; case svn_wc_notify_update_external: notifyString = i18n("Updating externals: %1", QString::fromUtf8( path ) ); break; case svn_wc_notify_status_completed: break; case svn_wc_notify_status_external: break; // various commit notifications case svn_wc_notify_commit_modified: notifyString = i18n( "Sending %1", QString::fromUtf8( path ) ); break; case svn_wc_notify_commit_added: if( mimetype ){ notifyString = i18n( "Adding %1 using mimetype %2.", QString::fromUtf8( path ), mimetype ); } else { notifyString = i18n( "Adding %1.", QString::fromUtf8( path ) ); } break; case svn_wc_notify_commit_deleted: notifyString = i18n( "Deleting %1.", QString::fromUtf8( path ) ); break; case svn_wc_notify_commit_replaced: notifyString = i18n( "Replacing %1.", QString::fromUtf8( path ) ); break; case svn_wc_notify_commit_postfix_txdelta: if ( sendFirstDelta ) { sendFirstDelta = false; notifyString=i18n("Transmitting file data "); } else { notifyString='.'; } break; case svn_wc_notify_blame_revision: notifyString = i18n( "Blame finished for revision %1, path %2", rev, QString::fromUtf8( path ) ); break; case svn_wc_notify_revert: notifyString = i18n( "Reverted working copy %1", QString::fromUtf8( path ) ); break; case svn_wc_notify_failed_revert: notifyString = i18n( "Reverting failed on working copy %1", QString::fromUtf8( path ) ); break; case svn_wc_notify_copy: notifyString = i18n( "Copied %1", QString::fromUtf8( path ) ); break; default: break; } emit showNotification( QString::fromUtf8( path ), notifyString ); } bool SvnInternalJobBase::contextCancel() { QMutexLocker lock( &m_killMutex ); return killed; } bool SvnInternalJobBase::contextGetLogMessage( std::string& msg ) { emit needCommitMessage(); m_guiSemaphore.acquire( 1 ); QMutexLocker l( &m_mutex ); QByteArray ba = m_commitMessage.toUtf8(); msg = std::string( ba.data() ); return true; } void SvnInternalJobBase::initBeforeRun() { connect( this, SIGNAL(needCommitMessage()), parent(), SLOT(askForCommitMessage()), Qt::QueuedConnection ); connect( this, SIGNAL(needLogin(QString)), parent(), SLOT(askForLogin(QString)), Qt::QueuedConnection ); connect( this, SIGNAL( needSslServerTrust( const QStringList&, const QString&, const QString&, const QString&, const QString&, const QString&, const QString& ) ), parent(), SLOT( askForSslServerTrust( const QStringList&, const QString&, const QString&, const QString&, const QString&, const QString&, const QString& ) ), Qt::QueuedConnection ); connect( this, SIGNAL(showNotification(QString,QString)), parent(), SLOT(showNotification(QString,QString)), Qt::QueuedConnection ); connect( this, SIGNAL(needSslClientCert(QString)), parent(), SLOT(askForSslClientCert(QString)), Qt::QueuedConnection ); connect( this, SIGNAL(needSslClientCertPassword(QString)), parent(), SLOT(askForSslClientCertPassword(QString)), Qt::QueuedConnection ); } svn::ContextListener::SslServerTrustAnswer SvnInternalJobBase::contextSslServerTrustPrompt( const svn::ContextListener::SslServerTrustData& data, apr_uint32_t& acceptedFailures ) { std::string host = data.hostname; std::string print = data.fingerprint; std::string from = data.validFrom; std::string until = data.validUntil; std::string issue = data.issuerDName; std::string realm = data.realm; acceptedFailures = data.failures; QStringList failures; if( data.failures & SVN_AUTH_SSL_NOTYETVALID ) { failures << i18n("Certificate is not yet valid."); } if( data.failures & SVN_AUTH_SSL_EXPIRED ) { failures << i18n("Certificate has expired."); } if( data.failures & SVN_AUTH_SSL_CNMISMATCH ) { failures << i18n("Certificate's CN (hostname) doesn't match the remote hostname."); } if( data.failures & SVN_AUTH_SSL_UNKNOWNCA ) { failures << i18n("Certificate authority is unknown."); } if( data.failures & SVN_AUTH_SSL_NOTYETVALID ) { failures << i18n("Other unknown error."); } emit needSslServerTrust( failures, QString::fromUtf8( host.c_str() ), QString::fromUtf8( print.c_str() ), QString::fromUtf8( from.c_str() ), QString::fromUtf8( until.c_str() ), QString::fromUtf8( issue.c_str() ), QString::fromUtf8( realm.c_str() ) ); m_guiSemaphore.acquire(1); QMutexLocker l(&m_mutex); return m_trustAnswer; } bool SvnInternalJobBase::contextSslClientCertPrompt( std::string& cert ) { emit needSslClientCert( QString::fromUtf8( cert.c_str() ) ); m_guiSemaphore.acquire( 1 ); return true; } bool SvnInternalJobBase::contextSslClientCertPwPrompt( std::string& pw, const std::string& realm, bool& maySave ) { Q_UNUSED(pw); Q_UNUSED(maySave); emit needSslClientCertPassword( QString::fromUtf8( realm.c_str() ) ); m_guiSemaphore.acquire( 1 ); return false; } bool SvnInternalJobBase::success() const { return m_success; } svn::Revision SvnInternalJobBase::createSvnCppRevisionFromVcsRevision( const KDevelop::VcsRevision& revision ) { svn::Revision rev; QVariant value = revision.revisionValue(); switch( revision.revisionType() ) { case KDevelop::VcsRevision::Special: { if( value.canConvert() ) { KDevelop::VcsRevision::RevisionSpecialType specialtype = value.value(); switch( specialtype ) { case KDevelop::VcsRevision::Head: rev = svn::Revision( svn::Revision::HEAD ); break; case KDevelop::VcsRevision::Working: rev = svn::Revision( svn::Revision::WORKING ); break; case KDevelop::VcsRevision::Base: rev = svn::Revision( svn::Revision::BASE ); break; case KDevelop::VcsRevision::Previous: rev = svn::Revision( svn_opt_revision_previous ); break; case KDevelop::VcsRevision::Start: rev = svn::Revision( svn::Revision::START ); break; default: break; } } break; } case KDevelop::VcsRevision::GlobalNumber: case KDevelop::VcsRevision::FileNumber: { bool ok; qlonglong number = value.toLongLong(&ok); if( ok ) { rev = svn::Revision( number ); } break; } case KDevelop::VcsRevision::Date: { QDateTime dt = value.toDateTime(); if( dt.isValid() ) { rev = svn::Revision( dt.toTime_t() ); } break; } default: break; } return rev; } void SvnInternalJobBase::setErrorMessage( const QString& msg ) { QMutexLocker lock( &m_mutex ); m_errorMessage = msg; } QString SvnInternalJobBase::errorMessage() const { QMutexLocker lock( &m_mutex ); return m_errorMessage; } void SvnInternalJobBase::kill() { QMutexLocker lock( &m_killMutex ); killed = true; } diff --git a/plugins/subversion/svnjobbase.cpp b/plugins/subversion/svnjobbase.cpp index d3c752c45..827370194 100644 --- a/plugins/subversion/svnjobbase.cpp +++ b/plugins/subversion/svnjobbase.cpp @@ -1,212 +1,212 @@ /*************************************************************************** * 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. * * * ***************************************************************************/ #include "svnjobbase.h" #include #include #include #include #include #include #include #include #include #include #include "svninternaljobbase.h" #include "svnssldialog.h" SvnJobBase::SvnJobBase( KDevSvnPlugin* parent, KDevelop::OutputJob::OutputJobVerbosity verbosity ) : VcsJob( parent, verbosity ), m_part( parent ), m_status( KDevelop::VcsJob::JobNotStarted ) { setCapabilities( KJob::Killable ); - setTitle( "Subversion" ); + setTitle( QStringLiteral("Subversion") ); } SvnJobBase::~SvnJobBase() { } void SvnJobBase::startInternalJob() { auto job = internalJob(); connect( job, &SvnInternalJobBase::failed, this, &SvnJobBase::internalJobFailed, Qt::QueuedConnection ); connect( job, &SvnInternalJobBase::done, this, &SvnJobBase::internalJobDone, Qt::QueuedConnection ); connect( job, &SvnInternalJobBase::started, this, &SvnJobBase::internalJobStarted, Qt::QueuedConnection ); m_part->jobQueue()->stream() << ThreadWeaver::make_job_raw(job); } bool SvnJobBase::doKill() { internalJob()->kill(); m_status = VcsJob::JobCanceled; return true; } KDevelop::VcsJob::JobStatus SvnJobBase::status() const { return m_status; } void SvnJobBase::askForLogin( const QString& realm ) { qCDebug(PLUGIN_SVN) << "login"; KPasswordDialog dlg( 0, KPasswordDialog::ShowUsernameLine | KPasswordDialog::ShowKeepPassword ); dlg.setPrompt( i18n("Enter Login for: %1", realm ) ); dlg.exec(); internalJob()->m_login_username = dlg.username(); internalJob()->m_login_password = dlg.password(); internalJob()->m_maySave = dlg.keepPassword(); internalJob()->m_guiSemaphore.release( 1 ); } void SvnJobBase::showNotification( const QString& path, const QString& msg ) { Q_UNUSED(path); outputMessage(msg); } void SvnJobBase::askForCommitMessage() { qCDebug(PLUGIN_SVN) << "commit msg"; internalJob()->m_guiSemaphore.release( 1 ); } void SvnJobBase::askForSslServerTrust( const QStringList& failures, const QString& host, const QString& print, const QString& from, const QString& until, const QString& issuer, const QString& realm ) { qCDebug(PLUGIN_SVN) << "servertrust"; SvnSSLTrustDialog dlg; dlg.setCertInfos( host, print, from, until, issuer, realm, failures ); if( dlg.exec() == QDialog::Accepted ) { qCDebug(PLUGIN_SVN) << "accepted with:" << dlg.useTemporarily(); if( dlg.useTemporarily() ) { internalJob()->m_trustAnswer = svn::ContextListener::ACCEPT_TEMPORARILY; }else { internalJob()->m_trustAnswer = svn::ContextListener::ACCEPT_PERMANENTLY; } }else { qCDebug(PLUGIN_SVN) << "didn't accept"; internalJob()->m_trustAnswer = svn::ContextListener::DONT_ACCEPT; } internalJob()->m_guiSemaphore.release( 1 ); } void SvnJobBase::askForSslClientCert( const QString& realm ) { KMessageBox::information( 0, realm ); qCDebug(PLUGIN_SVN) << "clientrust"; internalJob()->m_guiSemaphore.release( 1 ); } void SvnJobBase::askForSslClientCertPassword( const QString& ) { qCDebug(PLUGIN_SVN) << "clientpw"; internalJob()->m_guiSemaphore.release( 1 ); } void SvnJobBase::internalJobStarted() { qDebug() << "job started" << static_cast(internalJob()); m_status = KDevelop::VcsJob::JobRunning; } void SvnJobBase::internalJobDone() { qDebug() << "job done" << internalJob(); if ( m_status == VcsJob::JobFailed ) { // see: https://bugs.kde.org/show_bug.cgi?id=273759 // this gets also called when the internal job failed // then the emit result in internalJobFailed might trigger // a nested event loop (i.e. error dialog) // during that the internalJobDone gets called and triggers // deleteLater and eventually deletes this job // => havoc // // catching this state here works but I don't like it personally... return; } outputMessage(i18n("Completed")); if( m_status != VcsJob::JobCanceled ) { m_status = KDevelop::VcsJob::JobSucceeded; } emitResult(); if( m_status == VcsJob::JobCanceled ) { deleteLater(); } } void SvnJobBase::internalJobFailed() { qDebug() << "job failed" << internalJob(); setError( 255 ); QString msg = internalJob()->errorMessage(); if( !msg.isEmpty() ) setErrorText( i18n( "Error executing Job:\n%1", msg ) ); outputMessage(errorText()); qCDebug(PLUGIN_SVN) << "Job failed"; if( m_status != VcsJob::JobCanceled ) { m_status = KDevelop::VcsJob::JobFailed; } emitResult(); if( m_status == VcsJob::JobCanceled ) { deleteLater(); } } KDevelop::IPlugin* SvnJobBase::vcsPlugin() const { return m_part; } void SvnJobBase::outputMessage(const QString& message) { if (!model()) return; if (verbosity() == KDevelop::OutputJob::Silent) return; QStandardItemModel *m = qobject_cast(model()); QStandardItem *previous = m->item(m->rowCount()-1); - if (message == "." && previous && previous->text().contains(QRegExp("\\.+"))) + if (message == QLatin1String(".") && previous && previous->text().contains(QRegExp("\\.+"))) previous->setText(previous->text() + message); else m->appendRow(new QStandardItem(message)); - KDevelop::IPlugin* i = KDevelop::ICore::self()->pluginController()->pluginForExtension("org.kdevelop.IOutputView"); + KDevelop::IPlugin* i = KDevelop::ICore::self()->pluginController()->pluginForExtension(QStringLiteral("org.kdevelop.IOutputView")); if( i ) { KDevelop::IOutputView* view = i->extension(); if( view ) { view->raiseOutput( outputId() ); } } } diff --git a/plugins/subversion/svnlocationwidget.h b/plugins/subversion/svnlocationwidget.h index 983d2a2cf..6b8c5f0eb 100644 --- a/plugins/subversion/svnlocationwidget.h +++ b/plugins/subversion/svnlocationwidget.h @@ -1,25 +1,26 @@ /*************************************************************************** * 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) any later version. * * * ***************************************************************************/ #ifndef KDEVPLATFORM_PLUGIN_SVNLOCATIONWIDGET_H #define KDEVPLATFORM_PLUGIN_SVNLOCATIONWIDGET_H #include class SvnLocationWidget : public KDevelop::StandardVcsLocationWidget { + Q_OBJECT public: explicit SvnLocationWidget(QWidget* parent = 0, Qt::WindowFlags f = 0); virtual KDevelop::VcsLocation location() const override; virtual bool isCorrect() const override; }; #endif // KDEVPLATFORM_PLUGIN_SVNLOCATIONWIDGET_H diff --git a/plugins/subversion/svnssldialog.cpp b/plugins/subversion/svnssldialog.cpp index b654d7c7c..0a7b7667a 100644 --- a/plugins/subversion/svnssldialog.cpp +++ b/plugins/subversion/svnssldialog.cpp @@ -1,85 +1,85 @@ /*************************************************************************** * 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. * * * ***************************************************************************/ #include "svnssldialog.h" #include #include #include #include "ui_ssltrustdialog.h" class SvnSSLTrustDialogPrivate { public: Ui::SvnSSLTrustDialog ui; bool temporarily; }; SvnSSLTrustDialog::SvnSSLTrustDialog( QWidget *parent ) : QDialog( parent ), d( new SvnSSLTrustDialogPrivate ) { d->ui.setupUi( this ); d->temporarily = true; setWindowTitle( i18n( "Ssl Server Certificate" ) ); buttonBox = new QDialogButtonBox(QDialogButtonBox::Cancel); buttonBox->addButton(i18n("Trust Permanently"), QDialogButtonBox::YesRole); buttonBox->addButton(i18n("Trust Temporarily"), QDialogButtonBox::AcceptRole)->setDefault(true); auto layout = new QVBoxLayout(); setLayout(layout); layout->addWidget(buttonBox); connect(buttonBox, &QDialogButtonBox::clicked, this, &SvnSSLTrustDialog::buttonClicked); } SvnSSLTrustDialog::~SvnSSLTrustDialog() { delete d; } void SvnSSLTrustDialog::setCertInfos( const QString& hostname, const QString& fingerPrint, const QString& validfrom, const QString& validuntil, const QString& issuerName, const QString& realm, const QStringList& failures ) { - QString txt = "
    "; + QString txt = QStringLiteral("
      "); foreach( const QString &fail, failures ) { txt += "
    • "+fail+"
    • "; } d->ui.reasons->setHtml( txt ); d->ui.hostname->setText( hostname ); d->ui.fingerprint->setText( fingerPrint ); d->ui.validUntil->setText( validuntil ); d->ui.validFrom->setText( validfrom ); d->ui.issuer->setText( issuerName ); setWindowTitle( i18n( "Ssl Server Certificate: %1", realm ) ); } bool SvnSSLTrustDialog::useTemporarily() { return d->temporarily; } void SvnSSLTrustDialog::buttonClicked(QAbstractButton *button) { if (buttonBox->buttonRole(button) == QDialogButtonBox::AcceptRole) { d->temporarily = true; } else { d->temporarily = false; } accept(); } diff --git a/plugins/subversion/tests/svnimport.cpp b/plugins/subversion/tests/svnimport.cpp index 6fd5c80c4..d4a6ef8ca 100644 --- a/plugins/subversion/tests/svnimport.cpp +++ b/plugins/subversion/tests/svnimport.cpp @@ -1,164 +1,164 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2009 Fabian Wiesel * * * * 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 "svnimport.h" #include #include #include #include #include #include #include #include #include #include #define VERBOSE #if defined(VERBOSE) #define TRACE(X) qDebug() << X #else #define TRACE(X) { line = line; } #endif using namespace KDevelop; void validatingExecJob(VcsJob* j, VcsJob::JobStatus status = VcsJob::JobSucceeded) { QVERIFY(j); if (!j->exec()) { qDebug() << j->errorString(); // On error, wait for key in order to allow manual state inspection } QCOMPARE(j->status(), status); } void setupLocalRepository( const QString& name, VcsLocation & reposLoc ) { KProcess cmd; cmd.setWorkingDirectory(name); - cmd << "svnadmin" << "create" << name; + cmd << QStringLiteral("svnadmin") << QStringLiteral("create") << name; QCOMPARE(cmd.execute(10000), 0); reposLoc.setRepositoryServer("file://" + name ); } void setupSampleProject( const QString& name, const QString& content ) { QFile sampleFile( name + "/sample.file" ); sampleFile.open( QIODevice::WriteOnly ); sampleFile.write( content.toUtf8() ); sampleFile.close(); } void SvnImport::initTestCase() { QLoggingCategory::setFilterRules(QStringLiteral("*.debug=false\ndefault.debug=true\nkdevplatform.plugins.svn.debug=true\n")); AutoTestShell::init({QStringLiteral("kdevsubversion")}); TestCore::initialize(); - QList plugins = Core::self()->pluginController()->allPluginsForExtension("org.kdevelop.IBasicVersionControl"); + QList plugins = Core::self()->pluginController()->allPluginsForExtension(QStringLiteral("org.kdevelop.IBasicVersionControl")); foreach(IPlugin* p, plugins) { qDebug() << "checking plugin" << p; ICentralizedVersionControl* icentr = p->extension(); if (!icentr) continue; - if (icentr->name() == "Subversion") { + if (icentr->name() == QLatin1String("Subversion")) { vcs = icentr; break; } } qDebug() << "ok, got vcs" << vcs; QVERIFY(vcs); } void SvnImport::cleanupTestCase() { TestCore::shutdown(); } void SvnImport::testBasic() { QTemporaryDir reposDir; VcsLocation reposLoc; setupLocalRepository( reposDir.path(), reposLoc ); QTemporaryDir projectDir; - QString origcontent = "This is a Test"; + QString origcontent = QStringLiteral("This is a Test"); setupSampleProject( projectDir.path(), origcontent ); - VcsJob* job = vcs->import( "import test", QUrl::fromLocalFile( projectDir.path() ), reposLoc ); + VcsJob* job = vcs->import( QStringLiteral("import test"), QUrl::fromLocalFile( projectDir.path() ), reposLoc ); validatingExecJob(job); QTemporaryDir checkoutDir; validateImport( reposLoc.repositoryServer(), checkoutDir, origcontent ); } void SvnImport::testImportWithMissingDirs() { QTemporaryDir reposDir; VcsLocation reposLoc; setupLocalRepository( reposDir.path(), reposLoc ); QTemporaryDir projectDir; - QString origcontent = "This is a Test"; + QString origcontent = QStringLiteral("This is a Test"); setupSampleProject( projectDir.path(), origcontent ); reposLoc.setRepositoryServer( reposLoc.repositoryServer() + "/foobar/" + QDir( projectDir.path() ).dirName() ); - VcsJob* job = vcs->import( "import test", QUrl::fromLocalFile( projectDir.path() ), reposLoc ); + VcsJob* job = vcs->import( QStringLiteral("import test"), QUrl::fromLocalFile( projectDir.path() ), reposLoc ); validatingExecJob(job); QTemporaryDir checkoutDir; validateImport( reposLoc.repositoryServer(), checkoutDir, origcontent ); } void SvnImport::testImportIntoDir() { QTemporaryDir reposDir; VcsLocation reposLoc; setupLocalRepository( reposDir.path(), reposLoc ); QTemporaryDir projectDir; - QString origcontent = "This is a Test"; + QString origcontent = QStringLiteral("This is a Test"); setupSampleProject( projectDir.path(), origcontent ); reposLoc.setRepositoryServer( reposLoc.repositoryServer() + '/' + QDir( projectDir.path() ).dirName() ); - VcsJob* job = vcs->import( "import test", QUrl::fromLocalFile( projectDir.path() ), reposLoc ); + VcsJob* job = vcs->import( QStringLiteral("import test"), QUrl::fromLocalFile( projectDir.path() ), reposLoc ); validatingExecJob(job); QTemporaryDir checkoutDir; validateImport( reposLoc.repositoryServer(), checkoutDir, origcontent ); } void SvnImport::validateImport( const QString& repourl, QTemporaryDir& checkoutdir, const QString& origcontent ) { VcsLocation reposLoc; reposLoc.setRepositoryServer( repourl ); VcsJob* job = vcs->createWorkingCopy( reposLoc, QUrl::fromLocalFile(checkoutdir.path()) ); validatingExecJob(job); QFile newfile( checkoutdir.path() + "/sample.file" ); QVERIFY(newfile.exists()); QVERIFY(newfile.open(QIODevice::ReadOnly)); QCOMPARE(QString::fromUtf8( newfile.readAll() ), origcontent); } QTEST_MAIN(SvnImport) diff --git a/plugins/subversion/tests/svnrecursiveadd.cpp b/plugins/subversion/tests/svnrecursiveadd.cpp index 947f0e602..fb681609e 100644 --- a/plugins/subversion/tests/svnrecursiveadd.cpp +++ b/plugins/subversion/tests/svnrecursiveadd.cpp @@ -1,160 +1,160 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2009 Fabian Wiesel * * * * 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 "svnrecursiveadd.h" #include #include #include #include #include #include #include #include #include #define PATHETIC // A little motivator to make things work right :) #if defined(PATHETIC) -const QString vcsTestDir0("testdir0"); -const QString vcsTestDir1("testdir1"); -const QString vcsTest_FileName0("foo"); -const QString vcsTest_FileName1("bar"); -const QString keywordText("text"); +inline QString vcsTestDir0() { return QStringLiteral("testdir0"); } +inline QString vcsTestDir1() { return QStringLiteral("testdir1"); } +inline QString vcsTest_FileName0() { return QStringLiteral("foo"); } +inline QString vcsTest_FileName1() { return QStringLiteral("bar"); } +inline QString keywordText() { return QStringLiteral("text"); } #else -const QString vcsTestDir0("dvcs\t testdir"); // Directory containing whitespaces -const QString vcsTestDir1("--help"); // Starting with hyphen for command-line tools -const QString vcsTest_FileName0("foo\t bar"); -const QString vcsTest_FileName1("--help"); -const QString keywordText("Author:\nDate:\nCommit:\n------------------------------------------------------------------------\nr999999 | ehrman | 1989-11-09 18:53:00 +0100 (Thu, 09 Nov 1989) | 1 lines\nthe line\n"); // Text containing keywords of the various vcs-programs +inline QString vcsTestDir0() { return QStringLiteral("dvcs\t testdir"); } // Directory containing whitespaces +inline QString vcsTestDir1() { return QStringLiteral("--help"); } // Starting with hyphen for command-line tools +inline QString vcsTest_FileName0() { return QStringLiteral("foo\t bar"); } +inline QString vcsTest_FileName1() { return QStringLiteral("--help"); } +inline QString keywordText() { return QStringLiteral("Author:\nDate:\nCommit:\n------------------------------------------------------------------------\nr999999 | ehrman | 1989-11-09 18:53:00 +0100 (Thu, 09 Nov 1989) | 1 lines\nthe line\n"); } // Text containing keywords of the various vcs-programs #endif -const QString simpleText("It's foo!\n"); -const QString simpleAltText("No, foo()! It's bar()!\n"); +inline QString simpleText() { return QStringLiteral("It's foo!\n"); } +inline QString simpleAltText() { return QStringLiteral("No, foo()! It's bar()!\n"); } #define VERBOSE #if defined(VERBOSE) #define TRACE(X) qDebug() << X #else #define TRACE(X) { line = line; } #endif using namespace KDevelop; void validatingExecJob(VcsJob* j, VcsJob::JobStatus status = VcsJob::JobSucceeded) { QVERIFY(j); // Print the commands in full, for easier bug location #if 0 if (QLatin1String(j->metaObject()->className()) == "DVcsJob") { qDebug() << "Command: \"" << ((DVcsJob*)j)->getChildproc()->program() << ((DVcsJob*)j)->getChildproc()->workingDirectory(); qDebug() << "Output: \"" << ((DVcsJob*)j)->output(); } #endif if (!j->exec()) { qDebug() << "ooops, no exec"; qDebug() << j->errorString(); // On error, wait for key in order to allow manual state inspection #if 0 char c; std::cin.read(&c, 1); #endif } QCOMPARE(j->status(), status); } void verifiedWrite(QUrl const & url, QString const & contents) { QFile f(url.path()); QVERIFY(f.open(QIODevice::WriteOnly)); QTextStream filecontents(&f); filecontents << contents; filecontents.flush(); f.flush(); } void fillWorkingDirectory(QString const & dirname) { QDir dir(dirname); //we start it after repoInit, so we still have empty dvcs repo - QVERIFY(dir.mkdir(vcsTestDir0)); - QVERIFY(dir.cd(vcsTestDir0)); - QUrl file0 = QUrl::fromLocalFile(dir.absoluteFilePath(vcsTest_FileName0)); - QVERIFY(dir.mkdir(vcsTestDir1)); - QVERIFY(dir.cd(vcsTestDir1)); - QUrl file1 = QUrl::fromLocalFile(dir.absoluteFilePath(vcsTest_FileName1)); - verifiedWrite(file0, simpleText); - verifiedWrite(file1, keywordText); + QVERIFY(dir.mkdir(vcsTestDir0())); + QVERIFY(dir.cd(vcsTestDir0())); + QUrl file0 = QUrl::fromLocalFile(dir.absoluteFilePath(vcsTest_FileName0())); + QVERIFY(dir.mkdir(vcsTestDir1())); + QVERIFY(dir.cd(vcsTestDir1())); + QUrl file1 = QUrl::fromLocalFile(dir.absoluteFilePath(vcsTest_FileName1())); + verifiedWrite(file0, simpleText()); + verifiedWrite(file1, keywordText()); } void SvnRecursiveAdd::initTestCase() { AutoTestShell::init(); TestCore::initialize(); } void SvnRecursiveAdd::cleanupTestCase() { TestCore::shutdown(); } void SvnRecursiveAdd::test() { QTemporaryDir reposDir; KProcess cmd; cmd.setWorkingDirectory(reposDir.path()); - cmd << "svnadmin" << "create" << reposDir.path(); + cmd << QStringLiteral("svnadmin") << QStringLiteral("create") << reposDir.path(); QCOMPARE(cmd.execute(10000), 0); - QList plugins = Core::self()->pluginController()->allPluginsForExtension("org.kdevelop.IBasicVersionControl"); + QList plugins = Core::self()->pluginController()->allPluginsForExtension(QStringLiteral("org.kdevelop.IBasicVersionControl")); IBasicVersionControl* vcs = NULL; foreach(IPlugin* p, plugins) { qDebug() << "checking plugin" << p; ICentralizedVersionControl* icentr = p->extension(); if (!icentr) continue; - if (icentr->name() == "Subversion") { + if (icentr->name() == QLatin1String("Subversion")) { vcs = icentr; break; } } qDebug() << "ok, got vcs" << vcs; QVERIFY(vcs); VcsLocation reposLoc; reposLoc.setRepositoryServer("file://" + reposDir.path()); QTemporaryDir checkoutDir; QUrl checkoutLoc = QUrl::fromLocalFile(checkoutDir.path()); qDebug() << "Checking out from " << reposLoc.repositoryServer() << " to " << checkoutLoc; qDebug() << "creating job"; VcsJob* job = vcs->createWorkingCopy( reposLoc, checkoutLoc ); validatingExecJob(job); qDebug() << "filling wc"; fillWorkingDirectory(checkoutDir.path()); - QUrl addUrl = QUrl::fromLocalFile( checkoutDir.path() + '/' + vcsTestDir0 ); + QUrl addUrl = QUrl::fromLocalFile( checkoutDir.path() + '/' + vcsTestDir0() ); qDebug() << "Recursively adding files at " << addUrl; validatingExecJob(vcs->add({addUrl}, IBasicVersionControl::Recursive)); qDebug() << "Recursively reverting changes at " << addUrl; validatingExecJob(vcs->revert({addUrl}, IBasicVersionControl::Recursive)); } QTEST_MAIN(SvnRecursiveAdd) diff --git a/plugins/switchtobuddy/switchtobuddyplugin.cpp b/plugins/switchtobuddy/switchtobuddyplugin.cpp index 3678a45a4..f3833f5c2 100644 --- a/plugins/switchtobuddy/switchtobuddyplugin.cpp +++ b/plugins/switchtobuddy/switchtobuddyplugin.cpp @@ -1,301 +1,301 @@ /* * This file is part of KDevelop * Copyright 2012 André Stein * Copyright 2014 Kevin Funk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "switchtobuddyplugin.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include Q_LOGGING_CATEGORY(PLUGIN_SWITCHTOBUDDY, "kdevplatform.plugins.switchtobuddy") using namespace KDevelop; namespace { KTextEditor::Cursor normalizeCursor(KTextEditor::Cursor c) { c.setColumn(0); return c; } ///Tries to find a definition for the declaration at given cursor-position and document-url. DUChain must be locked. Declaration* definitionForCursorDeclaration(const KTextEditor::Cursor& cursor, const QUrl& url) { QList topContexts = DUChain::self()->chainsForDocument(url); foreach (TopDUContext* ctx, topContexts) { Declaration* decl = DUChainUtils::declarationInLine(cursor, ctx); if (!decl) { continue; } if (auto definition = FunctionDefinition::definition(decl)) { return definition; } } return 0; } QString findSwitchCandidate(const QUrl& docUrl) { QMimeDatabase db; IBuddyDocumentFinder* finder = IBuddyDocumentFinder::finderForMimeType(db.mimeTypeForUrl(docUrl).name()); if (finder) { // get the first entry that exists, use that as candidate foreach(const QUrl& buddyUrl, finder->getPotentialBuddies(docUrl)) { if (!QFile::exists(buddyUrl.toLocalFile())) { continue; } return buddyUrl.toLocalFile(); } } return QString(); } } K_PLUGIN_FACTORY_WITH_JSON(SwitchToBuddyPluginFactory, "kdevswitchtobuddy.json", registerPlugin(); ) SwitchToBuddyPlugin::SwitchToBuddyPlugin ( QObject* parent, const QVariantList& ) - : IPlugin ( "kdevswitchtobuddy", parent ) + : IPlugin ( QStringLiteral("kdevswitchtobuddy"), parent ) , m_signalMapper(0) { - setXMLFile("kdevswitchtobuddy.rc"); + setXMLFile(QStringLiteral("kdevswitchtobuddy.rc")); } SwitchToBuddyPlugin::~SwitchToBuddyPlugin() { } ContextMenuExtension SwitchToBuddyPlugin::contextMenuExtension(Context* context) { EditorContext* ctx = dynamic_cast(context); if (!ctx) { return ContextMenuExtension(); } QUrl currentUrl = ctx->url(); IBuddyDocumentFinder* buddyFinder = IBuddyDocumentFinder::finderForMimeType(QMimeDatabase().mimeTypeForUrl(currentUrl).name()); if (!buddyFinder) return ContextMenuExtension(); // Get all potential buddies for the current document and add a switch-to action // for each buddy who really exists in the file system. Note: if no buddies could be calculated // no extension actions are generated. const QVector& potentialBuddies = buddyFinder->getPotentialBuddies(currentUrl); ContextMenuExtension extension; if (m_signalMapper) { delete m_signalMapper; } m_signalMapper = new QSignalMapper(this); foreach(const QUrl& url, potentialBuddies) { if (!QFile::exists(url.toLocalFile())) { continue; } QAction* action = new QAction(i18n("Switch to '%1'", url.fileName()), this); connect(action, &QAction::triggered, m_signalMapper, static_cast(&QSignalMapper::map), Qt::QueuedConnection); m_signalMapper->setMapping(action, url.toLocalFile()); connect(m_signalMapper, static_cast(&QSignalMapper::mapped), this, &SwitchToBuddyPlugin::switchToBuddy, Qt::QueuedConnection); extension.addAction(ContextMenuExtension::ExtensionGroup, action); } return extension; } void SwitchToBuddyPlugin::createActionsForMainWindow(Sublime::MainWindow* /*window*/, QString& xmlFile, KActionCollection& actions) { xmlFile = this->xmlFile(); - QAction* switchDefinitionDeclaration = actions.addAction("switch_definition_declaration"); + QAction* switchDefinitionDeclaration = actions.addAction(QStringLiteral("switch_definition_declaration")); switchDefinitionDeclaration->setText( i18n("&Switch Definition/Declaration") ); actions.setDefaultShortcut( switchDefinitionDeclaration, Qt::CTRL | Qt::SHIFT | Qt::Key_C ); connect(switchDefinitionDeclaration, &QAction::triggered, this, &SwitchToBuddyPlugin::switchDefinitionDeclaration); } void SwitchToBuddyPlugin::switchToBuddy(const QString& url) { KDevelop::ICore::self()->documentController()->openDocument(QUrl::fromLocalFile(url)); } void SwitchToBuddyPlugin::switchDefinitionDeclaration() { qCDebug(PLUGIN_SWITCHTOBUDDY) << "switching definition/declaration"; QUrl docUrl; KTextEditor::Cursor cursor; ///Step 1: Find the current top-level context of type DUContext::Other(the highest code-context). ///-- If it belongs to a function-declaration or definition, it can be retrieved through owner(), and we are in a definition. ///-- If no such context could be found, search for a declaration on the same line as the cursor, and switch to the according definition { auto view = ICore::self()->documentController()->activeTextDocumentView(); if (!view) { qCDebug(PLUGIN_SWITCHTOBUDDY) << "No active document"; return; } docUrl = view->document()->url(); cursor = view->cursorPosition(); } QString switchCandidate = findSwitchCandidate(docUrl); if(!switchCandidate.isEmpty()) { DUChainReadLocker lock; //If the file has not been parsed yet, update it TopDUContext* ctx = DUChainUtils::standardContextForUrl(docUrl); //At least 'VisibleDeclarationsAndContexts' is required so we can do a switch if (!ctx || (ctx->parsingEnvironmentFile() && !ctx->parsingEnvironmentFile()->featuresSatisfied(TopDUContext::AllDeclarationsContextsAndUses))) { lock.unlock(); qCDebug(PLUGIN_SWITCHTOBUDDY) << "Parsing switch-candidate before switching" << switchCandidate; ReferencedTopDUContext updatedContext = DUChain::self()->waitForUpdate(IndexedString(switchCandidate), TopDUContext::AllDeclarationsContextsAndUses); if (!updatedContext) { qCDebug(PLUGIN_SWITCHTOBUDDY) << "Failed to update document:" << switchCandidate; return; } } } qCDebug(PLUGIN_SWITCHTOBUDDY) << "Document:" << docUrl; DUChainReadLocker lock; TopDUContext* standardCtx = DUChainUtils::standardContextForUrl(docUrl); bool wasSignal = false; if (standardCtx) { Declaration* definition = 0; DUContext* ctx = standardCtx->findContext(standardCtx->transformToLocalRevision(cursor)); if (!ctx) { ctx = standardCtx; } while (ctx && ctx->parentContext() && (ctx->parentContext()->type() == DUContext::Other || ctx->parentContext()->type() == DUContext::Function)) { ctx = ctx->parentContext(); } if (ctx && ctx->owner() && (ctx->type() == DUContext::Other || ctx->type() == DUContext::Function) && ctx->owner()->isDefinition()) { definition = ctx->owner(); qCDebug(PLUGIN_SWITCHTOBUDDY) << "found definition while traversing:" << definition->toString(); } if (!definition && ctx) { definition = DUChainUtils::declarationInLine(cursor, ctx); } if (ClassFunctionDeclaration* cDef = dynamic_cast(definition)) { if (cDef->isSignal()) { qCDebug(PLUGIN_SWITCHTOBUDDY) << "found definition is a signal, not switching to .moc implementation"; definition = 0; wasSignal = true; } } FunctionDefinition* def = dynamic_cast(definition); if (def && def->declaration()) { Declaration* declaration = def->declaration(); KTextEditor::Range targetRange = declaration->rangeInCurrentRevision(); const auto url = declaration->url().toUrl(); qCDebug(PLUGIN_SWITCHTOBUDDY) << "found definition that has declaration: " << definition->toString() << "range" << targetRange << "url" << url; lock.unlock(); auto view = ICore::self()->documentController()->activeTextDocumentView(); if (view && !targetRange.contains(view->cursorPosition())) { const auto pos = normalizeCursor(targetRange.start()); ICore::self()->documentController()->openDocument(url, KTextEditor::Range(pos, pos)); } else { ICore::self()->documentController()->openDocument(url); } return; } else { qCDebug(PLUGIN_SWITCHTOBUDDY) << "Definition has no assigned declaration"; } qCDebug(PLUGIN_SWITCHTOBUDDY) << "Could not get definition/declaration from context"; } else { qCDebug(PLUGIN_SWITCHTOBUDDY) << "Got no context for the current document"; } Declaration* def = 0; if (!wasSignal) { def = definitionForCursorDeclaration(cursor, docUrl); } if (def) { const auto url = def->url().toUrl(); KTextEditor::Range targetRange = def->rangeInCurrentRevision(); if (def->internalContext()) { targetRange.end() = def->internalContext()->rangeInCurrentRevision().end(); } else { qCDebug(PLUGIN_SWITCHTOBUDDY) << "Declaration does not have internal context"; } lock.unlock(); auto view = ICore::self()->documentController()->activeTextDocumentView(); if (view && !targetRange.contains(view->cursorPosition())) { KTextEditor::Cursor pos(normalizeCursor(targetRange.start())); ICore::self()->documentController()->openDocument(url, KTextEditor::Range(pos, pos)); } else { //The cursor is already in the target range, only open the document ICore::self()->documentController()->openDocument(url); } return; } else if (!wasSignal) { qWarning() << "Found no definition assigned to cursor position"; } lock.unlock(); ///- If no definition/declaration could be found to switch to, just switch the document using normal header/source heuristic by file-extension if (!switchCandidate.isEmpty()) { ICore::self()->documentController()->openDocument(QUrl::fromUserInput(switchCandidate)); } else { qCDebug(PLUGIN_SWITCHTOBUDDY) << "Found no source/header candidate to switch"; } } #include "switchtobuddyplugin.moc" diff --git a/plugins/testview/testview.cpp b/plugins/testview/testview.cpp index ef8a3ef10..84c0383b7 100644 --- a/plugins/testview/testview.cpp +++ b/plugins/testview/testview.cpp @@ -1,413 +1,413 @@ /* This file is part of KDevelop Copyright 2012 Miha Čančula This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "testview.h" #include "testviewplugin.h" #include "testviewdebug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; enum CustomRoles { ProjectRole = Qt::UserRole + 1, SuiteRole, CaseRole }; static const char sessionConfigGroup[] = "TestView"; static const char filterConfigKey[] = "filter"; TestView::TestView(TestViewPlugin* plugin, QWidget* parent) : QWidget(parent) , m_plugin(plugin) , m_tree(new QTreeView(this)) , m_filter(new KRecursiveFilterProxyModel(this)) { - setWindowIcon(QIcon::fromTheme("preflight-verifier", windowIcon())); + setWindowIcon(QIcon::fromTheme(QStringLiteral("preflight-verifier"), windowIcon())); setWindowTitle(i18n("Unit Tests")); QVBoxLayout* layout = new QVBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); setLayout(layout); layout->addWidget(m_tree); m_tree->setSortingEnabled(true); m_tree->header()->hide(); m_tree->setIndentation(10); m_tree->setEditTriggers(QTreeView::NoEditTriggers); m_tree->setSelectionBehavior(QTreeView::SelectRows); m_tree->setSelectionMode(QTreeView::SingleSelection); m_tree->setExpandsOnDoubleClick(false); m_tree->sortByColumn(0, Qt::AscendingOrder); connect(m_tree, &QTreeView::activated, this, &TestView::doubleClicked); m_model = new QStandardItemModel(this); m_filter->setSourceModel(m_model); m_tree->setModel(m_filter); - QAction* showSource = new QAction( QIcon::fromTheme("code-context"), i18n("Show Source"), this ); + QAction* showSource = new QAction( QIcon::fromTheme(QStringLiteral("code-context")), i18n("Show Source"), this ); connect (showSource, &QAction::triggered, this, &TestView::showSource); m_contextMenuActions << showSource; - addAction(plugin->actionCollection()->action("run_all_tests")); - addAction(plugin->actionCollection()->action("stop_running_tests")); + addAction(plugin->actionCollection()->action(QStringLiteral("run_all_tests"))); + addAction(plugin->actionCollection()->action(QStringLiteral("stop_running_tests"))); - QAction* runSelected = new QAction( QIcon::fromTheme("system-run"), i18n("Run Selected Tests"), this ); + QAction* runSelected = new QAction( QIcon::fromTheme(QStringLiteral("system-run")), i18n("Run Selected Tests"), this ); connect (runSelected, &QAction::triggered, this, &TestView::runSelectedTests); addAction(runSelected); QLineEdit* edit = new QLineEdit(parent); edit->setPlaceholderText(i18n("Filter...")); edit->setClearButtonEnabled(true); QWidgetAction* widgetAction = new QWidgetAction(this); widgetAction->setDefaultWidget(edit); connect(edit, &QLineEdit::textChanged, this, &TestView::changeFilter); addAction(widgetAction); IProjectController* pc = ICore::self()->projectController(); connect (pc, &IProjectController::projectClosed, this, &TestView::removeProject); ITestController* tc = ICore::self()->testController(); connect (tc, &ITestController::testSuiteAdded, this, &TestView::addTestSuite); connect (tc, &ITestController::testSuiteRemoved, this, &TestView::removeTestSuite); connect (tc, &ITestController::testRunFinished, this, &TestView::updateTestSuite); connect (tc, &ITestController::testRunStarted, this, &TestView::notifyTestCaseStarted); foreach (ITestSuite* suite, tc->testSuites()) { addTestSuite(suite); } } TestView::~TestView() { } void TestView::updateTestSuite(ITestSuite* suite, const TestResult& result) { QStandardItem* item = itemForSuite(suite); if (!item) { return; } qCDebug(PLUGIN_TESTVIEW) << "Updating test suite" << suite->name(); item->setIcon(iconForTestResult(result.suiteResult)); for (int i = 0; i < item->rowCount(); ++i) { qCDebug(PLUGIN_TESTVIEW) << "Found a test case" << item->child(i)->text(); QStandardItem* caseItem = item->child(i); if (result.testCaseResults.contains(caseItem->text())) { TestResult::TestCaseResult caseResult = result.testCaseResults.value(caseItem->text(), TestResult::NotRun); caseItem->setIcon(iconForTestResult(caseResult)); } } } void TestView::changeFilter(const QString &newFilter) { m_filter->setFilterWildcard(newFilter); if (newFilter.isEmpty()) { m_tree->collapseAll(); } else { m_tree->expandAll(); } } void TestView::notifyTestCaseStarted(ITestSuite* suite, const QStringList& test_cases) { QStandardItem* item = itemForSuite(suite); if (!item) { return; } qCDebug(PLUGIN_TESTVIEW) << "Notify a test of the suite " << suite->name() << " has started"; // Global test suite icon - item->setIcon(QIcon::fromTheme("process-idle")); + item->setIcon(QIcon::fromTheme(QStringLiteral("process-idle"))); for (int i = 0; i < item->rowCount(); ++i) { qCDebug(PLUGIN_TESTVIEW) << "Found a test case" << item->child(i)->text(); QStandardItem* caseItem = item->child(i); if (test_cases.contains(caseItem->text())) { // Each test case icon - caseItem->setIcon(QIcon::fromTheme("process-idle")); + caseItem->setIcon(QIcon::fromTheme(QStringLiteral("process-idle"))); } } } QIcon TestView::iconForTestResult(TestResult::TestCaseResult result) { switch (result) { case TestResult::NotRun: - return QIcon::fromTheme("code-function"); + return QIcon::fromTheme(QStringLiteral("code-function")); case TestResult::Skipped: - return QIcon::fromTheme("task-delegate"); + return QIcon::fromTheme(QStringLiteral("task-delegate")); case TestResult::Passed: - return QIcon::fromTheme("dialog-ok-apply"); + return QIcon::fromTheme(QStringLiteral("dialog-ok-apply")); case TestResult::UnexpectedPass: // This is a very rare occurrence, so the icon should stand out - return QIcon::fromTheme("dialog-warning"); + return QIcon::fromTheme(QStringLiteral("dialog-warning")); case TestResult::Failed: - return QIcon::fromTheme("edit-delete"); + return QIcon::fromTheme(QStringLiteral("edit-delete")); case TestResult::ExpectedFail: - return QIcon::fromTheme("dialog-ok"); + return QIcon::fromTheme(QStringLiteral("dialog-ok")); case TestResult::Error: - return QIcon::fromTheme("dialog-cancel"); + return QIcon::fromTheme(QStringLiteral("dialog-cancel")); default: - return QIcon::fromTheme(""); + return QIcon::fromTheme(QLatin1String("")); } } QStandardItem* TestView::itemForSuite(ITestSuite* suite) { foreach (QStandardItem* item, m_model->findItems(suite->name(), Qt::MatchRecursive)) { if (item->parent() && item->parent()->text() == suite->project()->name() && !item->parent()->parent()) { return item; } } return 0; } QStandardItem* TestView::itemForProject(IProject* project) { foreach (QStandardItem* item, m_model->findItems(project->name())) { return item; } return addProject(project); } void TestView::runSelectedTests() { QModelIndexList indexes = m_tree->selectionModel()->selectedIndexes(); if (indexes.isEmpty()) { //if there's no selection we'll run all of them (or only the filtered) //in case there's a filter. const int rc = m_filter->rowCount(); for(int i=0; iindex(i, 0); } } QList jobs; ITestController* tc = ICore::self()->testController(); /* * NOTE: If a test suite or a single test case was selected, * the job is launched in Verbose mode with raised output window. * If a project is selected, it is launched silently. * * This is the somewhat-intuitive approach. Maybe a configuration should be offered. */ foreach (const QModelIndex& idx, indexes) { QModelIndex index = m_filter->mapToSource(idx); if (index.parent().isValid() && indexes.contains(index.parent())) { continue; } QStandardItem* item = m_model->itemFromIndex(index); if (item->parent() == 0) { // A project was selected IProject* project = ICore::self()->projectController()->findProjectByName(item->data(ProjectRole).toString()); foreach (ITestSuite* suite, tc->testSuitesForProject(project)) { jobs << suite->launchAllCases(ITestSuite::Silent); } } else if (item->parent()->parent() == 0) { // A suite was selected IProject* project = ICore::self()->projectController()->findProjectByName(item->parent()->data(ProjectRole).toString()); ITestSuite* suite = tc->findTestSuite(project, item->data(SuiteRole).toString()); jobs << suite->launchAllCases(ITestSuite::Verbose); } else { // This was a single test case IProject* project = ICore::self()->projectController()->findProjectByName(item->parent()->parent()->data(ProjectRole).toString()); ITestSuite* suite = tc->findTestSuite(project, item->parent()->data(SuiteRole).toString()); const QString testCase = item->data(CaseRole).toString(); jobs << suite->launchCase(testCase, ITestSuite::Verbose); } } if (!jobs.isEmpty()) { KDevelop::ExecuteCompositeJob* compositeJob = new KDevelop::ExecuteCompositeJob(this, jobs); compositeJob->setObjectName(i18np("Run 1 test", "Run %1 tests", jobs.size())); compositeJob->setProperty("test_job", true); ICore::self()->runController()->registerJob(compositeJob); } } void TestView::showSource() { QModelIndexList indexes = m_tree->selectionModel()->selectedIndexes(); if (indexes.isEmpty()) { return; } IndexedDeclaration declaration; ITestController* tc = ICore::self()->testController(); QModelIndex index = m_filter->mapToSource(indexes.first()); QStandardItem* item = m_model->itemFromIndex(index); if (item->parent() == 0) { // No sense in finding source code for projects. return; } else if (item->parent()->parent() == 0) { IProject* project = ICore::self()->projectController()->findProjectByName(item->parent()->data(ProjectRole).toString()); ITestSuite* suite = tc->findTestSuite(project, item->data(SuiteRole).toString()); declaration = suite->declaration(); } else { IProject* project = ICore::self()->projectController()->findProjectByName(item->parent()->parent()->data(ProjectRole).toString()); ITestSuite* suite = tc->findTestSuite(project, item->parent()->data(SuiteRole).toString()); declaration = suite->caseDeclaration(item->data(CaseRole).toString()); } DUChainReadLocker locker; Declaration* d = declaration.data(); if (!d) { return; } QUrl url = d->url().toUrl(); KTextEditor::Cursor cursor = d->rangeInCurrentRevision().start(); locker.unlock(); IDocumentController* dc = ICore::self()->documentController(); qCDebug(PLUGIN_TESTVIEW) << "Activating declaration in" << url; dc->openDocument(url, cursor); } void TestView::addTestSuite(ITestSuite* suite) { QStandardItem* projectItem = itemForProject(suite->project()); Q_ASSERT(projectItem); - QStandardItem* suiteItem = new QStandardItem(QIcon::fromTheme("view-list-tree"), suite->name()); + QStandardItem* suiteItem = new QStandardItem(QIcon::fromTheme(QStringLiteral("view-list-tree")), suite->name()); suiteItem->setData(suite->name(), SuiteRole); foreach (const QString& caseName, suite->cases()) { QStandardItem* caseItem = new QStandardItem(iconForTestResult(TestResult::NotRun), caseName); caseItem->setData(caseName, CaseRole); suiteItem->appendRow(caseItem); } projectItem->appendRow(suiteItem); } void TestView::removeTestSuite(ITestSuite* suite) { QStandardItem* item = itemForSuite(suite); item->parent()->removeRow(item->row()); } QStandardItem* TestView::addProject(IProject* project) { - QStandardItem* projectItem = new QStandardItem(QIcon::fromTheme("project-development"), project->name()); + QStandardItem* projectItem = new QStandardItem(QIcon::fromTheme(QStringLiteral("project-development")), project->name()); projectItem->setData(project->name(), ProjectRole); m_model->appendRow(projectItem); return projectItem; } void TestView::removeProject(IProject* project) { QStandardItem* projectItem = itemForProject(project); m_model->removeRow(projectItem->row()); } void TestView::doubleClicked(const QModelIndex& index) { m_tree->selectionModel()->select(index, QItemSelectionModel::ClearAndSelect); runSelectedTests(); } QList< QAction* > TestView::contextMenuActions() { return m_contextMenuActions; } diff --git a/plugins/testview/testviewplugin.cpp b/plugins/testview/testviewplugin.cpp index 67d200eac..a0d4de12c 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 ) {} QWidget* create( QWidget *parent = 0 ) override { return new TestView( mplugin, parent ); } Qt::DockWidgetArea defaultPosition() override { return Qt::LeftDockWidgetArea; } QString id() const override { - return "org.kdevelop.TestView"; + 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("kdevtestview", parent) + : IPlugin(QStringLiteral("kdevtestview"), parent) { Q_UNUSED(args) - QAction* runAll = new QAction( QIcon::fromTheme("system-run"), i18n("Run All Tests"), this ); + QAction* runAll = new QAction( QIcon::fromTheme(QStringLiteral("system-run")), i18n("Run All Tests"), this ); connect(runAll, &QAction::triggered, this, &TestViewPlugin::runAllTests); - actionCollection()->addAction("run_all_tests", runAll); + actionCollection()->addAction(QStringLiteral("run_all_tests"), runAll); - QAction* stopTest = new QAction( QIcon::fromTheme("process-stop"), i18n("Stop Running Tests"), this ); + QAction* stopTest = new QAction( QIcon::fromTheme(QStringLiteral("process-stop")), i18n("Stop Running Tests"), this ); connect(stopTest, &QAction::triggered, this, &TestViewPlugin::stopRunningTests); - actionCollection()->addAction("stop_running_tests", stopTest); + actionCollection()->addAction(QStringLiteral("stop_running_tests"), stopTest); - setXMLFile("kdevtestview.rc"); + 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("run_all_tests")->setEnabled(!found); - actionCollection()->action("stop_running_tests")->setEnabled(found); + 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 d55b80ab1..efba9be66 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) {} QWidget* create(QWidget *parent = 0) 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 "org.kdevelop.VCSProject"; + return QStringLiteral("org.kdevelop.VCSProject"); } private: VcsProjectIntegrationPlugin *m_plugin; }; VcsProjectIntegrationPlugin::VcsProjectIntegrationPlugin(QObject* parent, const QVariantList&) : KDevelop::IPlugin(QStringLiteral("kdevvcsprojectintegration"), parent) , m_model(0) { 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/plugins/welcomepage/welcomepagedocument.cpp b/plugins/welcomepage/welcomepagedocument.cpp index ec7759a03..476f563e4 100644 --- a/plugins/welcomepage/welcomepagedocument.cpp +++ b/plugins/welcomepage/welcomepagedocument.cpp @@ -1,108 +1,109 @@ /* 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 "welcomepagedocument.h" #include #include #include #include #include #include #include #include "welcomepageview.h" using namespace KDevelop; -Q_GLOBAL_STATIC_WITH_ARGS(QUrl, s_welcomePageUrl, (QUrl("kdev:///awesome.kdevinternal"))); +inline QString awesome() { return QStringLiteral("kdev:///awesome.kdevinternal"); } +Q_GLOBAL_STATIC_WITH_ARGS(QUrl, s_welcomePageUrl, (QUrl(awesome()))); WelcomePageDocument::WelcomePageDocument() : Sublime::UrlDocument(ICore::self()->uiController()->controller(), *s_welcomePageUrl), IDocument(ICore::self()) { setTitle(i18n("Dashboard")); } void WelcomePageDocument::activate(Sublime::View* /*activeView*/, KParts::MainWindow* /*mainWindow*/) {} void WelcomePageDocument::setTextSelection(const KTextEditor::Range& /*range*/) {} void WelcomePageDocument::setCursorPosition(const KTextEditor::Cursor& /*cursor*/) {} KTextEditor::Cursor WelcomePageDocument::cursorPosition() const { return KTextEditor::Cursor(); } IDocument::DocumentState WelcomePageDocument::state() const { return IDocument::Clean; } bool WelcomePageDocument::isActive() const { return true; } bool WelcomePageDocument::close(KDevelop::IDocument::DocumentSaveMode /*mode*/) { return true; } void WelcomePageDocument::reload() {} bool WelcomePageDocument::save(KDevelop::IDocument::DocumentSaveMode /*mode*/) { return true; } KTextEditor::Document* WelcomePageDocument::textDocument() const { return 0; } KParts::Part* WelcomePageDocument::partForView(QWidget* /*view*/) const { return 0; } QMimeType WelcomePageDocument::mimeType() const { - return QMimeDatabase().mimeTypeForName("text/x-kdevelopinternal"); + return QMimeDatabase().mimeTypeForName(QStringLiteral("text/x-kdevelopinternal")); } Sublime::View* WelcomePageDocument::newView(Sublime::Document* doc) { if( dynamic_cast( doc ) ) return new WelcomePageView(doc, Sublime::View::TakeOwnership); return 0; } QUrl WelcomePageDocument::url() const { return *s_welcomePageUrl; } QUrl WelcomePageDocument::welcomePageUrl() { return *s_welcomePageUrl; } diff --git a/plugins/welcomepage/welcomepageplugin.cpp b/plugins/welcomepage/welcomepageplugin.cpp index 3032f8e17..e8554d6cb 100644 --- a/plugins/welcomepage/welcomepageplugin.cpp +++ b/plugins/welcomepage/welcomepageplugin.cpp @@ -1,77 +1,77 @@ /* 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 "welcomepageplugin.h" #include #include "welcomepageview.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "welcomepagedocument.h" K_PLUGIN_FACTORY_WITH_JSON(KDevWelcomePagePluginFactory, "kdevwelcomepage.json", registerPlugin();) using namespace KDevelop; class WelcomePageFactory : public KDevelop::IDocumentFactory { public: IDocument* create(const QUrl& /*url*/, ICore*) override { return new WelcomePageDocument(); } }; KDevWelcomePagePlugin::KDevWelcomePagePlugin( QObject* parent, const QVariantList& ) - : IPlugin("kdevwelcomepage", parent ) + : IPlugin(QStringLiteral("kdevwelcomepage"), parent ) { // ICore::self()->documentController()->registerDocumentForMimetype("text/x-kdevelop-internal", new WelcomePageFactory); // // //FIXME: When and where to open the welcome page? // //QTimer::singleShot(500, this, SLOT(openWelcomePage())); // // QAction* action = actionCollection()->addAction("show-welcomepage"); // action->setText("Show Welcome Page"); // action->setIcon(QIcon::fromTheme("meeting-organizer")); Sublime::MainWindow* mw = qobject_cast(ICore::self()->uiController()->activeMainWindow()); mw->setBackgroundCentralWidget(new WelcomePageWidget(QList(), mw)); } void KDevWelcomePagePlugin::openWelcomePage() { ICore::self()->documentController()->openDocument(WelcomePageDocument::welcomePageUrl()); } #include "welcomepageplugin.moc" diff --git a/plugins/welcomepage/welcomepageview.cpp b/plugins/welcomepage/welcomepageview.cpp index 0fbd31d99..dd6ad5d05 100644 --- a/plugins/welcomepage/welcomepageview.cpp +++ b/plugins/welcomepage/welcomepageview.cpp @@ -1,84 +1,84 @@ /* This file is part of KDevelop Copyright 2011 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 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 "welcomepageview.h" #include "uihelper.h" #include "sessionsmodel.h" #include "welcomepagedocument.h" #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; WelcomePageWidget::WelcomePageWidget(const QList & /*projects*/, QWidget* parent) : QQuickWidget(parent) { qRegisterMetaType("KDevelop::IProjectController*"); qRegisterMetaType("KDevelop::IPluginController*"); qRegisterMetaType("PatchReviewPlugin*"); qRegisterMetaType(); qmlRegisterType("org.kdevelop.welcomepage", 4, 3, "SessionsModel"); //setup kdeclarative library KDeclarative::KDeclarative kdeclarative; kdeclarative.setDeclarativeEngine(engine()); kdeclarative.setupBindings(); setResizeMode(QQuickWidget::SizeRootObjectToView); UiHelper* helper = new UiHelper(this); - rootContext()->setContextProperty("kdev", helper); - rootContext()->setContextProperty("ICore", KDevelop::ICore::self()); + rootContext()->setContextProperty(QStringLiteral("kdev"), helper); + rootContext()->setContextProperty(QStringLiteral("ICore"), KDevelop::ICore::self()); areaChanged(ICore::self()->uiController()->activeArea()); - setSource(QUrl("qrc:/qml/main.qml")); + setSource(QUrl(QStringLiteral("qrc:/qml/main.qml"))); if(!errors().isEmpty()) { qWarning() << "welcomepage errors:" << errors(); } connect(Core::self()->uiControllerInternal()->activeSublimeWindow(), &Sublime::MainWindow::areaChanged, this, &WelcomePageWidget::areaChanged); } void WelcomePageWidget::areaChanged(Sublime::Area* area) { - rootContext()->setContextProperty("area", area->objectName()); + rootContext()->setContextProperty(QStringLiteral("area"), area->objectName()); } WelcomePageView::WelcomePageView(Sublime::Document* doc, Sublime::View::WidgetOwnership ws) : View(doc, ws) { } QWidget* WelcomePageView::createWidget(QWidget* parent) { return new WelcomePageWidget(QList(), parent); } diff --git a/plugins/welcomepage/welcomepageview.h b/plugins/welcomepage/welcomepageview.h index 4a0f26427..ecf51c17e 100644 --- a/plugins/welcomepage/welcomepageview.h +++ b/plugins/welcomepage/welcomepageview.h @@ -1,53 +1,54 @@ /* This file is part of KDevelop Copyright 2011 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 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 WELCOMEPAGEVIEW_H #define WELCOMEPAGEVIEW_H #include #include namespace Sublime { class Area; } namespace KDevelop { class IProject; } class WelcomePageView : public Sublime::View { + Q_OBJECT public: explicit WelcomePageView(Sublime::Document* doc, WidgetOwnership ws = DoNotTakeOwnerShip); QWidget* createWidget(QWidget* parent) override; }; class WelcomePageWidget : public QQuickWidget { Q_OBJECT public: explicit WelcomePageWidget(const QList< KDevelop::IProject* >& projects, QWidget* parent = 0); public slots: void areaChanged(Sublime::Area* a); }; void trySetupWelcomePageView(); #endif // WELCOMEPAGEVIEW_H diff --git a/project/abstractfilemanagerplugin.cpp b/project/abstractfilemanagerplugin.cpp index feb104a45..758afdc57 100644 --- a/project/abstractfilemanagerplugin.cpp +++ b/project/abstractfilemanagerplugin.cpp @@ -1,659 +1,659 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2010-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. * ***************************************************************************/ #include "abstractfilemanagerplugin.h" #include "filemanagerlistjob.h" #include "projectmodel.h" #include "helper.h" #include #include #include #include #include #include #include #include #include #include #include "projectfiltermanager.h" #include "debug.h" #define ifDebug(x) using namespace KDevelop; //BEGIN Helper namespace { /** * Returns the parent folder item for a given item or the project root item if there is no parent. */ ProjectFolderItem* getParentFolder(ProjectBaseItem* item) { if ( item->parent() ) { return static_cast(item->parent()); } else { return item->project()->projectItem(); } } } //END Helper //BEGIN Private struct AbstractFileManagerPlugin::Private { explicit Private(AbstractFileManagerPlugin* qq) : q(qq) { } AbstractFileManagerPlugin* q; /// @p forceRecursion if true, existing folders will be re-read no matter what KIO::Job* eventuallyReadFolder( ProjectFolderItem* item, const bool forceRecursion = false ); void addJobItems(FileManagerListJob* job, ProjectFolderItem* baseItem, const KIO::UDSEntryList& entries, const bool forceRecursion); void deleted(const QString &path); void created(const QString &path); void projectClosing(IProject* project); void jobFinished(KJob* job); /// Stops watching the given folder for changes, only useful for local files. void stopWatcher(ProjectFolderItem* folder); /// Continues watching the given folder for changes. void continueWatcher(ProjectFolderItem* folder); /// Common renaming function. bool rename(ProjectBaseItem* item, const Path& newPath); void removeFolder(ProjectFolderItem* folder); QHash m_watchers; QHash > m_projectJobs; QVector m_stoppedFolders; ProjectFilterManager m_filters; }; void AbstractFileManagerPlugin::Private::projectClosing(IProject* project) { if ( m_projectJobs.contains(project) ) { // make sure the import job does not live longer than the project // see also addLotsOfFiles test foreach( FileManagerListJob* job, m_projectJobs[project] ) { qCDebug(FILEMANAGER) << "killing project job:" << job; job->abort(); } m_projectJobs.remove(project); } delete m_watchers.take(project); m_filters.remove(project); } KIO::Job* AbstractFileManagerPlugin::Private::eventuallyReadFolder( ProjectFolderItem* item, const bool forceRecursion ) { FileManagerListJob* listJob = new FileManagerListJob( item, forceRecursion ); m_projectJobs[ item->project() ] << listJob; qCDebug(FILEMANAGER) << "adding job" << listJob << item << item->path() << "for project" << item->project(); q->connect( listJob, &FileManagerListJob::finished, q, [&] (KJob* job) { jobFinished(job); } ); q->connect( listJob, &FileManagerListJob::entries, q, [&] (FileManagerListJob* job, ProjectFolderItem* baseItem, const KIO::UDSEntryList& entries, bool forceRecursion) { addJobItems(job, baseItem, entries, forceRecursion); } ); return listJob; } void AbstractFileManagerPlugin::Private::jobFinished(KJob* job) { FileManagerListJob* gmlJob = qobject_cast(job); if (gmlJob) { ifDebug(qCDebug(FILEMANAGER) << job << gmlJob << gmlJob->item();) m_projectJobs[ gmlJob->item()->project() ].removeOne( gmlJob ); } else { // job emitted its finished signal from its destructor // ensure we don't keep a dangling point in our list - for (auto& jobs : m_projectJobs) { + foreach (auto jobs, m_projectJobs) { if (jobs.removeOne(reinterpret_cast(job))) { break; } } } } void AbstractFileManagerPlugin::Private::addJobItems(FileManagerListJob* job, ProjectFolderItem* baseItem, const KIO::UDSEntryList& entries, const bool forceRecursion) { if ( entries.empty() ) { return; } qCDebug(FILEMANAGER) << "reading entries of" << baseItem->path(); // build lists of valid files and folders with paths relative to the project folder Path::List files; Path::List folders; foreach ( const KIO::UDSEntry& entry, entries ) { QString name = entry.stringValue( KIO::UDSEntry::UDS_NAME ); - if (name == "." || name == "..") { + if (name == QLatin1String(".") || name == QLatin1String("..")) { continue; } Path path(baseItem->path(), name); if ( !q->isValid( path, entry.isDir(), baseItem->project() ) ) { continue; } else { if ( entry.isDir() ) { if( entry.isLink() ) { const Path linkedPath = baseItem->path().cd(entry.stringValue( KIO::UDSEntry::UDS_LINK_DEST )); // make sure we don't end in an infinite loop if( linkedPath.isParentOf( baseItem->project()->path() ) || baseItem->project()->path().isParentOf( linkedPath ) || linkedPath == baseItem->project()->path() ) { continue; } } folders << path; } else { files << path; } } } ifDebug(qCDebug(FILEMANAGER) << "valid folders:" << folders;) ifDebug(qCDebug(FILEMANAGER) << "valid files:" << files;) // remove obsolete rows for ( int j = 0; j < baseItem->rowCount(); ++j ) { if ( ProjectFolderItem* f = baseItem->child(j)->folder() ) { // check if this is still a valid folder int index = folders.indexOf( f->path() ); if ( index == -1 ) { // folder got removed or is now invalid removeFolder(f); --j; } else { // this folder already exists in the view folders.remove( index ); if ( forceRecursion ) { //no need to add this item, but we still want to recurse into it job->addSubDir( f ); } emit q->reloadedFolderItem( f ); } } else if ( ProjectFileItem* f = baseItem->child(j)->file() ) { // check if this is still a valid file int index = files.indexOf( f->path() ); if ( index == -1 ) { // file got removed or is now invalid ifDebug(qCDebug(FILEMANAGER) << "removing file:" << f << f->path();) baseItem->removeRow( j ); --j; } else { // this file already exists in the view files.remove( index ); emit q->reloadedFileItem( f ); } } } // add new rows foreach ( const Path& path, files ) { ProjectFileItem* file = q->createFileItem( baseItem->project(), path, baseItem ); if (file) { emit q->fileAdded( file ); } } foreach ( const Path& path, folders ) { ProjectFolderItem* folder = q->createFolderItem( baseItem->project(), path, baseItem ); if (folder) { emit q->folderAdded( folder ); job->addSubDir( folder ); } } } void AbstractFileManagerPlugin::Private::created(const QString &path_) { qCDebug(FILEMANAGER) << "created:" << path_; QFileInfo info(path_); ///FIXME: share memory with parent const Path path(path_); const IndexedString indexedPath(path.pathOrUrl()); const IndexedString indexedParent(path.parent().pathOrUrl()); foreach ( IProject* p, m_watchers.keys() ) { if ( !p->projectItem()->model() ) { // not yet finished with loading // FIXME: how should this be handled? see unit test continue; } if ( !q->isValid(path, info.isDir(), p) ) { continue; } if ( info.isDir() ) { bool found = false; foreach ( ProjectFolderItem* folder, p->foldersForPath(indexedPath) ) { // exists already in this project, happens e.g. when we restart the dirwatcher // or if we delete and remove folders consecutively https://bugs.kde.org/show_bug.cgi?id=260741 qCDebug(FILEMANAGER) << "force reload of" << path << folder; eventuallyReadFolder( folder, true ); found = true; } if ( found ) { continue; } } else if (!p->filesForPath(indexedPath).isEmpty()) { // also gets triggered for kate's backup files continue; } foreach ( ProjectFolderItem* parentItem, p->foldersForPath(indexedParent) ) { if ( info.isDir() ) { ProjectFolderItem* folder = q->createFolderItem( p, path, parentItem ); if (folder) { emit q->folderAdded( folder ); eventuallyReadFolder( folder ); } } else { ProjectFileItem* file = q->createFileItem( p, path, parentItem ); if (file) { emit q->fileAdded( file ); } } } } } void AbstractFileManagerPlugin::Private::deleted(const QString &path_) { if ( QFile::exists(path_) ) { // stopDirScan... return; } // ensure that the path is not inside a stopped folder foreach(const QString& folder, m_stoppedFolders) { if (path_.startsWith(folder)) { return; } } qCDebug(FILEMANAGER) << "deleted:" << path_; const Path path(path_); const IndexedString indexed(path.pathOrUrl()); foreach ( IProject* p, m_watchers.keys() ) { if (path == p->path()) { KMessageBox::error(qApp->activeWindow(), i18n("The base folder of project %1" " got deleted or moved outside of KDevelop.\n" "The project has to be closed.", p->name()), i18n("Project Folder Deleted") ); ICore::self()->projectController()->closeProject(p); continue; } if ( !p->projectItem()->model() ) { // not yet finished with loading // FIXME: how should this be handled? see unit test continue; } foreach ( ProjectFolderItem* item, p->foldersForPath(indexed) ) { removeFolder(item); } foreach ( ProjectFileItem* item, p->filesForPath(indexed) ) { emit q->fileRemoved(item); ifDebug(qCDebug(FILEMANAGER) << "removing file" << item;) item->parent()->removeRow(item->row()); } } } bool AbstractFileManagerPlugin::Private::rename(ProjectBaseItem* item, const Path& newPath) { if ( !q->isValid(newPath, true, item->project()) ) { int cancel = KMessageBox::warningContinueCancel( qApp->activeWindow(), i18n("You tried to rename '%1' to '%2', but the latter is filtered and will be hidden.\n" "Do you want to continue?", item->text(), newPath.lastPathSegment()), - QString(), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), "GenericManagerRenameToFiltered" + QString(), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QStringLiteral("GenericManagerRenameToFiltered") ); if ( cancel == KMessageBox::Cancel ) { return false; } } foreach ( ProjectFolderItem* parent, item->project()->foldersForPath(IndexedString(newPath.parent().pathOrUrl())) ) { if ( parent->folder() ) { stopWatcher(parent); const Path source = item->path(); bool success = renameUrl( item->project(), source.toUrl(), newPath.toUrl() ); if ( success ) { item->setPath( newPath ); item->parent()->takeRow( item->row() ); parent->appendRow( item ); if (item->file()) { emit q->fileRenamed(source, item->file()); } else { Q_ASSERT(item->folder()); emit q->folderRenamed(source, item->folder()); } } continueWatcher(parent); return success; } } return false; } void AbstractFileManagerPlugin::Private::stopWatcher(ProjectFolderItem* folder) { if ( !folder->path().isLocalFile() ) { return; } Q_ASSERT(m_watchers.contains(folder->project())); const QString path = folder->path().toLocalFile(); m_watchers[folder->project()]->stopDirScan(path); m_stoppedFolders.append(path); } void AbstractFileManagerPlugin::Private::continueWatcher(ProjectFolderItem* folder) { if ( !folder->path().isLocalFile() ) { return; } Q_ASSERT(m_watchers.contains(folder->project())); const QString path = folder->path().toLocalFile(); m_watchers[folder->project()]->restartDirScan(path); const int idx = m_stoppedFolders.indexOf(path); if (idx != -1) { m_stoppedFolders.remove(idx); } } bool isChildItem(ProjectBaseItem* parent, ProjectBaseItem* child) { do { if (child == parent) { return true; } child = child->parent(); } while(child); return false; } void AbstractFileManagerPlugin::Private::removeFolder(ProjectFolderItem* folder) { ifDebug(qCDebug(FILEMANAGER) << "removing folder:" << folder << folder->path();) foreach(FileManagerListJob* job, m_projectJobs[folder->project()]) { if (isChildItem(folder, job->item())) { qCDebug(FILEMANAGER) << "killing list job for removed folder" << job << folder->path(); job->abort(); Q_ASSERT(!m_projectJobs.value(folder->project()).contains(job)); } else { job->removeSubDir(folder); } } folder->parent()->removeRow( folder->row() ); } //END Private //BEGIN Plugin AbstractFileManagerPlugin::AbstractFileManagerPlugin( const QString& componentName, QObject *parent, const QVariantList & /*args*/ ) : IProjectFileManager(), IPlugin( componentName, parent ), d(new Private(this)) { KDEV_USE_EXTENSION_INTERFACE( IProjectFileManager ) connect(core()->projectController(), &IProjectController::projectClosing, this, [&] (IProject* project) { d->projectClosing(project); }); } AbstractFileManagerPlugin::~AbstractFileManagerPlugin() { delete d; } IProjectFileManager::Features AbstractFileManagerPlugin::features() const { return Features( Folders | Files ); } QList AbstractFileManagerPlugin::parse( ProjectFolderItem *item ) { // we are async, can't return anything here qCDebug(FILEMANAGER) << "note: parse will always return an empty list"; Q_UNUSED(item); return QList(); } ProjectFolderItem *AbstractFileManagerPlugin::import( IProject *project ) { ProjectFolderItem *projectRoot = createFolderItem( project, project->path(), 0 ); emit folderAdded( projectRoot ); qCDebug(FILEMANAGER) << "imported new project" << project->name() << "at" << projectRoot->path(); ///TODO: check if this works for remote files when something gets changed through another KDE app if ( project->path().isLocalFile() ) { d->m_watchers[project] = new KDirWatch( project ); connect(d->m_watchers[project], &KDirWatch::created, this, [&] (const QString& path_) { d->created(path_); }); connect(d->m_watchers[project], &KDirWatch::deleted, this, [&] (const QString& path_) { d->deleted(path_); }); d->m_watchers[project]->addDir(project->path().toLocalFile(), KDirWatch::WatchSubDirs | KDirWatch:: WatchFiles ); } d->m_filters.add(project); return projectRoot; } KJob* AbstractFileManagerPlugin::createImportJob(ProjectFolderItem* item) { return d->eventuallyReadFolder(item); } bool AbstractFileManagerPlugin::reload( ProjectFolderItem* item ) { qCDebug(FILEMANAGER) << "reloading item" << item->path(); d->eventuallyReadFolder( item->folder(), true ); return true; } ProjectFolderItem* AbstractFileManagerPlugin::addFolder( const Path& folder, ProjectFolderItem * parent ) { qCDebug(FILEMANAGER) << "adding folder" << folder << "to" << parent->path(); ProjectFolderItem* created = 0; d->stopWatcher(parent); if ( createFolder(folder.toUrl()) ) { created = createFolderItem( parent->project(), folder, parent ); if (created) { emit folderAdded(created); } } d->continueWatcher(parent); return created; } ProjectFileItem* AbstractFileManagerPlugin::addFile( const Path& file, ProjectFolderItem * parent ) { qCDebug(FILEMANAGER) << "adding file" << file << "to" << parent->path(); ProjectFileItem* created = 0; d->stopWatcher(parent); if ( createFile(file.toUrl()) ) { created = createFileItem( parent->project(), file, parent ); if (created) { emit fileAdded(created); } } d->continueWatcher(parent); return created; } bool AbstractFileManagerPlugin::renameFolder(ProjectFolderItem* folder, const Path& newPath) { qCDebug(FILEMANAGER) << "trying to rename a folder:" << folder->path() << newPath; return d->rename(folder, newPath); } bool AbstractFileManagerPlugin::renameFile(ProjectFileItem* file, const Path& newPath) { qCDebug(FILEMANAGER) << "trying to rename a file:" << file->path() << newPath; return d->rename(file, newPath); } bool AbstractFileManagerPlugin::removeFilesAndFolders(const QList &items) { bool success = true; foreach(ProjectBaseItem* item, items) { Q_ASSERT(item->folder() || item->file()); ProjectFolderItem* parent = getParentFolder(item); d->stopWatcher(parent); success &= removeUrl(parent->project(), item->path().toUrl(), true); if ( success ) { if (item->file()) { emit fileRemoved(item->file()); } else { Q_ASSERT(item->folder()); emit folderRemoved(item->folder()); } item->parent()->removeRow( item->row() ); } d->continueWatcher(parent); if ( !success ) break; } return success; } bool AbstractFileManagerPlugin::moveFilesAndFolders(const QList< ProjectBaseItem* >& items, ProjectFolderItem* newParent) { bool success = true; foreach(ProjectBaseItem* item, items) { Q_ASSERT(item->folder() || item->file()); ProjectFolderItem* oldParent = getParentFolder(item); d->stopWatcher(oldParent); d->stopWatcher(newParent); const Path oldPath = item->path(); const Path newPath(newParent->path(), item->baseName()); success &= renameUrl(oldParent->project(), oldPath.toUrl(), newPath. toUrl()); if ( success ) { if (item->file()) { emit fileRemoved(item->file()); } else { emit folderRemoved(item->folder()); } oldParent->removeRow( item->row() ); KIO::Job *readJob = d->eventuallyReadFolder(newParent); // reload first level synchronously, deeper levels will run async // this is required for code that expects the new item to exist after // this method finished readJob->exec(); } d->continueWatcher(oldParent); d->continueWatcher(newParent); if ( !success ) break; } return success; } bool AbstractFileManagerPlugin::copyFilesAndFolders(const Path::List& items, ProjectFolderItem* newParent) { bool success = true; foreach(const Path& item, items) { d->stopWatcher(newParent); success &= copyUrl(newParent->project(), item.toUrl(), newParent->path().toUrl()); if ( success ) { KIO::Job *readJob = d->eventuallyReadFolder(newParent); // reload first level synchronously, deeper levels will run async // this is required for code that expects the new item to exist after // this method finished readJob->exec(); } d->continueWatcher(newParent); if ( !success ) break; } return success; } bool AbstractFileManagerPlugin::isValid( const Path& path, const bool isFolder, IProject* project ) const { return d->m_filters.isValid( path, isFolder, project ); } ProjectFileItem* AbstractFileManagerPlugin::createFileItem( IProject* project, const Path& path, ProjectBaseItem* parent ) { return new ProjectFileItem( project, path, parent ); } ProjectFolderItem* AbstractFileManagerPlugin::createFolderItem( IProject* project, const Path& path, ProjectBaseItem* parent ) { return new ProjectFolderItem( project, path, parent ); } KDirWatch* AbstractFileManagerPlugin::projectWatcher( IProject* project ) const { return d->m_watchers.value( project, 0 ); } //END Plugin #include "moc_abstractfilemanagerplugin.cpp" diff --git a/project/projectmodel.cpp b/project/projectmodel.cpp index 17bf41810..7156a339f 100644 --- a/project/projectmodel.cpp +++ b/project/projectmodel.cpp @@ -1,1191 +1,1191 @@ /* 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 ) { } ProjectBaseItem* rootItem; ProjectModel* model; ProjectBaseItem* itemFromIndex( const QModelIndex& idx ) { if( !idx.isValid() ) { return rootItem; } if( idx.model() != model ) { return 0; } return model->itemFromIndex( idx ); } // a hash of IndexedString::indexForString(path) <-> ProjectBaseItem for fast lookup QMultiHash pathLookupTable; }; class ProjectBaseItemPrivate { public: ProjectBaseItemPrivate() : project(0), parent(0), row(-1), model(0), 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 0; } 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 = 0; olditem->d_func()->row = -1; olditem->setModel( 0 ); 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 = 0; item->d_func()->row = -1; item->setModel( 0 ); delete item; } d->children.clear(); } else { for (int i = row; i < count; ++i) { ProjectBaseItem* item = d->children.at(i); item->d_func()->parent = 0; item->d_func()->row = -1; item->setModel( 0 ); 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 0; } 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 ""; + return QLatin1String(""); } ProjectFolderItem *ProjectBaseItem::folder() const { return 0; } ProjectTargetItem *ProjectBaseItem::target() const { return 0; } ProjectExecutableTargetItem *ProjectBaseItem::executable() const { return 0; } ProjectFileItem *ProjectBaseItem::file() const { return 0; } 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()==0; } 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 "folder"; + return QStringLiteral("folder"); } int ProjectBuildFolderItem::type() const { return ProjectBaseItem::BuildFolder; } QString ProjectBuildFolderItem::iconName() const { - return "folder-development"; + 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(); 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 "system-run"; + 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 0; } 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( 0, "", 0 ); + d->rootItem = new ProjectBaseItem( 0, QString(), 0 ); 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 0; } 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 ) { 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 ( 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.cpp b/project/tests/projectmodelperformancetest.cpp index 6adcc836f..7bc26f5a6 100644 --- a/project/tests/projectmodelperformancetest.cpp +++ b/project/tests/projectmodelperformancetest.cpp @@ -1,207 +1,207 @@ /*************************************************************************** * 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. * ***************************************************************************/ #include "projectmodelperformancetest.h" #include #include #include #include #include #include #include #include #include #include #include #include // Knobs to increase/decrease the amount of items being generated #define SMALL_DEPTH 2 #define SMALL_WIDTH 10 #define BIG_DEPTH 3 #define BIG_WIDTH 10 #define INIT_WIDTH 10 #define INIT_DEPTH 3 #include using KDevelop::ProjectModel; using KDevelop::ProjectFolderItem; using KDevelop::ProjectBaseItem; using KDevelop::ProjectFileItem; using KDevelop::Path; void generateChilds( ProjectBaseItem* parent, int count, int depth ) { for( int i = 0; i < 10; i++ ) { if( depth > 0 ) { ProjectFolderItem* item = new ProjectFolderItem( QStringLiteral( "f%1" ).arg( i ), parent ); generateChilds( item, count, depth - 1 ); } else { new ProjectFileItem( QStringLiteral( "f%1" ).arg( i ), parent ); } } } ProjectModelPerformanceTest::ProjectModelPerformanceTest(QWidget* parent ) : QWidget(parent) { QGridLayout * l = new QGridLayout( this ); setLayout( l ); view = new QTreeView( this ); // This is used so the treeview layout performance is not influencing the test view->setUniformRowHeights( true ); - QPushButton* b = new QPushButton( "Expand All", this ); + QPushButton* b = new QPushButton( QStringLiteral("Expand All"), this ); connect( b, &QPushButton::clicked, view, &QTreeView::expandAll ); l->addWidget( b, 0, 0 ); - b = new QPushButton( "Collapse All", this ); + b = new QPushButton( QStringLiteral("Collapse All"), this ); connect( b, &QPushButton::clicked, view, &QTreeView::collapseAll ); l->addWidget( b, 0, 1 ); - b = new QPushButton( "Add Small Subtree", this ); + b = new QPushButton( QStringLiteral("Add Small Subtree"), this ); connect( b, &QPushButton::clicked, this, &ProjectModelPerformanceTest::addSmallTree ); l->addWidget( b, 0, 2 ); - b = new QPushButton( "Add Big Subtree", this ); + b = new QPushButton( QStringLiteral("Add Big Subtree"), this ); connect( b, &QPushButton::clicked, this, &ProjectModelPerformanceTest::addBigTree ); l->addWidget( b, 0, 3 ); - b = new QPushButton( "Add Big Subtree in Chunks", this ); + b = new QPushButton( QStringLiteral("Add Big Subtree in Chunks"), this ); connect( b, &QPushButton::clicked, this, &ProjectModelPerformanceTest::addBigTreeDelayed ); l->addWidget( b, 0, 4 ); l->addWidget( view, 1, 0, 1, 6 ); } void ProjectModelPerformanceTest::init() { QElapsedTimer timer; timer.start(); KDevelop::AutoTestShell::init(); KDevelop::TestCore* core = new KDevelop::TestCore; core->setPluginController(new KDevelop::TestPluginController(core)); core->initialize(); qDebug() << "init core" << timer.elapsed(); timer.start(); model = new KDevelop::ProjectModel( this ); qDebug() << "create model" << timer.elapsed(); timer.start(); for( int i = 0; i < INIT_WIDTH; i++ ) { ProjectFolderItem* item = new ProjectFolderItem( 0, Path( QUrl::fromLocalFile( QStringLiteral( "/f%1" ).arg( i ) ) ) ); generateChilds( item, INIT_WIDTH, INIT_DEPTH ); model->appendRow( item ); } qDebug() << "init model" << timer.elapsed(); timer.start(); view->setModel( model ); qDebug() << "set model" << timer.elapsed(); timer.start(); } ProjectModelPerformanceTest::~ProjectModelPerformanceTest() { KDevelop::TestCore::shutdown(); QApplication::quit(); } void ProjectModelPerformanceTest::addBigTree() { QElapsedTimer timer; timer.start(); for( int i = 0; i < BIG_WIDTH; i++ ) { ProjectFolderItem* item = new ProjectFolderItem( 0, Path( QUrl::fromLocalFile( QStringLiteral( "/f%1" ).arg( i ) ) ) ); generateChilds( item, BIG_WIDTH, BIG_DEPTH ); model->appendRow( item ); } qDebug() << "addBigTree" << timer.elapsed(); } void ProjectModelPerformanceTest::addBigTreeDelayed() { originalWidth = model->rowCount(); QTimer::singleShot( 0, this, SLOT(addItemDelayed()) ); } void ProjectModelPerformanceTest::addItemDelayed() { QElapsedTimer timer; timer.start(); ProjectBaseItem* parent = 0; Path path; if( !currentParent.isEmpty() ) { parent = currentParent.top(); path = Path(parent->path(), QStringLiteral("f%1").arg(parent->rowCount())); } else { path = Path(QUrl::fromLocalFile(QStringLiteral("/f%1").arg(model->rowCount()))); } ProjectBaseItem* item = 0; if( currentParent.size() < BIG_DEPTH ) { item = new ProjectFolderItem(0, path, parent); } else { item = new ProjectFileItem( 0, path, parent ); } if( currentParent.isEmpty() ) { model->appendRow( item ); } // Abort/Continue conditions are: // Go one level deeper (by pushing item on stack) as long as we haven't reached the max depth or the max width // else if we've reached the max width then pop, i.e go one level up // else the next run will add a sibling to the just-generated item if( currentParent.size() < BIG_DEPTH && ( currentParent.isEmpty() || currentParent.top()->rowCount() < BIG_WIDTH ) ) { currentParent.push( item ); } else if( !currentParent.isEmpty() && currentParent.top()->rowCount() >= BIG_WIDTH ) { currentParent.pop(); } if( ( currentParent.isEmpty() && ( model->rowCount() - originalWidth ) < BIG_WIDTH ) || !currentParent.isEmpty() ) { QTimer::singleShot( 0, this, SLOT(addItemDelayed()) ); } qDebug() << "addBigTreeDelayed" << timer.elapsed(); } void ProjectModelPerformanceTest::addSmallTree() { QElapsedTimer timer; timer.start(); for( int i = 0; i < SMALL_WIDTH; i++ ) { ProjectFolderItem* item = new ProjectFolderItem( 0, Path(QUrl::fromLocalFile( QStringLiteral( "/f%1" ).arg( i ) )) ); generateChilds( item, SMALL_WIDTH, SMALL_DEPTH ); model->appendRow( item ); } qDebug() << "addSmallTree" << timer.elapsed(); } int main( int argc, char** argv ) { QApplication a( argc, argv ); ProjectModelPerformanceTest* w = new ProjectModelPerformanceTest; w->show(); w->setAttribute(Qt::WA_DeleteOnClose); QMetaObject::invokeMethod(w, "init"); return a.exec(); } diff --git a/project/tests/test_projectmodel.cpp b/project/tests/test_projectmodel.cpp index d73f4fd41..ea4e3907d 100644 --- a/project/tests/test_projectmodel.cpp +++ b/project/tests/test_projectmodel.cpp @@ -1,546 +1,546 @@ /*************************************************************************** * 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. * ***************************************************************************/ #include "test_projectmodel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; void debugItemModel(QAbstractItemModel* m, const QModelIndex& parent=QModelIndex(), int depth=0) { Q_ASSERT(m); qDebug() << QByteArray(depth*2, '-') << m->data(parent).toString(); for(int i=0; irowCount(parent); i++) { debugItemModel(m, m->index(i, 0, parent), depth+1); } } void TestProjectModel::initTestCase() { AutoTestShell::init(); TestCore::initialize(Core::NoUi); qRegisterMetaType("QModelIndex"); model = ICore::self()->projectController()->projectModel(); new ModelTest( model, this ); proxy = new ProjectProxyModel( model ); new ModelTest(proxy, proxy); proxy->setSourceModel(model); } void TestProjectModel::init() { model->clear(); } void TestProjectModel::cleanupTestCase() { TestCore::shutdown(); } void TestProjectModel::testCreateFileSystemItems() { QFETCH( int, itemType ); QFETCH( Path, itemPath ); QFETCH( Path, expectedItemPath ); QFETCH( QString, expectedItemText ); QFETCH( QStringList, expectedRelativeItemPath ); QFETCH( int, expectedItemRow ); ProjectBaseItem* newitem = 0; switch( itemType ) { case ProjectBaseItem::Folder: newitem = new ProjectFolderItem( 0, itemPath ); break; case ProjectBaseItem::BuildFolder: newitem = new ProjectBuildFolderItem( 0, itemPath ); break; case ProjectBaseItem::File: newitem = new ProjectFileItem( 0, itemPath ); break; } int origRowCount = model->rowCount(); model->appendRow( newitem ); QCOMPARE( model->rowCount(), origRowCount+1 ); QCOMPARE( newitem->row(), expectedItemRow ); QModelIndex idx = model->index( expectedItemRow, 0, QModelIndex() ); QVERIFY( model->itemFromIndex( idx ) ); QCOMPARE( model->itemFromIndex( idx ), newitem ); QCOMPARE( newitem->text(), expectedItemText ); QCOMPARE( newitem->path(), expectedItemPath ); if( itemType == ProjectBaseItem::File ) { QCOMPARE( dynamic_cast( newitem )->fileName(), expectedItemText ); } if( itemType == ProjectBaseItem::Folder || itemType == ProjectBaseItem::BuildFolder ) { QCOMPARE( dynamic_cast( newitem )->folderName(), expectedItemText ); } QCOMPARE( newitem->type(), itemType ); QCOMPARE( model->data( idx ).toString(), expectedItemText ); QCOMPARE( model->indexFromItem( newitem ), idx ); QCOMPARE( model->pathFromIndex( idx ), expectedRelativeItemPath ); QCOMPARE( model->pathToIndex( expectedRelativeItemPath ), idx ); } void TestProjectModel::testCreateFileSystemItems_data() { QTest::addColumn( "itemType" ); QTest::addColumn( "itemPath" ); QTest::addColumn( "expectedItemPath" ); QTest::addColumn( "expectedItemText" ); QTest::addColumn( "expectedRelativeItemPath" ); QTest::addColumn( "expectedItemRow" ); QTest::newRow("RootFolder") << (int)ProjectBaseItem::Folder - << Path(QUrl::fromLocalFile("/rootdir")) - << Path(QUrl::fromLocalFile("/rootdir/")) - << QString::fromLatin1("rootdir") - << ( QStringList() << "rootdir" ) + << Path(QUrl::fromLocalFile(QStringLiteral("/rootdir"))) + << Path(QUrl::fromLocalFile(QStringLiteral("/rootdir/"))) + << QStringLiteral("rootdir") + << ( QStringList() << QStringLiteral("rootdir") ) << 0; QTest::newRow("RootBuildFolder") << (int)ProjectBaseItem::BuildFolder - << Path(QUrl::fromLocalFile("/rootdir")) - << Path(QUrl::fromLocalFile("/rootdir/")) - << QString::fromLatin1("rootdir") - << ( QStringList() << "rootdir" ) + << Path(QUrl::fromLocalFile(QStringLiteral("/rootdir"))) + << Path(QUrl::fromLocalFile(QStringLiteral("/rootdir/"))) + << QStringLiteral("rootdir") + << ( QStringList() << QStringLiteral("rootdir") ) << 0; QTest::newRow("RootFile") << (int)ProjectBaseItem::File - << Path(QUrl::fromLocalFile("/rootfile")) - << Path(QUrl::fromLocalFile("/rootfile")) - << QString::fromLatin1("rootfile") - << ( QStringList() << "rootfile" ) + << Path(QUrl::fromLocalFile(QStringLiteral("/rootfile"))) + << Path(QUrl::fromLocalFile(QStringLiteral("/rootfile"))) + << QStringLiteral("rootfile") + << ( QStringList() << QStringLiteral("rootfile") ) << 0; } void TestProjectModel::testCreateTargetItems() { QFETCH( int, itemType ); QFETCH( QString, itemText ); QFETCH( QString, expectedItemText ); QFETCH( QStringList, expectedItemPath ); QFETCH( int, expectedItemRow ); ProjectBaseItem* newitem = 0; switch( itemType ) { case ProjectBaseItem::Target: newitem = new ProjectTargetItem( 0, itemText ); break; case ProjectBaseItem::LibraryTarget: newitem = new ProjectLibraryTargetItem( 0, itemText ); break; } int origRowCount = model->rowCount(); model->appendRow( newitem ); QCOMPARE( model->rowCount(), origRowCount+1 ); QCOMPARE( newitem->row(), expectedItemRow ); QModelIndex idx = model->index( expectedItemRow, 0, QModelIndex() ); QVERIFY( model->itemFromIndex( idx ) ); QCOMPARE( model->itemFromIndex( idx ), newitem ); QCOMPARE( newitem->text(), expectedItemText ); QCOMPARE( newitem->type(), itemType ); QCOMPARE( model->data( idx ).toString(), expectedItemText ); QCOMPARE( model->indexFromItem( newitem ), idx ); QCOMPARE( model->pathFromIndex( idx ), expectedItemPath ); QCOMPARE( model->pathToIndex( expectedItemPath ), idx ); } void TestProjectModel::testCreateTargetItems_data() { QTest::addColumn( "itemType" ); QTest::addColumn( "itemText" ); QTest::addColumn( "expectedItemText" ); QTest::addColumn( "expectedItemPath" ); QTest::addColumn( "expectedItemRow" ); QTest::newRow("RootTarget") << (int)ProjectBaseItem::Target << "target" - << QString::fromLatin1("target") - << ( QStringList() << "target" ) + << QStringLiteral("target") + << ( QStringList() << QStringLiteral("target") ) << 0; QTest::newRow("RootLibraryTarget") << (int)ProjectBaseItem::LibraryTarget << "libtarget" - << QString::fromLatin1("libtarget") - << ( QStringList() << "libtarget" ) + << QStringLiteral("libtarget") + << ( QStringList() << QStringLiteral("libtarget") ) << 0; } void TestProjectModel::testChangeWithProxyModel() { QSortFilterProxyModel* proxy = new QSortFilterProxyModel( this ); proxy->setSourceModel( model ); - ProjectFolderItem* root = new ProjectFolderItem( 0, Path(QUrl::fromLocalFile("/folder1")) ); - root->appendRow( new ProjectFileItem( 0, Path(QUrl::fromLocalFile("/folder1/file1")) ) ); + ProjectFolderItem* root = new ProjectFolderItem( 0, Path(QUrl::fromLocalFile(QStringLiteral("/folder1"))) ); + root->appendRow( new ProjectFileItem( 0, Path(QUrl::fromLocalFile(QStringLiteral("/folder1/file1"))) ) ); model->appendRow( root ); QCOMPARE( model->rowCount(), 1 ); QCOMPARE( proxy->rowCount(), 1 ); model->removeRow( 0 ); QCOMPARE( model->rowCount(), 0 ); QCOMPARE( proxy->rowCount(), 0 ); } void TestProjectModel::testCreateSimpleHierarchy() { - QString folderName = "rootfolder"; - QString fileName = "file"; - QString targetName = "testtarged"; - QString cppFileName = "file.cpp"; + QString folderName = QStringLiteral("rootfolder"); + QString fileName = QStringLiteral("file"); + QString targetName = QStringLiteral("testtarged"); + QString cppFileName = QStringLiteral("file.cpp"); ProjectFolderItem* rootFolder = new ProjectFolderItem( 0, Path(QUrl::fromLocalFile("/"+folderName)) ); QCOMPARE(rootFolder->baseName(), folderName); ProjectFileItem* file = new ProjectFileItem( fileName, rootFolder ); QCOMPARE(file->baseName(), fileName); ProjectTargetItem* target = new ProjectTargetItem( 0, targetName ); rootFolder->appendRow( target ); ProjectFileItem* targetfile = new ProjectFileItem( 0, Path(rootFolder->path(), cppFileName), target ); model->appendRow( rootFolder ); QCOMPARE( model->rowCount(), 1 ); QModelIndex folderIdx = model->index( 0, 0, QModelIndex() ); QCOMPARE( model->data( folderIdx ).toString(), folderName ); QCOMPARE( model->rowCount( folderIdx ), 2 ); QCOMPARE( model->itemFromIndex( folderIdx ), rootFolder ); QVERIFY( rootFolder->hasFileOrFolder( fileName ) ); QModelIndex fileIdx = model->index( 0, 0, folderIdx ); QCOMPARE( model->data( fileIdx ).toString(), fileName ); QCOMPARE( model->rowCount( fileIdx ), 0 ); QCOMPARE( model->itemFromIndex( fileIdx ), file ); QModelIndex targetIdx = model->index( 1, 0, folderIdx ); QCOMPARE( model->data( targetIdx ).toString(), targetName ); QCOMPARE( model->rowCount( targetIdx ), 1 ); QCOMPARE( model->itemFromIndex( targetIdx ), target ); QModelIndex targetFileIdx = model->index( 0, 0, targetIdx ); QCOMPARE( model->data( targetFileIdx ).toString(), cppFileName ); QCOMPARE( model->rowCount( targetFileIdx ), 0 ); QCOMPARE( model->itemFromIndex( targetFileIdx ), targetfile ); rootFolder->removeRow( 1 ); QCOMPARE( model->rowCount( folderIdx ), 1 ); delete file; file = 0; // Check that we also find a folder with the fileName new ProjectFolderItem( fileName, rootFolder ); QVERIFY( rootFolder->hasFileOrFolder( fileName ) ); delete rootFolder; QCOMPARE( model->rowCount(), 0 ); } void TestProjectModel::testItemSanity() { - ProjectBaseItem* parent = new ProjectBaseItem( 0, "test" ); - ProjectBaseItem* child = new ProjectBaseItem( 0, "test", parent ); - ProjectBaseItem* child2 = new ProjectBaseItem( 0, "ztest", parent ); - ProjectFileItem* child3 = new ProjectFileItem( 0, Path(QUrl("file:///bcd")), parent ); - ProjectFileItem* child4 = new ProjectFileItem( 0, Path(QUrl("file:///abcd")), parent ); + ProjectBaseItem* parent = new ProjectBaseItem( 0, QStringLiteral("test") ); + ProjectBaseItem* child = new ProjectBaseItem( 0, QStringLiteral("test"), parent ); + ProjectBaseItem* child2 = new ProjectBaseItem( 0, QStringLiteral("ztest"), parent ); + ProjectFileItem* child3 = new ProjectFileItem( 0, Path(QUrl(QStringLiteral("file:///bcd"))), parent ); + ProjectFileItem* child4 = new ProjectFileItem( 0, Path(QUrl(QStringLiteral("file:///abcd"))), parent ); // Just some basic santiy checks on the API QCOMPARE( parent->child( 0 ), child ); QCOMPARE( parent->row(), -1 ); QVERIFY( !parent->child( -1 ) ); QVERIFY( !parent->file() ); QVERIFY( !parent->folder() ); QVERIFY( !parent->project() ); QVERIFY( !parent->child( parent->rowCount() ) ); QCOMPARE( parent->iconName(), QString() ); QCOMPARE( parent->index(), QModelIndex() ); QCOMPARE( child->type(), (int)ProjectBaseItem::BaseItem ); QCOMPARE( child->lessThan( child2 ), true ); QCOMPARE( child3->lessThan( child4 ), false ); // Check that model is properly emitting data-changes model->appendRow( parent ); QCOMPARE( parent->index(), model->index(0, 0, QModelIndex()) ); QSignalSpy s( model, SIGNAL(dataChanged(QModelIndex,QModelIndex)) ); - parent->setPath( Path("/newtest") ); + parent->setPath( Path(QStringLiteral("/newtest")) ); QCOMPARE( s.count(), 1 ); QCOMPARE( model->data( parent->index() ).toString(), QStringLiteral("newtest") ); parent->removeRow( child->row() ); } void TestProjectModel::testTakeRow() { - ProjectBaseItem* parent = new ProjectBaseItem( 0, "test" ); - ProjectBaseItem* child = new ProjectBaseItem( 0, "test", parent ); - ProjectBaseItem* subchild = new ProjectBaseItem( 0, "subtest", child ); + ProjectBaseItem* parent = new ProjectBaseItem( 0, QStringLiteral("test") ); + ProjectBaseItem* child = new ProjectBaseItem( 0, QStringLiteral("test"), parent ); + ProjectBaseItem* subchild = new ProjectBaseItem( 0, QStringLiteral("subtest"), child ); model->appendRow( parent ); QCOMPARE( parent->model(), model ); QCOMPARE( child->model(), model ); QCOMPARE( subchild->model(), model ); parent->takeRow( child->row() ); QCOMPARE( child->model(), static_cast(0) ); QCOMPARE( subchild->model(), static_cast(0) ); } void TestProjectModel::testRename() { QFETCH( int, itemType ); QFETCH( QString, itemText ); QFETCH( QString, newName ); QFETCH( bool, datachangesignal ); QFETCH( QString, expectedItemText ); QFETCH( int, expectedRenameCode ); - const Path projectFolder = Path(QUrl::fromLocalFile("/dummyprojectfolder")); + const Path projectFolder = Path(QUrl::fromLocalFile(QStringLiteral("/dummyprojectfolder"))); TestProject* proj = new TestProject; ProjectFolderItem* rootItem = new ProjectFolderItem( proj, projectFolder, 0); proj->setProjectItem( rootItem ); - new ProjectFileItem("existing", rootItem); + new ProjectFileItem(QStringLiteral("existing"), rootItem); ProjectBaseItem* item = 0; if( itemType == ProjectBaseItem::Target ) { item = new ProjectTargetItem( proj, itemText, rootItem ); } else if( itemType == ProjectBaseItem::File ) { item = new ProjectFileItem( itemText, rootItem ); } else if( itemType == ProjectBaseItem::Folder ) { item = new ProjectFolderItem( itemText, rootItem ); } else if( itemType == ProjectBaseItem::BuildFolder ) { item = new ProjectBuildFolderItem( itemText, rootItem ); } Q_ASSERT( item ); QCOMPARE(item->model(), model); QSignalSpy s( model, SIGNAL(dataChanged(QModelIndex,QModelIndex)) ); ProjectBaseItem::RenameStatus stat = item->rename( newName ); QCOMPARE( (int)stat, expectedRenameCode ); if( datachangesignal ) { QCOMPARE( s.count(), 1 ); QCOMPARE( qvariant_cast( s.takeFirst().at(0) ), item->index() ); } else { QCOMPARE( s.count(), 0 ); } QCOMPARE( item->text(), expectedItemText ); } void TestProjectModel::testRename_data() { QTest::addColumn( "itemType" ); QTest::addColumn( "itemText" ); QTest::addColumn( "newName" ); QTest::addColumn( "datachangesignal" ); QTest::addColumn( "expectedItemText" ); QTest::addColumn( "expectedRenameCode" ); QTest::newRow("RenameableTarget") << (int)ProjectBaseItem::Target - << QString::fromLatin1("target") - << QString::fromLatin1("othertarget") + << QStringLiteral("target") + << QStringLiteral("othertarget") << true - << QString::fromLatin1("othertarget") + << QStringLiteral("othertarget") << (int)ProjectBaseItem::RenameOk; QTest::newRow("RenameableFile") << (int)ProjectBaseItem::File - << QString::fromLatin1("newfile.cpp") - << QString::fromLatin1("otherfile.cpp") + << QStringLiteral("newfile.cpp") + << QStringLiteral("otherfile.cpp") << true - << QString::fromLatin1("otherfile.cpp") + << QStringLiteral("otherfile.cpp") << (int)ProjectBaseItem::RenameOk; QTest::newRow("SourceAndDestinationFileEqual") << (int)ProjectBaseItem::File - << QString::fromLatin1("newfile.cpp") - << QString::fromLatin1("newfile.cpp") + << QStringLiteral("newfile.cpp") + << QStringLiteral("newfile.cpp") << false - << QString::fromLatin1("newfile.cpp") + << QStringLiteral("newfile.cpp") << (int)ProjectBaseItem::RenameOk; QTest::newRow("RenameableFolder") << (int)ProjectBaseItem::Folder - << QString::fromLatin1("newfolder") - << QString::fromLatin1("otherfolder") + << QStringLiteral("newfolder") + << QStringLiteral("otherfolder") << true - << QString::fromLatin1("otherfolder") + << QStringLiteral("otherfolder") << (int)ProjectBaseItem::RenameOk; QTest::newRow("SourceAndDestinationFolderEqual") << (int)ProjectBaseItem::Folder - << QString::fromLatin1("newfolder") - << QString::fromLatin1("newfolder") + << QStringLiteral("newfolder") + << QStringLiteral("newfolder") << false - << QString::fromLatin1("newfolder") + << QStringLiteral("newfolder") << (int)ProjectBaseItem::RenameOk; QTest::newRow("RenameableBuildFolder") << (int)ProjectBaseItem::BuildFolder - << QString::fromLatin1("newbfolder") - << QString::fromLatin1("otherbfolder") + << QStringLiteral("newbfolder") + << QStringLiteral("otherbfolder") << true - << QString::fromLatin1("otherbfolder") + << QStringLiteral("otherbfolder") << (int)ProjectBaseItem::RenameOk; QTest::newRow("SourceAndDestinationBuildFolderEqual") << (int)ProjectBaseItem::BuildFolder - << QString::fromLatin1("newbfolder") - << QString::fromLatin1("newbfolder") + << QStringLiteral("newbfolder") + << QStringLiteral("newbfolder") << false - << QString::fromLatin1("newbfolder") + << QStringLiteral("newbfolder") << (int)ProjectBaseItem::RenameOk; QTest::newRow("ExistingFileError") << (int)ProjectBaseItem::Folder - << QString::fromLatin1("mynew") - << QString::fromLatin1("existing") + << QStringLiteral("mynew") + << QStringLiteral("existing") << false - << QString::fromLatin1("mynew") + << QStringLiteral("mynew") << (int)ProjectBaseItem::ExistingItemSameName; QTest::newRow("InvalidNameError") << (int)ProjectBaseItem::File - << QString::fromLatin1("mynew") - << QString::fromLatin1("other/bash") + << QStringLiteral("mynew") + << QStringLiteral("other/bash") << false - << QString::fromLatin1("mynew") + << QStringLiteral("mynew") << (int)ProjectBaseItem::InvalidNewName; } void TestProjectModel::testWithProject() { TestProject* proj = new TestProject(); - ProjectFolderItem* rootItem = new ProjectFolderItem( proj, Path(QUrl::fromLocalFile("/dummyprojectfolder")), 0); + ProjectFolderItem* rootItem = new ProjectFolderItem( proj, Path(QUrl::fromLocalFile(QStringLiteral("/dummyprojectfolder"))), 0); proj->setProjectItem( rootItem ); ProjectBaseItem* item = model->itemFromIndex( model->index( 0, 0 ) ); QCOMPARE( item, rootItem ); QCOMPARE( item->text(), proj->name() ); QCOMPARE( item->path(), proj->path() ); } void TestProjectModel::testItemsForPath() { QFETCH(Path, path); QFETCH(ProjectBaseItem*, root); QFETCH(int, matches); model->appendRow(root); auto items = model->itemsForPath(IndexedString(path.pathOrUrl())); QCOMPARE(items.size(), matches); foreach(ProjectBaseItem* item, items) { QVERIFY(item->path() == path); } model->clear(); } void TestProjectModel::testItemsForPath_data() { QTest::addColumn("path"); QTest::addColumn("root"); QTest::addColumn("matches"); { - ProjectFolderItem* root = new ProjectFolderItem(0, Path(QUrl::fromLocalFile("/tmp/"))); - ProjectFileItem* file = new ProjectFileItem("a", root); + ProjectFolderItem* root = new ProjectFolderItem(0, Path(QUrl::fromLocalFile(QStringLiteral("/tmp/")))); + ProjectFileItem* file = new ProjectFileItem(QStringLiteral("a"), root); QTest::newRow("find one") << file->path() << static_cast(root) << 1; } { - ProjectFolderItem* root = new ProjectFolderItem(0, Path(QUrl::fromLocalFile("/tmp/"))); - ProjectFolderItem* folder = new ProjectFolderItem("a", root); - ProjectFileItem* file = new ProjectFileItem("foo", folder); - ProjectTargetItem* target = new ProjectTargetItem(0, "b", root); + ProjectFolderItem* root = new ProjectFolderItem(0, Path(QUrl::fromLocalFile(QStringLiteral("/tmp/")))); + ProjectFolderItem* folder = new ProjectFolderItem(QStringLiteral("a"), root); + ProjectFileItem* file = new ProjectFileItem(QStringLiteral("foo"), folder); + ProjectTargetItem* target = new ProjectTargetItem(0, QStringLiteral("b"), root); ProjectFileItem* file2 = new ProjectFileItem(0, file->path(), target); Q_UNUSED(file2); QTest::newRow("find two") << file->path() << static_cast(root) << 2; } } void TestProjectModel::testProjectProxyModel() { - ProjectFolderItem* root = new ProjectFolderItem(0, Path(QUrl::fromLocalFile("/tmp/"))); - new ProjectFileItem("b1", root); - new ProjectFileItem("a1", root); - new ProjectFileItem("d1", root); - new ProjectFileItem("c1", root); + ProjectFolderItem* root = new ProjectFolderItem(0, Path(QUrl::fromLocalFile(QStringLiteral("/tmp/")))); + new ProjectFileItem(QStringLiteral("b1"), root); + new ProjectFileItem(QStringLiteral("a1"), root); + new ProjectFileItem(QStringLiteral("d1"), root); + new ProjectFileItem(QStringLiteral("c1"), root); model->appendRow(root); QModelIndex proxyRoot = proxy->mapFromSource(root->index()); QCOMPARE(model->rowCount(root->index()), 4); QCOMPARE(proxy->rowCount(proxyRoot), 4); QCOMPARE(proxy->index(0, 0, proxy->index(0, 0)).data().toString(), QStringLiteral("a1")); QCOMPARE(proxy->index(1, 0, proxy->index(0, 0)).data().toString(), QStringLiteral("b1")); QCOMPARE(proxy->index(2, 0, proxy->index(0, 0)).data().toString(), QStringLiteral("c1")); QCOMPARE(proxy->index(3, 0, proxy->index(0, 0)).data().toString(), QStringLiteral("d1")); model->clear(); } void TestProjectModel::testProjectFileSet() { TestProject* project = new TestProject; QVERIFY(project->fileSet().isEmpty()); - Path path(QUrl::fromLocalFile("/tmp/a")); + Path path(QUrl::fromLocalFile(QStringLiteral("/tmp/a"))); ProjectFileItem* item = new ProjectFileItem(project, path, project->projectItem()); QCOMPARE(project->fileSet().size(), 1); - qDebug() << path << project->fileSet().begin()->toUrl(); - QCOMPARE(Path(project->fileSet().begin()->toUrl()), path); + qDebug() << path << project->fileSet().toList().at(0).toUrl(); + QCOMPARE(Path(project->fileSet().toList().at(0).toUrl()), path); delete item; QVERIFY(project->fileSet().isEmpty()); } void TestProjectModel::testProjectFileIcon() { QMimeDatabase db; - ProjectFileItem* item = new ProjectFileItem(0, Path("/tmp/foo.txt")); + ProjectFileItem* item = new ProjectFileItem(0, Path(QStringLiteral("/tmp/foo.txt"))); const QString txtIcon = db.mimeTypeForUrl(item->path().toUrl()).iconName(); QCOMPARE(item->iconName(), txtIcon); - item->setPath(Path("/tmp/bar.cpp")); + item->setPath(Path(QStringLiteral("/tmp/bar.cpp"))); QCOMPARE(item->iconName(), db.mimeTypeForUrl(item->path().toUrl()).iconName()); QVERIFY(item->iconName() != txtIcon); } QTEST_MAIN( TestProjectModel) diff --git a/serialization/tests/test_indexedstring.cpp b/serialization/tests/test_indexedstring.cpp index 4c424c18b..cd43c245d 100644 --- a/serialization/tests/test_indexedstring.cpp +++ b/serialization/tests/test_indexedstring.cpp @@ -1,251 +1,251 @@ /* * This file is part of KDevelop * Copyright 2012-2013 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_indexedstring.h" #include #include #include #include #include #include QTEST_GUILESS_MAIN(TestIndexedString); using namespace KDevelop; void TestIndexedString::initTestCase() { AutoTestShell::init(); TestCore::initialize(Core::NoUi); } void TestIndexedString::cleanupTestCase() { TestCore::shutdown(); } void TestIndexedString::testUrl_data() { QTest::addColumn("url"); QTest::addColumn("string"); QTest::newRow("empty") << QUrl() << QString(); - QTest::newRow("/") << QUrl::fromLocalFile("/") << QStringLiteral("/"); - QTest::newRow("/foo/bar") << QUrl::fromLocalFile("/foo/bar") << QStringLiteral("/foo/bar"); - QTest::newRow("http://foo.com/") << QUrl("http://foo.com/") << QStringLiteral("http://foo.com/"); - QTest::newRow("http://foo.com/bar/asdf") << QUrl("http://foo.com/bar/asdf") << QStringLiteral("http://foo.com/bar/asdf"); - QTest::newRow("file:///bar/asdf") << QUrl("file:///bar/asdf") << QStringLiteral("/bar/asdf"); + QTest::newRow("/") << QUrl::fromLocalFile(QStringLiteral("/")) << QStringLiteral("/"); + QTest::newRow("/foo/bar") << QUrl::fromLocalFile(QStringLiteral("/foo/bar")) << QStringLiteral("/foo/bar"); + QTest::newRow("http://foo.com/") << QUrl(QStringLiteral("http://foo.com/")) << QStringLiteral("http://foo.com/"); + QTest::newRow("http://foo.com/bar/asdf") << QUrl(QStringLiteral("http://foo.com/bar/asdf")) << QStringLiteral("http://foo.com/bar/asdf"); + QTest::newRow("file:///bar/asdf") << QUrl(QStringLiteral("file:///bar/asdf")) << QStringLiteral("/bar/asdf"); #ifdef Q_OS_WIN // Make sure we're not running into https://bugreports.qt.io/browse/QTBUG-41729 QTest::newRow("file:///C:/bar/asdf") << QUrl("file:///C:/bar/asdf") << QStringLiteral("C:/bar/asdf"); #endif } void TestIndexedString::testUrl() { QFETCH(QUrl, url); IndexedString indexed(url); QCOMPARE(indexed.toUrl(), url); QTEST(indexed.str(), "string"); } static QVector generateData() { QVector data; static const int NUM_ITEMS = 100000; data.resize(NUM_ITEMS); for(int i = 0; i < NUM_ITEMS; ++i) { data[i] = QStringLiteral("/foo/%1").arg(i); } return data; } void TestIndexedString::bench_index() { QVector data = generateData(); QBENCHMARK { foreach(const QString& item, data) { IndexedString idx(item); Q_UNUSED(idx); } } } static QVector setupTest() { QVector data = generateData(); QVector indices; indices.reserve(data.size()); foreach(const QString& item, data) { IndexedString idx(item); indices << idx.index(); } return indices; } void TestIndexedString::bench_length() { QVector indices = setupTest(); QBENCHMARK { foreach(uint index, indices) { IndexedString str = IndexedString::fromIndex(index); str.length(); } } } void TestIndexedString::bench_qstring() { QVector indices = setupTest(); QBENCHMARK { foreach(uint index, indices) { IndexedString str = IndexedString::fromIndex(index); str.str(); } } } void TestIndexedString::bench_kurl() { QVector indices = setupTest(); QBENCHMARK { foreach(uint index, indices) { IndexedString str = IndexedString::fromIndex(index); str.toUrl(); } } } void TestIndexedString::bench_qhashQString() { QVector data = generateData(); quint64 sum = 0; QBENCHMARK { - for (const auto& string : data) { + foreach (const auto& string, data) { sum += qHash(string); } } QVERIFY(sum > 0); } void TestIndexedString::bench_qhashIndexedString() { QVector indices = setupTest(); quint64 sum = 0; QBENCHMARK { foreach(uint index, indices) { sum += qHash(IndexedString::fromIndex(index)); } } QVERIFY(sum > 0); } void TestIndexedString::bench_hashString() { QVector strings = generateData(); QVector byteArrays; byteArrays.reserve(strings.size()); - for (const auto& string : strings) { + foreach (const auto& string, strings) { byteArrays << string.toUtf8(); } quint64 sum = 0; QBENCHMARK { - for (const auto& array : byteArrays) { + foreach (const auto& array, byteArrays) { sum += IndexedString::hashString(array.constData(), array.length()); } } QVERIFY(sum > 0); } void TestIndexedString::bench_kdevhash() { QVector strings = generateData(); QVector byteArrays; byteArrays.reserve(strings.size()); - for (const auto& string : strings) { + foreach (const auto& string, strings) { byteArrays << string.toUtf8(); } quint64 sum = 0; QBENCHMARK { - for (const auto& array : byteArrays) { + foreach (const auto& array, byteArrays) { sum += KDevHash() << array; } } QVERIFY(sum > 0); } void TestIndexedString::bench_qSet() { QVector indices = setupTest(); QSet set; QBENCHMARK { foreach(uint index, indices) { set.insert(IndexedString::fromIndex(index)); } } } void TestIndexedString::test() { QFETCH(QString, data); IndexedString indexed(data); QCOMPARE(indexed.str(), data); QCOMPARE(indexed.index(), IndexedString::indexForString(data)); const auto byteArrayData = data.toUtf8(); QEXPECT_FAIL("char-utf8", "UTF-8 gets decoded and the char data is stored internally", Continue); QEXPECT_FAIL("string-utf8", "UTF-8 gets decoded and the char data is stored internally", Continue); QCOMPARE(indexed.length(), data.length()); // fallback until we rely on internal utf8 encoding QCOMPARE(indexed.length(), byteArrayData.length()); QCOMPARE(indexed.byteArray(), byteArrayData); QVERIFY(!strncmp(indexed.c_str(), byteArrayData.data(), byteArrayData.length())); QCOMPARE(indexed.index(), IndexedString::indexForString(byteArrayData.data(), byteArrayData.length())); IndexedString moved = std::move(indexed); QCOMPARE(indexed, IndexedString()); QVERIFY(indexed.isEmpty()); QCOMPARE(moved.str(), data); } void TestIndexedString::test_data() { QTest::addColumn("data"); QTest::newRow("empty") << QString(); QTest::newRow("char-ascii") << QStringLiteral("a"); - QTest::newRow("char-utf8") << QString::fromUtf8("ä"); - QTest::newRow("string-ascii") << QString::fromLatin1("asdf()?="); - QTest::newRow("string-utf8") << QString::fromUtf8("æſðđäöü"); + QTest::newRow("char-utf8") << QStringLiteral("ä"); + QTest::newRow("string-ascii") << QStringLiteral("asdf()?="); + QTest::newRow("string-utf8") << QStringLiteral("æſðđäöü"); } void TestIndexedString::testCString() { IndexedString str(nullptr); QCOMPARE(str.index(), 0u); QVERIFY(str.isEmpty()); } diff --git a/serialization/tests/test_itemrepository.cpp b/serialization/tests/test_itemrepository.cpp index 4a09417a9..57d61dd41 100644 --- a/serialization/tests/test_itemrepository.cpp +++ b/serialization/tests/test_itemrepository.cpp @@ -1,358 +1,358 @@ #include #include #include #include #include #include #include #include struct TestItem { TestItem(uint hash, uint dataSize) : m_hash(hash), m_dataSize(dataSize) { } //Every item has to implement this function, and return a valid hash. //Must be exactly the same hash value as ExampleItemRequest::hash() has returned while creating the item. unsigned int hash() const { return m_hash; } //Every item has to implement this function, and return the complete size this item takes in memory. //Must be exactly the same value as ExampleItemRequest::itemSize() has returned while creating the item. unsigned int itemSize() const { return sizeof(TestItem) + m_dataSize; } bool equals(const TestItem* rhs) const { return rhs->m_hash == m_hash && itemSize() == rhs->itemSize() && memcmp((char*)this, rhs, itemSize()) == 0; } uint m_hash; uint m_dataSize; }; struct TestItemRequest { TestItem& m_item; bool m_compareData; TestItemRequest(TestItem& item, bool compareData = false) : m_item(item) , m_compareData(compareData) { } enum { AverageSize = 700 //This should be the approximate average size of an Item }; uint hash() const { return m_item.hash(); } //Should return the size of an item created with createItem size_t itemSize() const { return m_item.itemSize(); } void createItem(TestItem* item) const { memcpy(item, &m_item, m_item.itemSize()); } static void destroy(TestItem* /*item*/, KDevelop::AbstractItemRepository&) { //Nothing to do } static bool persistent(const TestItem* /*item*/) { return true; } //Should return whether the here requested item equals the given item bool equals(const TestItem* item) const { return hash() == item->hash() && (!m_compareData || m_item.equals(item)); } }; uint smallItemsFraction = 20; //Fraction of items betwen 0 and 1 kb uint largeItemsFraction = 1; //Fraction of items between 0 and 200 kb uint cycles = 100000; uint deletionProbability = 1; //Percentual probability that a checked item is deleted. Per-cycle probability must be multiplied with checksPerCycle. uint checksPerCycle = 10; TestItem* createItem(uint id, uint size) { TestItem* ret; char* data = new char[size]; uint dataSize = size - sizeof(TestItem); ret = new (data) TestItem(id, dataSize); //Fill in same random pattern for(uint a = 0; a < dataSize; ++a) data[sizeof(TestItem) + a] = (char)(a + id); return ret; } ///@todo Add a test where the complete content is deleted again, and make sure the result has a nice structure ///@todo More consistency and lost-space tests, especially about monster-buckets. Make sure their space is re-claimed class TestItemRepository : public QObject { Q_OBJECT private slots: void initTestCase() { KDevelop::AutoTestShell::init(); KDevelop::TestCore* core = new KDevelop::TestCore(); core->initialize(KDevelop::Core::NoUi); } void cleanupTestCase() { KDevelop::TestCore::shutdown(); } void testItemRepository() { - KDevelop::ItemRepository repository("TestItemRepository"); + KDevelop::ItemRepository repository(QStringLiteral("TestItemRepository")); uint itemId = 0; QHash realItemsByIndex; QHash realItemsById; uint totalInsertions = 0, totalDeletions = 0; uint maxSize = 0; uint totalSize = 0; srand(time(NULL)); uint highestSeenIndex = 0; for(uint a = 0; a < cycles; ++a) { { //Insert an item uint itemDecision = rand() % (smallItemsFraction + largeItemsFraction); uint itemSize; if(itemDecision < largeItemsFraction) { //Create a large item: Up to 200kb itemSize = (rand() % 200000) + sizeof(TestItem); } else itemSize = (rand() % 1000) + sizeof(TestItem); TestItem* item = createItem(++itemId, itemSize); Q_ASSERT(item->hash() == itemId); QVERIFY(item->equals(item)); uint index = repository.index(TestItemRequest(*item)); if(index > highestSeenIndex) highestSeenIndex = index; Q_ASSERT(index); realItemsByIndex.insert(index, item); realItemsById.insert(itemId, item); ++totalInsertions; totalSize += itemSize; if(itemSize > maxSize) maxSize = itemSize; } for(uint a = 0; a < checksPerCycle; ++a) { //Check an item uint pick = rand() % itemId; if(realItemsById.contains(pick)) { uint index = repository.findIndex(*realItemsById[pick]); QVERIFY(index); QVERIFY(realItemsByIndex.contains(index)); QVERIFY(realItemsByIndex[index]->equals(repository.itemFromIndex(index))); if((uint) (rand() % 100) < deletionProbability) { ++totalDeletions; //Delete the item repository.deleteItem(index); QVERIFY(!repository.findIndex(*realItemsById[pick])); uint newIndex = repository.index(*realItemsById[pick]); QVERIFY(newIndex); QVERIFY(realItemsByIndex[index]->equals(repository.itemFromIndex(newIndex))); #ifdef POSITION_TEST //Since we have previously deleted the item, there must be enough space if(!((newIndex >> 16) <= (highestSeenIndex >> 16))) { qDebug() << "size:" << realItemsById[pick]->itemSize(); qDebug() << "previous highest seen bucket:" << (highestSeenIndex >> 16); qDebug() << "new bucket:" << (newIndex >> 16); } QVERIFY((newIndex >> 16) <= (highestSeenIndex >> 16)); #endif repository.deleteItem(newIndex); QVERIFY(!repository.findIndex(*realItemsById[pick])); delete[] realItemsById[pick]; realItemsById.remove(pick); realItemsByIndex.remove(index); } } } } qDebug() << "total insertions:" << totalInsertions << "total deletions:" << totalDeletions << "average item size:" << (totalSize / totalInsertions) << "biggest item size:" << maxSize; KDevelop::ItemRepository::Statistics stats = repository.statistics(); qDebug() << stats; QVERIFY(stats.freeUnreachableSpace < stats.freeSpaceInBuckets/100); // < 1% of the free space is unreachable QVERIFY(stats.freeSpaceInBuckets < stats.usedSpaceForBuckets); // < 20% free space } void testLeaks() { - KDevelop::ItemRepository repository("TestItemRepository"); + KDevelop::ItemRepository repository(QStringLiteral("TestItemRepository")); QList items; for(int i = 0; i < 10000; ++i) { TestItem* item = createItem(i, (rand() % 1000) + sizeof(TestItem)); items << item; repository.index(TestItemRequest(*item)); } foreach(auto item, items) { delete[] item; } } void testStringSharing() { QString qString; qString.fill('.', 1000); KDevelop::IndexedString indexedString(qString); const int repeat = 10000; QVector strings; strings.resize(repeat); for(int i = 0; i < repeat; ++i) { strings[i] = indexedString.str(); QCOMPARE(qString, strings[i]); } } void deleteClashingMonsterBucket() { - KDevelop::ItemRepository repository("TestItemRepository"); + KDevelop::ItemRepository repository(QStringLiteral("TestItemRepository")); const uint hash = 1235; QScopedArrayPointer monsterItem(createItem(hash, KDevelop::ItemRepositoryBucketSize + 10)); QScopedArrayPointer smallItem(createItem(hash, 20)); QVERIFY(!monsterItem->equals(smallItem.data())); uint smallIndex = repository.index(TestItemRequest(*smallItem, true)); uint monsterIndex = repository.index(TestItemRequest(*monsterItem, true)); QVERIFY(monsterIndex != smallIndex); repository.deleteItem(smallIndex); QVERIFY(!repository.findIndex(TestItemRequest(*smallItem, true))); QCOMPARE(monsterIndex, repository.findIndex(TestItemRequest(*monsterItem, true))); repository.deleteItem(monsterIndex); // now in reverse order, with different data see: https://bugs.kde.org/show_bug.cgi?id=272408 monsterItem.reset(createItem(hash + 1, KDevelop::ItemRepositoryBucketSize + 10)); smallItem.reset(createItem(hash + 1, 20)); QVERIFY(!monsterItem->equals(smallItem.data())); monsterIndex = repository.index(TestItemRequest(*monsterItem, true)); smallIndex = repository.index(TestItemRequest(*smallItem, true)); repository.deleteItem(monsterIndex); QCOMPARE(smallIndex, repository.findIndex(TestItemRequest(*smallItem, true))); QVERIFY(!repository.findIndex(TestItemRequest(*monsterItem, true))); repository.deleteItem(smallIndex); } void usePermissiveModuloWhenRemovingClashLinks() { - KDevelop::ItemRepository repository("PermissiveModulo"); + KDevelop::ItemRepository repository(QStringLiteral("PermissiveModulo")); const uint bucketHashSize = decltype(repository)::bucketHashSize; const uint nextBucketHashSize = decltype(repository)::MyBucket::NextBucketHashSize; auto bucketNumberForIndex = [](const uint index) { return index >> 16; }; const uint clashValue = 2; // Choose sizes that ensure that the items fit in the desired buckets const uint bigItemSize = KDevelop::ItemRepositoryBucketSize * 0.55 - 1; const uint smallItemSize = KDevelop::ItemRepositoryBucketSize * 0.25 - 1; // Will get placed in bucket 1 (bucket zero is invalid), so the root bucket table at position 'clashValue' will be '1' const QScopedArrayPointer firstChainFirstLink(createItem(clashValue, bigItemSize)); const uint firstChainFirstLinkIndex = repository.index(*firstChainFirstLink); QCOMPARE(bucketNumberForIndex(firstChainFirstLinkIndex), 1u); // Will also get placed in bucket 1, so root bucket table at position 'nextBucketHashSize + clashValue' will be '1' const QScopedArrayPointer secondChainFirstLink(createItem(nextBucketHashSize + clashValue, smallItemSize)); const uint secondChainFirstLinkIndex = repository.index(*secondChainFirstLink); QCOMPARE(bucketNumberForIndex(secondChainFirstLinkIndex), 1u); // Will get placed in bucket 2, so bucket 1's next hash table at position 'clashValue' will be '2' const QScopedArrayPointer firstChainSecondLink(createItem(bucketHashSize + clashValue, bigItemSize)); const uint firstChainSecondLinkIndex = repository.index(*firstChainSecondLink); QCOMPARE(bucketNumberForIndex(firstChainSecondLinkIndex), 2u); // Will also get placed in bucket 2, reachable since bucket 1's next hash table at position 'clashValue' is '2' const QScopedArrayPointer secondChainSecondLink(createItem(bucketHashSize + nextBucketHashSize + clashValue, smallItemSize)); const uint secondChainSecondLinkIndex = repository.index(*secondChainSecondLink); QCOMPARE(bucketNumberForIndex(secondChainSecondLinkIndex), 2u); /* * At this point we have two chains in the repository, rooted at 'clashValue' and 'nextBucketHashSize + clashValue' * Both of the chains start in bucket 1 and end in bucket 2, but both chains share the same link to bucket 2 * This is because two of the hashes clash the other two when % bucketHashSize, but all of them clash % nextBucketHashSize */ repository.deleteItem(firstChainSecondLinkIndex); /* * Now we've deleted the second item in the first chain, this means the first chain no longer requires a link to the * second bucket where that item was... but the link must remain, since it's shared (clashed) by the second chain. * * When cutting a link out of the middle of the chain, we need to check if its items clash using the "permissive" * modulus (the size of the /next/ buckets map), which is always a factor of the "stricter" modulus (the size of the * /root/ buckets map). * * This behavior implies that there will sometimes be useless buckets in the bucket chain for a given hash, so when * cutting out the root link, it's safe to skip over them to the first clash with the 'stricter' modulus. */ // The second item of the second chain must still be reachable QCOMPARE(repository.findIndex(*secondChainSecondLink), secondChainSecondLinkIndex); /* * As a memo to anyone who's still reading this, this also means the following situation can exist: * * bucketHashSize == 8 * nextBucketHashSize == 4 * U is a link table * B is a bucket * [...] are the hashes of the contained items * * U * U * U -> B1 * U * U * U * U -> B2 * U * * B0 (Invalid) * B1 -> [2, 6] * U * U * U -> B3 * U * B2 -> [14] * U * U * U -> B1 * U * B3 -> [10] * U * U * U * U * * The chain for hash 6 is: * Root[6] -> B2[2] -> B1[2] -> B3 * * If you remove the item with hash 6, 6 and 2 will clash with mod 4 (permissive) * * So the useless link `B2[2] -> B1` will be preserved, even though its useless * as the item with hash 2 is reachable directly from the root. * * So TODO: Don't preserve links to items accessible from root buckets. This cannot * be done correctly using only Bucket::hasClashingItem as of now. */ } }; #include "test_itemrepository.moc" QTEST_MAIN(TestItemRepository) diff --git a/shell/assistantpopup.cpp b/shell/assistantpopup.cpp index efd8249f8..cfc6f5a22 100644 --- a/shell/assistantpopup.cpp +++ b/shell/assistantpopup.cpp @@ -1,380 +1,380 @@ /* Copyright 2009 David Nolden Copyright 2012 Milian Wolff Copyright 2014 Sven Brauch 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 "assistantpopup.h" #include "sublime/holdupdates.h" #include "util/kdevstringhandler.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; namespace { const int ASSISTANT_MODIFIER = #ifdef Q_OS_MAC Qt::CTRL; #else Qt::ALT; #endif const int ASSISTANT_MOD_KEY = #ifdef Q_OS_MAC Qt::Key_Control; #else Qt::Key_Alt; #endif QWidget* findByClassname(const KTextEditor::View* view, const QString& klass) { auto children = view->findChildren(); - for ( auto child: children ) { + foreach ( auto child, children ) { if ( child->metaObject()->className() == klass ) { return child; } } return nullptr; }; /** * @brief Get the geometry of the inner part (with the text) of the KTextEditor::View being used. */ QRect textWidgetGeometry(const KTextEditor::View *view) { // Subtract the width of the right scrollbar int scrollbarWidth = 0; - if ( auto scrollbar = findByClassname(view, "KateScrollBar") ) { + if ( auto scrollbar = findByClassname(view, QStringLiteral("KateScrollBar")) ) { scrollbarWidth = scrollbar->width(); } // Subtract the width of the bottom scrollbar int bottomScrollbarWidth = 0; - if ( auto bottom = findByClassname(view, "QScrollBar") ) { + if ( auto bottom = findByClassname(view, QStringLiteral("QScrollBar")) ) { bottomScrollbarWidth = bottom->height(); } auto geom = view->geometry(); geom.adjust(0, 0, -scrollbarWidth, -bottomScrollbarWidth); return geom; } } AssistantPopupConfig::AssistantPopupConfig(QObject *parent) : QObject(parent) , m_active(false) { } void AssistantPopupConfig::setColorsFromView(QObject *view) { auto iface = dynamic_cast(view); Q_ASSERT(iface); - m_foreground = iface->configValue("line-number-color").value(); - m_background = iface->configValue("icon-border-color").value(); - m_highlight = iface->configValue("folding-marker-color").value(); + m_foreground = iface->configValue(QStringLiteral("line-number-color")).value(); + m_background = iface->configValue(QStringLiteral("icon-border-color")).value(); + m_highlight = iface->configValue(QStringLiteral("folding-marker-color")).value(); if ( KColorUtils::luma(m_background) < 0.3 ) { m_foreground = KColorUtils::lighten(m_foreground, 0.7); } const float lumaDiff = KColorUtils::luma(m_highlight) - KColorUtils::luma(m_background); if ( qAbs(lumaDiff) < 0.5 ) { m_highlight = QColor::fromHsv(m_highlight.hue(), qMin(255, m_highlight.saturation() + 80), lumaDiff > 0 ? qMin(255, m_highlight.value() + 120) : qMax(80, m_highlight.value() - 40)); } emit colorsChanged(); } bool AssistantPopupConfig::isActive() const { return m_active; } void AssistantPopupConfig::setActive(bool active) { if (m_active == active) { return; } m_active = active; emit activeChanged(m_active); } void AssistantPopupConfig::setViewSize(const QSize& size) { if (size != m_viewSize) { m_viewSize = size; emit viewSizeChanged(size); } } void AssistantPopupConfig::setTitle(const QString& title) { if (m_title == title) { return; } m_title = title; emit titleChanged(m_title); } void AssistantPopupConfig::setModel(const QList& model) { if (m_model == model) { return; } qDeleteAll( m_model ); m_model = model; emit modelChanged(model); } AssistantPopup::AssistantPopup() // main window as parent to use maximal space available in worst case : QQuickWidget(ICore::self()->uiController()->activeMainWindow()) , m_config(new AssistantPopupConfig(this)) , m_firstLayoutCompleted(false) { setAttribute(Qt::WA_ShowWithoutActivating); - rootContext()->setContextProperty("config", m_config); + rootContext()->setContextProperty(QStringLiteral("config"), m_config); - setSource(QUrl::fromLocalFile(QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kdevelop/assistantpopup.qml"))); + setSource(QUrl::fromLocalFile(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kdevelop/assistantpopup.qml")))); if (!rootObject()) { qWarning() << "Failed to load assistant markup! The assistant will not work."; } else { connect(rootObject(), &QQuickItem::widthChanged, this, &AssistantPopup::updateLayout); connect(rootObject(), &QQuickItem::heightChanged, this, &AssistantPopup::updateLayout); } for (int i = Qt::Key_0; i <= Qt::Key_9; ++i) { m_shortcuts.append(new QShortcut(ASSISTANT_MODIFIER + i, this)); } setActive(false); connect(qApp, &QApplication::applicationStateChanged, this, [this]{ setActive(false); }); } void AssistantPopup::reset(KTextEditor::View* view, const IAssistant::Ptr& assistant) { setView(view); setAssistant(assistant); updateState(); } void AssistantPopup::setView(KTextEditor::View* view) { if (m_view == view) { return; } setActive(false); if (m_view) { m_view->removeEventFilter(this); disconnect(m_view.data(), &KTextEditor::View::verticalScrollPositionChanged, this, &AssistantPopup::updatePosition); } m_view = view; m_config->setViewSize(m_view ? m_view->size() : QSize()); if (m_view) { m_view->installEventFilter(this); connect(m_view.data(), &KTextEditor::View::verticalScrollPositionChanged, this, &AssistantPopup::updatePosition); } } void AssistantPopup::setAssistant(const IAssistant::Ptr& assistant) { if (m_assistant == assistant) { return; } if (m_assistant) { disconnect(m_assistant.data(), &IAssistant::hide, this, &AssistantPopup::hideAssistant); disconnect(m_assistant.data(), &IAssistant::actionsChanged, this, &AssistantPopup::updateState); } m_assistant = assistant; if (m_assistant) { connect(m_assistant.data(), &IAssistant::hide, this, &AssistantPopup::hideAssistant); connect(m_assistant.data(), &IAssistant::actionsChanged, this, &AssistantPopup::updateState); } else { hide(); } } void AssistantPopup::setActive(bool active) { m_config->setActive(active); - for (auto shortcut : m_shortcuts) { + foreach (auto shortcut, m_shortcuts) { shortcut->setEnabled(active); } } bool AssistantPopup::eventFilter(QObject* object, QEvent* event) { Q_UNUSED(object); if (!m_view || (object != m_view.data())) return false; if (event->type() == QEvent::Resize) { updateLayout(); } else if (event->type() == QEvent::Hide) { executeHideAction(); } else if (event->type() == QEvent::KeyPress) { auto keyEvent = static_cast(event); if (keyEvent->modifiers() == ASSISTANT_MODIFIER) { setActive(true); } if (keyEvent->key() == Qt::Key_Escape) { executeHideAction(); } } else if (event->type() == QEvent::KeyRelease) { auto keyEvent = static_cast(event); if (keyEvent->modifiers() == ASSISTANT_MODIFIER || keyEvent->key() == ASSISTANT_MOD_KEY) { setActive(false); } } return false; } void AssistantPopup::updatePosition(KTextEditor::View* view, const KTextEditor::Cursor& newPos) { static const int MARGIN = 12; if (newPos.isValid() && newPos.line() == 0) { // the position is not going to change; don't waste time return; } auto editorGeometry = textWidgetGeometry(view); const auto startCursorCoordinate = view->cursorToCoordinate(KTextEditor::Cursor(0, 0)); // algorithm for popup positioning: // if we are scrolled to the top: show at bottom // else: // if: current cursor position is in upper half => show at bottom // else: show at top const bool showAtBottom = startCursorCoordinate.y() == 0 ? true : view->cursorPositionCoordinates().y() < view->height()/2; const QPoint targetLocation = showAtBottom ? parentWidget()->mapFromGlobal(view->mapToGlobal(editorGeometry.bottomRight() + QPoint(-width() - MARGIN, -MARGIN - height()))) : parentWidget()->mapFromGlobal(view->mapToGlobal(editorGeometry.topRight() + QPoint(-width() - MARGIN, MARGIN))); if (pos() == targetLocation) { return; } Sublime::HoldUpdates hold(ICore::self()->uiController()->activeMainWindow()); move(targetLocation); } IAssistant::Ptr AssistantPopup::assistant() const { return m_assistant; } void AssistantPopup::executeHideAction() { if ( isVisible() ) { m_assistant->doHide(); } } void AssistantPopup::hideAssistant() { reset(nullptr, {}); // indirectly calls hide() } void AssistantPopup::updateLayout() { if ( !m_view ) { return; } m_config->setViewSize(m_view->size()); // https://bugreports.qt.io/browse/QTBUG-44876 resize(rootObject()->width(), rootObject()->height()); updatePosition(m_view, KTextEditor::Cursor::invalid()); // HACK: QQuickWidget is corrupted due to above resize on the first show if (!m_firstLayoutCompleted) { hide(); show(); m_firstLayoutCompleted = true; } } void AssistantPopup::updateState() { if (!m_assistant || m_assistant->actions().isEmpty() || !m_view) { hide(); return; } auto curShortcut = m_shortcuts.constBegin(); auto hideAction = new QAction(i18n("Hide"), this); connect(*curShortcut, &QShortcut::activated, hideAction, &QAction::trigger); connect(hideAction, &QAction::triggered, this, &AssistantPopup::executeHideAction); QList items; foreach (IAssistantAction::Ptr action, m_assistant->actions()) { QAction* asQAction = action->toKAction(); items << asQAction; asQAction->setParent(this); //For some reason, QAction's setShortcut does nothing, so we manage with QShortcut if (++curShortcut != m_shortcuts.constEnd()) { connect(*curShortcut, &QShortcut::activated, asQAction, &QAction::trigger); } connect(action.data(), SIGNAL(executed(IAssistantAction*)), hideAction, SLOT(trigger())); } items << hideAction; auto view = ICore::self()->documentController()->activeTextDocumentView(); m_config->setColorsFromView(view); m_config->setModel(items); m_config->setTitle(m_assistant->title()); setActive(false); show(); } diff --git a/template/filters/kdevfilters.cpp b/template/filters/kdevfilters.cpp index 175726ddc..58c3a45b8 100644 --- a/template/filters/kdevfilters.cpp +++ b/template/filters/kdevfilters.cpp @@ -1,181 +1,181 @@ /* 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 "kdevfilters.h" #include #include #include #include #include using namespace KDevelop; QString getSafeString(const QVariant& variant) { if (variant.canConvert()) { return variant.value().get(); } else { return variant.toString(); } } QStringList words(const QVariant& input) { QString string = getSafeString(input); if (string == string.toLower() && !string.contains('_')) { return QStringList(string); } if (string.contains('_')) { return string.toLower().split('_'); } int n = string.size(); QStringList ret; int last = 0; for (int i = 1; i < n; ++i) { if (string[i].isUpper()) { ret << string.mid(last, i-last).toLower(); last = i; } } ret << string.mid(last).toLower(); return ret; } QVariant CamelCaseFilter::doFilter(const QVariant& input, const QVariant& /*argument*/, bool /*autoescape*/) const { QString ret; foreach (const QString& word, words(input)) { QString w = word; w[0] = w[0].toUpper(); ret += w; } return Grantlee::SafeString(ret); } QVariant LowerCamelCaseFilter::doFilter(const QVariant& input, const QVariant& /*argument*/, bool /*autoescape*/) const { QString ret; foreach (const QString& word, words(input)) { QString w = word; w[0] = w[0].toUpper(); ret += w; } if (!ret.isEmpty()) { ret[0] = ret[0].toUpper(); } return Grantlee::SafeString(ret); } QVariant UnderscoreFilter::doFilter(const QVariant& input, const QVariant& /*argument*/, bool /*autoescape*/) const { - QString ret = words(input).join("_"); + QString ret = words(input).join(QStringLiteral("_")); return Grantlee::SafeString(ret); } QVariant UpperFirstFilter::doFilter(const QVariant& input, const QVariant& /*argument*/, bool /*autoescape*/) const { QString in = getSafeString(input); if (!in.isEmpty()) { in[0] = in[0].toUpper(); } return Grantlee::SafeString(in); } QVariant SplitLinesFilter::doFilter(const QVariant& input, const QVariant& argument, bool /*autoescape*/) const { QStringList retLines; QString start = getSafeString(argument); foreach (const QString& line, getSafeString(input).split('\n', QString::KeepEmptyParts)) { retLines << start + line; } return Grantlee::SafeString(retLines.join(QString('\n'))); } QVariant ArgumentTypeFilter::doFilter (const QVariant& input, const QVariant& /*argument*/, bool /*autoescape*/) const { QString type = getSafeString(input); DUChainReadLocker locker(DUChain::lock()); PersistentSymbolTable::Declarations decl = PersistentSymbolTable::self().getDeclarations(IndexedQualifiedIdentifier(QualifiedIdentifier(type))); for(PersistentSymbolTable::Declarations::Iterator it = decl.iterator(); it; ++it) { DeclarationPointer declaration = DeclarationPointer(it->declaration()); if(declaration->isForwardDeclaration()) { continue; } // Check if it's a class/struct/etc if(declaration->type()) { QString refType = QStringLiteral("const %1&").arg(type); return Grantlee::SafeString(refType); } } return Grantlee::SafeString(type); } KDevFilters::KDevFilters(QObject* parent, const QVariantList &) : QObject(parent) { } KDevFilters::~KDevFilters() { } QHash< QString, Grantlee::Filter* > KDevFilters::filters(const QString& name) { Q_UNUSED(name); QHash< QString, Grantlee::Filter* > filters; - filters["camel_case"] = new CamelCaseFilter(); - filters["camel_case_lower"] = new LowerCamelCaseFilter(); - filters["underscores"] = new UnderscoreFilter(); - filters["lines_prepend"] = new SplitLinesFilter(); - filters["upper_first"] = new UpperFirstFilter(); - filters["arg_type"] = new ArgumentTypeFilter(); + filters[QStringLiteral("camel_case")] = new CamelCaseFilter(); + filters[QStringLiteral("camel_case_lower")] = new LowerCamelCaseFilter(); + filters[QStringLiteral("underscores")] = new UnderscoreFilter(); + filters[QStringLiteral("lines_prepend")] = new SplitLinesFilter(); + filters[QStringLiteral("upper_first")] = new UpperFirstFilter(); + filters[QStringLiteral("arg_type")] = new ArgumentTypeFilter(); return filters; } diff --git a/tests/json/delayedoutput.cpp b/tests/json/delayedoutput.cpp index 1483d4f72..59f0a2e02 100644 --- a/tests/json/delayedoutput.cpp +++ b/tests/json/delayedoutput.cpp @@ -1,70 +1,70 @@ /* 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. */ #include #include "delayedoutput.h" namespace KDevelop { typedef QPair DepthedOutput; class DelayedOutputPrivate { public: void flushOutput() { - while (output.size()) + while (!output.isEmpty()) { DepthedOutput curOutput = output.pop(); qDebug().nospace() << qPrintable(QString(curOutput.second -1, ' ')) << curOutput.first.toUtf8().data(); } } QStack output; int delayDepth; }; DelayedOutput::Delay::Delay(DelayedOutput* output) { m_output = output; ++m_output->d->delayDepth; } DelayedOutput::Delay::~Delay() { --m_output->d->delayDepth; if (!m_output->d->delayDepth) m_output->d->flushOutput(); } DelayedOutput::DelayedOutput() : d(new DelayedOutputPrivate()) { } DelayedOutput::~DelayedOutput() { } DelayedOutput& DelayedOutput::self() { static DelayedOutput _inst; return _inst; } void DelayedOutput::push(const QString& output) { d->output.push(DepthedOutput(output, d->delayDepth)); } } diff --git a/tests/json/jsondeclarationtests.h b/tests/json/jsondeclarationtests.h index fed47b115..e41d9199f 100644 --- a/tests/json/jsondeclarationtests.h +++ b/tests/json/jsondeclarationtests.h @@ -1,346 +1,346 @@ /* 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_JSONDECLARATIONTESTS_H #define KDEVPLATFORM_JSONDECLARATIONTESTS_H #include "language/duchain/ducontext.h" #include "language/duchain/declaration.h" #include "language/duchain/indexeddeclaration.h" #include "language/duchain/identifier.h" #include "language/duchain/abstractfunctiondeclaration.h" #include "language/duchain/types/typeutils.h" #include "language/duchain/types/identifiedtype.h" #include #include "language/duchain/duchain.h" #include "language/duchain/functiondefinition.h" #include "language/duchain/definitions.h" #include #include "jsontesthelpers.h" /** * JSON Object Specification: * DeclTestObject: Mapping of (string) declaration test names to values * TypeTestObject: Mapping of (string) type test names to values * CtxtTestObject: Mapping of (string) context test names to values * * Quick Reference: * useCount : int * useRanges : string array * identifier : string * qualifiedIdentifier : string * internalContext : CtxtTestObject * internalFunctionContext : CtxtTestObject * type : TypeTestObject * unaliasedType : TypeTestObject * targetType : TypeTestObject * returnType : TypeTestObject * isAbstract : bool * isMutable : bool * isVirtual : bool * isStatic : bool * declaration : DeclTestObject * definition : DeclTestObject * identifiedTypeDeclaration : DeclTestObject * null : bool * defaultParameter : string * toString : string * range : string * kind : string * isDeprecated : bool * isDefinition : bool */ namespace KDevelop { template<> QString TestSuite::objectInformation(Declaration *decl) { VERIFY_NOT_NULL(decl); return QStringLiteral("(Declaration on line %1 in %2)") .arg(decl->range().start.line + 1) .arg(decl->topContext()->url().str()); } namespace DeclarationTests { using namespace JsonTestHelpers; ///JSON type: int ///@returns whether the declaration's number of uses matches the given value DeclarationTest(useCount) { int uses = 0; foreach(const QList& useRanges, decl->uses()) { uses += useRanges.size(); } - return compareValues(uses, value, "Declaration's use count "); + return compareValues(uses, value, QStringLiteral("Declaration's use count ")); } ///JSON type: string array ///@returns whether the declaration's ranges match the given value DeclarationTest(useRanges) { QStringList ranges; foreach(const QList& useRanges, decl->uses()) { - foreach(const RangeInRevision &range, useRanges) { + foreach(const RangeInRevision range, useRanges) { ranges << rangeStr(range); } } const QStringList testValues = value.toStringList(); - return ranges == testValues ? SUCCESS - : QStringLiteral("Declaration's use ranges (\"%1\") don't match test data (\"%2\").").arg(ranges.join(", ")).arg(testValues.join(", ")); + return ranges == testValues ? SUCCESS() + : QStringLiteral("Declaration's use ranges (\"%1\") don't match test data (\"%2\").").arg(ranges.join(QStringLiteral(", ")), testValues.join(QStringLiteral(", "))); } ///JSON type: string ///@returns whether the declaration's identifier matches the given value DeclarationTest(identifier) { VERIFY_NOT_NULL(decl); - return compareValues(decl->identifier().toString(), value, "Declaration's identifier"); + return compareValues(decl->identifier().toString(), value, QStringLiteral("Declaration's identifier")); } ///JSON type: string ///@returns whether the declaration's qualified identifier matches the given value DeclarationTest(qualifiedIdentifier) { VERIFY_NOT_NULL(decl); - return compareValues(decl->qualifiedIdentifier().toString(), value, "Declaration's qualified identifier"); + return compareValues(decl->qualifiedIdentifier().toString(), value, QStringLiteral("Declaration's qualified identifier")); } ///JSON type: CtxtTestObject ///@returns whether the tests for the declaration's internal context pass DeclarationTest(internalContext) { VERIFY_NOT_NULL(decl); - return testObject(decl->internalContext(), value, "Declaration's internal context"); + return testObject(decl->internalContext(), value, QStringLiteral("Declaration's internal context")); } ///JSON type: CtxtTestObject ///@returns whether the tests for the declaration's internal function context pass DeclarationTest(internalFunctionContext) { - const QString NO_INTERNAL_CTXT = "%1 has no internal function context."; + const QString NO_INTERNAL_CTXT = QStringLiteral("%1 has no internal function context."); AbstractFunctionDeclaration *absFuncDecl = dynamic_cast(decl); if (!absFuncDecl || !absFuncDecl->internalFunctionContext()) return NO_INTERNAL_CTXT.arg(decl->qualifiedIdentifier().toString()); - return testObject(absFuncDecl->internalFunctionContext(), value, "Declaration's internal function context"); + return testObject(absFuncDecl->internalFunctionContext(), value, QStringLiteral("Declaration's internal function context")); } /*FIXME: The type functions need some renaming and moving around * Some (all?) functions from cpp's TypeUtils should be moved to the kdevplatform type utils * targetType is exactly like realType except it also tosses pointers * shortenTypeForViewing should go to type utils (and it's broken, it places const to the left of all *'s when it should be left or right of the type) * UnaliasedType seems to be unable to understand aliases involving templates, perhaps a cpp version is in order */ ///JSON type: TypeTestObject ///@returns whether the tests for the declaration's type pass DeclarationTest(type) { VERIFY_NOT_NULL(decl); - return testObject(decl->abstractType(), value, "Declaration's type"); + return testObject(decl->abstractType(), value, QStringLiteral("Declaration's type")); } ///JSON type: TypeTestObject ///@returns whether the tests for the declaration's unaliased type pass (TypeUtils::unaliasedType) DeclarationTest(unaliasedType) { VERIFY_NOT_NULL(decl); - return testObject(TypeUtils::unAliasedType(decl->abstractType()), value, "Declaration's unaliased type"); + return testObject(TypeUtils::unAliasedType(decl->abstractType()), value, QStringLiteral("Declaration's unaliased type")); } ///JSON type: TypeTestObject ///@returns whether the tests for the declaration's target type pass (TypeUtils::targetType) DeclarationTest(targetType) { VERIFY_NOT_NULL(decl); - return testObject(TypeUtils::targetType(decl->abstractType(), decl->topContext()), value, "Declaration's target type"); + return testObject(TypeUtils::targetType(decl->abstractType(), decl->topContext()), value, QStringLiteral("Declaration's target type")); } ///JSON type: TestTypeObject ///@returns the DeclarationTest(returnType) { FunctionType::Ptr functionType = decl->abstractType().cast(); AbstractType::Ptr returnType; if (functionType) { returnType = functionType->returnType(); } - return testObject(returnType, value, "Declaration's return type"); + return testObject(returnType, value, QStringLiteral("Declaration's return type")); } ///JSON type: DeclTestObject ///@returns The IdentifiedType's declaration DeclarationTest(identifiedTypeDeclaration) { - const QString UN_ID_ERROR = "Unable to identify declaration of type \"%1\"."; + const QString UN_ID_ERROR = QStringLiteral("Unable to identify declaration of type \"%1\"."); AbstractType::Ptr type = TypeUtils::targetType(decl->abstractType(), decl->topContext()); IdentifiedType* idType = dynamic_cast(type.data()); Declaration* idDecl = idType ? idType->declaration(decl->topContext()) : 0; if (!idDecl) return UN_ID_ERROR.arg(type->toString()); - return testObject(idDecl, value, "IdentifiedType's declaration"); + return testObject(idDecl, value, QStringLiteral("IdentifiedType's declaration")); } ///JSON type: bool ///@returns whether the (function) declaration's isVirtual matches the given value DeclarationTest(isVirtual) { - const QString NOT_A_FUNCTION = "Non-function declaration cannot be virtual."; + const QString NOT_A_FUNCTION = QStringLiteral("Non-function declaration cannot be virtual."); AbstractFunctionDeclaration *absFuncDecl = dynamic_cast(decl); if (!absFuncDecl) return NOT_A_FUNCTION; - return compareValues(absFuncDecl->isVirtual(), value, "Declaration's isVirtual"); + return compareValues(absFuncDecl->isVirtual(), value, QStringLiteral("Declaration's isVirtual")); } ///JSON type: bool ///@returns whether the (function) declaration's isAbstract matches the given value DeclarationTest(isAbstract) { - const QString NOT_A_FUNCTION = "Non-class-member declaration cannot be abstract."; + const QString NOT_A_FUNCTION = QStringLiteral("Non-class-member declaration cannot be abstract."); auto *absFuncDecl = dynamic_cast(decl); if (!absFuncDecl) return NOT_A_FUNCTION; - return compareValues(absFuncDecl->isAbstract(), value, "Declaration's isAbstract"); + return compareValues(absFuncDecl->isAbstract(), value, QStringLiteral("Declaration's isAbstract")); } ///JSON type: bool ///@returns whether the (class-member) declaration's isStatic matches the given value DeclarationTest(isStatic) { - const QString NOT_A_MEMBER = "Non-class-member declaration cannot be static."; + const QString NOT_A_MEMBER = QStringLiteral("Non-class-member declaration cannot be static."); auto memberDecl = dynamic_cast(decl); if (!memberDecl) return NOT_A_MEMBER; - return compareValues(memberDecl->isStatic(), value, "Declaration's isStatic"); + return compareValues(memberDecl->isStatic(), value, QStringLiteral("Declaration's isStatic")); } ///JSON type: bool ///@returns whether the (class-member) declaration's isMutable matches the given value DeclarationTest(isMutable) { - const QString NOT_A_MEMBER = "Non-class-member declaration cannot be mutable."; + const QString NOT_A_MEMBER = QStringLiteral("Non-class-member declaration cannot be mutable."); auto memberDecl = dynamic_cast(decl); if (!memberDecl) return NOT_A_MEMBER; - return compareValues(memberDecl->isMutable(), value, "Declaration's isMutable"); + return compareValues(memberDecl->isMutable(), value, QStringLiteral("Declaration's isMutable")); } ///JSON type: DeclTestObject ///@returns whether the tests for the function declaration's definition pass DeclarationTest(definition) { KDevVarLengthArray definitions = DUChain::definitions()->definitions(decl->id()); Declaration *declDef = 0; - if (definitions.size()) + if (!definitions.isEmpty()) declDef = definitions.at(0).declaration(); - return testObject(declDef, value, "Declaration's definition"); + return testObject(declDef, value, QStringLiteral("Declaration's definition")); } ///JSON type: DeclTestObject ///@returns whether the tests for the function definition's declaration pass DeclarationTest(declaration) { FunctionDefinition *def = dynamic_cast(decl); Declaration *defDecl = nullptr; if (def) defDecl = def->declaration(decl->topContext()); - return testObject(defDecl, value, "Definition's declaration"); + return testObject(defDecl, value, QStringLiteral("Definition's declaration")); } ///JSON type: bool ///@returns whether the declaration's nullity matches the given value DeclarationTest(null) { - return compareValues(decl == 0, value, "Declaration's nullity"); + return compareValues(decl == 0, value, QStringLiteral("Declaration's nullity")); } ///JSON type: bool ///@returns whether the declaration's default parameter matches the given value DeclarationTest(defaultParameter) { - const QString NOT_IN_FUNC_CTXT = "Asked for a default parameter for a declaration outside of a function context."; - const QString OWNER_NOT_FUNC = "Function context not owned by function declaration (what on earth did you do?)."; + const QString NOT_IN_FUNC_CTXT = QStringLiteral("Asked for a default parameter for a declaration outside of a function context."); + const QString OWNER_NOT_FUNC = QStringLiteral("Function context not owned by function declaration (what on earth did you do?)."); DUContext *context = decl->context(); if (!context || context->type() != DUContext::Function) return NOT_IN_FUNC_CTXT; AbstractFunctionDeclaration *funcDecl = dynamic_cast(context->owner()); if (!funcDecl) return OWNER_NOT_FUNC; int argIndex = context->localDeclarations().indexOf(decl); - return compareValues(funcDecl->defaultParameterForArgument(argIndex).str(), value, "Declaration's default parameter"); + return compareValues(funcDecl->defaultParameterForArgument(argIndex).str(), value, QStringLiteral("Declaration's default parameter")); } ///JSON type: string ///@returns stringified declaration DeclarationTest(toString) { VERIFY_NOT_NULL(decl); - return compareValues(decl->toString(), value, "Declaration's toString"); + return compareValues(decl->toString(), value, QStringLiteral("Declaration's toString")); } ///JSON type: string ///@returns stringified declaration range DeclarationTest(range) { VERIFY_NOT_NULL(decl); - return compareValues(rangeStr(decl->range()), value, "Declaration's range"); + return compareValues(rangeStr(decl->range()), value, QStringLiteral("Declaration's range")); } ///JSON type: string ///@returns stringified declaration kind DeclarationTest(kind) { VERIFY_NOT_NULL(decl); QString kind; switch (decl->kind()) { case KDevelop::Declaration::Alias: - kind = "Alias"; + kind = QStringLiteral("Alias"); break; case KDevelop::Declaration::Import: - kind = "Import"; + kind = QStringLiteral("Import"); break; case KDevelop::Declaration::Instance: - kind = "Instance"; + kind = QStringLiteral("Instance"); break; case KDevelop::Declaration::Namespace: - kind = "Namespace"; + kind = QStringLiteral("Namespace"); break; case KDevelop::Declaration::NamespaceAlias: - kind = "NamespaceAlias"; + kind = QStringLiteral("NamespaceAlias"); break; case KDevelop::Declaration::Type: - kind = "Type"; + kind = QStringLiteral("Type"); break; case KDevelop::Declaration::Macro: - kind = "Macro"; + kind = QStringLiteral("Macro"); break; } - return compareValues(kind, value, "Declaration's kind"); + return compareValues(kind, value, QStringLiteral("Declaration's kind")); } ///JSON type: bool ///@returns whether the declaration's isDeprecated matches the given value DeclarationTest(isDeprecated) { VERIFY_NOT_NULL(decl); - return compareValues(decl->isDeprecated(), value, "Declaration's isDeprecated"); + return compareValues(decl->isDeprecated(), value, QStringLiteral("Declaration's isDeprecated")); } ///JSON type: bool ///@returns whether the declaration's isDefinition matches the given value DeclarationTest(isDefinition) { VERIFY_NOT_NULL(decl); - return compareValues(decl->isDefinition(), value, "Declaration's isDefinition"); + return compareValues(decl->isDefinition(), value, QStringLiteral("Declaration's isDefinition")); } } } #endif //KDEVPLATFORM_JSONDECLARATIONTESTS_H diff --git a/tests/json/jsonducontexttests.h b/tests/json/jsonducontexttests.h index 11dbbe533..6c569786b 100644 --- a/tests/json/jsonducontexttests.h +++ b/tests/json/jsonducontexttests.h @@ -1,179 +1,179 @@ /* 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_JSONDUCONTEXTTESTS_H #define KDEVPLATFORM_JSONDUCONTEXTTESTS_H #include "language/duchain/ducontext.h" #include "jsontesthelpers.h" /** * JSON Object Specification: * FindDeclObject: Mapping of (string) search ids to DeclTestObjects * IndexDeclObject: Mapping of (string) declaration position indexes to DeclTestObjects * IndexCtxtObject: Mapping of (string) context position indexes to CtxtTestObjects * * Quick Reference: * findDeclarations : FindDeclObject * declarations : IndexDeclObject * childCount : int * localDeclarationCount : int * type : string * null : bool * owner : DeclTestObject * importedParents : IndexCtxtObject * range : string */ namespace KDevelop { namespace DUContextTests { using namespace JsonTestHelpers; ///JSON type: FindDeclObject ///@returns whether each declaration can be found and passes its tests ContextTest(findDeclarations) { VERIFY_TYPE(QVariantMap); QString INVALID_ERROR = "Attempted to test invalid context."; QString NOT_FOUND_ERROR = "Could not find declaration \"%1\"."; QString DECL_ERROR = "Declaration found with \"%1\" did not pass tests."; if (!ctxt) return INVALID_ERROR; QVariantMap findDecls = value.toMap(); for (QVariantMap::iterator it = findDecls.begin(); it != findDecls.end(); ++it) { QualifiedIdentifier searchId(it.key()); QList ret = ctxt->findDeclarations(searchId, CursorInRevision::invalid()); if (!ret.size()) return NOT_FOUND_ERROR.arg(it.key()); if (!runTests(it.value().toMap(), ret.first())) return DECL_ERROR.arg(it.key()); } - return SUCCESS; + return SUCCESS(); } ///JSON type: IndexDeclObject ///@returns whether a declaration exists at each index and each declaration passes its tests ContextTest(declarations) { VERIFY_TYPE(QVariantMap); QString INVALID_ERROR = "Attempted to test invalid context."; QString NOT_FOUND_ERROR = "No declaration at index \"%1\"."; QString DECL_ERROR = "Declaration at index \"%1\" did not pass tests."; if (!ctxt) return INVALID_ERROR; QVariantMap findDecls = value.toMap(); for (QVariantMap::iterator it = findDecls.begin(); it != findDecls.end(); ++it) { int index = it.key().toInt(); QVector decls = ctxt->localDeclarations(0); if (decls.size() <= index) return NOT_FOUND_ERROR; if (!runTests(it.value().toMap(), decls.at(index))) return DECL_ERROR.arg(it.key()); } - return SUCCESS; + return SUCCESS(); } ///JSON type: int ///@returns whether the number of child contexts matches the given value ContextTest(childCount) { return compareValues(ctxt->childContexts().size(), value, "Context's child count"); } ///JSON type: int ///@returns whether the number of local declarations matches the given value ContextTest(localDeclarationCount) { return compareValues(ctxt->localDeclarations().size(), value, "Context's local declaration count"); } ///JSON type: string ///@returns whether the context's type matches the given value ContextTest(type) { QString contextTypeString; switch(ctxt->type()) { case DUContext::Class: contextTypeString = "Class"; break; case DUContext::Enum: contextTypeString = "Enum"; break; case DUContext::Namespace: contextTypeString = "Namespace"; break; case DUContext::Function: contextTypeString = "Function"; break; case DUContext::Template: contextTypeString = "Template"; break; case DUContext::Global: contextTypeString = "Global"; break; case DUContext::Helper: contextTypeString = "Helper"; break; case DUContext::Other: contextTypeString = "Other"; break; } return compareValues(contextTypeString, value, "Context's type"); } ///JSON type: bool ///@returns whether the context's nullity matches the given value ContextTest(null) { return compareValues(ctxt == 0, value, "Context's nullity"); } //JSON type: DeclTestObject ///@returns the context's owner ContextTest(owner) { return testObject(ctxt->owner(), value, "Context's owner"); } ///JSON type: IndexCtxtObject ///@returns whether a context exists at each index and each context passes its tests ContextTest(importedParents) { VERIFY_TYPE(QVariantMap); QString INVALID_ERROR = "Attempted to test invalid context."; QString NOT_FOUND_ERROR = "No imported context at index \"%1\"."; QString CONTEXT_ERROR = "Context at index \"%1\" did not pass tests."; if (!ctxt) return INVALID_ERROR; QVariantMap findDecls = value.toMap(); for (QVariantMap::iterator it = findDecls.begin(); it != findDecls.end(); ++it) { int index = it.key().toInt(); QVector imports = ctxt->importedParentContexts(); if (imports.size() <= index) return NOT_FOUND_ERROR; if (!runTests(it.value().toMap(), imports.at(index).context(ctxt->topContext()))) return CONTEXT_ERROR.arg(it.key()); } - return SUCCESS; + return SUCCESS(); } ///JSON type: string ///@returns stringified context range ContextTest(range) { if (!ctxt) { return "Invalid Context"; } return compareValues(rangeStr(ctxt->range()), value, "Contexts's range"); } } } #endif //KDEVPLATFORM_JSONDUCONTEXTTESTS_H diff --git a/tests/json/jsontesthelpers.h b/tests/json/jsontesthelpers.h index 564c5a78c..3063ffe98 100644 --- a/tests/json/jsontesthelpers.h +++ b/tests/json/jsontesthelpers.h @@ -1,84 +1,84 @@ /* 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_JSONTESTHELPERS_H #define KDEVPLATFORM_JSONTESTHELPERS_H #include "testsuite.h" #define VERIFY_NOT_NULL(ptr)\ if (!ptr) \ - return JsonTestHelpers::INVALID_POINTER + return JsonTestHelpers::INVALID_POINTER() #define VERIFY_TYPE(qvariantType)\ if (!value.canConvert())\ - return JsonTestHelpers::INVALID_JSON_TYPE.arg(#qvariantType, QVariant::typeToName(value.type())) + return JsonTestHelpers::INVALID_JSON_TYPE().arg(QStringLiteral(#qvariantType), QVariant::typeToName(value.type())) #define __AddTest(testName, objType)\ -bool testName##Added = KDevelop::TestSuite::get().addTest(#testName, &testName) +bool testName##Added = KDevelop::TestSuite::get().addTest(QStringLiteral(#testName), &testName) #define __DefineTest(testName, objType, objName)\ QString testName(const QVariant&, objType);\ __AddTest(testName, objType);\ QString testName(const QVariant &value, objType objName) #define DeclarationTest(testName) __DefineTest(testName, KDevelop::Declaration*, decl) #define TypeTest(testName) __DefineTest(testName, KDevelop::AbstractType::Ptr, type) #define ContextTest(testName) __DefineTest(testName, KDevelop::DUContext*, ctxt) namespace KDevelop { namespace JsonTestHelpers { -const QString SUCCESS = QString(); -const QString INVALID_JSON_TYPE = QStringLiteral("Incorrect JSON type provided for test. Actual: %1, Expected: %2"); -const QString INVALID_POINTER = QStringLiteral("Null pointer passed to test."); +inline QString SUCCESS() { return QString(); } +inline QString INVALID_JSON_TYPE() { return QStringLiteral("Incorrect JSON type provided for test. Actual: %1, Expected: %2"); } +inline QString INVALID_POINTER() { return QStringLiteral("Null pointer passed to test."); } template inline QString compareValues(Type realValue, const QVariant &value, const QString &errorDesc) { VERIFY_TYPE(Type); - const QString ERROR_MESSAGE = "%1 (\"%2\") doesn't match test data (\"%3\")."; + const QString ERROR_MESSAGE = QStringLiteral("%1 (\"%2\") doesn't match test data (\"%3\")."); return realValue == value.value() ? - SUCCESS : ERROR_MESSAGE.arg(errorDesc).arg(realValue).arg(value.value()); + SUCCESS() : ERROR_MESSAGE.arg(errorDesc).arg(realValue).arg(value.value()); } template inline QString testObject(Object obj, const QVariant &value, const QString &errorDesc) { VERIFY_TYPE(QVariantMap); - const QString ERROR_MESSAGE = "%1 did not pass tests."; - return KDevelop::TestSuite::get().runTests(value.toMap(), obj) ? SUCCESS : ERROR_MESSAGE.arg(errorDesc); + const QString ERROR_MESSAGE = QStringLiteral("%1 did not pass tests."); + return KDevelop::TestSuite::get().runTests(value.toMap(), obj) ? SUCCESS() : ERROR_MESSAGE.arg(errorDesc); } inline QString rangeStr(const KDevelop::RangeInRevision &range) { return QStringLiteral("[(%1, %2), (%3, %4)]") .arg(range.start.line) .arg(range.start.column) .arg(range.end.line) .arg(range.end.column); } } } #endif //KDEVPLATFORM_JSONTESTHELPERS_H diff --git a/tests/json/jsontypetests.h b/tests/json/jsontypetests.h index 693406928..5efe6e3ad 100644 --- a/tests/json/jsontypetests.h +++ b/tests/json/jsontypetests.h @@ -1,82 +1,82 @@ /* 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_JSONTYPETESTS_H #define KDEVPLATFORM_JSONTYPETESTS_H #include "language/duchain/types/abstracttype.h" #include "language/duchain/types/constantintegraltype.h" #include "language/duchain/types/delayedtype.h" #include "language/duchain/types/functiontype.h" #include "jsontesthelpers.h" /** * Quick Reference: * toString : string * isConst : bool * plainValue : qint64 */ namespace KDevelop { namespace TypeTests { using namespace JsonTestHelpers; ///JSON type: string ///@returns whether the type toString matches the given value TypeTest(toString) { QString typeStr = type ? type->toString() : ""; return compareValues(typeStr, value, "Type's toString"); } ///JSON type: bool ///@returns whether type's constness matches the given value TypeTest(isConst) { VERIFY_TYPE(bool); bool typeIsConst = false; if (DelayedType::Ptr delayed = type.cast()) typeIsConst = delayed->identifier().isConstant(); else typeIsConst = (type->modifiers() & AbstractType::ConstModifier); if (typeIsConst != value.toBool()) return typeIsConst ? "Type is constant, but test data expects non-const." : "Type is non-const, but test data expects constant."; - return SUCCESS; + return SUCCESS(); } ///JSON type: qint64 ///@returns Whether ConstantIntegralType's plainValue matches the given value TypeTest(plainValue) { VERIFY_TYPE(qint64); auto constantIntegralType = type.cast(); VERIFY_NOT_NULL(constantIntegralType); return compareValues(constantIntegralType->plainValue(), value, "ConstantIntegralType's plainValue"); } } } #endif //KDEVPLATFORM_JSONTYPETESTS_H diff --git a/tests/json/testsuite.h b/tests/json/testsuite.h index b28708a73..fdffe94a6 100644 --- a/tests/json/testsuite.h +++ b/tests/json/testsuite.h @@ -1,137 +1,137 @@ /* 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_TESTSUITE_H #define KDEVPLATFORM_TESTSUITE_H #include #include "delayedoutput.h" #include #include namespace KDevelop { class DUContext; class Declaration; -const QString EXPECT_FAIL = "EXPECT_FAIL"; -const QString FAILED_TO_FAIL = "\"%1\" FAILED TO FAIL AS EXPECTED: \"%2\" %3"; -const QString EXPECTED_FAIL = "\"%1\" FAILED (expected): %2 %3"; -const QString FAIL = "\"%1\" FAILED: %2 %3"; -const QString TEST_NOT_FOUND = "Test not found"; +inline QString EXPECT_FAIL() { return QStringLiteral("EXPECT_FAIL"); } +inline QString FAILED_TO_FAIL() { return QStringLiteral("\"%1\" FAILED TO FAIL AS EXPECTED: \"%2\" %3"); } +inline QString EXPECTED_FAIL() { return QStringLiteral("\"%1\" FAILED (expected): %2 %3"); } +inline QString FAIL() { return QStringLiteral("\"%1\" FAILED: %2 %3"); } +inline QString TEST_NOT_FOUND() { return QStringLiteral("Test not found"); } template class TestSuite; KDEVPLATFORMTESTS_EXPORT TestSuite& declarationTestSuite(); KDEVPLATFORMTESTS_EXPORT TestSuite& contextTestSuite(); KDEVPLATFORMTESTS_EXPORT TestSuite& typeTestSuite(); template class KDEVPLATFORMTESTS_EXPORT TestSuite { public: typedef QString (*TestFunction)(const QVariant&, T); static TestSuite& get(); bool addTest(const QString& testName, TestFunction testFunc) { m_testFunctions.insert(testName, testFunc); return true; } bool runTests(const QVariantMap &testData, T object) { QVariantMap expectedFails = expectedFailures(testData); QVariantMap::const_iterator it; DelayedOutput::Delay delay(&DelayedOutput::self()); for (it = testData.begin(); it != testData.end(); ++it) { - if (it.key() == EXPECT_FAIL) + if (it.key() == EXPECT_FAIL()) continue; QString result = m_testFunctions.value(it.key(), &TestSuite::noSuchTest)(it.value(), object); QString expectedFailure = expectedFails.value(it.key(), QString()).toString(); //Either ("expected failure" & "no result failure") or ("no expected failure" & "result failure") - if ((bool)expectedFailure.size() ^ (bool)result.size()) + if (expectedFailure.isEmpty() ^ result.isEmpty()) { - DelayedOutput::self().push(result.size() ? FAIL.arg(it.key(), result, objectInformation(object)) : - FAILED_TO_FAIL.arg(it.key(), expectedFailure, objectInformation(object))); + DelayedOutput::self().push(result.isEmpty() ? FAILED_TO_FAIL().arg(it.key(), expectedFailure, objectInformation(object)) : + FAIL().arg(it.key(), result, objectInformation(object))); return false; } - if (expectedFailure.size()) - qDebug() << EXPECTED_FAIL.arg(it.key(), expectedFailure, objectInformation(object)).toUtf8().data(); + if (!expectedFailure.isEmpty()) + qDebug() << EXPECTED_FAIL().arg(it.key(), expectedFailure, objectInformation(object)).toUtf8().data(); } return true; } private: QVariantMap expectedFailures(const QVariantMap &testData) { - if (!testData.contains(EXPECT_FAIL)) + if (!testData.contains(EXPECT_FAIL())) return QVariantMap(); - return testData[EXPECT_FAIL].toMap(); + return testData[EXPECT_FAIL()].toMap(); } static QString noSuchTest(const QVariant&, T) { - return TEST_NOT_FOUND; + return TEST_NOT_FOUND(); } static QString objectInformation(T) { return QString(); } QHash m_testFunctions; TestSuite() { } Q_DISABLE_COPY(TestSuite); friend TestSuite& declarationTestSuite(); friend TestSuite& contextTestSuite(); friend TestSuite& typeTestSuite(); }; template inline bool runTests(const QVariantMap &data, T object) { return TestSuite::get().runTests(data, object); } ///TODO: Once we can use C++11, see whether this can be cleaned up by extern templates template<> inline TestSuite& TestSuite::get() { return declarationTestSuite(); } template<> inline TestSuite& TestSuite::get() { return contextTestSuite(); } template<> inline TestSuite& TestSuite::get() { return typeTestSuite(); } } #endif //KDEVPLATFORM_TESTSUITE_H diff --git a/tests/testhelpers.h b/tests/testhelpers.h index 1bb72429a..f423f9629 100644 --- a/tests/testhelpers.h +++ b/tests/testhelpers.h @@ -1,86 +1,86 @@ /* * 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_TESTHELPERS_H #define KDEVPLATFORM_TESTHELPERS_H #include "testsexport.h" #include #include #include #include #include namespace QTest { template<> inline char* toString(const KDevelop::CursorInRevision& c) { return toString(c.castToSimpleCursor()); } template<> inline char* toString(const KDevelop::RangeInRevision& r) { return toString(r.castToSimpleRange()); } template<> inline char* toString(const KDevelop::QualifiedIdentifier& id) { return qstrdup(qPrintable(id.toString())); } template<> inline char* toString(const KDevelop::Identifier& id) { return qstrdup(qPrintable(id.toString())); } template<> inline char* toString(const KDevelop::Declaration& dec) { QString s = QStringLiteral("Declaration %1 (%2): %3") - .arg(dec.identifier().toString()) - .arg(dec.qualifiedIdentifier().toString()) - .arg(reinterpret_cast(&dec)); + .arg(dec.identifier().toString(), + dec.qualifiedIdentifier().toString(), + QString::number(reinterpret_cast(&dec), 10)); return qstrdup(s.toLatin1().constData()); } template<> inline char* toString(const KDevelop::AbstractType::Ptr& type) { QString s = QStringLiteral("Type: %1") .arg(type ? type->toString() : QStringLiteral("")); return qstrdup(s.toLatin1().constData()); } template<> inline char* toString(const KDevelop::IndexedString& string) { return qstrdup(qPrintable(string.str())); } } #endif // KDEVPLATFORM_TESTHELPERS_H diff --git a/tests/testproject.cpp b/tests/testproject.cpp index 40321105b..0a8e59878 100644 --- a/tests/testproject.cpp +++ b/tests/testproject.cpp @@ -1,132 +1,132 @@ /*************************************************************************** * 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. * ***************************************************************************/ #include "testproject.h" #include #include using namespace KDevelop; TestProject::TestProject(const Path& path, QObject* parent) : IProject(parent) , m_root(0) , m_projectConfiguration(KSharedConfig::openConfig()) { - m_path = path.isValid() ? path : Path("/tmp/kdev-testproject/"); + m_path = path.isValid() ? path : Path(QStringLiteral("/tmp/kdev-testproject/")); m_root = new ProjectFolderItem(this, m_path); ICore::self()->projectController()->projectModel()->appendRow( m_root ); } void TestProject::setPath(const Path& path) { m_path = path; if (m_root) { m_root->setPath(path); } } TestProject::~TestProject() { if (m_root) { delete m_root; } } ProjectFolderItem* TestProject::projectItem() const { return m_root; } void TestProject::setProjectItem(ProjectFolderItem* item) { if (m_root) { ICore::self()->projectController()->projectModel()->removeRow( m_root->row() ); m_root = 0; m_path.clear(); } if (item) { m_root = item; m_path = item->path(); ICore::self()->projectController()->projectModel()->appendRow( m_root ); } } Path TestProject::projectFile() const { return Path(m_path, m_path.lastPathSegment() + ".kdev4"); } Path TestProject::path() const { return m_path; } bool TestProject::inProject(const IndexedString& path) const { return m_path.isParentOf(Path(path.str())); } void findFileItems(ProjectBaseItem* root, QList& items, const Path& path = {}) { foreach(ProjectBaseItem* item, root->children()) { if (item->file() && (path.isEmpty() || item->path() == path)) { items << item->file(); } if (item->rowCount()) { findFileItems(item, items, path); } } } QList< ProjectFileItem* > TestProject::files() const { QList ret; findFileItems(m_root, ret); return ret; } QList TestProject::filesForPath(const IndexedString& path) const { QList ret; findFileItems(m_root, ret, Path(path.toUrl())); return ret; } void TestProject::addToFileSet(ProjectFileItem* file) { if (!m_fileSet.contains(file->indexedPath())) { m_fileSet.insert(file->indexedPath()); emit fileAddedToSet(file); } } void TestProject::removeFromFileSet(ProjectFileItem* file) { if (m_fileSet.remove(file->indexedPath())) { emit fileRemovedFromSet(file); } } void TestProjectController::initialize() { } diff --git a/tests/testproject.h b/tests/testproject.h index 7f3761996..0f6358437 100644 --- a/tests/testproject.h +++ b/tests/testproject.h @@ -1,108 +1,108 @@ /*************************************************************************** * 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 = 0); ~TestProject() override; IProjectFileManager* projectFileManager() const override { return 0; } IBuildSystemManager* buildSystemManager() const override { return 0; } IPlugin* managerPlugin() const override { return 0; } IPlugin* versionControlPlugin() const override { return 0; } ProjectFolderItem* projectItem() const override; void setProjectItem(ProjectFolderItem* item); int fileCount() const { return 0; } ProjectFileItem* fileAt( int ) const { return 0; } 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 "Test Project"; } + 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) {} IProject* projectAt( int i ) const override { return m_projects.at(i); } int projectCount() const override { return m_projects.count(); } QList projects() const override { return m_projects; } public: using ProjectController::addProject; using ProjectController::takeProject; void initialize() override; private: QList m_projects; }; } #endif diff --git a/util/duchainify/main.cpp b/util/duchainify/main.cpp index 452e9189c..1f19fdcc4 100644 --- a/util/duchainify/main.cpp +++ b/util/duchainify/main.cpp @@ -1,327 +1,327 @@ /*************************************************************************** * Copyright 2009 David Nolden * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU 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 "main.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 bool verbose=false, warnings=false; using namespace KDevelop; void messageOutput(QtMsgType type, const QMessageLogContext& context, const QString& msg) { Q_UNUSED(context); switch (type) { #if QT_VERSION >= 0x050500 case QtInfoMsg: // fall-through #endif case QtDebugMsg: if(verbose) std::cerr << qPrintable(msg) << std::endl; break; case QtWarningMsg: if(warnings) std::cerr << qPrintable(msg) << std::endl; break; case QtCriticalMsg: std::cerr << qPrintable(msg) << std::endl; break; case QtFatalMsg: std::cerr << qPrintable(msg) << std::endl; abort(); } } Manager::Manager(QCommandLineParser* args) : m_total(0), m_args(args), m_allFilesAdded(0) { } void Manager::init() { QList includes; if(m_args->positionalArguments().isEmpty()) { std::cerr << "Need file or directory to duchainify" << std::endl; QCoreApplication::exit(1); } uint features = TopDUContext::VisibleDeclarationsAndContexts; - if(m_args->isSet("features")) + if(m_args->isSet(QStringLiteral("features"))) { - QString featuresStr = m_args->value("features"); - if(featuresStr == "visible-declarations") + QString featuresStr = m_args->value(QStringLiteral("features")); + if(featuresStr == QLatin1String("visible-declarations")) { features = TopDUContext::VisibleDeclarationsAndContexts; } - else if(featuresStr == "all-declarations") + else if(featuresStr == QLatin1String("all-declarations")) { features = TopDUContext::AllDeclarationsAndContexts; } - else if(featuresStr == "all-declarations-and-uses") + else if(featuresStr == QLatin1String("all-declarations-and-uses")) { features = TopDUContext::AllDeclarationsContextsAndUses; } - else if(featuresStr == "all-declarations-and-uses-and-AST") + else if(featuresStr == QLatin1String("all-declarations-and-uses-and-AST")) { features = TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::AST; } - else if(featuresStr == "empty") + else if(featuresStr == QLatin1String("empty")) { features = TopDUContext::Empty; } - else if(featuresStr == "simplified-visible-declarations") + else if(featuresStr == QLatin1String("simplified-visible-declarations")) { features = TopDUContext::SimplifiedVisibleDeclarationsAndContexts; } else{ std::cerr << "Wrong feature-string given\n"; QCoreApplication::exit(2); return; } } - if(m_args->isSet("force-update")) + if(m_args->isSet(QStringLiteral("force-update"))) features |= TopDUContext::ForceUpdate; - if(m_args->isSet("force-update-recursive")) + if(m_args->isSet(QStringLiteral("force-update-recursive"))) features |= TopDUContext::ForceUpdateRecursive; - if(m_args->isSet("threads")) + if(m_args->isSet(QStringLiteral("threads"))) { bool ok = false; - int count = m_args->value("threads").toInt(&ok); + int count = m_args->value(QStringLiteral("threads")).toInt(&ok); ICore::self()->languageController()->backgroundParser()->setThreadCount(count); if(!ok) { std::cerr << "bad thread count\n"; QCoreApplication::exit(3); return; } } // quit when everything is done // background parser emits hideProgress() signal in two situations: // when everything is done and when bgparser is suspended // later doesn't happen in duchain, so just rely on hideProgress() // and quit when it's emitted connect(ICore::self()->languageController()->backgroundParser(), &BackgroundParser::hideProgress, this, &Manager::finish); foreach (const auto& file, m_args->positionalArguments()) { addToBackgroundParser(file, (TopDUContext::Features)features); } m_allFilesAdded = 1; if ( m_total ) { std::cerr << "Added " << m_total << " files to the background parser" << std::endl; const int threads = ICore::self()->languageController()->backgroundParser()->threadCount(); std::cerr << "parsing with " << threads << " threads" << std::endl; ICore::self()->languageController()->backgroundParser()->parseDocuments(); } else { std::cerr << "no files added to the background parser" << std::endl; QCoreApplication::exit(0); return; } } void Manager::updateReady(IndexedString url, ReferencedTopDUContext topContext) { qDebug() << "finished" << url.toUrl().toLocalFile() << "success: " << (bool)topContext; m_waiting.remove(url.toUrl()); std::cerr << "processed " << (m_total - m_waiting.size()) << " out of " << m_total << "\n"; dump(topContext); } void Manager::dump(const ReferencedTopDUContext& topContext) { if (!topContext) { return; } QTextStream stream(stdout); std::cerr << "\n"; - if (m_args->isSet("dump-definitions")) { + if (m_args->isSet(QStringLiteral("dump-definitions"))) { DUChainReadLocker lock; std::cerr << "Definitions:" << std::endl; DUChain::definitions()->dump(stream); std::cerr << std::endl; } - if (m_args->isSet("dump-symboltable")) { + if (m_args->isSet(QStringLiteral("dump-symboltable"))) { DUChainReadLocker lock; std::cerr << "PersistentSymbolTable:" << std::endl; PersistentSymbolTable::self().dump(stream); std::cerr << std::endl; } DUChainDumper::Features features; - if (m_args->isSet("dump-context")) { + if (m_args->isSet(QStringLiteral("dump-context"))) { features |= DUChainDumper::DumpContext; } - if (m_args->isSet("dump-errors")) { + if (m_args->isSet(QStringLiteral("dump-errors"))) { features |= DUChainDumper::DumpProblems; } - if (auto depth = m_args->value("dump-depth").toInt()) { + if (auto depth = m_args->value(QStringLiteral("dump-depth")).toInt()) { DUChainReadLocker lock; std::cerr << "Context:" << std::endl; DUChainDumper dumpChain(features); dumpChain.dump(topContext, depth); } - if (m_args->isSet("dump-graph")) { + if (m_args->isSet(QStringLiteral("dump-graph"))) { DUChainReadLocker lock; DumpDotGraph dumpGraph; const QString dotOutput = dumpGraph.dotGraph(topContext); std::cout << qPrintable(dotOutput) << std::endl; } - if (m_args->isSet("dump-imported-errors")) { + if (m_args->isSet(QStringLiteral("dump-imported-errors"))) { DUChainReadLocker lock; foreach(const auto& import, topContext->importedParentContexts()) { auto top = dynamic_cast(import.indexedContext().context()); if (top && top != topContext && !top->problems().isEmpty()) { DUChainDumper dumpChain(DUChainDumper::DumpProblems); dumpChain.dump(top, 0); } } } } void Manager::addToBackgroundParser(QString path, TopDUContext::Features features) { QFileInfo info(path); if(info.isFile()) { qDebug() << "adding file" << path; QUrl pathUrl = QUrl::fromLocalFile(info.canonicalFilePath()); m_waiting << pathUrl; ++m_total; KDevelop::DUChain::self()->updateContextForUrl(KDevelop::IndexedString(pathUrl), features, this); }else if(info.isDir()) { QDirIterator contents(path); while(contents.hasNext()) { QString newPath = contents.next(); if(!newPath.endsWith('.')) addToBackgroundParser(newPath, features); } } } QSet< QUrl > Manager::waiting() { return m_waiting; } void Manager::finish() { std::cerr << "ready" << std::endl; QApplication::quit(); } using namespace KDevelop; int main(int argc, char** argv) { - KAboutData aboutData( "duchainify", i18n( "duchainify" ), - "1", i18n("DUChain builder application"), KAboutLicense::GPL, - i18n( "(c) 2009 David Nolden" ), QString(), "http://www.kdevelop.org" ); + KAboutData aboutData( QStringLiteral("duchainify"), i18n( "duchainify" ), + QStringLiteral("1"), i18n("DUChain builder application"), KAboutLicense::GPL, + i18n( "(c) 2009 David Nolden" ), QString(), QStringLiteral("http://www.kdevelop.org") ); QApplication app(argc, argv); KAboutData::setApplicationData(aboutData); QCommandLineParser parser; aboutData.setupCommandLine(&parser); parser.addVersionOption(); parser.addHelpOption(); - parser.addPositionalArgument("paths", i18n("file or directory"), "[PATH...]"); + parser.addPositionalArgument(QStringLiteral("paths"), i18n("file or directory"), QStringLiteral("[PATH...]")); - parser.addOption(QCommandLineOption{QStringList{"w", "warnings"}, i18n("Show warnings")}); - parser.addOption(QCommandLineOption{QStringList{"V", "verbose"}, i18n("Show warnings and debug output")}); - parser.addOption(QCommandLineOption{QStringList{"u", "force-update"}, i18n("Enforce an update of the top-contexts corresponding to the given files")}); - parser.addOption(QCommandLineOption{QStringList{"r", "force-update-recursive"}, i18n("Enforce an update of the top-contexts corresponding to the given files and all included files")}); - parser.addOption(QCommandLineOption{QStringList{"t", "threads"}, i18n("Number of threads to use"), "count"}); - parser.addOption(QCommandLineOption{QStringList{"f", "features"}, i18n("Features to build. Options: empty, simplified-visible-declarations, visible-declarations (default), all-declarations, all-declarations-and-uses, all-declarations-and-uses-and-AST"), "features"}); + parser.addOption(QCommandLineOption{QStringList{QStringLiteral("w"), QStringLiteral("warnings")}, i18n("Show warnings")}); + parser.addOption(QCommandLineOption{QStringList{QStringLiteral("V"), QStringLiteral("verbose")}, i18n("Show warnings and debug output")}); + parser.addOption(QCommandLineOption{QStringList{QStringLiteral("u"), QStringLiteral("force-update")}, i18n("Enforce an update of the top-contexts corresponding to the given files")}); + parser.addOption(QCommandLineOption{QStringList{QStringLiteral("r"), QStringLiteral("force-update-recursive")}, i18n("Enforce an update of the top-contexts corresponding to the given files and all included files")}); + parser.addOption(QCommandLineOption{QStringList{QStringLiteral("t"), QStringLiteral("threads")}, i18n("Number of threads to use"), QStringLiteral("count")}); + parser.addOption(QCommandLineOption{QStringList{QStringLiteral("f"), QStringLiteral("features")}, i18n("Features to build. Options: empty, simplified-visible-declarations, visible-declarations (default), all-declarations, all-declarations-and-uses, all-declarations-and-uses-and-AST"), QStringLiteral("features")}); - parser.addOption(QCommandLineOption{QStringList{"dump-context"}, i18n("Print complete Definition-Use Chain on successful parse")}); - parser.addOption(QCommandLineOption{QStringList{"dump-definitions"}, i18n("Print complete DUChain Definitions repository on successful parse")}); - parser.addOption(QCommandLineOption{QStringList{"dump-symboltable"}, i18n("Print complete DUChain PersistentSymbolTable repository on successful parse")}); - parser.addOption(QCommandLineOption{QStringList{"dump-depth"}, i18n("Number defining the maximum depth where declaration details are printed"), "depth"}); - parser.addOption(QCommandLineOption{QStringList{"dump-graph"}, i18n("Dump DUChain graph (in .dot format)")}); - parser.addOption(QCommandLineOption{QStringList{"d", "dump-errors"}, i18n("Print problems encountered during parsing")}); - parser.addOption(QCommandLineOption{QStringList{"dump-imported-errors"}, i18n("Recursively dump errors from imported contexts.")}); + parser.addOption(QCommandLineOption{QStringList{QStringLiteral("dump-context")}, i18n("Print complete Definition-Use Chain on successful parse")}); + parser.addOption(QCommandLineOption{QStringList{QStringLiteral("dump-definitions")}, i18n("Print complete DUChain Definitions repository on successful parse")}); + parser.addOption(QCommandLineOption{QStringList{QStringLiteral("dump-symboltable")}, i18n("Print complete DUChain PersistentSymbolTable repository on successful parse")}); + parser.addOption(QCommandLineOption{QStringList{QStringLiteral("dump-depth")}, i18n("Number defining the maximum depth where declaration details are printed"), QStringLiteral("depth")}); + parser.addOption(QCommandLineOption{QStringList{QStringLiteral("dump-graph")}, i18n("Dump DUChain graph (in .dot format)")}); + parser.addOption(QCommandLineOption{QStringList{QStringLiteral("d"), QStringLiteral("dump-errors")}, i18n("Print problems encountered during parsing")}); + parser.addOption(QCommandLineOption{QStringList{QStringLiteral("dump-imported-errors")}, i18n("Recursively dump errors from imported contexts.")}); parser.process(app); aboutData.processCommandLine(&parser); - verbose = parser.isSet("verbose"); - warnings = parser.isSet("warnings"); + verbose = parser.isSet(QStringLiteral("verbose")); + warnings = parser.isSet(QStringLiteral("warnings")); qInstallMessageHandler(messageOutput); AutoTestShell::init(); - TestCore::initialize(Core::NoUi, "duchainify"); + TestCore::initialize(Core::NoUi, QStringLiteral("duchainify")); Manager manager(&parser); QTimer::singleShot(0, &manager, SLOT(init())); int ret = app.exec(); TestCore::shutdown(); return ret; } diff --git a/util/environmentgrouplist.cpp b/util/environmentgrouplist.cpp index e2e100b78..9aa9ace43 100644 --- a/util/environmentgrouplist.cpp +++ b/util/environmentgrouplist.cpp @@ -1,223 +1,223 @@ /* This file is part of KDevelop 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 "environmentgrouplist.h" #include #include #include #include #include #include namespace KDevelop { class EnvironmentGroupListPrivate { public: QMap > m_groups; QString m_defaultGroup; }; } using namespace KDevelop; namespace { namespace Strings { inline QString defaultEnvGroupKey() { return QStringLiteral("Default Environment Group"); } inline QString envGroup() { return QStringLiteral("Environment Settings"); } inline QString groupListKey() { return QStringLiteral("Group List"); } inline QString defaultGroup() { return QStringLiteral("default"); } } void decode( KConfig* config, EnvironmentGroupListPrivate* d ) { KConfigGroup cfg( config, Strings::envGroup() ); d->m_defaultGroup = cfg.readEntry( Strings::defaultEnvGroupKey(), Strings::defaultGroup() ); QStringList grouplist = cfg.readEntry( Strings::groupListKey(), QStringList{Strings::defaultGroup()} ); foreach( const QString &envgrpname, grouplist ) { KConfigGroup envgrp( &cfg, envgrpname ); QMap variables; foreach( const QString &varname, envgrp.keyList() ) { variables[varname] = envgrp.readEntry( varname, QString() ); } d->m_groups.insert( envgrpname, variables ); } } void encode( KConfig* config, EnvironmentGroupListPrivate* d ) { KConfigGroup cfg( config, Strings::envGroup() ); cfg.writeEntry( Strings::defaultEnvGroupKey(), d->m_defaultGroup ); cfg.writeEntry( Strings::groupListKey(), d->m_groups.keys() ); foreach( const QString &group, cfg.groupList() ) { if( !d->m_groups.keys().contains( group ) ) { cfg.deleteGroup( group ); } } foreach( const QString &group, d->m_groups.keys() ) { KConfigGroup envgrp( &cfg, group ); envgrp.deleteGroup(); foreach( const QString &var, d->m_groups[group].keys() ) { envgrp.writeEntry( var, d->m_groups[group][var] ); } } cfg.sync(); } } EnvironmentGroupList::EnvironmentGroupList( const EnvironmentGroupList& rhs ) : d( new EnvironmentGroupListPrivate( *rhs.d ) ) { } EnvironmentGroupList& EnvironmentGroupList::operator=( const EnvironmentGroupList& rhs ) { *d = *rhs.d; return *this; } EnvironmentGroupList::EnvironmentGroupList( KSharedConfigPtr config ) : d( new EnvironmentGroupListPrivate ) { decode( config.data(), d ); } EnvironmentGroupList::EnvironmentGroupList( KConfig* config ) : d( new EnvironmentGroupListPrivate ) { decode( config, d ); } EnvironmentGroupList::~EnvironmentGroupList() { delete d; } const QMap EnvironmentGroupList::variables( const QString& group ) const { return d->m_groups[group.isEmpty() ? d->m_defaultGroup : group]; } QMap& EnvironmentGroupList::variables( const QString& group ) { return d->m_groups[group.isEmpty() ? d->m_defaultGroup : group]; } QString EnvironmentGroupList::defaultGroup() const { return d->m_defaultGroup; } void EnvironmentGroupList::setDefaultGroup( const QString& group ) { if( group.isEmpty() ) { return; } if( d->m_groups.contains( group ) ) { d->m_defaultGroup = group; } } void EnvironmentGroupList::saveSettings( KConfig* config ) const { encode( config, d ); config->sync(); } void EnvironmentGroupList::loadSettings( KConfig* config ) { d->m_groups.clear(); decode( config, d ); } QStringList EnvironmentGroupList::groups() const { return d->m_groups.keys(); } void EnvironmentGroupList::removeGroup( const QString& group ) { d->m_groups.remove( group ); } EnvironmentGroupList::EnvironmentGroupList() : d( new EnvironmentGroupListPrivate ) { } QStringList EnvironmentGroupList::createEnvironment( const QString & group, const QStringList & defaultEnvironment ) const { QMap retMap; foreach( const QString &line, defaultEnvironment ) { QString varName = line.section( '=', 0, 0 ); QString varValue = line.section( '=', 1 ); retMap.insert( varName, varValue ); } if( !group.isEmpty() ) { QMap userMap = variables(group); for( QMap::const_iterator it = userMap.constBegin(); it != userMap.constEnd(); ++it ) { retMap.insert( it.key(), it.value() ); } } QStringList env; for( QMap::const_iterator it = retMap.constBegin(); it != retMap.constEnd(); ++it ) { env << it.key() + '=' + it.value(); } return env; } void KDevelop::expandVariables(QMap& variables, const QProcessEnvironment& environment) { - QRegularExpression rVar("(? This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_FORMATTINGHELPERS_H #define KDEVPLATFORM_FORMATTINGHELPERS_H #include "utilexport.h" #include class QString; namespace KDevelop { /** * Helps extracting a re-formatted version of a text fragment, within a specific left and right context. * The re-formatting must be an operation which only changes whitespace, and keeps whitespace boundaries * between identifiers intact. If this is not the case, the original text is returned. * * @param formattedMergedText The re-formatted merged text: format(leftContext + text + rightContext) * @param originalMergedText The original merged text: (leftContext + text + rightContext) * @param text The text fragment of which the re-formatted version will be returned * @param leftContext The left context of the text fragment * @param rightContext The right context of the text fragment * @param tabWidth The width of one tab, required while matching tabs vs. spaces * @param fuzzyCharacters Characters which are ignored in case of mismatches * * @return The re-formatted version of @p text * */ -KDEVPLATFORMUTIL_EXPORT QString extractFormattedTextFromContext(const QString& formattedMergedText, const QString& text, const QString& leftContext, const QString& rightContext, int tabWidth = 4, const QString& fuzzyCharacters = "{}()/*/"); +KDEVPLATFORMUTIL_EXPORT QString extractFormattedTextFromContext(const QString& formattedMergedText, const QString& text, const QString& leftContext, const QString& rightContext, int tabWidth = 4, const QString& fuzzyCharacters = QStringLiteral("{}()/*/")); } #endif // KDEVPLATFORM_FORMATTINGHELPERS_H diff --git a/util/kdevstringhandler.cpp b/util/kdevstringhandler.cpp index fbb5f6401..fffa36b12 100644 --- a/util/kdevstringhandler.cpp +++ b/util/kdevstringhandler.cpp @@ -1,231 +1,231 @@ /* 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. This file mostly code takes from Qt's QSettings class, the copyright header from that file follows: **************************************************************************** ** ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of the QtCore module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial Usage ** Licensees holding valid Qt Commercial licenses may use this file in ** accordance with the Qt Commercial License Agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and Nokia. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain ** additional rights. These rights are described in the Nokia Qt LGPL ** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this ** package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3.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 the GNU General Public License version 3.0 requirements will be ** met: http://www.gnu.org/copyleft/gpl.html. ** ** If you are unsure which license is appropriate for your use, please ** contact the sales department at http://www.qtsoftware.com/contact. ** $QT_END_LICENSE$ ** **************************************************************************** */ #include "kdevstringhandler.h" #include #include #include #include #include #include #include namespace KDevelop { QString joinWithEscaping( const QStringList& input, const QChar& joinchar, const QChar& escapechar ) { QStringList tmp = input; return tmp.replaceInStrings( joinchar, QString( joinchar ) + QString( escapechar ) ).join( joinchar ); } QStringList splitWithEscaping( const QString& input, const QChar& splitchar, const QChar& escapechar ) { enum State { Normal, SeenEscape } state; state = Normal; QStringList result; QString currentstring; for( int i = 0; i < input.size(); i++ ) { switch( state ) { case Normal: if( input[i] == escapechar ) { state = SeenEscape; } else if( input[i] == splitchar ) { result << currentstring; - currentstring = ""; + currentstring.clear(); } else { currentstring += input[i]; } break; case SeenEscape: currentstring += input[i]; state = Normal; break; } } if( !currentstring.isEmpty() ) { result << currentstring; } return result; } QVariant stringToQVariant(const QString& s) { // Taken from qsettings.cpp, stringToVariant() if (s.startsWith(QLatin1Char('@'))) { if (s.endsWith(QLatin1Char(')'))) { if (s.startsWith(QLatin1String("@Variant("))) { QByteArray a(s.toLatin1().mid(9)); QDataStream stream(&a, QIODevice::ReadOnly); stream.setVersion(QDataStream::Qt_4_4); QVariant result; stream >> result; return result; } } } return QVariant(); } QString qvariantToString(const QVariant& variant) { // Taken from qsettings.cpp, variantToString() QByteArray a; { QDataStream s(&a, QIODevice::WriteOnly); s.setVersion(QDataStream::Qt_4_4); s << variant; } - QString result = QLatin1String("@Variant("); + QString result = QStringLiteral("@Variant("); result += QString::fromLatin1(a.constData(), a.size()); result += QLatin1Char(')'); return result; } QString htmlToPlainText(const QString& s, HtmlToPlainTextMode mode) { switch (mode) { case FastMode: { QString result(s); result.remove(QRegExp("<[^>]+>")); return result; } case CompleteMode: { QTextDocument doc; doc.setHtml(s); return doc.toPlainText(); } } return QString(); // never reached } } QString KDevelop::stripAnsiSequences(const QString& str) { if (str.isEmpty()) { return QString(); // fast path } enum { PLAIN, ANSI_START, ANSI_CSI, ANSI_SEQUENCE, ANSI_WAITING_FOR_ST, ANSI_ST_STARTED } state = PLAIN; QString result; result.reserve(str.count()); foreach (const QChar c, str) { const auto val = c.unicode(); switch (state) { case PLAIN: if (val == 27) // 'ESC' state = ANSI_START; else if (val == 155) // equivalent to 'ESC'-'[' state = ANSI_CSI; else result.append(c); break; case ANSI_START: if (val == 91) // [ state = ANSI_CSI; else if (val == 80 || val == 93 || val == 94 || val == 95) // 'P', ']', '^' and '_' state = ANSI_WAITING_FOR_ST; else if (val >= 64 && val <= 95) state = PLAIN; else state = ANSI_SEQUENCE; break; case ANSI_CSI: if (val >= 64 && val <= 126) // Anything between '@' and '~' state = PLAIN; break; case ANSI_SEQUENCE: if (val >= 64 && val <= 95) // Anything between '@' and '_' state = PLAIN; break; case ANSI_WAITING_FOR_ST: if (val == 7) // 'BEL' state = PLAIN; else if (val == 27) // 'ESC' state = ANSI_ST_STARTED; break; case ANSI_ST_STARTED: if (val == 92) // '\' state = PLAIN; else state = ANSI_WAITING_FOR_ST; break; } } return result; } diff --git a/util/path.cpp b/util/path.cpp index b646fef9a..53650f438 100644 --- a/util/path.cpp +++ b/util/path.cpp @@ -1,518 +1,518 @@ /* * This file is part of KDevelop * Copyright 2012 Milian Wolff * Copyright 2015 Kevin Funk * * 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 "path.h" #include #include #include #include using namespace KDevelop; namespace { inline bool isWindowsDriveLetter(const QString& segment) { #ifdef Q_OS_WIN return segment.size() == 2 && segment.at(0).isLetter() && segment.at(1) == ':'; #else Q_UNUSED(segment); return false; #endif } inline bool isAbsolutePath(const QString& path) { if (path.startsWith('/')) { return true; // Even on Windows: Potentially a path of a remote URL } #ifdef Q_OS_WIN return path.size() >= 2 && path.at(0).isLetter() && path.at(1) == ':'; #else return false; #endif } } QString KDevelop::toUrlOrLocalFile(const QUrl& url, QUrl::FormattingOptions options) { const auto str = url.toString(options | QUrl::PreferLocalFile); #ifdef Q_OS_WIN // potentially strip leading slash if (url.isLocalFile() && !str.isEmpty() && str[0] == '/') { return str.mid(1); // expensive copying, but we'd like toString(...) to properly format everything first } #endif return str; } Path::Path() { } Path::Path(const QString& pathOrUrl) #if QT_VERSION >= 0x050400 : Path(QUrl::fromUserInput(pathOrUrl, QString(), QUrl::DefaultResolution)) #else : Path(QUrl::fromUserInput(pathOrUrl)) #endif { } Path::Path(const QUrl& url) { if (!url.isValid()) { // empty or invalid Path return; } // we do not support urls with: // - fragments // - sub urls // - query // nor do we support relative urls if (url.hasFragment() || url.hasQuery() || url.isRelative() || url.path().isEmpty()) { // invalid qWarning("Path::init: invalid/unsupported Path encountered: \"%s\"", qPrintable(url.toDisplayString(QUrl::PreferLocalFile))); return; } if (!url.isLocalFile()) { // handle remote urls QString urlPrefix; urlPrefix += url.scheme(); - urlPrefix += "://"; + urlPrefix += QLatin1String("://"); const QString user = url.userName(); if (!user.isEmpty()) { urlPrefix += user; urlPrefix += '@'; } urlPrefix += url.host(); if (url.port() != -1) { urlPrefix += ':' + QString::number(url.port()); } m_data << urlPrefix; } addPath(url.isLocalFile() ? url.toLocalFile() : url.path()); // support for root paths, they are valid but don't really contain any data if (m_data.isEmpty() || (isRemote() && m_data.size() == 1)) { m_data << QString(); } } Path::Path(const Path& other, const QString& child) : m_data(other.m_data) { if (isAbsolutePath(child)) { // absolute path: only share the remote part of @p other m_data.resize(isRemote() ? 1 : 0); } else if (!other.isValid() && !child.isEmpty()) { qWarning("Path::Path: tried to append relative path \"%s\" to invalid base", qPrintable(child)); return; } addPath(child); } static QString generatePathOrUrl(bool onlyPath, bool isLocalFile, const QVector& data) { // more or less a copy of QtPrivate::QStringList_join const int size = data.size(); if (size == 0) { return QString(); } int totalLength = 0; // separators: '/' totalLength += size; // skip Path segment if we only want the path int start = (onlyPath && !isLocalFile) ? 1 : 0; // path and url prefix for (int i = start; i < size; ++i) { totalLength += data.at(i).size(); } // build string representation QString res; res.reserve(totalLength); #ifdef Q_OS_WIN if (start == 0 && isLocalFile) { Q_ASSERT(data.at(0).endsWith(':')); // assume something along "C:" res += data.at(0); start++; } #endif for (int i = start; i < size; ++i) { if (i || isLocalFile) { res += '/'; } res += data.at(i); } return res; } QString Path::pathOrUrl() const { return generatePathOrUrl(false, isLocalFile(), m_data); } QString Path::path() const { return generatePathOrUrl(true, isLocalFile(), m_data); } QString Path::toLocalFile() const { return isLocalFile() ? path() : QString(); } QString Path::relativePath(const Path& path) const { if (!path.isValid()) { return QString(); } if (!isValid() || remotePrefix() != path.remotePrefix()) { // different remote destinations or we are invalid, return input as-is return path.pathOrUrl(); } // while I'd love to use QUrl::relativePath here, it seems to behave pretty // strangely, and adds unexpected "./" at the start for example // so instead, do it on our own based on _relativePath in kurl.cpp // this should also be more performant I think // Find where they meet int level = isRemote() ? 1 : 0; const int maxLevel = qMin(m_data.count(), path.m_data.count()); while(level < maxLevel && m_data.at(level) == path.m_data.at(level)) { ++level; } // Need to go down out of our path to the common branch. // but keep in mind that e.g. '/' paths have an empty name int backwardSegments = m_data.count() - level; if (backwardSegments && level < maxLevel && m_data.at(level).isEmpty()) { --backwardSegments; } // Now up up from the common branch to the second path. int forwardSegmentsLength = 0; for (int i = level; i < path.m_data.count(); ++i) { forwardSegmentsLength += path.m_data.at(i).length(); // slashes if (i + 1 != path.m_data.count()) { forwardSegmentsLength += 1; } } QString relativePath; relativePath.reserve((backwardSegments * 3) + forwardSegmentsLength); for(int i = 0; i < backwardSegments; ++i) { relativePath.append(QLatin1String("../")); } for (int i = level; i < path.m_data.count(); ++i) { relativePath.append(path.m_data.at(i)); if (i + 1 != path.m_data.count()) { relativePath.append(QLatin1Char('/')); } } Q_ASSERT(relativePath.length() == ((backwardSegments * 3) + forwardSegmentsLength)); return relativePath; } static bool isParentPath(const QVector& parent, const QVector& child, bool direct) { if (direct && child.size() != parent.size() + 1) { return false; } else if (!direct && child.size() <= parent.size()) { return false; } for (int i = 0; i < parent.size(); ++i) { if (child.at(i) != parent.at(i)) { // support for trailing '/' if (i + 1 == parent.size() && parent.at(i).isEmpty()) { return true; } // otherwise we take a different branch here return false; } } return true; } bool Path::isParentOf(const Path& path) const { if (!isValid() || !path.isValid() || remotePrefix() != path.remotePrefix()) { return false; } return isParentPath(m_data, path.m_data, false); } bool Path::isDirectParentOf(const Path& path) const { if (!isValid() || !path.isValid() || remotePrefix() != path.remotePrefix()) { return false; } return isParentPath(m_data, path.m_data, true); } QString Path::remotePrefix() const { return isRemote() ? m_data.first() : QString(); } bool Path::operator<(const Path& other) const { const int size = m_data.size(); const int otherSize = other.m_data.size(); const int toCompare = qMin(size, otherSize); // compare each Path segment in turn and try to return early for (int i = 0; i < toCompare; ++i) { int comparison = m_data.at(i).compare(other.m_data.at(i)); if (comparison == 0) { // equal, try next segment continue; } else { // return whether our segment is less then the other one return comparison < 0; } } // when we reach this point, all elements that we compared where equal // thus return whether we have less items than the other Path return size < otherSize; } QUrl Path::toUrl() const { return QUrl::fromUserInput(pathOrUrl()); } bool Path::isLocalFile() const { // if the first data element contains a '/' it is a Path prefix return !m_data.isEmpty() && !m_data.first().contains(QLatin1Char('/')); } bool Path::isRemote() const { return !m_data.isEmpty() && m_data.first().contains(QLatin1Char('/')); } QString Path::lastPathSegment() const { // remote Paths are offset by one, thus never return the first item of them as file name if (m_data.isEmpty() || (!isLocalFile() && m_data.size() == 1)) { return QString(); } return m_data.last(); } void Path::setLastPathSegment(const QString& name) { // remote Paths are offset by one, thus never return the first item of them as file name if (m_data.isEmpty() || (!isLocalFile() && m_data.size() == 1)) { // append the name to empty Paths or remote Paths only containing the Path prefix m_data.append(name); } else { // overwrite the last data member m_data.last() = name; } } static void cleanPath(QVector* data, const bool isRemote) { if (data->isEmpty()) { return; } const int startOffset = isRemote ? 1 : 0; const auto start = data->begin() + startOffset; auto it = start; while(it != data->end()) { if (*it == QLatin1String("..")) { if (it == start) { it = data->erase(it); } else { if (isWindowsDriveLetter(*(it - 1))) { it = data->erase(it); // keep the drive letter } else { it = data->erase(it - 1, it + 1); } } } else if (*it == QLatin1String(".")) { it = data->erase(it); } else { ++it; } } if (data->count() == startOffset) { data->append(QString()); } } // Optimized QString::split code for the specific Path use-case static QVarLengthArray splitPath(const QString &source) { QVarLengthArray list; int start = 0; int end = 0; while ((end = source.indexOf(QLatin1Char('/'), start)) != -1) { if (start != end) { list.append(source.mid(start, end - start)); } start = end + 1; } if (start != source.size()) { list.append(source.mid(start, -1)); } return list; } void Path::addPath(const QString& path) { if (path.isEmpty()) { return; } const auto& newData = splitPath(path); if (newData.isEmpty()) { if (m_data.size() == (isRemote() ? 1 : 0)) { // this represents the root path, we just turned an invalid path into it m_data << QString(); } return; } auto it = newData.begin(); if (!m_data.isEmpty() && m_data.last().isEmpty()) { // the root item is empty, set its contents and continue appending m_data.last() = *it; ++it; } std::copy(it, newData.end(), std::back_inserter(m_data)); cleanPath(&m_data, isRemote()); } Path Path::parent() const { if (m_data.isEmpty()) { return Path(); } Path ret(*this); if (m_data.size() == (1 + (isRemote() ? 1 : 0))) { // keep the root item, but clear it, otherwise we'd make the path invalid // or a URL a local path auto& root = ret.m_data.last(); if (!isWindowsDriveLetter(root)) { root.clear(); } } else { ret.m_data.pop_back(); } return ret; } bool Path::hasParent() const { const int rootIdx = isRemote() ? 1 : 0; return m_data.size() > rootIdx && !m_data[rootIdx].isEmpty(); } void Path::clear() { m_data.clear(); } Path Path::cd(const QString& dir) const { if (!isValid()) { return Path(); } return Path(*this, dir); } namespace KDevelop { uint qHash(const Path& path) { KDevHash hash; foreach (const QString& segment, path.segments()) { hash << qHash(segment); } return hash; } template static Path::List toPathList_impl(const Container& list) { Path::List ret; ret.reserve(list.size()); foreach (const auto& entry, list) { Path path(entry); if (path.isValid()) { ret << path; } } return ret; } Path::List toPathList(const QList& list) { return toPathList_impl(list); } Path::List toPathList(const QList< QString >& list) { return toPathList_impl(list); } } QDebug operator<<(QDebug s, const Path& string) { s.nospace() << string.pathOrUrl(); return s.space(); } namespace QTest { template<> char *toString(const Path &path) { return qstrdup(qPrintable(path.pathOrUrl())); } } diff --git a/util/shellutils.cpp b/util/shellutils.cpp index 34dfeada7..29fbca43f 100644 --- a/util/shellutils.cpp +++ b/util/shellutils.cpp @@ -1,68 +1,68 @@ /* * This file is part of KDevelop * * Copyright 2012 Ivan Shapovalov * * 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 "shellutils.h" #include #include #include #include namespace KDevelop { bool askUser( const QString& mainText, const QString& ttyPrompt, const QString& mboxTitle, const QString& mboxAdditionalText, bool ttyDefaultToYes ) { if( !qobject_cast(qApp) ) { // no ui-mode e.g. for duchainify and other tools QTextStream out( stdout ); out << mainText << endl; QTextStream in( stdin ); QString input; forever { if( ttyDefaultToYes ) { out << QStringLiteral( "%1: [Y/n] " ).arg( ttyPrompt ) << flush; } else { out << QStringLiteral( "%1: [y/N] ").arg( ttyPrompt ) << flush; } input = in.readLine().trimmed(); if( input.isEmpty() ) { return ttyDefaultToYes; - } else if( input.toLower() == "y" ) { + } else if( input.toLower() == QLatin1String("y") ) { return true; - } else if( input.toLower() == "n" ) { + } else if( input.toLower() == QLatin1String("n") ) { return false; } } } else { int userAnswer = KMessageBox::questionYesNo( 0, mainText + "\n\n" + mboxAdditionalText, mboxTitle, KStandardGuiItem::ok(), KStandardGuiItem::cancel() ); return userAnswer == KMessageBox::Yes; } } } diff --git a/util/tests/test_embeddedfreetree.cpp b/util/tests/test_embeddedfreetree.cpp index 08e4a4049..a2d1b73ce 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("test repository"); + static Utils::BasicSetRepository repository(QStringLiteral("test repository")); return &repository; } }; struct UintSetVisitor { std::set& s; 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(NULL)); 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(NULL)); 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_environment.cpp b/util/tests/test_environment.cpp index c20200dae..815d51363 100644 --- a/util/tests/test_environment.cpp +++ b/util/tests/test_environment.cpp @@ -1,81 +1,81 @@ /* * This file is part of KDevelop * * Copyright 2015 Artur Puzio * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "test_environment.h" #include "util/environmentgrouplist.h" #include QTEST_MAIN(TestEnvironment); using ProcEnv = QMap; void TestEnvironment::testExpandVariables_data() { QTest::addColumn("env"); QTest::addColumn("expectedEnv"); QTest::newRow("no variables") << ProcEnv({}) << ProcEnv({}); QTest::newRow("simple variables") << ProcEnv{ {"VAR1","data"}, {"Var2","some other data"} } << ProcEnv({ {"VAR1","data"}, {"Var2","some other data"} }); QTest::newRow("PATH append and prepend") << ProcEnv({ {"PATH","/home/usr/bin:$PATH:/home/user/folder"} }) << ProcEnv({ {"PATH", "/home/usr/bin:/bin:/usr/bin:/home/user/folder"} }); QTest::newRow("\\$VAR") << ProcEnv({ {"MY_VAR","\\$PATH something \\$HOME"} }) << ProcEnv({ {"MY_VAR","$PATH something $HOME"} }); QTest::newRow("spaces, \\$VAR after $VAR") << ProcEnv({ {"MY_VAR","$PATH:$HOME something \\$HOME"} }) << ProcEnv({ {"MY_VAR","/bin:/usr/bin:/home/tom something $HOME"} }); QTest::newRow("VAR2=$VAR1") << ProcEnv({ {"VAR1","/some/path"},{"VAR2","$VAR1"} }) << ProcEnv({ {"VAR1","/some/path"},{"VAR2",""} }); } void TestEnvironment::testExpandVariables() { QFETCH(ProcEnv, env); QFETCH(ProcEnv, expectedEnv); QProcessEnvironment fakeSysEnv; - fakeSysEnv.insert("PATH","/bin:/usr/bin"); - fakeSysEnv.insert("HOME","/home/tom"); + fakeSysEnv.insert(QStringLiteral("PATH"),QStringLiteral("/bin:/usr/bin")); + fakeSysEnv.insert(QStringLiteral("HOME"),QStringLiteral("/home/tom")); KDevelop::expandVariables(env, fakeSysEnv); for (auto it = expectedEnv.cbegin(); it!= expectedEnv.cend(); ++it) { QCOMPARE(env.value(it.key()), it.value()); } } diff --git a/util/tests/test_path.cpp b/util/tests/test_path.cpp index d2a8781e2..05ecb391c 100644 --- a/util/tests/test_path.cpp +++ b/util/tests/test_path.cpp @@ -1,625 +1,625 @@ /* * This file is part of KDevelop * Copyright 2012 Milian Wolff * Copyright 2015 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 "test_path.h" #include #include #include #include #include QTEST_MAIN(TestPath); using namespace KDevelop; static const int FILES_PER_FOLDER = 10; static const int FOLDERS_PER_FOLDER = 5; static const int TREE_DEPTH = 5; template T stringToUrl(const QString& path) { return T(path); } template<> QStringList stringToUrl(const QString& path) { return path.split('/'); } template T childUrl(const T& parent, const QString& child) { return T(parent, child); } template<> QStringList childUrl(const QStringList& parent, const QString& child) { QStringList ret = parent; ret << child; return ret; } template<> QUrl childUrl(const QUrl& parent, const QString& child) { QUrl ret = parent; ret.setPath(ret.path() + '/' + child); return ret; } template QVector generateData(const T& parent, int level) { QVector ret; // files per folder for (int i = 0; i < FILES_PER_FOLDER; ++i) { const QString fileName = QStringLiteral("file%1.txt").arg(i); const T file = childUrl(parent, fileName); Q_ASSERT(!ret.contains(file)); ret << file; } // nesting depth if (level < TREE_DEPTH) { // folders per folder for (int i = 0; i < FOLDERS_PER_FOLDER; ++i) { const QString folderName = QStringLiteral("folder%1").arg(i); const T folder = childUrl(parent, folderName); Q_ASSERT(!ret.contains(folder)); ret << folder; ret += generateData(folder, level + 1); } } return ret; } template void runBenchmark() { QBENCHMARK { const T base = stringToUrl("/tmp/foo/bar"); generateData(base, 0); } } void TestPath::initTestCase() { // TODO: is this really needed? It doesn't seem like any kdevelop shell is needed AutoTestShell::init(); TestCore::initialize(Core::NoUi); } void TestPath::cleanupTestCase() { TestCore::shutdown(); } void TestPath::bench_qurl() { runBenchmark(); } void TestPath::bench_qstringlist() { runBenchmark(); } void TestPath::bench_path() { runBenchmark(); } void TestPath::bench_fromLocalPath() { QFETCH(int, variant); - const QString input("/foo/bar/asdf/bla/blub.h"); + const QString input(QStringLiteral("/foo/bar/asdf/bla/blub.h")); const int repeat = 1000; if (variant == 1) { QBENCHMARK { for(int i = 0; i < repeat; ++i) { Path path = Path(QUrl::fromLocalFile(input)); Q_UNUSED(path); } } } else if (variant == 2) { QBENCHMARK { for(int i = 0; i < repeat; ++i) { Path path = Path(input); Q_UNUSED(path); } } } else { QFAIL("unexpected variant"); } } void TestPath::bench_fromLocalPath_data() { QTest::addColumn("variant"); QTest::newRow("QUrl::fromLocalFile") << 1; QTest::newRow("QString") << 2; } void TestPath::bench_hash() { - const Path path("/my/very/long/path/to/a/file.cpp"); + const Path path(QStringLiteral("/my/very/long/path/to/a/file.cpp")); QBENCHMARK { auto hash = qHash(path); Q_UNUSED(hash); } } /// Invoke @p op on URL @p base, but preserve drive letter if @p op removes it template QUrl preserveWindowsDriveLetter(const QUrl& base, Func op) { #ifndef Q_OS_WIN return op(base); #else // only apply to local files if (!base.isLocalFile()) { return op(base); } // save drive letter const QString windowsDriveLetter = base.toLocalFile().mid(0, 2); QUrl url = op(base); // restore drive letter if (url.toLocalFile().startsWith('/')) { url = QUrl::fromLocalFile(windowsDriveLetter + url.toLocalFile()); } return url; #endif } QUrl resolvedUrl(const QUrl& base, const QUrl& relative) { return preserveWindowsDriveLetter(base, [&](const QUrl& url) { return url.resolved(relative); }); } QUrl comparableUpUrl(const QUrl& url) { QUrl ret = preserveWindowsDriveLetter(url, [&](const QUrl& url) { return KIO::upUrl(url).adjusted(QUrl::RemovePassword); }); return ret.adjusted(QUrl::StripTrailingSlash); } void TestPath::testPath() { QFETCH(QString, input); QUrl url = QUrl::fromUserInput(input); url = url.adjusted(QUrl::StripTrailingSlash | QUrl::NormalizePathSegments); Path optUrl(input); if (!url.password().isEmpty()) { QUrl urlNoPass = url.adjusted(QUrl::RemovePassword); QCOMPARE(optUrl.toUrl(), urlNoPass); } else { QCOMPARE(optUrl.toUrl(), url); } QCOMPARE(optUrl.isLocalFile(), url.isLocalFile()); QCOMPARE(optUrl.pathOrUrl(), toUrlOrLocalFile(url, QUrl::RemovePassword)); QCOMPARE(optUrl.isValid(), url.isValid()); QCOMPARE(optUrl.isEmpty(), url.isEmpty()); QCOMPARE(optUrl.lastPathSegment(), url.fileName()); QCOMPARE(optUrl.path(), url.isLocalFile() ? url.toLocalFile() : url.path()); QCOMPARE(optUrl.parent().toUrl(), comparableUpUrl(url)); QCOMPARE(optUrl.toLocalFile(), url.toLocalFile()); QCOMPARE(optUrl, Path(input)); QCOMPARE(optUrl, Path(optUrl)); QVERIFY(optUrl != Path(input + "/asdf")); - if (url.isLocalFile() && !input.startsWith("file://")) { + if (url.isLocalFile() && !input.startsWith(QLatin1String("file://"))) { QCOMPARE(optUrl, Path(QUrl::fromLocalFile(input))); } QCOMPARE(optUrl, Path(url)); if (url.isValid()) { QVERIFY(optUrl.relativePath(optUrl).isEmpty()); - Path relativePath(optUrl, "foo/bar"); + Path relativePath(optUrl, QStringLiteral("foo/bar")); QCOMPARE(optUrl.relativePath(relativePath), QLatin1String("foo/bar")); QCOMPARE(relativePath.relativePath(optUrl), QLatin1String("../../")); QVERIFY(optUrl.isParentOf(relativePath)); QVERIFY(!relativePath.isParentOf(optUrl)); #ifndef Q_OS_WIN - Path absolutePath(optUrl, "/laa/loo"); + Path absolutePath(optUrl, QStringLiteral("/laa/loo")); QCOMPARE(absolutePath.path(), QLatin1String("/laa/loo")); - QCOMPARE(url.resolved(QUrl("/laa/loo")).path(), QLatin1String("/laa/loo")); + QCOMPARE(url.resolved(QUrl(QStringLiteral("/laa/loo"))).path(), QLatin1String("/laa/loo")); - Path absolutePath2(optUrl, "/"); + Path absolutePath2(optUrl, QStringLiteral("/")); QCOMPARE(absolutePath2.path(), QLatin1String("/")); - QCOMPARE(url.resolved(QUrl("/")).path(), QLatin1String("/")); + QCOMPARE(url.resolved(QUrl(QStringLiteral("/"))).path(), QLatin1String("/")); #endif - Path unrelatedPath("https://test@blubasdf.com:12345/"); + Path unrelatedPath(QStringLiteral("https://test@blubasdf.com:12345/")); QCOMPARE(optUrl.relativePath(unrelatedPath), unrelatedPath.pathOrUrl()); QCOMPARE(unrelatedPath.relativePath(optUrl), optUrl.pathOrUrl()); QVERIFY(!unrelatedPath.isParentOf(optUrl)); QVERIFY(!optUrl.isParentOf(unrelatedPath)); } QCOMPARE(Path().relativePath(optUrl), optUrl.pathOrUrl()); QVERIFY(optUrl.relativePath(Path()).isEmpty()); QVERIFY(Path().relativePath(Path()).isEmpty()); QVERIFY(!optUrl.isParentOf(Path())); QVERIFY(!Path().isParentOf(optUrl)); QVERIFY(!Path().isParentOf(Path())); QVERIFY(!optUrl.isParentOf(optUrl)); QCOMPARE(optUrl.isRemote(), optUrl.isValid() && !optUrl.isLocalFile()); QCOMPARE(optUrl.isRemote(), optUrl.isValid() && !optUrl.remotePrefix().isEmpty()); url.setPath(url.path() + "/test/foo/bar"); if (url.scheme().isEmpty()) { - url.setScheme("file"); + url.setScheme(QStringLiteral("file")); } - optUrl.addPath("test/foo/bar"); + optUrl.addPath(QStringLiteral("test/foo/bar")); QCOMPARE(optUrl.lastPathSegment(), url.fileName()); QCOMPARE(optUrl.path(), url.isLocalFile() ? url.toLocalFile() : url.path()); url = url.adjusted(QUrl::RemoveFilename); url.setPath(url.path() + "lalalala_adsf.txt"); - optUrl.setLastPathSegment("lalalala_adsf.txt"); + optUrl.setLastPathSegment(QStringLiteral("lalalala_adsf.txt")); QCOMPARE(optUrl.lastPathSegment(), url.fileName()); QCOMPARE(optUrl.path(), url.isLocalFile() ? url.toLocalFile() : url.path()); QCOMPARE(optUrl.parent().toUrl(), comparableUpUrl(url)); QVERIFY(optUrl.parent().isDirectParentOf(optUrl)); QVERIFY(!optUrl.parent().parent().isDirectParentOf(optUrl)); #ifndef Q_OS_WIN - Path a("/foo/bar/asdf/"); - Path b("/foo/bar/"); + Path a(QStringLiteral("/foo/bar/asdf/")); + Path b(QStringLiteral("/foo/bar/")); QVERIFY(b.isDirectParentOf(a)); - Path c("/foo/bar"); + Path c(QStringLiteral("/foo/bar")); QVERIFY(c.isDirectParentOf(a)); #endif optUrl.clear(); url.clear(); QCOMPARE(optUrl.toUrl(), url); } void TestPath::testPath_data() { QTest::addColumn("input"); #ifndef Q_OS_WIN QTest::newRow("invalid") << ""; QTest::newRow("path") << "/tmp/foo/asdf.txt"; QTest::newRow("path-folder") << "/tmp/foo/asdf/"; QTest::newRow("root") << "/"; QTest::newRow("clean-path") << "/tmp/..///asdf/"; QTest::newRow("file") << "file:///tmp/foo/asdf.txt"; QTest::newRow("file-folder") << "file:///tmp/foo/bar/"; #else QTest::newRow("path") << "C:/tmp/foo/asdf.txt"; QTest::newRow("path-folder") << "C:/tmp/foo/asdf/"; QTest::newRow("root") << "C:/"; QTest::newRow("clean-path") << "C:/tmp/..///asdf/"; QTest::newRow("file") << "file:///C:/tmp/foo/asdf.txt"; QTest::newRow("file-folder") << "file:///C:/tmp/foo/bar/"; #endif QTest::newRow("remote-root") << "http://www.test.com/"; QTest::newRow("http") << "http://www.test.com/tmp/asdf.txt"; QTest::newRow("ftps") << "ftps://user@host.com/tmp/foo/asdf.txt"; QTest::newRow("password") << "ftps://user:password@host.com/tmp/asdf.txt"; QTest::newRow("port") << "http://localhost:8080/foo/bar/test.txt"; } void TestPath::testPathInvalid() { QFETCH(QString, input); Path url(input); QVERIFY(!url.isValid()); QVERIFY(url.isEmpty()); } void TestPath::testPathInvalid_data() { QTest::addColumn("input"); QTest::newRow("empty") << ""; QTest::newRow("fragment") << "http://test.com/#hello"; QTest::newRow("query") << "http://test.com/?hello"; QTest::newRow("suburl") << "file:///home/weis/kde.tgz#gzip:/#tar:/kdebase"; QTest::newRow("relative") << "../foo/bar"; QTest::newRow("name") << "asdfasdf"; QTest::newRow("remote-nopath") << "http://www.test.com"; } void TestPath::testPathOperators() { QFETCH(Path, left); QFETCH(Path, right); QFETCH(bool, equal); QFETCH(bool, less); bool greater = !equal && !less; QVERIFY(left == left); QVERIFY(right == right); QCOMPARE(left == right, equal); QCOMPARE(right == left, equal); QCOMPARE(left < right, less); QCOMPARE(left <= right, less || equal); QCOMPARE(left > right, greater); QCOMPARE(left >= right, greater || equal); QCOMPARE(right < left, greater); QCOMPARE(right <= left, greater || equal); QCOMPARE(right > left, less); QCOMPARE(right >= left, less || equal); } void TestPath::testPathOperators_data() { QTest::addColumn("left"); QTest::addColumn("right"); QTest::addColumn("equal"); QTest::addColumn("less"); - Path a("/tmp/a"); - Path b("/tmp/b"); - Path c("/tmp/ac"); - Path d("/d"); - Path e("/tmp"); - Path f("/tmp/"); + Path a(QStringLiteral("/tmp/a")); + Path b(QStringLiteral("/tmp/b")); + Path c(QStringLiteral("/tmp/ac")); + Path d(QStringLiteral("/d")); + Path e(QStringLiteral("/tmp")); + Path f(QStringLiteral("/tmp/")); Path invalid; QTest::newRow("a-b") << a << b << false << true; QTest::newRow("a-copy") << a << Path(a) << true << false; QTest::newRow("c-a") << c << a << false << false; QTest::newRow("c-invalid") << c << invalid << false << false; QTest::newRow("c-d") << c << d << false << false; QTest::newRow("e-f") << e << f << true << false; } void TestPath::testPathAddData() { QFETCH(QString, pathToAdd); const QStringList bases = { #ifndef Q_OS_WIN - "/", - "/foo/bar/asdf/", - "file:///foo/bar/asdf/", + QStringLiteral("/"), + QStringLiteral("/foo/bar/asdf/"), + QStringLiteral("file:///foo/bar/asdf/"), #else "C:/", "C:/foo/bar/asdf/", "file:///C:/foo/bar/asdf/", #endif - "http://www.asdf.com/foo/bar/asdf/", + QStringLiteral("http://www.asdf.com/foo/bar/asdf/"), }; foreach(const QString& base, bases) { QUrl baseUrl = QUrl::fromUserInput(base); if (QDir::isRelativePath(pathToAdd)) { baseUrl = resolvedUrl(baseUrl, QUrl(pathToAdd)); } else { baseUrl.setPath(baseUrl.path() + pathToAdd); } baseUrl = baseUrl.adjusted(QUrl::NormalizePathSegments); // QUrl::StripTrailingSlash converts file:/// to file: which is not what we want if (baseUrl.path() != QLatin1String("/")) { baseUrl = baseUrl.adjusted(QUrl::StripTrailingSlash); } Path basePath(base); basePath.addPath(pathToAdd); QCOMPARE(basePath.pathOrUrl(), toUrlOrLocalFile(baseUrl)); QCOMPARE(basePath.toUrl(), baseUrl); } } void TestPath::testPathAddData_data() { QTest::addColumn("pathToAdd"); const QStringList paths = QStringList() - << "file.txt" - << "path/file.txt" - << "path//file.txt" - << "/absolute" - << "../" - << ".." - << "../../../" - << "./foo" - << "../relative" - << "../../relative" - << "../foo/../bar" - << "../foo/./bar" - << "../../../../../../../invalid"; + << QStringLiteral("file.txt") + << QStringLiteral("path/file.txt") + << QStringLiteral("path//file.txt") + << QStringLiteral("/absolute") + << QStringLiteral("../") + << QStringLiteral("..") + << QStringLiteral("../../../") + << QStringLiteral("./foo") + << QStringLiteral("../relative") + << QStringLiteral("../../relative") + << QStringLiteral("../foo/../bar") + << QStringLiteral("../foo/./bar") + << QStringLiteral("../../../../../../../invalid"); foreach(const QString &path, paths) { QTest::newRow(qstrdup(path.toUtf8().constData())) << path; } } void TestPath::testPathBaseCtor() { QFETCH(QString, base); QFETCH(QString, subPath); QFETCH(QString, expected); const Path basePath(base); const Path path(basePath, subPath); QCOMPARE(path.pathOrUrl(), expected); } void TestPath::testPathBaseCtor_data() { QTest::addColumn("base"); QTest::addColumn("subPath"); QTest::addColumn("expected"); QTest::newRow("empty") << "" << "" << ""; QTest::newRow("empty-relative") << "" << "foo" << ""; #ifndef Q_OS_WIN QTest::newRow("root-empty") << "/" << "" << "/"; QTest::newRow("root-root") << "/" << "/" << "/"; QTest::newRow("root-relative") << "/" << "bar" << "/bar"; QTest::newRow("root-relative-dirty") << "/" << "bar//foo/a/.." << "/bar/foo"; QTest::newRow("path-relative") << "/foo/bar" << "bar/foo" << "/foo/bar/bar/foo"; QTest::newRow("path-absolute") << "/foo/bar" << "/bar/foo" << "/bar/foo"; #else QTest::newRow("root-empty") << "C:/" << "" << "C:"; QTest::newRow("root1-empty") << "C:" << "" << "C:"; QTest::newRow("root-root") << "C:/" << "C:/" << "C:"; QTest::newRow("root-relative") << "C:/" << "bar" << "C:/bar"; QTest::newRow("root1-relative") << "C:" << "bar" << "C:/bar"; QTest::newRow("root-relative-dirty") << "C:/" << "bar//foo/a/.." << "C:/bar/foo"; QTest::newRow("path-relative") << "C:/foo/bar" << "bar/foo" << "C:/foo/bar/bar/foo"; QTest::newRow("path-absolute") << "C:/foo/bar" << "C:/bar/foo" << "C:/bar/foo"; #endif QTest::newRow("remote-path-absolute") << "http://foo.com/foo/bar" << "/bar/foo" << "http://foo.com/bar/foo"; QTest::newRow("remote-path-relative") << "http://foo.com/foo/bar" << "bar/foo" << "http://foo.com/foo/bar/bar/foo"; } // there is no cd() in QUrl, emulate what KUrl did static bool cdQUrl(QUrl& url, const QString& path) { if (path.isEmpty() || !url.isValid()) { return false; } // have to append slash otherwise last segment is treated as a file name and not a directory if (!url.path().endsWith('/')) { url.setPath(url.path() + '/'); } url = url.resolved(QUrl(path)).adjusted(QUrl::RemoveFragment | QUrl::RemoveQuery); return true; } void TestPath::testPathCd() { QFETCH(QString, base); QFETCH(QString, change); Path path = base.isEmpty() ? Path() : Path(base); QUrl url = QUrl::fromUserInput(base); Path changed = path.cd(change); if (cdQUrl(url, change)) { QVERIFY(changed.isValid()); } url = url.adjusted(QUrl::NormalizePathSegments); QCOMPARE(changed.pathOrUrl(), toUrlOrLocalFile(url, QUrl::StripTrailingSlash)); } void TestPath::testPathCd_data() { QTest::addColumn("base"); QTest::addColumn("change"); const QVector bases{ - "", + QLatin1String(""), #ifndef Q_OS_WIN - "/foo", "/foo/bar/asdf", + QStringLiteral("/foo"), QStringLiteral("/foo/bar/asdf"), #else "C:/foo", "C:/foo/bar/asdf", #endif - "http://foo.com/", "http://foo.com/foo", "http://foo.com/foo/bar/asdf" + QStringLiteral("http://foo.com/"), QStringLiteral("http://foo.com/foo"), QStringLiteral("http://foo.com/foo/bar/asdf") }; foreach (const QString& base, bases) { QTest::newRow(qstrdup(qPrintable(base + "-"))) << base << ""; QTest::newRow(qstrdup(qPrintable(base + "-.."))) << base << ".."; QTest::newRow(qstrdup(qPrintable(base + "-../"))) << base << "../"; QTest::newRow(qstrdup(qPrintable(base + "v../foo"))) << base << "../foo"; QTest::newRow(qstrdup(qPrintable(base + "-."))) << base << "."; QTest::newRow(qstrdup(qPrintable(base + "-./"))) << base << "./"; QTest::newRow(qstrdup(qPrintable(base + "-./foo"))) << base << "./foo"; QTest::newRow(qstrdup(qPrintable(base + "-./foo/bar"))) << base << "./foo/bar"; QTest::newRow(qstrdup(qPrintable(base + "-foo/.."))) << base << "foo/.."; QTest::newRow(qstrdup(qPrintable(base + "-foo/"))) << base << "foo/"; QTest::newRow(qstrdup(qPrintable(base + "-foo/../bar"))) << base << "foo/../bar"; #ifdef Q_OS_WIN if (!base.startsWith("C:/") ) { // only add next rows for remote URLs on Windows #endif QTest::newRow(qstrdup(qPrintable(base + "-/foo"))) << base << "/foo"; QTest::newRow(qstrdup(qPrintable(base + "-/foo/../bar"))) << base << "/foo/../bar"; #ifdef Q_OS_WIN } #endif } } void TestPath::testHasParent_data() { QTest::addColumn("input"); QTest::addColumn("hasParent"); QTest::newRow("empty") << QString() << false; QTest::newRow("/") << QStringLiteral("/") << false; QTest::newRow("/foo") << QStringLiteral("/foo") << true; QTest::newRow("/foo/bar") << QStringLiteral("/foo/bar") << true; QTest::newRow("//foo/bar") << QStringLiteral("//foo/bar") << true; QTest::newRow("http://foo.bar") << QStringLiteral("http://foo.bar") << false; QTest::newRow("http://foo.bar/") << QStringLiteral("http://foo.bar/") << false; QTest::newRow("http://foo.bar/asdf") << QStringLiteral("http://foo.bar/asdf") << true; QTest::newRow("http://foo.bar/asdf/asdf") << QStringLiteral("http://foo.bar/asdf/asdf") << true; } void TestPath::testHasParent() { QFETCH(QString, input); Path path(input); QTEST(path.hasParent(), "hasParent"); } void TestPath::QUrl_acceptance() { - const QUrl baseLocal = QUrl("file:///foo.h"); + const QUrl baseLocal = QUrl(QStringLiteral("file:///foo.h")); QCOMPARE(baseLocal.isValid(), true); QCOMPARE(baseLocal.isRelative(), false); - QCOMPARE(baseLocal, QUrl::fromLocalFile("/foo.h")); - QCOMPARE(baseLocal, QUrl::fromUserInput("/foo.h")); + QCOMPARE(baseLocal, QUrl::fromLocalFile(QStringLiteral("/foo.h"))); + QCOMPARE(baseLocal, QUrl::fromUserInput(QStringLiteral("/foo.h"))); - QUrl relative("bar.h"); + QUrl relative(QStringLiteral("bar.h")); QCOMPARE(relative.isRelative(), true); - QCOMPARE(baseLocal.resolved(relative), QUrl("file:///bar.h")); - QUrl relative2("/foo/bar.h"); + QCOMPARE(baseLocal.resolved(relative), QUrl(QStringLiteral("file:///bar.h"))); + QUrl relative2(QStringLiteral("/foo/bar.h")); QCOMPARE(relative2.isRelative(), true); - QCOMPARE(baseLocal.resolved(relative2), QUrl("file:///foo/bar.h")); + QCOMPARE(baseLocal.resolved(relative2), QUrl(QStringLiteral("file:///foo/bar.h"))); - const QUrl baseRemote = QUrl("http://foo.org/asdf/foo/asdf"); - QCOMPARE(baseRemote.resolved(relative), QUrl("http://foo.org/asdf/foo/bar.h")); + const QUrl baseRemote = QUrl(QStringLiteral("http://foo.org/asdf/foo/asdf")); + QCOMPARE(baseRemote.resolved(relative), QUrl(QStringLiteral("http://foo.org/asdf/foo/bar.h"))); } diff --git a/vcs/dvcs/dvcsjob.cpp b/vcs/dvcs/dvcsjob.cpp index fb456bbfe..a45d87ce3 100644 --- a/vcs/dvcs/dvcsjob.cpp +++ b/vcs/dvcs/dvcsjob.cpp @@ -1,333 +1,333 @@ /*************************************************************************** * This file was partly taken from KDevelop's cvs plugin * * Copyright 2002-2003 Christian Loose * * Copyright 2007 Robert Gruber * * * * Adapted for DVCS * * Copyright 2008 Evgeniy Ivanov * * Copyright Aleix Pol Gonzalez * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "dvcsjob.h" #include "../debug.h" #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; struct DVcsJobPrivate { DVcsJobPrivate() : childproc(new KProcess), vcsplugin(0), ignoreError(false) {} ~DVcsJobPrivate() { delete childproc; } KProcess* childproc; VcsJob::JobStatus status; QByteArray output; QByteArray errorOutput; IPlugin* vcsplugin; QVariant results; OutputModel* model; bool ignoreError; }; DVcsJob::DVcsJob(const QDir& workingDir, IPlugin* parent, OutputJob::OutputJobVerbosity verbosity) : VcsJob(parent, verbosity), d(new DVcsJobPrivate) { Q_ASSERT(workingDir.exists()); d->status = JobNotStarted; d->vcsplugin = parent; d->childproc->setWorkingDirectory(workingDir.absolutePath()); d->model = new OutputModel; d->ignoreError = false; setModel(d->model); setCapabilities(Killable); connect(d->childproc, static_cast(&KProcess::finished), this, &DVcsJob::slotProcessExited); connect(d->childproc, static_cast(&KProcess::error), this, &DVcsJob::slotProcessError); connect(d->childproc, &KProcess::readyReadStandardOutput, this, &DVcsJob::slotReceivedStdout); } DVcsJob::~DVcsJob() { delete d; } QDir DVcsJob::directory() const { return QDir(d->childproc->workingDirectory()); } DVcsJob& DVcsJob::operator<<(const QString& arg) { *d->childproc << arg; return *this; } DVcsJob& DVcsJob::operator<<(const char* arg) { *d->childproc << arg; return *this; } DVcsJob& DVcsJob::operator<<(const QStringList& args) { *d->childproc << args; return *this; } QStringList DVcsJob::dvcsCommand() const { return d->childproc->program(); } QString DVcsJob::output() const { QByteArray stdoutbuf = rawOutput(); int endpos = stdoutbuf.size(); if (d->status==JobRunning) { // We may have received only part of a code-point. apol: ASSERT? endpos = stdoutbuf.lastIndexOf('\n')+1; // Include the final newline or become 0, when there is no newline } return QString::fromLocal8Bit(stdoutbuf, endpos); } QByteArray DVcsJob::rawOutput() const { return d->output; } QByteArray DVcsJob::errorOutput() const { return d->errorOutput; } void DVcsJob::setIgnoreError(bool ignore) { d->ignoreError = ignore; } void DVcsJob::setResults(const QVariant &res) { d->results = res; } QVariant DVcsJob::fetchResults() { return d->results; } void DVcsJob::start() { Q_ASSERT_X(d->status != JobRunning, "DVCSjob::start", "Another proccess was started using this job class"); const QDir& workingdir = directory(); if( !workingdir.exists() ) { QString error = i18n( "Working Directory does not exist: %1", d->childproc->workingDirectory() ); d->model->appendLine(error); setError( 255 ); setErrorText(error); d->status = JobFailed; emitResult(); return; } if( !workingdir.isAbsolute() ) { QString error = i18n( "Working Directory is not absolute: %1", d->childproc->workingDirectory() ); d->model->appendLine(error); setError( 255 ); setErrorText(error); d->status = JobFailed; emitResult(); return; } QString commandDisplay = KShell::joinArgs(dvcsCommand()); qCDebug(VCS) << "Execute dvcs command:" << commandDisplay; QString service; if(d->vcsplugin) service = d->vcsplugin->objectName(); setObjectName(service+": "+commandDisplay); d->status = JobRunning; d->childproc->setOutputChannelMode(KProcess::SeparateChannels); //the started() and error() signals may be delayed! It causes crash with deferred deletion!!! d->childproc->start(); d->model->appendLine(directory().path() + "> " + commandDisplay); } void DVcsJob::setCommunicationMode(KProcess::OutputChannelMode comm) { d->childproc->setOutputChannelMode(comm); } void DVcsJob::cancel() { d->childproc->kill(); } void DVcsJob::slotProcessError( QProcess::ProcessError err ) { d->status = JobFailed; setError(OutputJob::FailedShownError); //we don't want to trigger a message box d->errorOutput = d->childproc->readAllStandardError(); QString displayCommand = KShell::joinArgs(dvcsCommand()); QString completeErrorText = i18n("Process '%1' exited with status %2\n%3", displayCommand, d->childproc->exitCode(), QString::fromLocal8Bit(d->errorOutput) ); setErrorText( completeErrorText ); QString errorValue; //if trolls add Q_ENUMS for QProcess, then we can use better solution than switch: - //QMetaObject::indexOfEnumerator(char*), QLatin1String(QMetaEnum::valueToKey())... + //QMetaObject::indexOfEnumerator(char*), QQStringLiteral(QMetaEnum::valueToKey())... switch (err) { case QProcess::FailedToStart: - errorValue = "FailedToStart"; + errorValue = QStringLiteral("FailedToStart"); break; case QProcess::Crashed: - errorValue = "Crashed"; + errorValue = QStringLiteral("Crashed"); break; case QProcess::Timedout: - errorValue = "Timedout"; + errorValue = QStringLiteral("Timedout"); break; case QProcess::WriteError: - errorValue = "WriteError"; + errorValue = QStringLiteral("WriteError"); break; case QProcess::ReadError: - errorValue = "ReadError"; + errorValue = QStringLiteral("ReadError"); break; case QProcess::UnknownError: - errorValue = "UnknownError"; + errorValue = QStringLiteral("UnknownError"); break; } qCDebug(VCS) << "Found an error while running" << displayCommand << ":" << errorValue << "Exit code is:" << d->childproc->exitCode(); qCDebug(VCS) << "Error:" << completeErrorText; displayOutput(QString::fromLocal8Bit(d->errorOutput)); d->model->appendLine(i18n("Command finished with error %1.", errorValue)); if(verbosity()==Silent) { setVerbosity(Verbose); startOutput(); } emitResult(); } void DVcsJob::slotProcessExited(int exitCode, QProcess::ExitStatus exitStatus) { d->status = JobSucceeded; d->model->appendLine(i18n("Command exited with value %1.", exitCode)); if (exitStatus == QProcess::CrashExit) slotProcessError(QProcess::Crashed); else if (exitCode != 0 && !d->ignoreError) slotProcessError(QProcess::UnknownError); else jobIsReady(); } void DVcsJob::displayOutput(const QString& data) { d->model->appendLines(data.split('\n')); } void DVcsJob::slotReceivedStdout() { QByteArray output = d->childproc->readAllStandardOutput(); // accumulate output d->output.append(output); displayOutput(QString::fromLocal8Bit(output)); } VcsJob::JobStatus DVcsJob::status() const { return d->status; } IPlugin* DVcsJob::vcsPlugin() const { return d->vcsplugin; } DVcsJob& DVcsJob::operator<<(const QUrl& url) { *d->childproc << url.toLocalFile(); return *this; } DVcsJob& DVcsJob::operator<<(const QList< QUrl >& urls) { foreach(const QUrl &url, urls) operator<<(url); return *this; } bool DVcsJob::doKill() { if (d->childproc->state() == QProcess::NotRunning) { return true; } static const int terminateKillTimeout = 1000; // ms d->childproc->terminate(); bool terminated = d->childproc->waitForFinished( terminateKillTimeout ); if( !terminated ) { d->childproc->kill(); terminated = d->childproc->waitForFinished( terminateKillTimeout ); } return terminated; } void DVcsJob::jobIsReady() { emit readyForParsing(this); //let parsers to set status emitResult(); //KJob emit resultsReady(this); //VcsJob } KProcess* DVcsJob::process() {return d->childproc;} diff --git a/vcs/dvcs/tests/test_dvcsjob.cpp b/vcs/dvcs/tests/test_dvcsjob.cpp index 2b199586b..0c24829b4 100644 --- a/vcs/dvcs/tests/test_dvcsjob.cpp +++ b/vcs/dvcs/tests/test_dvcsjob.cpp @@ -1,59 +1,59 @@ /*************************************************************************** * Copyright 2008 Evgeniy Ivanov * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "test_dvcsjob.h" #include #include #include #include using namespace KDevelop; void TestDVcsJob::initTestCase() { AutoTestShell::init(); TestCore::initialize(Core::NoUi); } void TestDVcsJob::cleanupTestCase() { TestCore::shutdown(); } void TestDVcsJob::testJob() { KDevelop::DVcsJob* job = new KDevelop::DVcsJob(QDir::temp()); QVERIFY(job); QVERIFY(job->status() == KDevelop::VcsJob::JobNotStarted); //try the command like "echo -n test" //should fail, because command and arg are in one string. We can change opearator<<(QString) to split, //but it will be a wrong style to work with jobs. - const QString echoCommand("echo -n test"); + const QString echoCommand(QStringLiteral("echo -n test")); *job << echoCommand; QVERIFY(!job->exec()); QVERIFY(job->status() == KDevelop::VcsJob::JobFailed); - QCOMPARE(job->dvcsCommand().join(";;"), echoCommand); + QCOMPARE(job->dvcsCommand().join(QStringLiteral(";;")), echoCommand); } QTEST_MAIN(TestDVcsJob) diff --git a/vcs/models/brancheslistmodel.cpp b/vcs/models/brancheslistmodel.cpp index c78d29a6f..980e0255b 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) : QStandardItem(name) { setEditable(true); setCurrent(current); } void setCurrent(bool current) { setData(current, BranchesListModel::CurrentRole); - setIcon(QIcon::fromTheme( current ? "arrow-right" : "")); + 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(0, KMessageBox::Sorry, i18n("Branch \"%1\" already exists.", newBranch)); return; } int ret = KMessageBox::messageBox(0, 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/tests/test_models.cpp b/vcs/models/tests/test_models.cpp index d3f7111ce..5fde7bd10 100644 --- a/vcs/models/tests/test_models.cpp +++ b/vcs/models/tests/test_models.cpp @@ -1,134 +1,134 @@ /*************************************************************************** * Copyright 2011 Andrey Batyiev * * * * 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_models.h" #include #include #include #include using namespace KDevelop; void TestModels::initTestCase() { AutoTestShell::init(); TestCore::initialize(); } void TestModels::cleanupTestCase() { TestCore::shutdown(); } void TestModels::testVcsFileChangesModel() { VcsFileChangesModel *model = new VcsFileChangesModel(this); // Newly created model should be empty QVERIFY(model->rowCount() == 0); // Pull some files into QUrl filenames[] = { - QUrl::fromLocalFile("foo"), - QUrl::fromLocalFile("bar"), - QUrl::fromLocalFile("pew"), - QUrl::fromLocalFile("trash") + QUrl::fromLocalFile(QStringLiteral("foo")), + QUrl::fromLocalFile(QStringLiteral("bar")), + QUrl::fromLocalFile(QStringLiteral("pew")), + QUrl::fromLocalFile(QStringLiteral("trash")) }; VcsStatusInfo::State states[] = {VcsStatusInfo::ItemAdded, VcsStatusInfo::ItemModified, VcsStatusInfo::ItemDeleted, VcsStatusInfo::ItemUpToDate}; VcsStatusInfo status; for(int i = 0; i < 3; i++) { status.setUrl(filenames[i]); status.setState(states[i]); model->updateState(status); QCOMPARE(model->rowCount(), (i+1)); } // Pulling up-to-date file doesn't change anything { status.setUrl(filenames[3]); status.setState(states[3]); model->updateState(status); QCOMPARE(model->rowCount(), 3); } // Check that all OK for(int i = 0; i < 3; i++) { QModelIndex idx = model->indexForUrl(filenames[i]); QVERIFY(idx.isValid()); VcsStatusInfo info = model->statusInfo(idx); QVERIFY(info.url().isValid()); QCOMPARE(info.url(), filenames[i]); QCOMPARE(info.state(), states[i]); } // Pull it all again = nothing changed for(int i = 0; i < 3; i++) { status.setUrl(filenames[i]); status.setState(states[i]); model->updateState(status); QCOMPARE(model->rowCount(), 3); } // Check that all OK for(int i = 0; i < 3; i++) { QModelIndex item = model->indexForUrl(filenames[i]); QVERIFY(item.isValid()); VcsStatusInfo info = model->statusInfo(item); QCOMPARE(info.url(), filenames[i]); QCOMPARE(info.state(), states[i]); } // Remove one file { states[1] = VcsStatusInfo::ItemUpToDate; status.setUrl(filenames[1]); status.setState(states[1]); model->updateState(status); QCOMPARE(model->rowCount(), 2); } // Check them all for(int i = 0; i < 3; i++) { if(states[i] != VcsStatusInfo::ItemUpToDate && states[i] != VcsStatusInfo::ItemUnknown) { QModelIndex item = model->indexForUrl(filenames[i]); QVERIFY(item.isValid()); VcsStatusInfo info = model->statusInfo(item); QCOMPARE(info.url(), filenames[i]); QCOMPARE(info.state(), states[i]); } } // Delete them all model->removeRows(0, model->rowCount()); QCOMPARE(model->rowCount(), 0); // Pull it all again for(int i = 0; i < 3; i++) { status.setUrl(filenames[i]); status.setState(states[i]); model->updateState(status); } QCOMPARE(model->rowCount(), 2); } QTEST_MAIN(TestModels); diff --git a/vcs/vcsrevision.cpp b/vcs/vcsrevision.cpp index e06abe506..887d5db37 100644 --- a/vcs/vcsrevision.cpp +++ b/vcs/vcsrevision.cpp @@ -1,183 +1,183 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2007 Andreas Pakulat * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "vcsrevision.h" #include #include #include #include namespace KDevelop { VcsRevision VcsRevision::createSpecialRevision( KDevelop::VcsRevision::RevisionSpecialType _type ) { VcsRevision rev; rev.setRevisionValue( QVariant::fromValue( _type ), VcsRevision::Special ); return rev; } class VcsRevisionPrivate { public: QVariant value; VcsRevision::RevisionType type; QMap internalValues; }; VcsRevision::VcsRevision() : d(new VcsRevisionPrivate) { d->type = VcsRevision::Invalid; } VcsRevision::VcsRevision( const VcsRevision& rhs ) : d(new VcsRevisionPrivate) { d->value = rhs.d->value; d->internalValues = rhs.d->internalValues; d->type = rhs.d->type; } VcsRevision::~VcsRevision() { delete d; } VcsRevision& VcsRevision::operator=( const VcsRevision& rhs) { if(this == &rhs) return *this; d->value = rhs.d->value; d->type = rhs.d->type; d->internalValues = rhs.d->internalValues; return *this; } void VcsRevision::setRevisionValue( const QVariant& rev, VcsRevision::RevisionType type ) { d->value = rev; d->type = type; } VcsRevision::RevisionType VcsRevision::revisionType() const { return d->type; } VcsRevision::RevisionSpecialType VcsRevision::specialType() const { Q_ASSERT(d->type==Special); return d->value.value(); } QVariant VcsRevision::revisionValue() const { return d->value; } QStringList VcsRevision::keys() const { return d->internalValues.keys(); } QVariant VcsRevision::getValue( const QString& key ) const { if( d->internalValues.contains(key) ) { return d->internalValues[key]; } return QVariant(); } void VcsRevision::setValue( const QString& key, const QVariant& value ) { d->internalValues[key] = value; } void VcsRevision::setType( RevisionType t) { d->type = t; } void VcsRevision::setSpecialType( RevisionSpecialType t) { d->value = QVariant(t); } void VcsRevision::setValue( const QVariant& v ) { d->value = v; } bool VcsRevision::operator==( const KDevelop::VcsRevision& rhs ) const { return ( d->type == rhs.d->type && d->value == rhs.d->value && d->internalValues == rhs.d->internalValues ); } QString VcsRevision::prettyValue() const { switch( revisionType() ) { case GlobalNumber: case FileNumber: return (revisionValue().type() == QVariant::String ? revisionValue().toString() : QString::number(revisionValue().toLongLong())); break; case Special: switch( revisionValue().value( ) ) { case VcsRevision::Head: - return "Head"; + return QStringLiteral("Head"); break; case VcsRevision::Base: - return "Base"; + return QStringLiteral("Base"); break; case VcsRevision::Working: - return "Working"; + return QStringLiteral("Working"); break; case VcsRevision::Previous: - return "Previous"; + return QStringLiteral("Previous"); break; case VcsRevision::Start: - return "Start"; + return QStringLiteral("Start"); break; default: - return "User"; + return QStringLiteral("User"); break; } break; case Date: return revisionValue().toDateTime().toString( Qt::LocalDate ); break; default: return revisionValue().toString(); break; } } } uint KDevelop::qHash( const KDevelop::VcsRevision& rev) { return rev.revisionValue().toULongLong(); } diff --git a/vcs/widgets/vcsdiffpatchsources.cpp b/vcs/widgets/vcsdiffpatchsources.cpp index a77bf13ad..a339c8967 100644 --- a/vcs/widgets/vcsdiffpatchsources.cpp +++ b/vcs/widgets/vcsdiffpatchsources.cpp @@ -1,325 +1,318 @@ /* Copyright 2009 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "vcsdiffpatchsources.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "vcsdiff.h" #include "vcsjob.h" #include "../debug.h" using namespace KDevelop; VCSCommitDiffPatchSource::VCSCommitDiffPatchSource(VCSDiffUpdater* updater) : VCSDiffPatchSource(updater), m_vcs(updater->vcs()) { Q_ASSERT(m_vcs); m_commitMessageWidget = new QWidget; QVBoxLayout* layout = new QVBoxLayout(m_commitMessageWidget.data()); m_commitMessageEdit = new KTextEdit; m_commitMessageEdit.data()->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); m_commitMessageEdit.data()->setLineWrapMode(QTextEdit::NoWrap); m_vcs->setupCommitMessageEditor(updater->url(), m_commitMessageEdit.data()); QHBoxLayout* titleLayout = new QHBoxLayout; titleLayout->addWidget(new QLabel(i18n("Commit Message:"))); m_oldMessages = new KComboBox(m_commitMessageWidget.data()); m_oldMessages->addItem(i18n("Old Messages")); foreach(const QString& message, oldMessages()) m_oldMessages->addItem(message, message); m_oldMessages->setMaximumWidth(200); connect(m_oldMessages, static_cast(&KComboBox::currentIndexChanged), this, &VCSCommitDiffPatchSource::oldMessageChanged); titleLayout->addWidget(m_oldMessages); layout->addLayout(titleLayout); layout->addWidget(m_commitMessageEdit.data()); connect(this, &VCSCommitDiffPatchSource::reviewCancelled, this, &VCSCommitDiffPatchSource::addMessageToHistory); connect(this, &VCSCommitDiffPatchSource::reviewFinished, this, &VCSCommitDiffPatchSource::addMessageToHistory); } QStringList VCSCommitDiffPatchSource::oldMessages() const { KConfigGroup vcsGroup(ICore::self()->activeSession()->config(), "VCS"); return vcsGroup.readEntry("OldCommitMessages", QStringList()); } void VCSCommitDiffPatchSource::addMessageToHistory(const QString& message) { if(ICore::self()->shuttingDown()) return; KConfigGroup vcsGroup(ICore::self()->activeSession()->config(), "VCS"); const int maxMessages = 10; QStringList oldMessages = vcsGroup.readEntry("OldCommitMessages", QStringList()); oldMessages.removeAll(message); oldMessages.push_front(message); oldMessages = oldMessages.mid(0, maxMessages); vcsGroup.writeEntry("OldCommitMessages", oldMessages); } void VCSCommitDiffPatchSource::oldMessageChanged(QString text) { if(m_oldMessages->currentIndex() != 0) { m_oldMessages->setCurrentIndex(0); m_commitMessageEdit.data()->setText(text); } } void VCSCommitDiffPatchSource::jobFinished(KJob *job) { if (!job || job->error() != 0 ) { QString details = job ? job->errorText() : QString(); if (details.isEmpty()) { //errorText may be empty details = i18n("For more detailed information please see the Version Control toolview"); } KMessageBox::detailedError(0, i18n("Unable to commit"), details, i18n("Commit unsuccessful")); } deleteLater(); } VCSDiffPatchSource::VCSDiffPatchSource(VCSDiffUpdater* updater) : m_updater(updater) { update(); KDevelop::IBasicVersionControl* vcs = m_updater->vcs(); QUrl url = m_updater->url(); QScopedPointer statusJob(vcs->status(QList() << url)); QVariant varlist; if( statusJob->exec() && statusJob->status() == VcsJob::JobSucceeded ) { varlist = statusJob->fetchResults(); foreach( const QVariant &var, varlist.toList() ) { VcsStatusInfo info = var.value(); m_infos += info; if(info.state()!=VcsStatusInfo::ItemUpToDate) m_selectable[info.url()] = info.state(); } } else qCDebug(VCS) << "Couldn't get status for urls: " << url; } VCSDiffPatchSource::VCSDiffPatchSource(const KDevelop::VcsDiff& diff) : m_updater(0) { updateFromDiff(diff); } VCSDiffPatchSource::~VCSDiffPatchSource() { QFile::remove(m_file.toLocalFile()); delete m_updater; } QUrl VCSDiffPatchSource::baseDir() const { return m_base; } QUrl VCSDiffPatchSource::file() const { return m_file; } QString VCSDiffPatchSource::name() const { return m_name; } uint VCSDiffPatchSource::depth() const { return m_depth; } void VCSDiffPatchSource::updateFromDiff(VcsDiff vcsdiff) { if(!m_file.isValid()) { QTemporaryFile temp2(QDir::tempPath() + QLatin1String("/kdevelop_XXXXXX.patch")); temp2.setAutoRemove(false); temp2.open(); QTextStream t2(&temp2); t2 << vcsdiff.diff(); qCDebug(VCS) << "filename:" << temp2.fileName(); m_file = QUrl::fromLocalFile(temp2.fileName()); temp2.close(); }else{ QFile file(m_file.path()); file.open(QIODevice::WriteOnly); QTextStream t2(&file); t2 << vcsdiff.diff(); } qCDebug(VCS) << "using file" << m_file << vcsdiff.diff() << "base" << vcsdiff.baseDiff(); - m_name = "VCS Diff"; + m_name = QStringLiteral("VCS Diff"); m_base = vcsdiff.baseDiff(); m_depth = vcsdiff.depth(); emit patchChanged(); } void VCSDiffPatchSource::update() { if(!m_updater) return; updateFromDiff(m_updater->update()); } VCSCommitDiffPatchSource::~VCSCommitDiffPatchSource() { delete m_commitMessageWidget.data(); } bool VCSCommitDiffPatchSource::canSelectFiles() const { return true; } QMap< QUrl, KDevelop::VcsStatusInfo::State> VCSDiffPatchSource::additionalSelectableFiles() const { return m_selectable; } QWidget* VCSCommitDiffPatchSource::customWidget() const { return m_commitMessageWidget.data(); } QString VCSCommitDiffPatchSource::finishReviewCustomText() const { return i18nc("@action:button To make a commit", "Commit"); } bool VCSCommitDiffPatchSource::canCancel() const { return true; } void VCSCommitDiffPatchSource::cancelReview() { QString message; if (m_commitMessageEdit) message = m_commitMessageEdit.data()->toPlainText(); emit reviewCancelled(message); deleteLater(); } bool VCSCommitDiffPatchSource::finishReview(QList< QUrl > selection) { QString message; if (m_commitMessageEdit) message = m_commitMessageEdit.data()->toPlainText(); qCDebug(VCS) << "Finishing with selection" << selection; QString files; foreach(const QUrl& url, selection) files += "
    • "+ICore::self()->projectController()->prettyFileName(url, KDevelop::IProjectController::FormatPlain) + "
    • "; QString text = i18n("Files will be committed:\n
        %1
      \nWith message:\n
      %2
      ", files, message); int res = KMessageBox::warningContinueCancel(0, text, i18n("About to commit to repository"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QStringLiteral("ShouldAskConfirmCommit")); if (res != KMessageBox::Continue) { return false; } emit reviewFinished(message, selection); VcsJob* job = m_vcs->commit(message, selection, KDevelop::IBasicVersionControl::NonRecursive); if (!job) { return false; } connect (job, &VcsJob::finished, this, &VCSCommitDiffPatchSource::jobFinished); ICore::self()->runController()->registerJob(job); return true; } -static KDevelop::IPatchSource::Ptr currentShownDiff; - bool showVcsDiff(IPatchSource* vcsDiff) { KDevelop::IPatchReview* patchReview = ICore::self()->pluginController()->extensionForPlugin(QStringLiteral("org.kdevelop.IPatchReview")); - //Only give one VCS diff at a time to the patch review plugin - delete currentShownDiff; - - currentShownDiff = vcsDiff; - if( patchReview ) { - patchReview->startReview(currentShownDiff); + patchReview->startReview(vcsDiff); return true; } else { qWarning() << "Patch review plugin not found"; return false; } } VcsDiff VCSStandardDiffUpdater::update() const { QScopedPointer diffJob(m_vcs->diff(m_url, KDevelop::VcsRevision::createSpecialRevision(KDevelop::VcsRevision::Base), KDevelop::VcsRevision::createSpecialRevision(KDevelop::VcsRevision::Working))); const bool success = diffJob ? diffJob->exec() : false; if (!success) { KMessageBox::error(0, i18n("Could not create a patch for the current version.")); return {}; } return diffJob->fetchResults().value(); } VCSStandardDiffUpdater::VCSStandardDiffUpdater(IBasicVersionControl* vcs, QUrl url) : m_vcs(vcs), m_url(url) { } VCSStandardDiffUpdater::~VCSStandardDiffUpdater() { } VCSDiffUpdater::~VCSDiffUpdater() { } diff --git a/vcs/widgets/vcsdiffpatchsources.h b/vcs/widgets/vcsdiffpatchsources.h index 866848047..796eb9eb0 100644 --- a/vcs/widgets/vcsdiffpatchsources.h +++ b/vcs/widgets/vcsdiffpatchsources.h @@ -1,133 +1,134 @@ /* 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. */ /** * This is an internal header */ #ifndef KDEVPLATFORM_VCSDIFFPATCHSOURCES_H #define KDEVPLATFORM_VCSDIFFPATCHSOURCES_H #include #include "vcs/vcsstatusinfo.h" #include "vcs/vcsjob.h" #include class KTextEdit; class KComboBox; namespace KDevelop { class VcsCommitDialog; class IBasicVersionControl; class VcsDiff; } class QWidget; class VCSDiffUpdater { public: virtual ~VCSDiffUpdater(); virtual KDevelop::VcsDiff update() const = 0; virtual KDevelop::IBasicVersionControl* vcs() const = 0; virtual QUrl url() const = 0; }; class KDEVPLATFORMVCS_EXPORT VCSStandardDiffUpdater : public VCSDiffUpdater { public: VCSStandardDiffUpdater(KDevelop::IBasicVersionControl* vcs, QUrl url); ~VCSStandardDiffUpdater() override; KDevelop::VcsDiff update() const override; KDevelop::IBasicVersionControl* vcs() const override { return m_vcs; } QUrl url() const override { return m_url; } private: KDevelop::IBasicVersionControl* m_vcs; QUrl m_url; }; class KDEVPLATFORMVCS_EXPORT VCSDiffPatchSource : public KDevelop::IPatchSource { + Q_OBJECT public: /// The ownership of the updater is taken explicit VCSDiffPatchSource(VCSDiffUpdater* updater); explicit VCSDiffPatchSource(const KDevelop::VcsDiff& diff); ~VCSDiffPatchSource() override; QUrl baseDir() const override ; QUrl file() const override ; QString name() const override ; uint depth() const override ; void update() override ; bool isAlreadyApplied() const override { return true; } QMap additionalSelectableFiles() const override ; QUrl m_base, m_file; QString m_name; VCSDiffUpdater* m_updater; QList m_infos; QMap m_selectable; private: void updateFromDiff(KDevelop::VcsDiff diff); uint m_depth = 0; }; class KDEVPLATFORMVCS_EXPORT VCSCommitDiffPatchSource : public VCSDiffPatchSource { Q_OBJECT public: /// The ownership of the updater is taken explicit VCSCommitDiffPatchSource(VCSDiffUpdater* updater); ~VCSCommitDiffPatchSource() override ; QStringList oldMessages() const; bool canSelectFiles() const override ; QWidget* customWidget() const override ; QString finishReviewCustomText() const override ; bool canCancel() const override; void cancelReview() override; bool finishReview(QList< QUrl > selection) override ; QList infos() const { return m_infos; } Q_SIGNALS: void reviewFinished(QString message, QList selection); void reviewCancelled(QString message); public: QPointer m_commitMessageWidget; QPointer m_commitMessageEdit; KDevelop::IBasicVersionControl* m_vcs; KComboBox* m_oldMessages; public slots: void addMessageToHistory(const QString& message); void oldMessageChanged(QString); void jobFinished(KJob*); }; ///Sends the diff to the patch-review plugin. ///Returns whether the diff was shown successfully. bool KDEVPLATFORMVCS_EXPORT showVcsDiff(KDevelop::IPatchSource* vcsDiff); #endif // KDEVPLATFORM_VCSDIFFPATCHSOURCES_H