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 | |
18 | KateKeywordCompletionModel::KateKeywordCompletionModel(QObject *parent) |
19 | : CodeCompletionModel(parent) |
20 | { |
21 | setHasGroups(false); |
22 | } |
23 | |
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, cursor: range.end()); |
33 | std::sort(first: m_items.begin(), last: m_items.end()); |
34 | } |
35 | |
36 | QModelIndex 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 | |
45 | QModelIndex 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 | |
64 | 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 | |
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(i: position.line()); |
80 | return highlight->isInWord(c, attrib: line.attribute(pos: position.column() - 1)); |
81 | } |
82 | |
83 | KTextEditor::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 | |
99 | bool KateKeywordCompletionModel::shouldAbortCompletion(KTextEditor::View *view, const KTextEditor::Range &range, const QString ¤tCompletion) |
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 | |
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(c: QLatin1Char(' ')) && insertedText.at(i: insertedText.size() - 1).isLetter()) { |
120 | return true; |
121 | } |
122 | return false; |
123 | } |
124 | |
125 | bool KateKeywordCompletionModel::shouldHideItemsWithEqualNames() const |
126 | { |
127 | return true; |
128 | } |
129 | |
130 | 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(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 | |
161 | KTextEditor::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 | |