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 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 48 AutoComplete::~AutoComplete() { 49 if (lb) { 50 lb->Destroy(); 51 } 52 } 53 54 bool AutoComplete::Active() const noexcept { 55 return active; 56 } 57 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 71 void AutoComplete::SetStopChars(const char *stopChars_) { 72 stopChars = stopChars_; 73 } 74 75 bool AutoComplete::IsStopChar(char ch) const noexcept { 76 return ch && (stopChars.find(ch) != std::string::npos); 77 } 78 79 void AutoComplete::SetFillUpChars(const char *fillUpChars_) { 80 fillUpChars = fillUpChars_; 81 } 82 83 bool AutoComplete::IsFillUpChar(char ch) const noexcept { 84 return ch && (fillUpChars.find(ch) != std::string::npos); 85 } 86 87 void AutoComplete::SetSeparator(char separator_) { 88 separator = separator_; 89 } 90 91 char AutoComplete::GetSeparator() const noexcept { 92 return separator; 93 } 94 95 void AutoComplete::SetTypesep(char separator_) { 96 typesep = separator_; 97 } 98 99 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 108 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 131 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 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 192 int AutoComplete::GetSelection() const { 193 return lb->GetSelection(); 194 } 195 196 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 202 void AutoComplete::Show(bool show) { 203 lb->Show(show); 204 if (show) 205 lb->Select(0); 206 } 207 208 void AutoComplete::Cancel() { 209 if (lb->Created()) { 210 lb->Clear(); 211 lb->Destroy(); 212 active = false; 213 } 214 } 215 216 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 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