1 #include "working_files.h"
2
3 #include "lex_utils.h"
4 #include "position.h"
5
6 #include <doctest/doctest.h>
7 #include <loguru.hpp>
8
9 #include <algorithm>
10 #include <climits>
11 #include <numeric>
12
13 namespace {
14
15 // When finding a best match of buffer line and index line, limit the max edit
16 // distance.
17 constexpr int kMaxDiff = 20;
18 // Don't align index line to buffer line if one of the lengths is larger than
19 // |kMaxColumnAlignSize|.
20 constexpr int kMaxColumnAlignSize = 200;
21
GetPositionForOffset(const std::string & content,int offset)22 lsPosition GetPositionForOffset(const std::string& content, int offset) {
23 if (offset >= content.size())
24 offset = (int)content.size() - 1;
25
26 lsPosition result;
27 int i = 0;
28 while (i < offset) {
29 if (content[i] == '\n') {
30 result.line += 1;
31 result.character = 0;
32 } else {
33 result.character += 1;
34 }
35 ++i;
36 }
37
38 return result;
39 }
40
41 // Computes the edit distance of strings [a,a+la) and [b,b+lb) with Eugene W.
42 // Myers' O(ND) diff algorithm.
43 // Costs: insertion=1, deletion=1, no substitution.
44 // If the distance is larger than threshold, returns threshould + 1.
MyersDiff(const char * a,int la,const char * b,int lb,int threshold)45 int MyersDiff(const char* a, int la, const char* b, int lb, int threshold) {
46 assert(threshold <= kMaxDiff);
47 static int v_static[2 * kMaxColumnAlignSize + 2];
48 const char *ea = a + la, *eb = b + lb;
49 // Strip prefix
50 for (; a < ea && b < eb && *a == *b; a++, b++) {
51 }
52 // Strip suffix
53 for (; a < ea && b < eb && ea[-1] == eb[-1]; ea--, eb--) {
54 }
55 la = int(ea - a);
56 lb = int(eb - b);
57 // If the sum of lengths exceeds what we can handle, return a lower bound.
58 if (la + lb > 2 * kMaxColumnAlignSize)
59 return std::min(abs(la - lb), threshold + 1);
60
61 int* v = v_static + lb;
62 v[1] = 0;
63 for (int di = 0; di <= threshold; di++) {
64 int low = -di + 2 * std::max(0, di - lb),
65 high = di - 2 * std::max(0, di - la);
66 for (int i = low; i <= high; i += 2) {
67 int x = i == -di || (i != di && v[i - 1] < v[i + 1]) ? v[i + 1]
68 : v[i - 1] + 1,
69 y = x - i;
70 while (x < la && y < lb && a[x] == b[y])
71 x++, y++;
72 v[i] = x;
73 if (x == la && y == lb)
74 return di;
75 }
76 }
77 return threshold + 1;
78 }
79
MyersDiff(const std::string & a,const std::string & b,int threshold)80 int MyersDiff(const std::string& a, const std::string& b, int threshold) {
81 return MyersDiff(a.data(), a.size(), b.data(), b.size(), threshold);
82 }
83
84 // Computes edit distance with O(N*M) Needleman-Wunsch algorithm
85 // and returns a distance vector where d[i] = cost of aligning a to b[0,i).
86 //
87 // Myers' diff algorithm is used to find best matching line while this one is
88 // used to align a single column because Myers' needs some twiddling to return
89 // distance vector.
EditDistanceVector(std::string a,std::string b)90 std::vector<int> EditDistanceVector(std::string a, std::string b) {
91 std::vector<int> d(b.size() + 1);
92 std::iota(d.begin(), d.end(), 0);
93 for (int i = 0; i < (int)a.size(); i++) {
94 int ul = d[0];
95 d[0] = i + 1;
96 for (int j = 0; j < (int)b.size(); j++) {
97 int t = d[j + 1];
98 d[j + 1] = a[i] == b[j] ? ul : std::min(d[j], d[j + 1]) + 1;
99 ul = t;
100 }
101 }
102 return d;
103 }
104
105 // Find matching position of |a[column]| in |b|.
106 // This is actually a single step of Hirschberg's sequence alignment algorithm.
AlignColumn(const std::string & a,int column,std::string b,bool is_end)107 int AlignColumn(const std::string& a, int column, std::string b, bool is_end) {
108 int head = 0, tail = 0;
109 while (head < (int)a.size() && head < (int)b.size() && a[head] == b[head])
110 head++;
111 while (tail < (int)a.size() && tail < (int)b.size() &&
112 a[a.size() - 1 - tail] == b[b.size() - 1 - tail])
113 tail++;
114 if (column < head)
115 return column;
116 if ((int)a.size() - tail < column)
117 return column + b.size() - a.size();
118 if (std::max(a.size(), b.size()) - head - tail >= kMaxColumnAlignSize)
119 return std::min(column, (int)b.size());
120
121 // b[head, b.size() - tail)
122 b = b.substr(head, b.size() - tail - head);
123
124 // left[i] = cost of aligning a[head, column) to b[head, head + i)
125 std::vector<int> left = EditDistanceVector(a.substr(head, column - head), b);
126
127 // right[i] = cost of aligning a[column, a.size() - tail) to b[head + i,
128 // b.size() - tail)
129 std::string a_rev = a.substr(column, a.size() - tail - column);
130 std::reverse(a_rev.begin(), a_rev.end());
131 std::reverse(b.begin(), b.end());
132 std::vector<int> right = EditDistanceVector(a_rev, b);
133 std::reverse(right.begin(), right.end());
134
135 int best = 0, best_cost = INT_MAX;
136 for (size_t i = 0; i < left.size(); i++) {
137 int cost = left[i] + right[i];
138 if (is_end ? cost < best_cost : cost <= best_cost) {
139 best_cost = cost;
140 best = i;
141 }
142 }
143 return head + best;
144 }
145
146 // Find matching buffer line of index_lines[line].
147 // By symmetry, this can also be used to find matching index line of a buffer
148 // line.
FindMatchingLine(const std::vector<std::string> & index_lines,const std::vector<int> & index_to_buffer,int line,int * column,const std::vector<std::string> & buffer_lines,bool is_end)149 optional<int> FindMatchingLine(const std::vector<std::string>& index_lines,
150 const std::vector<int>& index_to_buffer,
151 int line,
152 int* column,
153 const std::vector<std::string>& buffer_lines,
154 bool is_end) {
155 // If this is a confident mapping, returns.
156 if (index_to_buffer[line] >= 0) {
157 int ret = index_to_buffer[line];
158 if (column)
159 *column =
160 AlignColumn(index_lines[line], *column, buffer_lines[ret], is_end);
161 return ret;
162 }
163
164 // Find the nearest two confident lines above and below.
165 int up = line, down = line;
166 while (--up >= 0 && index_to_buffer[up] < 0) {
167 }
168 while (++down < int(index_to_buffer.size()) && index_to_buffer[down] < 0) {
169 }
170 up = up < 0 ? 0 : index_to_buffer[up];
171 down = down >= int(index_to_buffer.size()) ? int(buffer_lines.size()) - 1
172 : index_to_buffer[down];
173 if (up > down)
174 return nullopt;
175
176 // Search for lines [up,down] and use Myers's diff algorithm to find the best
177 // match (least edit distance).
178 int best = up, best_dist = kMaxDiff + 1;
179 const std::string& needle = index_lines[line];
180 for (int i = up; i <= down; i++) {
181 int dist = MyersDiff(needle, buffer_lines[i], kMaxDiff);
182 if (dist < best_dist) {
183 best_dist = dist;
184 best = i;
185 }
186 }
187 if (column)
188 *column =
189 AlignColumn(index_lines[line], *column, buffer_lines[best], is_end);
190 return best;
191 }
192
193 } // namespace
194
AsUnsavedFiles() const195 std::vector<CXUnsavedFile> WorkingFiles::Snapshot::AsUnsavedFiles() const {
196 std::vector<CXUnsavedFile> result;
197 result.reserve(files.size());
198 for (auto& file : files) {
199 CXUnsavedFile unsaved;
200 unsaved.Filename = file.filename.c_str();
201 unsaved.Contents = file.content.c_str();
202 unsaved.Length = (unsigned long)file.content.size();
203
204 result.push_back(unsaved);
205 }
206 return result;
207 }
208
WorkingFile(const AbsolutePath & filename,const std::string & buffer_content)209 WorkingFile::WorkingFile(const AbsolutePath& filename,
210 const std::string& buffer_content)
211 : filename(filename), buffer_content(buffer_content) {
212 OnBufferContentUpdated();
213
214 // SetIndexContent gets called when the file is opened.
215 }
216
SetIndexContent(const std::string & index_content)217 void WorkingFile::SetIndexContent(const std::string& index_content) {
218 index_lines = ToLines(index_content, false /*trim_whitespace*/);
219
220 index_to_buffer.clear();
221 buffer_to_index.clear();
222 }
223
OnBufferContentUpdated()224 void WorkingFile::OnBufferContentUpdated() {
225 buffer_lines = ToLines(buffer_content, false /*trim_whitespace*/);
226
227 index_to_buffer.clear();
228 buffer_to_index.clear();
229 }
230
231 // Variant of Paul Heckel's diff algorithm to compute |index_to_buffer| and
232 // |buffer_to_index|.
233 // The core idea is that if a line is unique in both index and buffer,
234 // we are confident that the line appeared in index maps to the one appeared in
235 // buffer. And then using them as start points to extend upwards and downwards
236 // to align other identical lines (but not unique).
ComputeLineMapping()237 void WorkingFile::ComputeLineMapping() {
238 std::unordered_map<uint64_t, int> hash_to_unique;
239 std::vector<uint64_t> index_hashes(index_lines.size());
240 std::vector<uint64_t> buffer_hashes(buffer_lines.size());
241 index_to_buffer.resize(index_lines.size());
242 buffer_to_index.resize(buffer_lines.size());
243 hash_to_unique.reserve(
244 std::max(index_to_buffer.size(), buffer_to_index.size()));
245
246 // For index line i, set index_to_buffer[i] to -1 if line i is duplicated.
247 int i = 0;
248 for (auto& line : index_lines) {
249 std::string trimmed = Trim(line);
250 uint64_t h = HashUsr(trimmed);
251 auto it = hash_to_unique.find(h);
252 if (it == hash_to_unique.end()) {
253 hash_to_unique[h] = i;
254 index_to_buffer[i] = i;
255 } else {
256 if (it->second >= 0)
257 index_to_buffer[it->second] = -1;
258 index_to_buffer[i] = it->second = -1;
259 }
260 index_hashes[i++] = h;
261 }
262
263 // For buffer line i, set buffer_to_index[i] to -1 if line i is duplicated.
264 i = 0;
265 hash_to_unique.clear();
266 for (auto& line : buffer_lines) {
267 std::string trimmed = Trim(line);
268 uint64_t h = HashUsr(trimmed);
269 auto it = hash_to_unique.find(h);
270 if (it == hash_to_unique.end()) {
271 hash_to_unique[h] = i;
272 buffer_to_index[i] = i;
273 } else {
274 if (it->second >= 0)
275 buffer_to_index[it->second] = -1;
276 buffer_to_index[i] = it->second = -1;
277 }
278 buffer_hashes[i++] = h;
279 }
280
281 // If index line i is the identical to buffer line j, and they are both
282 // unique, align them by pointing from_index[i] to j.
283 i = 0;
284 for (auto h : index_hashes) {
285 if (index_to_buffer[i] >= 0) {
286 auto it = hash_to_unique.find(h);
287 if (it != hash_to_unique.end() && it->second >= 0 &&
288 buffer_to_index[it->second] >= 0)
289 index_to_buffer[i] = it->second;
290 else
291 index_to_buffer[i] = -1;
292 }
293 i++;
294 }
295
296 // Starting at unique lines, extend upwards and downwards.
297 for (i = 0; i < (int)index_hashes.size() - 1; i++) {
298 int j = index_to_buffer[i];
299 if (0 <= j && j + 1 < buffer_hashes.size() &&
300 index_hashes[i + 1] == buffer_hashes[j + 1])
301 index_to_buffer[i + 1] = j + 1;
302 }
303 for (i = (int)index_hashes.size(); --i > 0;) {
304 int j = index_to_buffer[i];
305 if (0 < j && index_hashes[i - 1] == buffer_hashes[j - 1])
306 index_to_buffer[i - 1] = j - 1;
307 }
308
309 // |buffer_to_index| is a inverse mapping of |index_to_buffer|.
310 std::fill(buffer_to_index.begin(), buffer_to_index.end(), -1);
311 for (i = 0; i < (int)index_hashes.size(); i++)
312 if (index_to_buffer[i] >= 0)
313 buffer_to_index[index_to_buffer[i]] = i;
314 }
315
GetBufferPosFromIndexPos(int line,int * column,bool is_end)316 optional<int> WorkingFile::GetBufferPosFromIndexPos(int line,
317 int* column,
318 bool is_end) {
319 // The implementation is simple but works pretty well for most cases. We
320 // lookup the line contents in the indexed file contents, and try to find the
321 // most similar line in the current buffer file.
322 //
323 // Previously, this was implemented by tracking edits and by running myers
324 // diff algorithm. They were complex implementations that did not work as
325 // well.
326
327 // Note: |index_line| and |buffer_line| are 1-based.
328
329 // TODO: reenable this assert once we are using the real indexed file.
330 // assert(index_line >= 1 && index_line <= index_lines.size());
331 if (line < 0 || line >= (int)index_lines.size()) {
332 loguru::Text stack = loguru::stacktrace();
333 LOG_S(WARNING) << "Bad index_line (got " << line << ", expected [0, "
334 << index_lines.size() << ")) in " << filename
335 << stack.c_str();
336 return nullopt;
337 }
338
339 if (index_to_buffer.empty())
340 ComputeLineMapping();
341 return FindMatchingLine(index_lines, index_to_buffer, line, column,
342 buffer_lines, is_end);
343 }
344
GetIndexPosFromBufferPos(int line,int * column,bool is_end)345 optional<int> WorkingFile::GetIndexPosFromBufferPos(int line,
346 int* column,
347 bool is_end) {
348 // See GetBufferLineFromIndexLine for additional comments.
349 if (line < 0 || line >= (int)buffer_lines.size())
350 return nullopt;
351
352 if (buffer_to_index.empty())
353 ComputeLineMapping();
354 return FindMatchingLine(buffer_lines, buffer_to_index, line, column,
355 index_lines, is_end);
356 }
357
FindClosestCallNameInBuffer(lsPosition position,int * active_parameter,lsPosition * completion_position) const358 std::string WorkingFile::FindClosestCallNameInBuffer(
359 lsPosition position,
360 int* active_parameter,
361 lsPosition* completion_position) const {
362 *active_parameter = 0;
363
364 int offset = GetOffsetForPosition(position, buffer_content);
365
366 // If vscode auto-inserts closing ')' we will begin on ')' token in foo()
367 // which will make the below algorithm think it's a nested call.
368 if (offset > 0 && buffer_content[offset] == ')')
369 --offset;
370
371 // Scan back out of call context.
372 int balance = 0;
373 while (offset > 0) {
374 char c = buffer_content[offset];
375 if (c == ')')
376 ++balance;
377 else if (c == '(')
378 --balance;
379
380 if (balance == 0 && c == ',')
381 *active_parameter += 1;
382
383 --offset;
384
385 if (balance == -1)
386 break;
387 }
388
389 if (offset < 0)
390 return "";
391
392 // Scan back entire identifier.
393 int start_offset = offset;
394 while (offset > 0) {
395 char c = buffer_content[offset - 1];
396 if (isalnum(c) == false && c != '_')
397 break;
398 --offset;
399 }
400
401 if (completion_position)
402 *completion_position = GetPositionForOffset(buffer_content, offset);
403
404 return buffer_content.substr(offset, start_offset - offset + 1);
405 }
406
FindStableCompletionSource(lsPosition position,bool * is_global_completion,std::string * existing_completion,lsPosition * replace_end_position) const407 lsPosition WorkingFile::FindStableCompletionSource(
408 lsPosition position,
409 bool* is_global_completion,
410 std::string* existing_completion,
411 lsPosition* replace_end_position) const {
412 *is_global_completion = true;
413
414 int start_offset = GetOffsetForPosition(position, buffer_content);
415 int offset = start_offset;
416
417 while (offset > 0) {
418 char c = buffer_content[offset - 1];
419 if (!isalnum(c) && c != '_') {
420 // Global completion is everything except for dot (.), arrow (->), and
421 // double colon (::)
422 if (c == '.')
423 *is_global_completion = false;
424 if (offset > 2) {
425 char pc = buffer_content[offset - 2];
426 if (pc == ':' && c == ':')
427 *is_global_completion = false;
428 else if (pc == '-' && c == '>')
429 *is_global_completion = false;
430 }
431
432 break;
433 }
434 --offset;
435 }
436
437 *replace_end_position = position;
438 int end_offset = start_offset;
439 while (end_offset < buffer_content.size()) {
440 char c = buffer_content[end_offset];
441 if (!isalnum(c) && c != '_')
442 break;
443 ++end_offset;
444 // We know that replace_end_position and position are on the same line.
445 ++replace_end_position->character;
446 }
447
448 *existing_completion = buffer_content.substr(offset, start_offset - offset);
449 return GetPositionForOffset(buffer_content, offset);
450 }
451
GetFileByFilename(const AbsolutePath & filename)452 WorkingFile* WorkingFiles::GetFileByFilename(const AbsolutePath& filename) {
453 std::lock_guard<std::mutex> lock(files_mutex);
454 return GetFileByFilenameNoLock(filename);
455 }
456
GetFileByFilenameNoLock(const AbsolutePath & filename)457 WorkingFile* WorkingFiles::GetFileByFilenameNoLock(
458 const AbsolutePath& filename) {
459 for (auto& file : files) {
460 if (file->filename == filename)
461 return file.get();
462 }
463 return nullptr;
464 }
465
DoAction(const std::function<void ()> & action)466 void WorkingFiles::DoAction(const std::function<void()>& action) {
467 std::lock_guard<std::mutex> lock(files_mutex);
468 action();
469 }
470
DoActionOnFile(const AbsolutePath & filename,const std::function<void (WorkingFile * file)> & action)471 void WorkingFiles::DoActionOnFile(
472 const AbsolutePath& filename,
473 const std::function<void(WorkingFile* file)>& action) {
474 std::lock_guard<std::mutex> lock(files_mutex);
475 WorkingFile* file = GetFileByFilenameNoLock(filename);
476 action(file);
477 }
478
OnOpen(const lsTextDocumentItem & open)479 WorkingFile* WorkingFiles::OnOpen(const lsTextDocumentItem& open) {
480 std::lock_guard<std::mutex> lock(files_mutex);
481
482 AbsolutePath filename = open.uri.GetAbsolutePath();
483 std::string content = open.text;
484
485 // The file may already be open.
486 if (WorkingFile* file = GetFileByFilenameNoLock(filename)) {
487 file->version = open.version;
488 file->buffer_content = content;
489 file->OnBufferContentUpdated();
490 return file;
491 }
492
493 files.push_back(std::make_unique<WorkingFile>(filename, content));
494 return files[files.size() - 1].get();
495 }
496
OnChange(const lsTextDocumentDidChangeParams & change)497 void WorkingFiles::OnChange(const lsTextDocumentDidChangeParams& change) {
498 std::lock_guard<std::mutex> lock(files_mutex);
499
500 AbsolutePath filename = change.textDocument.uri.GetAbsolutePath();
501 WorkingFile* file = GetFileByFilenameNoLock(filename);
502 if (!file) {
503 LOG_S(WARNING) << "Could not change " << filename
504 << " because it was not open";
505 return;
506 }
507
508 if (change.textDocument.version)
509 file->version = *change.textDocument.version;
510
511 for (const lsTextDocumentContentChangeEvent& diff : change.contentChanges) {
512 // Per the spec replace everything if the rangeLength and range are not set.
513 // See https://github.com/Microsoft/language-server-protocol/issues/9.
514 if (!diff.range) {
515 file->buffer_content = diff.text;
516 file->OnBufferContentUpdated();
517 } else {
518 int start_offset =
519 GetOffsetForPosition(diff.range->start, file->buffer_content);
520 // Ignore TextDocumentContentChangeEvent.rangeLength which causes trouble
521 // when UTF-16 surrogate pairs are used.
522 int end_offset =
523 GetOffsetForPosition(diff.range->end, file->buffer_content);
524 file->buffer_content.replace(file->buffer_content.begin() + start_offset,
525 file->buffer_content.begin() + end_offset,
526 diff.text);
527 file->OnBufferContentUpdated();
528 }
529 }
530 }
531
OnClose(const lsTextDocumentIdentifier & close)532 void WorkingFiles::OnClose(const lsTextDocumentIdentifier& close) {
533 std::lock_guard<std::mutex> lock(files_mutex);
534
535 AbsolutePath filename = close.uri.GetAbsolutePath();
536
537 for (int i = 0; i < files.size(); ++i) {
538 if (files[i]->filename == filename) {
539 files.erase(files.begin() + i);
540 return;
541 }
542 }
543
544 LOG_S(WARNING) << "Could not close " << filename
545 << " because it was not open";
546 }
547
AsSnapshot(const std::vector<std::string> & filter_paths)548 WorkingFiles::Snapshot WorkingFiles::AsSnapshot(
549 const std::vector<std::string>& filter_paths) {
550 std::lock_guard<std::mutex> lock(files_mutex);
551
552 Snapshot result;
553 result.files.reserve(files.size());
554 for (const auto& file : files) {
555 if (filter_paths.empty() ||
556 FindAnyPartial(file->filename.path, filter_paths))
557 result.files.push_back({file->filename.path, file->buffer_content});
558 }
559 return result;
560 }
561
CharPos(const WorkingFile & file,char character,int character_offset=0)562 lsPosition CharPos(const WorkingFile& file,
563 char character,
564 int character_offset = 0) {
565 return CharPos(file.buffer_content, character, character_offset);
566 }
567
568 TEST_SUITE("WorkingFile") {
569 TEST_CASE("simple call") {
570 WorkingFile f(AbsolutePath::BuildDoNotUse("foo.cc"), "abcd(1, 2");
571 int active_param = 0;
572 REQUIRE(f.FindClosestCallNameInBuffer(CharPos(f, '('), &active_param) ==
573 "abcd");
574 REQUIRE(active_param == 0);
575 REQUIRE(f.FindClosestCallNameInBuffer(CharPos(f, '1'), &active_param) ==
576 "abcd");
577 REQUIRE(active_param == 0);
578 REQUIRE(f.FindClosestCallNameInBuffer(CharPos(f, ','), &active_param) ==
579 "abcd");
580 REQUIRE(active_param == 1);
581 REQUIRE(f.FindClosestCallNameInBuffer(CharPos(f, ' '), &active_param) ==
582 "abcd");
583 REQUIRE(active_param == 1);
584 REQUIRE(f.FindClosestCallNameInBuffer(CharPos(f, '2'), &active_param) ==
585 "abcd");
586 REQUIRE(active_param == 1);
587 }
588
589 TEST_CASE("nested call") {
590 WorkingFile f(AbsolutePath::BuildDoNotUse("foo.cc"), "abcd(efg(), 2");
591 int active_param = 0;
592 REQUIRE(f.FindClosestCallNameInBuffer(CharPos(f, '('), &active_param) ==
593 "abcd");
594 REQUIRE(active_param == 0);
595 REQUIRE(f.FindClosestCallNameInBuffer(CharPos(f, 'e'), &active_param) ==
596 "abcd");
597 REQUIRE(active_param == 0);
598 REQUIRE(f.FindClosestCallNameInBuffer(CharPos(f, 'f'), &active_param) ==
599 "abcd");
600 REQUIRE(active_param == 0);
601 REQUIRE(f.FindClosestCallNameInBuffer(CharPos(f, 'g'), &active_param) ==
602 "abcd");
603 REQUIRE(active_param == 0);
604 REQUIRE(f.FindClosestCallNameInBuffer(CharPos(f, 'g', 1), &active_param) ==
605 "efg");
606 REQUIRE(active_param == 0);
607 REQUIRE(f.FindClosestCallNameInBuffer(CharPos(f, 'g', 2), &active_param) ==
608 "efg");
609 REQUIRE(active_param == 0);
610 REQUIRE(f.FindClosestCallNameInBuffer(CharPos(f, ','), &active_param) ==
611 "abcd");
612 REQUIRE(active_param == 1);
613 REQUIRE(f.FindClosestCallNameInBuffer(CharPos(f, ' '), &active_param) ==
614 "abcd");
615 REQUIRE(active_param == 1);
616 }
617
618 TEST_CASE("auto-insert )") {
619 WorkingFile f(AbsolutePath::BuildDoNotUse("foo.cc"), "abc()");
620 int active_param = 0;
621 REQUIRE(f.FindClosestCallNameInBuffer(CharPos(f, ')'), &active_param) ==
622 "abc");
623 REQUIRE(active_param == 0);
624 }
625
626 TEST_CASE("existing completion") {
627 WorkingFile f(AbsolutePath::BuildDoNotUse("foo.cc"), "zzz.asdf ");
628 bool is_global_completion;
629 std::string existing_completion;
630 lsPosition end_pos;
631
632 f.FindStableCompletionSource(CharPos(f, '.'), &is_global_completion,
633 &existing_completion, &end_pos);
634 REQUIRE(existing_completion == "zzz");
635 REQUIRE(end_pos.line == CharPos(f, '.').line);
636 REQUIRE(end_pos.character == CharPos(f, '.').character);
637 f.FindStableCompletionSource(CharPos(f, 'a', 1), &is_global_completion,
638 &existing_completion, &end_pos);
639 REQUIRE(existing_completion == "a");
640 REQUIRE(end_pos.line == CharPos(f, ' ').line);
641 REQUIRE(end_pos.character == CharPos(f, ' ').character);
642 f.FindStableCompletionSource(CharPos(f, 's', 1), &is_global_completion,
643 &existing_completion, &end_pos);
644 REQUIRE(existing_completion == "as");
645 REQUIRE(end_pos.line == CharPos(f, ' ').line);
646 REQUIRE(end_pos.character == CharPos(f, ' ').character);
647 f.FindStableCompletionSource(CharPos(f, 'd', 1), &is_global_completion,
648 &existing_completion, &end_pos);
649 REQUIRE(existing_completion == "asd");
650 REQUIRE(end_pos.line == CharPos(f, ' ').line);
651 REQUIRE(end_pos.character == CharPos(f, ' ').character);
652 f.FindStableCompletionSource(CharPos(f, 'f', 1), &is_global_completion,
653 &existing_completion, &end_pos);
654 REQUIRE(existing_completion == "asdf");
655 REQUIRE(end_pos.line == CharPos(f, ' ').line);
656 REQUIRE(end_pos.character == CharPos(f, ' ').character);
657 }
658
659 TEST_CASE("existing completion underscore") {
660 WorkingFile f(AbsolutePath::BuildDoNotUse("foo.cc"), "ABC_DEF ");
661 bool is_global_completion;
662 std::string existing_completion;
663 lsPosition end_pos;
664
665 f.FindStableCompletionSource(CharPos(f, 'C'), &is_global_completion,
666 &existing_completion, &end_pos);
667 REQUIRE(existing_completion == "AB");
668 REQUIRE(end_pos.line == CharPos(f, ' ').line);
669 REQUIRE(end_pos.character == CharPos(f, ' ').character);
670 f.FindStableCompletionSource(CharPos(f, '_'), &is_global_completion,
671 &existing_completion, &end_pos);
672 REQUIRE(existing_completion == "ABC");
673 REQUIRE(end_pos.line == CharPos(f, ' ').line);
674 REQUIRE(end_pos.character == CharPos(f, ' ').character);
675 f.FindStableCompletionSource(CharPos(f, 'D'), &is_global_completion,
676 &existing_completion, &end_pos);
677 REQUIRE(existing_completion == "ABC_");
678 REQUIRE(end_pos.line == CharPos(f, ' ').line);
679 REQUIRE(end_pos.character == CharPos(f, ' ').character);
680 }
681 }
682