File indexing completed on 2024-04-28 05:49:07
0001 /* This file is part of the Kate project. 0002 * 0003 * SPDX-FileCopyrightText: 2012 Christoph Cullmann <cullmann@kde.org> 0004 * SPDX-FileCopyrightText: 2003 Anders Lund <anders.lund@lund.tdcadsl.dk> 0005 * 0006 * SPDX-License-Identifier: LGPL-2.0-or-later 0007 */ 0008 0009 #include "kateprojectcompletion.h" 0010 #include "kateproject.h" 0011 #include "kateprojectindex.h" 0012 #include "kateprojectplugin.h" 0013 0014 #include <KLocalizedString> 0015 0016 #include <QIcon> 0017 0018 // get KTextEditor config if feasible 0019 static int minimalCompletionLength(KTextEditor::View *view) 0020 { 0021 bool valueFound = false; 0022 const int length = view->configValue(QStringLiteral("word-completion-minimal-word-length")).toInt(&valueFound); 0023 0024 // handle bogus values or old versions that don't export that setting 0025 return valueFound ? length : 3; 0026 } 0027 0028 KateProjectCompletion::KateProjectCompletion(KateProjectPlugin *plugin) 0029 : KTextEditor::CodeCompletionModel(nullptr) 0030 , m_plugin(plugin) 0031 { 0032 } 0033 0034 KateProjectCompletion::~KateProjectCompletion() 0035 { 0036 } 0037 0038 void KateProjectCompletion::saveMatches(KTextEditor::View *view, const KTextEditor::Range &range) 0039 { 0040 m_matches.clear(); 0041 allMatches(m_matches, view, range); 0042 } 0043 0044 QVariant KateProjectCompletion::data(const QModelIndex &index, int role) const 0045 { 0046 if (role == InheritanceDepth) { 0047 return 10010; // Very high value, so the word-completion group and items are shown behind any other groups/items if there is multiple 0048 } 0049 0050 if (!index.parent().isValid()) { 0051 // It is the group header 0052 switch (role) { 0053 case Qt::DisplayRole: 0054 return i18n("Project Completion"); 0055 case GroupRole: 0056 return Qt::DisplayRole; 0057 } 0058 } 0059 0060 if (index.column() == KTextEditor::CodeCompletionModel::Name && role == Qt::DisplayRole) { 0061 return m_matches.item(index.row())->data(Qt::DisplayRole); 0062 } 0063 0064 if (index.column() == KTextEditor::CodeCompletionModel::Icon && role == Qt::DecorationRole) { 0065 static QIcon icon(QIcon::fromTheme(QStringLiteral("insert-text")).pixmap(QSize(16, 16))); 0066 return icon; 0067 } 0068 0069 return QVariant(); 0070 } 0071 0072 QModelIndex KateProjectCompletion::parent(const QModelIndex &index) const 0073 { 0074 if (index.internalId()) { 0075 return createIndex(0, 0, quintptr(0)); 0076 } else { 0077 return QModelIndex(); 0078 } 0079 } 0080 0081 QModelIndex KateProjectCompletion::index(int row, int column, const QModelIndex &parent) const 0082 { 0083 if (!parent.isValid()) { 0084 if (row == 0) { 0085 return createIndex(row, column, quintptr(0)); 0086 } else { 0087 return QModelIndex(); 0088 } 0089 0090 } else if (parent.parent().isValid()) { 0091 return QModelIndex(); 0092 } 0093 0094 if (row < 0 || row >= m_matches.rowCount() || column < 0 || column >= ColumnCount) { 0095 return QModelIndex(); 0096 } 0097 0098 return createIndex(row, column, 1); 0099 } 0100 0101 int KateProjectCompletion::rowCount(const QModelIndex &parent) const 0102 { 0103 if (!parent.isValid() && !(m_matches.rowCount() == 0)) { 0104 return 1; // One root node to define the custom group 0105 } else if (parent.parent().isValid()) { 0106 return 0; // Completion-items have no children 0107 } else { 0108 return m_matches.rowCount(); 0109 } 0110 } 0111 0112 bool KateProjectCompletion::shouldStartCompletion(KTextEditor::View *view, const QString &insertedText, bool userInsertion, const KTextEditor::Cursor &position) 0113 { 0114 if (!userInsertion) { 0115 return false; 0116 } 0117 if (insertedText.isEmpty()) { 0118 return false; 0119 } 0120 0121 QString text = view->document()->line(position.line()).left(position.column()); 0122 0123 const int check = minimalCompletionLength(view); 0124 if (check <= 0) { 0125 return true; 0126 } 0127 int start = text.length(); 0128 int end = text.length() - check; 0129 if (end < 0) { 0130 return false; 0131 } 0132 for (int i = start - 1; i >= end; i--) { 0133 QChar c = text.at(i); 0134 if (!(c.isLetter() || (c.isNumber()) || c == QLatin1Char('_'))) { 0135 return false; 0136 } 0137 } 0138 0139 return true; 0140 } 0141 0142 bool KateProjectCompletion::shouldAbortCompletion(KTextEditor::View *view, const KTextEditor::Range &range, const QString ¤tCompletion) 0143 { 0144 if (m_automatic) { 0145 if (currentCompletion.length() < minimalCompletionLength(view)) { 0146 return true; 0147 } 0148 } 0149 0150 return CodeCompletionModelControllerInterface::shouldAbortCompletion(view, range, currentCompletion); 0151 } 0152 0153 void KateProjectCompletion::completionInvoked(KTextEditor::View *view, const KTextEditor::Range &range, InvocationType it) 0154 { 0155 /** 0156 * auto invoke... 0157 */ 0158 m_automatic = false; 0159 if (it == AutomaticInvocation) { 0160 m_automatic = true; 0161 0162 if (range.columnWidth() >= minimalCompletionLength(view)) { 0163 saveMatches(view, range); 0164 } else { 0165 m_matches.clear(); 0166 } 0167 0168 // done here... 0169 return; 0170 } 0171 0172 // normal case ;) 0173 saveMatches(view, range); 0174 } 0175 0176 // Scan throughout the entire document for possible completions, 0177 // ignoring any dublets 0178 void KateProjectCompletion::allMatches(QStandardItemModel &model, KTextEditor::View *view, const KTextEditor::Range &range) const 0179 { 0180 /** 0181 * get project scope for this document, else fail 0182 */ 0183 QList<KateProject *> projects; 0184 if (m_plugin->multiProjectCompletion()) { 0185 projects = m_plugin->projects(); 0186 } else { 0187 auto project = m_plugin->projectForDocument(view->document()); 0188 if (project) { 0189 projects.push_back(project); 0190 } 0191 } 0192 0193 /** 0194 * let project index fill the completion for this document 0195 */ 0196 for (const auto project : qAsConst(projects)) { 0197 if (project->projectIndex()) { 0198 project->projectIndex()->findMatches(model, view->document()->text(range), KateProjectIndex::CompletionMatches); 0199 } 0200 } 0201 } 0202 0203 KTextEditor::CodeCompletionModelControllerInterface::MatchReaction KateProjectCompletion::matchingItem(const QModelIndex & /*matched*/) 0204 { 0205 return HideListIfAutomaticInvocation; 0206 } 0207 0208 // Return the range containing the word left of the cursor 0209 KTextEditor::Range KateProjectCompletion::completionRange(KTextEditor::View *view, const KTextEditor::Cursor &position) 0210 { 0211 int line = position.line(); 0212 int col = position.column(); 0213 0214 KTextEditor::Document *doc = view->document(); 0215 while (col > 0) { 0216 QChar c = (doc->characterAt(KTextEditor::Cursor(line, col - 1))); 0217 if (c.isLetterOrNumber() || c.isMark() || c == QLatin1Char('_')) { 0218 col--; 0219 continue; 0220 } 0221 0222 break; 0223 } 0224 0225 return KTextEditor::Range(KTextEditor::Cursor(line, col), position); 0226 } 0227 0228 #include "moc_kateprojectcompletion.cpp"