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 &currentCompletion)
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"