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
18KateKeywordCompletionModel::KateKeywordCompletionModel(QObject *parent)
19 : CodeCompletionModel(parent)
20{
21 setHasGroups(false);
22}
23
24void 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, cursor: range.end());
33 std::sort(first: m_items.begin(), last: m_items.end());
34}
35
36QModelIndex KateKeywordCompletionModel::parent(const QModelIndex &index) const
37{
38 if (index.internalId()) {
39 return createIndex(arow: 0, acolumn: 0);
40 } else {
41 return QModelIndex();
42 }
43}
44
45QModelIndex KateKeywordCompletionModel::index(int row, int column, const QModelIndex &parent) const
46{
47 if (!parent.isValid()) {
48 if (row == 0) {
49 return createIndex(arow: row, acolumn: 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(arow: row, acolumn: column, aid: 1);
62}
63
64int 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
75static 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(i: position.line());
80 return highlight->isInWord(c, attrib: line.attribute(pos: position.column() - 1));
81}
82
83KTextEditor::Range KateKeywordCompletionModel::completionRange(KTextEditor::View *view, const KTextEditor::Cursor &position)
84{
85 const QString &text = view->document()->text(range: 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, c: text.at(i: 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
99bool 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, position: range.start(), c)) {
108 return true;
109 }
110 }
111 return false;
112}
113
114bool 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(c: QLatin1Char(' ')) && insertedText.at(i: insertedText.size() - 1).isLetter()) {
120 return true;
121 }
122 return false;
123}
124
125bool KateKeywordCompletionModel::shouldHideItemsWithEqualNames() const
126{
127 return true;
128}
129
130QVariant 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(i: 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(size: QSize(16, 16)));
155 return icon;
156 }
157
158 return QVariant();
159}
160
161KTextEditor::CodeCompletionModelControllerInterface::MatchReaction KateKeywordCompletionModel::matchingItem(const QModelIndex & /*matched*/)
162{
163 return KTextEditor::CodeCompletionModelControllerInterface::HideListIfAutomaticInvocation;
164}
165
166#include "moc_katekeywordcompletion.cpp"
167
168// kate: indent-width 4; replace-tabs on
169

source code of ktexteditor/src/completion/katekeywordcompletion.cpp