File indexing completed on 2024-05-12 04:38:08
0001 /* 0002 SPDX-FileCopyrightText: 2007 David Nolden <david.nolden.kdevelop@art-master.de> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #ifndef KDEVPLATFORM_QUICKOPEN_FILTER_H 0008 #define KDEVPLATFORM_QUICKOPEN_FILTER_H 0009 0010 #include <QStringList> 0011 0012 #include "abbreviations.h" 0013 0014 #include <util/path.h> 0015 0016 namespace KDevelop { 0017 /** 0018 * This is a simple filter-implementation that helps you implementing own quickopen data-providers. 0019 * You should use it when possible, because that way additional features(like regexp filtering) can 0020 * be implemented in a central place. 0021 * 0022 * This implementation does incremental filtering while 0023 * typing text, so it quite efficient for the most common case. 0024 * 0025 * The simplest way of using this is by reimplementing your data-provider 0026 * based on QuickOpenDataProviderBase and KDevelop::Filter\<Item\>. 0027 * 0028 * What you need to do to use it: 0029 * 0030 * Reimplement itemText(..) to provide the text filtering 0031 * should be performed on (This must be efficient). 0032 * 0033 * Call setItems(..) when starting a new quickopen session, or when the content 0034 * changes, to initialize the filter with your data. 0035 * 0036 * Call setFilter(..) with the text that should be filtered for on user-input. 0037 * 0038 * Use filteredItems() to provide data to quickopen. 0039 * 0040 * @tparam Item should be the type that holds all the information you need. 0041 * The filter will hold the data, and you can access it through "items()". 0042 */ 0043 template <class Item> 0044 class Filter 0045 { 0046 public: 0047 virtual ~Filter() 0048 { 0049 } 0050 ///Clears the filter, but not the data. 0051 void clearFilter() 0052 { 0053 m_filtered = m_items; 0054 m_oldFilterText.clear(); 0055 } 0056 0057 ///Clears the filter and sets new data. The filter-text will be lost. 0058 void setItems(const QVector<Item>& data) 0059 { 0060 m_items = data; 0061 clearFilter(); 0062 } 0063 0064 const QVector<Item>& items() const 0065 { 0066 return m_items; 0067 } 0068 0069 ///Returns the data that is left after the filtering 0070 const QVector<Item>& filteredItems() const 0071 { 0072 return m_filtered; 0073 } 0074 0075 ///Changes the filter-text and refilters the data 0076 void setFilter(const QString& text) 0077 { 0078 if (m_oldFilterText == text) { 0079 return; 0080 } 0081 if (text.isEmpty()) { 0082 clearFilter(); 0083 return; 0084 } 0085 0086 const QVector<Item> filterBase = text.startsWith(m_oldFilterText) ? 0087 m_filtered : 0088 m_items; //Start filtering based on the whole data 0089 0090 m_filtered.clear(); 0091 0092 QStringList typedFragments = text.split(QStringLiteral("::"), Qt::SkipEmptyParts); 0093 if (typedFragments.isEmpty()) { 0094 clearFilter(); 0095 return; 0096 } 0097 if (typedFragments.last().endsWith(QLatin1Char(':'))) { 0098 // remove the trailing colon if there's only one; otherwise, 0099 // this breaks incremental filtering 0100 typedFragments.last().chop(1); 0101 } 0102 if (typedFragments.size() == 1 && typedFragments.last().isEmpty()) { 0103 clearFilter(); 0104 return; 0105 } 0106 for (const Item& data : filterBase) { 0107 const QString& itemData = itemText(data); 0108 if (itemData.contains(text, Qt::CaseInsensitive) || matchesAbbreviationMulti(itemData, typedFragments)) { 0109 m_filtered << data; 0110 } 0111 } 0112 0113 m_oldFilterText = text; 0114 } 0115 0116 protected: 0117 ///Should return the text an item should be filtered by. 0118 virtual QString itemText(const Item& data) const = 0; 0119 0120 private: 0121 QString m_oldFilterText; 0122 QVector<Item> m_filtered; 0123 QVector<Item> m_items; 0124 }; 0125 0126 template <class Item, class Parent> 0127 class PathFilter 0128 { 0129 public: 0130 /** 0131 * Clears the filter and sets new data. The filter-text will be lost. 0132 * 0133 * The complexity of the callback is necessary to avoid redundant possibly 0134 * huge memory allocations, redundant construction and destruction of QVector 0135 * elements, which can dominate the time spent in this function. 0136 * 0137 * @param callback a function that actually sets new data. The callback should 0138 * take a single QVector<Item>& argument, modify it without discarding its 0139 * capacity, and without redundant element destruction and construction. The 0140 * callback should not assign some other QVector to its argument because of 0141 * the immediate costly destruction of the existing elements of m_items, then 0142 * a very likely allocation and element construction when the other QVector is 0143 * modified or during a future call to updateItems(). An exception from the 0144 * above performance guideline: the callback may assign a temporary detached 0145 * QVector, whose data is about to be destroyed, to its argument. 0146 */ 0147 template<typename UpdateCallback> 0148 void updateItems(UpdateCallback callback) 0149 { 0150 // "Detach" m_filtered from m_items to avoid an allocation and element 0151 // construction inside the callback; element destruction and deallocation 0152 // in clearFilter() where m_items is assigned to m_filtered. 0153 m_filtered = {}; 0154 callback(m_items); 0155 clearFilter(); 0156 } 0157 0158 const QVector<Item>& items() const 0159 { 0160 return m_items; 0161 } 0162 0163 ///Returns the data that is left after the filtering 0164 const QVector<Item>& filteredItems() const 0165 { 0166 return m_filtered; 0167 } 0168 0169 ///Changes the filter-text and refilters the data 0170 void setFilter(const QStringList& text) 0171 { 0172 if (m_oldFilterText == text) { 0173 return; 0174 } 0175 if (text.isEmpty()) { 0176 clearFilter(); 0177 return; 0178 } 0179 0180 QVector<Item> filterBase = m_filtered; 0181 0182 if (m_oldFilterText.isEmpty()) { 0183 filterBase = m_items; 0184 // m_filtered either hasn't been modified after construction or is shared with m_items after a call 0185 // to clearFilter(). Assign a default-initialized value to m_filtered in order to avoid detaching 0186 // it and copying its items to a new buffer when m_filtered is resized below. This copying would be 0187 // a waste of CPU time, because new values are immediately assigned to all elements of m_filtered. 0188 m_filtered = {}; 0189 } else if (m_oldFilterText.mid(0, m_oldFilterText.count() - 1) == text.mid(0, text.count() - 1) 0190 && text.last().startsWith(m_oldFilterText.last())) { 0191 //Good, the prefix is the same, and the last item has been extended 0192 } else if (m_oldFilterText.size() == text.size() - 1 && m_oldFilterText == text.mid(0, text.size() - 1)) { 0193 //Good, an item has been added 0194 } else { 0195 //Start filtering based on the whole data, there was a big change to the filter 0196 filterBase = m_items; 0197 } 0198 0199 QVector<QPair<int, int>> matches; 0200 for (int i = 0, c = filterBase.size(); i < c; ++i) { 0201 const auto& data = filterBase.at(i); 0202 const auto matchQuality = matchPathFilter(static_cast<Parent*>(this)->itemPath(data), text, 0203 static_cast<Parent*>(this)->itemPrefixPath(data)); 0204 if (matchQuality == -1) { 0205 continue; 0206 } 0207 matches.push_back({matchQuality, i}); 0208 } 0209 0210 std::stable_sort(matches.begin(), matches.end(), 0211 [](const QPair<int, int>& lhs, const QPair<int, int>& rhs) 0212 { 0213 return lhs.first < rhs.first; 0214 }); 0215 m_filtered.resize(matches.size()); 0216 std::transform(matches.begin(), matches.end(), m_filtered.begin(), 0217 [&filterBase](const QPair<int, int>& match) { 0218 return filterBase.at(match.second); 0219 }); 0220 m_oldFilterText = text; 0221 } 0222 0223 private: 0224 ///Clears the filter, but not the data. 0225 void clearFilter() 0226 { 0227 m_filtered = m_items; 0228 m_oldFilterText.clear(); 0229 } 0230 0231 QStringList m_oldFilterText; 0232 QVector<Item> m_filtered; 0233 QVector<Item> m_items; 0234 }; 0235 } 0236 0237 #endif