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 <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