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