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