1 // Scintilla source code edit control
2 /** @file AutoComplete.cxx
3 ** Defines the auto completion list box.
4 **/
5 // Copyright 1998-2003 by Neil Hodgson <[email protected]>
6 // The License.txt file describes the conditions under which this software may be distributed.
7
8 #include <cstddef>
9 #include <cstdlib>
10 #include <cassert>
11 #include <cstring>
12 #include <cstdio>
13
14 #include <stdexcept>
15 #include <string>
16 #include <string_view>
17 #include <vector>
18 #include <algorithm>
19 #include <memory>
20
21 #include "Platform.h"
22
23 #include "Scintilla.h"
24 #include "CharacterSet.h"
25 #include "Position.h"
26 #include "AutoComplete.h"
27
28 using namespace Scintilla;
29
AutoComplete()30 AutoComplete::AutoComplete() :
31 active(false),
32 separator(' '),
33 typesep('?'),
34 ignoreCase(false),
35 chooseSingle(false),
36 posStart(0),
37 startLen(0),
38 cancelAtStartPos(true),
39 autoHide(true),
40 dropRestOfWord(false),
41 ignoreCaseBehaviour(SC_CASEINSENSITIVEBEHAVIOUR_RESPECTCASE),
42 widthLBDefault(100),
43 heightLBDefault(100),
44 autoSort(SC_ORDER_PRESORTED) {
45 lb.reset(ListBox::Allocate());
46 }
47
~AutoComplete()48 AutoComplete::~AutoComplete() {
49 if (lb) {
50 lb->Destroy();
51 }
52 }
53
Active() const54 bool AutoComplete::Active() const noexcept {
55 return active;
56 }
57
Start(Window & parent,int ctrlID,Sci::Position position,Point location,Sci::Position startLen_,int lineHeight,bool unicodeMode,int technology)58 void AutoComplete::Start(Window &parent, int ctrlID,
59 Sci::Position position, Point location, Sci::Position startLen_,
60 int lineHeight, bool unicodeMode, int technology) {
61 if (active) {
62 Cancel();
63 }
64 lb->Create(parent, ctrlID, location, lineHeight, unicodeMode, technology);
65 lb->Clear();
66 active = true;
67 startLen = startLen_;
68 posStart = position;
69 }
70
SetStopChars(const char * stopChars_)71 void AutoComplete::SetStopChars(const char *stopChars_) {
72 stopChars = stopChars_;
73 }
74
IsStopChar(char ch) const75 bool AutoComplete::IsStopChar(char ch) const noexcept {
76 return ch && (stopChars.find(ch) != std::string::npos);
77 }
78
SetFillUpChars(const char * fillUpChars_)79 void AutoComplete::SetFillUpChars(const char *fillUpChars_) {
80 fillUpChars = fillUpChars_;
81 }
82
IsFillUpChar(char ch) const83 bool AutoComplete::IsFillUpChar(char ch) const noexcept {
84 return ch && (fillUpChars.find(ch) != std::string::npos);
85 }
86
SetSeparator(char separator_)87 void AutoComplete::SetSeparator(char separator_) {
88 separator = separator_;
89 }
90
GetSeparator() const91 char AutoComplete::GetSeparator() const noexcept {
92 return separator;
93 }
94
SetTypesep(char separator_)95 void AutoComplete::SetTypesep(char separator_) {
96 typesep = separator_;
97 }
98
GetTypesep() const99 char AutoComplete::GetTypesep() const noexcept {
100 return typesep;
101 }
102
103 struct Sorter {
104 AutoComplete *ac;
105 const char *list;
106 std::vector<int> indices;
107
SorterSorter108 Sorter(AutoComplete *ac_, const char *list_) : ac(ac_), list(list_) {
109 int i = 0;
110 while (list[i]) {
111 indices.push_back(i); // word start
112 while (list[i] != ac->GetTypesep() && list[i] != ac->GetSeparator() && list[i])
113 ++i;
114 indices.push_back(i); // word end
115 if (list[i] == ac->GetTypesep()) {
116 while (list[i] != ac->GetSeparator() && list[i])
117 ++i;
118 }
119 if (list[i] == ac->GetSeparator()) {
120 ++i;
121 // preserve trailing separator as blank entry
122 if (!list[i]) {
123 indices.push_back(i);
124 indices.push_back(i);
125 }
126 }
127 }
128 indices.push_back(i); // index of last position
129 }
130
operator ()Sorter131 bool operator()(int a, int b) {
132 const int lenA = indices[a * 2 + 1] - indices[a * 2];
133 const int lenB = indices[b * 2 + 1] - indices[b * 2];
134 const int len = std::min(lenA, lenB);
135 int cmp;
136 if (ac->ignoreCase)
137 cmp = CompareNCaseInsensitive(list + indices[a * 2], list + indices[b * 2], len);
138 else
139 cmp = strncmp(list + indices[a * 2], list + indices[b * 2], len);
140 if (cmp == 0)
141 cmp = lenA - lenB;
142 return cmp < 0;
143 }
144 };
145
SetList(const char * list)146 void AutoComplete::SetList(const char *list) {
147 if (autoSort == SC_ORDER_PRESORTED) {
148 lb->SetList(list, separator, typesep);
149 sortMatrix.clear();
150 for (int i = 0; i < lb->Length(); ++i)
151 sortMatrix.push_back(i);
152 return;
153 }
154
155 Sorter IndexSort(this, list);
156 sortMatrix.clear();
157 for (int i = 0; i < static_cast<int>(IndexSort.indices.size()) / 2; ++i)
158 sortMatrix.push_back(i);
159 std::sort(sortMatrix.begin(), sortMatrix.end(), IndexSort);
160 if (autoSort == SC_ORDER_CUSTOM || sortMatrix.size() < 2) {
161 lb->SetList(list, separator, typesep);
162 PLATFORM_ASSERT(lb->Length() == static_cast<int>(sortMatrix.size()));
163 return;
164 }
165
166 std::string sortedList;
167 char item[maxItemLen];
168 for (size_t i = 0; i < sortMatrix.size(); ++i) {
169 int wordLen = IndexSort.indices[sortMatrix[i] * 2 + 2] - IndexSort.indices[sortMatrix[i] * 2];
170 if (wordLen > maxItemLen-2)
171 wordLen = maxItemLen - 2;
172 memcpy(item, list + IndexSort.indices[sortMatrix[i] * 2], wordLen);
173 if ((i+1) == sortMatrix.size()) {
174 // Last item so remove separator if present
175 if ((wordLen > 0) && (item[wordLen-1] == separator))
176 wordLen--;
177 } else {
178 // Item before last needs a separator
179 if ((wordLen == 0) || (item[wordLen-1] != separator)) {
180 item[wordLen] = separator;
181 wordLen++;
182 }
183 }
184 item[wordLen] = '\0';
185 sortedList += item;
186 }
187 for (int i = 0; i < static_cast<int>(sortMatrix.size()); ++i)
188 sortMatrix[i] = i;
189 lb->SetList(sortedList.c_str(), separator, typesep);
190 }
191
GetSelection() const192 int AutoComplete::GetSelection() const {
193 return lb->GetSelection();
194 }
195
GetValue(int item) const196 std::string AutoComplete::GetValue(int item) const {
197 char value[maxItemLen];
198 lb->GetValue(item, value, sizeof(value));
199 return std::string(value);
200 }
201
Show(bool show)202 void AutoComplete::Show(bool show) {
203 lb->Show(show);
204 if (show)
205 lb->Select(0);
206 }
207
Cancel()208 void AutoComplete::Cancel() {
209 if (lb->Created()) {
210 lb->Clear();
211 lb->Destroy();
212 active = false;
213 }
214 }
215
216
Move(int delta)217 void AutoComplete::Move(int delta) {
218 const int count = lb->Length();
219 int current = lb->GetSelection();
220 current += delta;
221 if (current >= count)
222 current = count - 1;
223 if (current < 0)
224 current = 0;
225 lb->Select(current);
226 }
227
Select(const char * word)228 void AutoComplete::Select(const char *word) {
229 const size_t lenWord = strlen(word);
230 int location = -1;
231 int start = 0; // lower bound of the api array block to search
232 int end = lb->Length() - 1; // upper bound of the api array block to search
233 while ((start <= end) && (location == -1)) { // Binary searching loop
234 int pivot = (start + end) / 2;
235 char item[maxItemLen];
236 lb->GetValue(sortMatrix[pivot], item, maxItemLen);
237 int cond;
238 if (ignoreCase)
239 cond = CompareNCaseInsensitive(word, item, lenWord);
240 else
241 cond = strncmp(word, item, lenWord);
242 if (!cond) {
243 // Find first match
244 while (pivot > start) {
245 lb->GetValue(sortMatrix[pivot-1], item, maxItemLen);
246 if (ignoreCase)
247 cond = CompareNCaseInsensitive(word, item, lenWord);
248 else
249 cond = strncmp(word, item, lenWord);
250 if (0 != cond)
251 break;
252 --pivot;
253 }
254 location = pivot;
255 if (ignoreCase
256 && ignoreCaseBehaviour == SC_CASEINSENSITIVEBEHAVIOUR_RESPECTCASE) {
257 // Check for exact-case match
258 for (; pivot <= end; pivot++) {
259 lb->GetValue(sortMatrix[pivot], item, maxItemLen);
260 if (!strncmp(word, item, lenWord)) {
261 location = pivot;
262 break;
263 }
264 if (CompareNCaseInsensitive(word, item, lenWord))
265 break;
266 }
267 }
268 } else if (cond < 0) {
269 end = pivot - 1;
270 } else if (cond > 0) {
271 start = pivot + 1;
272 }
273 }
274 if (location == -1) {
275 if (autoHide)
276 Cancel();
277 else
278 lb->Select(-1);
279 } else {
280 if (autoSort == SC_ORDER_CUSTOM) {
281 // Check for a logically earlier match
282 char item[maxItemLen];
283 for (int i = location + 1; i <= end; ++i) {
284 lb->GetValue(sortMatrix[i], item, maxItemLen);
285 if (CompareNCaseInsensitive(word, item, lenWord))
286 break;
287 if (sortMatrix[i] < sortMatrix[location] && !strncmp(word, item, lenWord))
288 location = i;
289 }
290 }
291 lb->Select(sortMatrix[location]);
292 }
293 }
294
295