| 1 | /* |
| 2 | SPDX-FileCopyrightText: 2005-2006 Hamish Rodda <rodda@kde.org> |
| 3 | SPDX-FileCopyrightText: 2007-2008 David Nolden <david.nolden.kdevelop@art-master.de> |
| 4 | |
| 5 | SPDX-License-Identifier: LGPL-2.0-or-later |
| 6 | */ |
| 7 | |
| 8 | #ifndef KATECOMPLETIONMODEL_H |
| 9 | #define KATECOMPLETIONMODEL_H |
| 10 | |
| 11 | #include <QAbstractProxyModel> |
| 12 | #include <QList> |
| 13 | #include <QPair> |
| 14 | |
| 15 | #include <ktexteditor/codecompletionmodel.h> |
| 16 | |
| 17 | #include "expandingtree/expandingwidgetmodel.h" |
| 18 | #include <ktexteditor_export.h> |
| 19 | |
| 20 | #include <set> |
| 21 | |
| 22 | class KateCompletionWidget; |
| 23 | class KateArgumentHintModel; |
| 24 | namespace KTextEditor |
| 25 | { |
| 26 | class ViewPrivate; |
| 27 | } |
| 28 | class QWidget; |
| 29 | class QTextEdit; |
| 30 | class QTimer; |
| 31 | class HierarchicalModelHandler; |
| 32 | |
| 33 | /** |
| 34 | * This class has the responsibility for filtering, sorting, and manipulating |
| 35 | * code completion data provided by a CodeCompletionModel. |
| 36 | * |
| 37 | * @author Hamish Rodda <rodda@kde.org> |
| 38 | */ |
| 39 | class KateCompletionModel : public ExpandingWidgetModel |
| 40 | { |
| 41 | Q_OBJECT |
| 42 | |
| 43 | public: |
| 44 | enum InternalRole { |
| 45 | IsNonEmptyGroup = KTextEditor::CodeCompletionModel::LastExtraItemDataRole + 1, |
| 46 | }; |
| 47 | |
| 48 | explicit KateCompletionModel(KateCompletionWidget *parent = nullptr); |
| 49 | ~KateCompletionModel() override; |
| 50 | |
| 51 | QList<KTextEditor::CodeCompletionModel *> completionModels() const; |
| 52 | void clearCompletionModels(); |
| 53 | KTEXTEDITOR_EXPORT void addCompletionModel(KTextEditor::CodeCompletionModel *model); |
| 54 | KTEXTEDITOR_EXPORT void setCompletionModel(KTextEditor::CodeCompletionModel *model); |
| 55 | void setCompletionModels(const QList<KTextEditor::CodeCompletionModel *> &models); |
| 56 | KTEXTEDITOR_EXPORT void removeCompletionModel(KTextEditor::CodeCompletionModel *model); |
| 57 | |
| 58 | KTextEditor::ViewPrivate *view() const; |
| 59 | KateCompletionWidget *widget() const; |
| 60 | |
| 61 | KTEXTEDITOR_EXPORT QString currentCompletion(KTextEditor::CodeCompletionModel *model) const; |
| 62 | void setCurrentCompletion(QMap<KTextEditor::CodeCompletionModel *, QString> currentMatch); |
| 63 | |
| 64 | int translateColumn(int sourceColumn) const; |
| 65 | |
| 66 | /// Returns a common prefix for all current visible completion entries |
| 67 | /// If there is no common prefix, extracts the next useful prefix for the selected index |
| 68 | QString commonPrefix(QModelIndex selectedIndex) const; |
| 69 | |
| 70 | void rowSelected(const QModelIndex &row) const; |
| 71 | |
| 72 | bool indexIsItem(const QModelIndex &index) const override; |
| 73 | |
| 74 | int columnCount(const QModelIndex &parent = QModelIndex()) const override; |
| 75 | QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; |
| 76 | Qt::ItemFlags flags(const QModelIndex &index) const override; |
| 77 | bool hasChildren(const QModelIndex &parent = QModelIndex()) const override; |
| 78 | virtual bool hasIndex(int row, int column, const QModelIndex &parent = QModelIndex()) const; |
| 79 | QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; |
| 80 | |
| 81 | // Disabled in case of bugs, reenable once fully debugged. |
| 82 | // virtual QMap<int, QVariant> itemData ( const QModelIndex & index ) const; |
| 83 | QModelIndex parent(const QModelIndex &index) const override; |
| 84 | int rowCount(const QModelIndex &parent = QModelIndex()) const override; |
| 85 | |
| 86 | /// Maps from this display-model into the appropriate source code-completion model |
| 87 | virtual QModelIndex mapToSource(const QModelIndex &proxyIndex) const; |
| 88 | |
| 89 | /// Maps from an index in a source-model to the index of the item in this display-model |
| 90 | virtual QModelIndex mapFromSource(const QModelIndex &sourceIndex) const; |
| 91 | |
| 92 | enum gm { |
| 93 | ScopeType = 0x1, |
| 94 | Scope = 0x2, |
| 95 | AccessType = 0x4, |
| 96 | ItemType = 0x8 |
| 97 | }; |
| 98 | |
| 99 | enum { // An own property that will be used to mark the best-matches group internally |
| 100 | BestMatchesProperty = 2 * KTextEditor::CodeCompletionModel::LastProperty |
| 101 | }; |
| 102 | |
| 103 | Q_DECLARE_FLAGS(GroupingMethods, gm) |
| 104 | |
| 105 | static const int ScopeTypeMask = 0x380000; |
| 106 | static const int AccessTypeMask = 0x7; |
| 107 | static const int ItemTypeMask = 0xfe0; |
| 108 | |
| 109 | void debugStats(); |
| 110 | |
| 111 | /// Returns whether one of the filtered items exactly matches its completion string |
| 112 | bool shouldMatchHideCompletionList() const; |
| 113 | |
| 114 | KTEXTEDITOR_EXPORT uint filteredItemCount() const; |
| 115 | |
| 116 | protected: |
| 117 | int contextMatchQuality(const QModelIndex &index) const override; |
| 118 | |
| 119 | Q_SIGNALS: |
| 120 | void expandIndex(const QModelIndex &index); |
| 121 | // Emitted whenever something has changed about the group of argument-hints |
| 122 | void argumentHintsChanged(); |
| 123 | |
| 124 | private Q_SLOTS: |
| 125 | void slotRowsInserted(const QModelIndex &parent, int start, int end); |
| 126 | void slotRowsRemoved(const QModelIndex &parent, int start, int end); |
| 127 | void slotModelReset(); |
| 128 | |
| 129 | // Updates the best-matches group |
| 130 | void updateBestMatches(); |
| 131 | // Makes sure that the ungrouped group contains each item only once |
| 132 | // Must only be called right after the group was created |
| 133 | void makeGroupItemsUnique(bool onlyFiltered = false); |
| 134 | |
| 135 | private: |
| 136 | typedef QPair<KTextEditor::CodeCompletionModel *, QModelIndex> ModelRow; |
| 137 | virtual int contextMatchQuality(const ModelRow &sourceRow) const; |
| 138 | |
| 139 | QTreeView *treeView() const override; |
| 140 | |
| 141 | friend class KateArgumentHintModel; |
| 142 | static ModelRow modelRowPair(const QModelIndex &index); |
| 143 | |
| 144 | // Represents a source row; provides sorting method |
| 145 | class Item |
| 146 | { |
| 147 | public: |
| 148 | Item(bool doInitialMatch, KateCompletionModel *model, const HierarchicalModelHandler &handler, ModelRow sourceRow); |
| 149 | |
| 150 | // Returns true if the item is not filtered and matches the current completion string |
| 151 | bool isVisible() const; |
| 152 | |
| 153 | enum MatchType { |
| 154 | NoMatch = 0, |
| 155 | PerfectMatch, |
| 156 | StartsWithMatch, |
| 157 | AbbreviationMatch, |
| 158 | ContainsMatch |
| 159 | }; |
| 160 | MatchType match(KateCompletionModel *model); |
| 161 | |
| 162 | const ModelRow &sourceRow() const; |
| 163 | |
| 164 | // Sorting operator |
| 165 | bool lessThan(KateCompletionModel *model, const Item &rhs) const; |
| 166 | |
| 167 | bool haveExactMatch() const |
| 168 | { |
| 169 | return m_haveExactMatch; |
| 170 | } |
| 171 | |
| 172 | QString name() const |
| 173 | { |
| 174 | return m_nameColumn; |
| 175 | } |
| 176 | |
| 177 | private: |
| 178 | ModelRow m_sourceRow; |
| 179 | |
| 180 | QString m_nameColumn; |
| 181 | |
| 182 | int inheritanceDepth; |
| 183 | |
| 184 | // True when currently matching completion string |
| 185 | MatchType matchCompletion; |
| 186 | bool m_haveExactMatch; |
| 187 | bool m_unimportant; |
| 188 | }; |
| 189 | |
| 190 | public: |
| 191 | // Grouping and sorting of rows |
| 192 | class Group |
| 193 | { |
| 194 | public: |
| 195 | explicit Group(const QString &title, int attribute, KateCompletionModel *model); |
| 196 | |
| 197 | void addItem(const Item &i, bool notifyModel = false); |
| 198 | /// Removes the item specified by \a row. Returns true if a change was made to rows. |
| 199 | bool removeItem(const ModelRow &row); |
| 200 | void resort(); |
| 201 | void clear(); |
| 202 | // Returns whether this group should be ordered before other |
| 203 | bool orderBefore(Group *other) const; |
| 204 | // Returns a number that can be used for ordering |
| 205 | int orderNumber() const; |
| 206 | |
| 207 | /// Returns the row in the this group's filtered list of the given model-row in a source-model |
| 208 | ///-1 if the item is not in the filtered list |
| 209 | ///@todo Implement an efficient way of doing this map, that does _not_ iterate over all items! |
| 210 | int rowOf(const ModelRow &item) |
| 211 | { |
| 212 | for (int a = 0; a < (int)filtered.size(); ++a) { |
| 213 | if (filtered[a].sourceRow() == item) { |
| 214 | return a; |
| 215 | } |
| 216 | } |
| 217 | return -1; |
| 218 | } |
| 219 | |
| 220 | KateCompletionModel *model; |
| 221 | int attribute; |
| 222 | QString title, scope; |
| 223 | std::vector<Item> filtered; |
| 224 | std::vector<Item> prefilter; |
| 225 | bool isEmpty; |
| 226 | //-1 if none was set |
| 227 | int customSortingKey; |
| 228 | }; |
| 229 | |
| 230 | typedef std::set<Group *> GroupSet; |
| 231 | |
| 232 | bool hasGroups() const |
| 233 | { |
| 234 | // qCDebug(LOG_KTE) << "m_groupHash.size()"<<m_groupHash.size(); |
| 235 | // qCDebug(LOG_KTE) << "m_rowTable.count()"<<m_rowTable.count(); |
| 236 | return m_hasGroups; |
| 237 | } |
| 238 | |
| 239 | private: |
| 240 | QString commonPrefixInternal(const QString &forcePrefix) const; |
| 241 | /// @note performs model reset |
| 242 | void createGroups(); |
| 243 | /// Creates all sub-items of index i, or the item corresponding to index i. Returns the affected groups. |
| 244 | /// i must be an index in the source model |
| 245 | GroupSet createItems(const HierarchicalModelHandler &, const QModelIndex &i, bool notifyModel = false); |
| 246 | /// Deletes all sub-items of index i, or the item corresponding to index i. Returns the affected groups. |
| 247 | /// i must be an index in the source model |
| 248 | GroupSet deleteItems(const QModelIndex &i); |
| 249 | Group *createItem(const HierarchicalModelHandler &, const QModelIndex &i, bool notifyModel = false); |
| 250 | /// @note Make sure you're in a {begin,end}ResetModel block when calling this! |
| 251 | void clearGroups(); |
| 252 | void hideOrShowGroup(Group *g, bool notifyModel = false); |
| 253 | /// When forceGrouping is enabled, all given attributes will be used for grouping, regardless of the completion settings. |
| 254 | Group *fetchGroup(int attribute, bool forceGrouping = false); |
| 255 | // If this returns nonzero on an index, the index is the header of the returned group |
| 256 | Group *groupForIndex(const QModelIndex &index) const; |
| 257 | inline Group *groupOfParent(const QModelIndex &child) const |
| 258 | { |
| 259 | return static_cast<Group *>(child.internalPointer()); |
| 260 | } |
| 261 | QModelIndex indexForRow(Group *g, int row) const; |
| 262 | QModelIndex indexForGroup(Group *g) const; |
| 263 | |
| 264 | enum changeTypes { |
| 265 | Broaden, |
| 266 | Narrow, |
| 267 | Change |
| 268 | }; |
| 269 | |
| 270 | // Returns whether the model needs to be reset |
| 271 | void changeCompletions(Group *g); |
| 272 | |
| 273 | bool hasCompletionModel() const; |
| 274 | |
| 275 | /// Removes attributes not used in grouping from the input \a attribute |
| 276 | int groupingAttributes(int attribute) const; |
| 277 | static int countBits(int value); |
| 278 | |
| 279 | void resort(); |
| 280 | |
| 281 | KTEXTEDITOR_EXPORT static bool matchesAbbreviation(const QString &word, const QString &typed, int &score); |
| 282 | // exported for completion_test |
| 283 | |
| 284 | bool m_hasGroups = false; |
| 285 | |
| 286 | // ### Runtime state |
| 287 | // General |
| 288 | QList<KTextEditor::CodeCompletionModel *> m_completionModels; |
| 289 | QMap<KTextEditor::CodeCompletionModel *, QString> m_currentMatch; |
| 290 | |
| 291 | // Column merging |
| 292 | const std::array<std::vector<int>, 3> m_columnMerges = {._M_elems: { |
| 293 | {0}, |
| 294 | {1, 2, 3, 4}, |
| 295 | {5}, |
| 296 | }}; |
| 297 | |
| 298 | QTimer *m_updateBestMatchesTimer; |
| 299 | |
| 300 | Group *m_ungrouped; |
| 301 | Group *m_argumentHints; // The argument-hints will be passed on to another model, to be shown in another widget |
| 302 | Group *m_bestMatches; // A temporary group used for holding the best matches of all visible items |
| 303 | |
| 304 | // Storing the sorted order |
| 305 | std::vector<Group *> m_rowTable; |
| 306 | std::vector<Group *> m_emptyGroups; |
| 307 | // Quick access to each specific group (if it exists) |
| 308 | QMultiHash<int, Group *> m_groupHash; |
| 309 | // Maps custom group-names to their specific groups |
| 310 | QHash<QString, Group *> m_customGroupHash; |
| 311 | |
| 312 | friend class CompletionTest; |
| 313 | }; |
| 314 | |
| 315 | Q_DECLARE_OPERATORS_FOR_FLAGS(KateCompletionModel::GroupingMethods) |
| 316 | |
| 317 | #endif |
| 318 | |