1 /*
2     SPDX-FileCopyrightText: 2014 Sven Brauch <svenbrauch@gmail.com>
3 
4     SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include "katekeywordcompletion.h"
8 
9 #include "katedocument.h"
10 #include "katehighlight.h"
11 #include "katetextline.h"
12 
13 #include <ktexteditor/view.h>
14 
15 #include <KLocalizedString>
16 #include <QString>
17 
KateKeywordCompletionModel(QObject * parent)18 KateKeywordCompletionModel::KateKeywordCompletionModel(QObject *parent)
19     : CodeCompletionModel(parent)
20 {
21     setHasGroups(false);
22 }
23 
completionInvoked(KTextEditor::View * view,const KTextEditor::Range & range,KTextEditor::CodeCompletionModel::InvocationType)24 void KateKeywordCompletionModel::completionInvoked(KTextEditor::View *view,
25                                                    const KTextEditor::Range &range,
26                                                    KTextEditor::CodeCompletionModel::InvocationType /*invocationType*/)
27 {
28     KTextEditor::DocumentPrivate *doc = static_cast<KTextEditor::DocumentPrivate *>(view->document());
29     if (!doc->highlight() || doc->highlight()->noHighlighting()) {
30         return;
31     }
32     m_items = doc->highlight()->keywordsForLocation(doc, range.end());
33     std::sort(m_items.begin(), m_items.end());
34 }
35 
parent(const QModelIndex & index) const36 QModelIndex KateKeywordCompletionModel::parent(const QModelIndex &index) const
37 {
38     if (index.internalId()) {
39         return createIndex(0, 0);
40     } else {
41         return QModelIndex();
42     }
43 }
44 
index(int row,int column,const QModelIndex & parent) const45 QModelIndex KateKeywordCompletionModel::index(int row, int column, const QModelIndex &parent) const
46 {
47     if (!parent.isValid()) {
48         if (row == 0) {
49             return createIndex(row, column);
50         } else {
51             return QModelIndex();
52         }
53     } else if (parent.parent().isValid()) {
54         return QModelIndex();
55     }
56 
57     if (row < 0 || row >= m_items.count() || column < 0 || column >= ColumnCount) {
58         return QModelIndex();
59     }
60 
61     return createIndex(row, column, 1);
62 }
63 
rowCount(const QModelIndex & parent) const64 int KateKeywordCompletionModel::rowCount(const QModelIndex &parent) const
65 {
66     if (!parent.isValid() && !m_items.isEmpty()) {
67         return 1; // One root node to define the custom group
68     } else if (parent.parent().isValid()) {
69         return 0; // Completion-items have no children
70     } else {
71         return m_items.count();
72     }
73 }
74 
isInWord(const KTextEditor::View * view,const KTextEditor::Cursor & position,QChar c)75 static bool isInWord(const KTextEditor::View *view, const KTextEditor::Cursor &position, QChar c)
76 {
77     KTextEditor::DocumentPrivate *document = static_cast<KTextEditor::DocumentPrivate *>(view->document());
78     KateHighlighting *highlight = document->highlight();
79     Kate::TextLine line = document->kateTextLine(position.line());
80     return highlight->isInWord(c, line->attribute(position.column() - 1));
81 }
82 
completionRange(KTextEditor::View * view,const KTextEditor::Cursor & position)83 KTextEditor::Range KateKeywordCompletionModel::completionRange(KTextEditor::View *view, const KTextEditor::Cursor &position)
84 {
85     const QString &text = view->document()->text(KTextEditor::Range(position, KTextEditor::Cursor(position.line(), 0)));
86     int pos;
87     for (pos = text.size() - 1; pos >= 0; pos--) {
88         if (isInWord(view, position, text.at(pos))) {
89             // This needs to be aware of what characters are word-characters in the
90             // active language, so that languages which prefix commands with e.g. @
91             // or \ have properly working completion.
92             continue;
93         }
94         break;
95     }
96     return KTextEditor::Range(KTextEditor::Cursor(position.line(), pos + 1), position);
97 }
98 
shouldAbortCompletion(KTextEditor::View * view,const KTextEditor::Range & range,const QString & currentCompletion)99 bool KateKeywordCompletionModel::shouldAbortCompletion(KTextEditor::View *view, const KTextEditor::Range &range, const QString &currentCompletion)
100 {
101     if (view->cursorPosition() < range.start() || view->cursorPosition() > range.end()) {
102         return true; // Always abort when the completion-range has been left
103     }
104     // Do not abort completions when the text has been empty already before and a newline has been entered
105 
106     for (QChar c : currentCompletion) {
107         if (!isInWord(view, range.start(), c)) {
108             return true;
109         }
110     }
111     return false;
112 }
113 
shouldStartCompletion(KTextEditor::View *,const QString & insertedText,bool userInsertion,const KTextEditor::Cursor &)114 bool KateKeywordCompletionModel::shouldStartCompletion(KTextEditor::View * /*view*/,
115                                                        const QString &insertedText,
116                                                        bool userInsertion,
117                                                        const KTextEditor::Cursor & /*position*/)
118 {
119     if (userInsertion && insertedText.size() > 3 && !insertedText.contains(QLatin1Char(' ')) && insertedText.at(insertedText.size() - 1).isLetter()) {
120         return true;
121     }
122     return false;
123 }
124 
shouldHideItemsWithEqualNames() const125 bool KateKeywordCompletionModel::shouldHideItemsWithEqualNames() const
126 {
127     return true;
128 }
129 
data(const QModelIndex & index,int role) const130 QVariant KateKeywordCompletionModel::data(const QModelIndex &index, int role) const
131 {
132     if (role == UnimportantItemRole) {
133         return QVariant(true);
134     }
135     if (role == InheritanceDepth) {
136         return 9000;
137     }
138 
139     if (!index.parent().isValid()) {
140         // group header
141         switch (role) {
142         case Qt::DisplayRole:
143             return i18n("Language keywords");
144         case GroupRole:
145             return Qt::DisplayRole;
146         }
147     }
148 
149     if (index.column() == KTextEditor::CodeCompletionModel::Name && role == Qt::DisplayRole) {
150         return m_items.at(index.row());
151     }
152 
153     if (index.column() == KTextEditor::CodeCompletionModel::Icon && role == Qt::DecorationRole) {
154         static const QIcon icon(QIcon::fromTheme(QStringLiteral("code-variable")).pixmap(QSize(16, 16)));
155         return icon;
156     }
157 
158     return QVariant();
159 }
160 
matchingItem(const QModelIndex &)161 KTextEditor::CodeCompletionModelControllerInterface::MatchReaction KateKeywordCompletionModel::matchingItem(const QModelIndex & /*matched*/)
162 {
163     return KTextEditor::CodeCompletionModelControllerInterface::HideListIfAutomaticInvocation;
164 }
165 
166 // kate: indent-width 4; replace-tabs on
167