File indexing completed on 2024-05-05 16:46:51
0001 /* 0002 SPDX-FileCopyrightText: 2007 David Nolden <david.nolden.kdevelop@art-master.de> 0003 0004 SPDX-License-Identifier: LGPL-2.0-only 0005 */ 0006 0007 #include "quickopenmodel.h" 0008 #include "debug.h" 0009 0010 #include <QTreeView> 0011 #include <QTimer> 0012 0013 #include <KTextEditor/CodeCompletionModel> 0014 #include <typeinfo> 0015 #include <utility> 0016 #include <vector> 0017 0018 #include <util/algorithm.h> 0019 0020 #include "expandingtree/expandingtree.h" 0021 #include "projectfilequickopen.h" 0022 #include "duchainitemquickopen.h" 0023 0024 #define QUICKOPEN_USE_ITEM_CACHING 0025 0026 using namespace KDevelop; 0027 0028 QuickOpenModel::QuickOpenModel(QWidget* parent) : ExpandingWidgetModel(parent) 0029 , m_treeView(nullptr) 0030 , m_expandingWidgetHeightIncrease(0) 0031 , m_resetBehindRow(0) 0032 { 0033 m_resetTimer = new QTimer(this); 0034 m_resetTimer->setSingleShot(true); 0035 m_resetTimer->setInterval(0); 0036 connect(m_resetTimer, &QTimer::timeout, this, &QuickOpenModel::resetTimer); 0037 } 0038 0039 void QuickOpenModel::setExpandingWidgetHeightIncrease(int pixels) 0040 { 0041 m_expandingWidgetHeightIncrease = pixels; 0042 } 0043 0044 QStringList QuickOpenModel::allScopes() const 0045 { 0046 QStringList scopes; 0047 for (const ProviderEntry& provider : m_providers) { 0048 for (const QString& scope : provider.scopes) { 0049 if (!scopes.contains(scope)) { 0050 scopes << scope; 0051 } 0052 } 0053 } 0054 0055 return scopes; 0056 } 0057 0058 QStringList QuickOpenModel::allTypes() const 0059 { 0060 QSet<QString> types; 0061 for (const ProviderEntry& provider : m_providers) { 0062 types += provider.types; 0063 } 0064 0065 return types.values(); 0066 } 0067 0068 void QuickOpenModel::registerProvider(const QStringList& scopes, const QStringList& types, KDevelop::QuickOpenDataProviderBase* provider) 0069 { 0070 ProviderEntry e; 0071 e.scopes = QSet<QString>(scopes.begin(), scopes.end()); 0072 e.types = QSet<QString>(types.begin(), types.end()); 0073 e.provider = provider; 0074 0075 m_providers << e; //.insert( types, e ); 0076 0077 connect(provider, &QuickOpenDataProviderBase::destroyed, this, &QuickOpenModel::destroyed); 0078 0079 restart(true); 0080 } 0081 0082 bool QuickOpenModel::removeProvider(KDevelop::QuickOpenDataProviderBase* provider) 0083 { 0084 bool ret = false; 0085 for (ProviderList::iterator it = m_providers.begin(); it != m_providers.end(); ++it) { 0086 if ((*it).provider == provider) { 0087 m_providers.erase(it); 0088 disconnect(provider, &QuickOpenDataProviderBase::destroyed, this, &QuickOpenModel::destroyed); 0089 ret = true; 0090 break; 0091 } 0092 } 0093 0094 restart(true); 0095 0096 return ret; 0097 } 0098 0099 void QuickOpenModel::enableProviders(const QStringList& _items, const QStringList& _scopes) 0100 { 0101 const QSet<QString> items(_items.begin(), _items.end()); 0102 const QSet<QString> scopes(_scopes.begin(), _scopes.end()); 0103 if (m_enabledItems == items && m_enabledScopes == scopes && !items.isEmpty() && !scopes.isEmpty()) { 0104 return; 0105 } 0106 m_enabledItems = items; 0107 m_enabledScopes = scopes; 0108 qCDebug(PLUGIN_QUICKOPEN) << "params " << items << " " << scopes; 0109 0110 //We use 2 iterations here: In the first iteration, all providers that implement QuickOpenFileSetInterface are initialized, then the other ones. 0111 //The reason is that the second group can refer to the first one. 0112 for (auto& provider : m_providers) { 0113 if (!qobject_cast<QuickOpenFileSetInterface*>(provider.provider)) { 0114 continue; 0115 } 0116 qCDebug(PLUGIN_QUICKOPEN) << "comparing" << provider.scopes << provider.types; 0117 if ((scopes.isEmpty() || !(scopes & provider.scopes).isEmpty()) && (!(items & provider.types).isEmpty() || items.isEmpty())) { 0118 qCDebug(PLUGIN_QUICKOPEN) << "enabling " << provider.types << " " << provider.scopes; 0119 provider.enabled = true; 0120 provider.provider->enableData(_items, _scopes); 0121 } else { 0122 qCDebug(PLUGIN_QUICKOPEN) << "disabling " << provider.types << " " << provider.scopes; 0123 provider.enabled = false; 0124 if ((scopes.isEmpty() || !(scopes & provider.scopes).isEmpty())) { 0125 provider.provider->enableData(_items, _scopes); //The provider may still provide files 0126 } 0127 } 0128 } 0129 0130 for (auto & provider : m_providers) { 0131 if (qobject_cast<QuickOpenFileSetInterface*>(provider.provider)) { 0132 continue; 0133 } 0134 qCDebug(PLUGIN_QUICKOPEN) << "comparing" << provider.scopes << provider.types; 0135 if ((scopes.isEmpty() || !(scopes & provider.scopes).isEmpty()) && (!(items & provider.types).isEmpty() || items.isEmpty())) { 0136 qCDebug(PLUGIN_QUICKOPEN) << "enabling " << provider.types << " " << provider.scopes; 0137 provider.enabled = true; 0138 provider.provider->enableData(_items, _scopes); 0139 } else { 0140 qCDebug(PLUGIN_QUICKOPEN) << "disabling " << provider.types << " " << provider.scopes; 0141 provider.enabled = false; 0142 } 0143 } 0144 0145 restart(true); 0146 } 0147 0148 void QuickOpenModel::textChanged(const QString& str) 0149 { 0150 if (m_filterText == str) { 0151 return; 0152 } 0153 0154 beginResetModel(); 0155 0156 m_filterText = str; 0157 for (const ProviderEntry& provider : qAsConst(m_providers)) { 0158 if (provider.enabled) { 0159 provider.provider->setFilterText(str); 0160 } 0161 } 0162 0163 m_cachedData.clear(); 0164 clearExpanding(); 0165 0166 //Get the 50 first items, so the data-providers notice changes without ui-glitches due to resetting 0167 for (int a = 0; a < 50 && a < rowCount(QModelIndex()); ++a) { 0168 getItem(a, true); 0169 } 0170 0171 endResetModel(); 0172 } 0173 0174 void QuickOpenModel::restart(bool keepFilterText) 0175 { 0176 // make sure we do not restart recursively which could lead to 0177 // recursive loading of provider plugins e.g. (happened for the cpp plugin) 0178 QMetaObject::invokeMethod(this, "restart_internal", Qt::QueuedConnection, 0179 Q_ARG(bool, keepFilterText)); 0180 } 0181 0182 void QuickOpenModel::restart_internal(bool keepFilterText) 0183 { 0184 if (!keepFilterText) { 0185 m_filterText.clear(); 0186 } 0187 0188 bool anyEnabled = std::any_of(m_providers.constBegin(), m_providers.constEnd(), [](const ProviderEntry& e) { 0189 return e.enabled; 0190 }); 0191 0192 if (!anyEnabled) { 0193 return; 0194 } 0195 0196 for (const ProviderEntry& provider : qAsConst(m_providers)) { 0197 if (!qobject_cast<QuickOpenFileSetInterface*>(provider.provider)) { 0198 continue; 0199 } 0200 0201 ///Always reset providers that implement QuickOpenFileSetInterface and have some matching scopes, because they may be needed by other providers. 0202 if (m_enabledScopes.isEmpty() || !(m_enabledScopes & provider.scopes).isEmpty()) { 0203 provider.provider->reset(); 0204 } 0205 } 0206 0207 for (const ProviderEntry& provider : qAsConst(m_providers)) { 0208 if (qobject_cast<QuickOpenFileSetInterface*>(provider.provider)) { 0209 continue; 0210 } 0211 0212 if (provider.enabled && provider.provider) { 0213 provider.provider->reset(); 0214 } 0215 } 0216 0217 if (keepFilterText) { 0218 textChanged(m_filterText); 0219 } else { 0220 beginResetModel(); 0221 m_cachedData.clear(); 0222 clearExpanding(); 0223 endResetModel(); 0224 } 0225 } 0226 0227 void QuickOpenModel::destroyed(QObject* obj) 0228 { 0229 removeProvider(static_cast<KDevelop::QuickOpenDataProviderBase*>(obj)); 0230 } 0231 0232 QModelIndex QuickOpenModel::index(int row, int column, const QModelIndex& /*parent*/) const 0233 { 0234 if (column >= columnCount() || row >= rowCount(QModelIndex())) { 0235 return QModelIndex(); 0236 } 0237 if (row < 0 || column < 0) { 0238 return QModelIndex(); 0239 } 0240 return createIndex(row, column); 0241 } 0242 0243 QModelIndex QuickOpenModel::parent(const QModelIndex&) const 0244 { 0245 return QModelIndex(); 0246 } 0247 0248 int QuickOpenModel::rowCount(const QModelIndex& i) const 0249 { 0250 if (i.isValid()) { 0251 return 0; 0252 } 0253 0254 int count = 0; 0255 for (const ProviderEntry& provider : m_providers) { 0256 if (provider.enabled) { 0257 count += provider.provider->itemCount(); 0258 } 0259 } 0260 0261 return count; 0262 } 0263 0264 int QuickOpenModel::unfilteredRowCount() const 0265 { 0266 int count = 0; 0267 for (const ProviderEntry& provider : m_providers) { 0268 if (provider.enabled) { 0269 count += provider.provider->unfilteredItemCount(); 0270 } 0271 } 0272 0273 return count; 0274 } 0275 0276 int QuickOpenModel::columnCount() const 0277 { 0278 return 2; 0279 } 0280 0281 int QuickOpenModel::columnCount(const QModelIndex& index) const 0282 { 0283 if (index.parent().isValid()) { 0284 return 0; 0285 } else { 0286 return columnCount(); 0287 } 0288 } 0289 0290 QVariant QuickOpenModel::data(const QModelIndex& index, int role) const 0291 { 0292 QuickOpenDataPointer d = getItem(index.row()); 0293 0294 if (!d) { 0295 return QVariant(); 0296 } 0297 0298 switch (role) { 0299 case KTextEditor::CodeCompletionModel::ItemSelected: { 0300 QString desc = d->htmlDescription(); 0301 if (desc.isEmpty()) { 0302 return QVariant(); 0303 } else { 0304 return desc; 0305 } 0306 } 0307 0308 case KTextEditor::CodeCompletionModel::IsExpandable: 0309 return d->isExpandable(); 0310 case KTextEditor::CodeCompletionModel::ExpandingWidget: { 0311 QVariant v; 0312 QWidget* w = d->expandingWidget(); 0313 if (w && m_expandingWidgetHeightIncrease) { 0314 w->resize(w->width(), w->height() + m_expandingWidgetHeightIncrease); 0315 } 0316 0317 v.setValue<QWidget*>(w); 0318 return v; 0319 } 0320 case ExpandingTree::ProjectPathRole: 0321 // TODO: put this into the QuickOpenDataBase API 0322 // we cannot do this in 5.0, cannot change ABI 0323 if (auto projectFile = dynamic_cast<const ProjectFileData*>(d.constData())) { 0324 return QVariant::fromValue(projectFile->projectPath()); 0325 } else if (auto duchainItem = dynamic_cast<const DUChainItemData*>(d.constData())) { 0326 return QVariant::fromValue(duchainItem->projectPath()); 0327 } 0328 } 0329 0330 if (index.column() == 1) { //This column contains the actual content 0331 switch (role) { 0332 case Qt::DecorationRole: 0333 return d->icon(); 0334 0335 case Qt::DisplayRole: 0336 return d->text(); 0337 case KTextEditor::CodeCompletionModel::HighlightingMethod: 0338 return KTextEditor::CodeCompletionModel::CustomHighlighting; 0339 case KTextEditor::CodeCompletionModel::CustomHighlight: 0340 return d->highlighting(); 0341 } 0342 } else if (index.column() == 0) { //This column only contains the expanded/not expanded icon 0343 switch (role) { 0344 case Qt::DecorationRole: 0345 { 0346 if (isExpandable(index)) { 0347 //Show the expanded/unexpanded handles 0348 if (isExpanded(index)) { 0349 return QIcon::fromTheme(QStringLiteral("arrow-down")); 0350 } else { 0351 return QIcon::fromTheme(QStringLiteral("arrow-right")); 0352 } 0353 } 0354 } 0355 } 0356 } 0357 0358 return ExpandingWidgetModel::data(index, role); 0359 } 0360 0361 void QuickOpenModel::resetTimer() 0362 { 0363 int currentRow = treeView() ? mapToSource(treeView()->currentIndex()).row() : -1; 0364 0365 beginResetModel(); 0366 //Remove all cached data behind row m_resetBehindRow 0367 for (DataList::iterator it = m_cachedData.begin(); it != m_cachedData.end(); ) { 0368 if (it.key() > m_resetBehindRow) { 0369 it = m_cachedData.erase(it); 0370 } else { 0371 ++it; 0372 } 0373 } 0374 0375 endResetModel(); 0376 0377 if (currentRow != -1) { 0378 treeView()->setCurrentIndex(mapFromSource(index(currentRow, 0, QModelIndex()))); //Preserve the current index 0379 } 0380 m_resetBehindRow = 0; 0381 } 0382 0383 QuickOpenDataPointer QuickOpenModel::getItem(int row, bool noReset) const 0384 { 0385 ///@todo mix all the models alphabetically here. For now, they are simply ordered. 0386 ///@todo Deal with unexpected item-counts, like for example in the case of overloaded function-declarations 0387 0388 #ifdef QUICKOPEN_USE_ITEM_CACHING 0389 const auto dataIt = m_cachedData.constFind(row); 0390 if (dataIt != m_cachedData.constEnd()) { 0391 return *dataIt; 0392 } 0393 #endif 0394 int rowOffset = 0; 0395 0396 Q_ASSERT(row < rowCount(QModelIndex())); 0397 0398 for (const ProviderEntry& provider : m_providers) { 0399 if (!provider.enabled) { 0400 continue; 0401 } 0402 uint itemCount = provider.provider->itemCount(); 0403 if (( uint )row < itemCount) { 0404 QuickOpenDataPointer item = provider.provider->data(row); 0405 0406 if (!noReset && provider.provider->itemCount() != itemCount) { 0407 qCDebug(PLUGIN_QUICKOPEN) << "item-count in provider has changed, resetting model"; 0408 m_resetTimer->start(); 0409 m_resetBehindRow = rowOffset + row; //Don't reset everything, only everything behind this position 0410 } 0411 0412 #ifdef QUICKOPEN_USE_ITEM_CACHING 0413 m_cachedData[row + rowOffset] = item; 0414 #endif 0415 return item; 0416 } else { 0417 row -= provider.provider->itemCount(); 0418 rowOffset += provider.provider->itemCount(); 0419 } 0420 } 0421 0422 // qWarning() << "No item for row " << row; 0423 0424 return QuickOpenDataPointer(); 0425 } 0426 0427 QSet<IndexedString> QuickOpenModel::fileSet() const 0428 { 0429 std::vector<QSet<IndexedString>> sets; 0430 for (const ProviderEntry& provider : m_providers) { 0431 if (m_enabledScopes.isEmpty() || !(m_enabledScopes & provider.scopes).isEmpty()) { 0432 if (auto* iface = qobject_cast<QuickOpenFileSetInterface*>(provider.provider)) { 0433 sets.push_back(iface->files()); 0434 //qCDebug(PLUGIN_QUICKOPEN) << "got file-list with" << ifiles.count() << "entries from data-provider" << typeid(*iface).name(); 0435 } 0436 } 0437 } 0438 return Algorithm::unite(std::move(sets)); 0439 } 0440 0441 QTreeView* QuickOpenModel::treeView() const 0442 { 0443 return m_treeView; 0444 } 0445 0446 bool QuickOpenModel::indexIsItem(const QModelIndex& index) const 0447 { 0448 Q_ASSERT(index.model() == this); 0449 Q_UNUSED(index); 0450 return true; 0451 } 0452 0453 void QuickOpenModel::setTreeView(QTreeView* view) 0454 { 0455 m_treeView = view; 0456 } 0457 0458 int QuickOpenModel::contextMatchQuality(const QModelIndex& /*index*/) const 0459 { 0460 return -1; 0461 } 0462 0463 bool QuickOpenModel::execute(const QModelIndex& index, QString& filterText) 0464 { 0465 qCDebug(PLUGIN_QUICKOPEN) << "executing model"; 0466 if (!index.isValid()) { 0467 qCWarning(PLUGIN_QUICKOPEN) << "Invalid index executed"; 0468 return false; 0469 } 0470 0471 QuickOpenDataPointer item = getItem(index.row()); 0472 0473 if (item) { 0474 return item->execute(filterText); 0475 } else { 0476 qCWarning(PLUGIN_QUICKOPEN) << "Got no item for row " << index.row() << " "; 0477 } 0478 0479 return false; 0480 } 0481 0482 #include "moc_quickopenmodel.cpp"