1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25 
26 #include "clangassistproposalitem.h"
27 
28 #include "clangcompletionchunkstotextconverter.h"
29 #include "clangfixitoperation.h"
30 #include "clangutils.h"
31 
32 #include <cplusplus/Icons.h>
33 #include <cplusplus/MatchingText.h>
34 #include <cplusplus/SimpleLexer.h>
35 #include <cplusplus/Token.h>
36 
37 #include <texteditor/completionsettings.h>
38 #include <texteditor/texteditor.h>
39 #include <texteditor/texteditorsettings.h>
40 
41 #include <QCoreApplication>
42 #include <QTextBlock>
43 #include <QTextCursor>
44 #include <QTextDocument>
45 
46 #include <utils/algorithm.h>
47 #include <utils/textutils.h>
48 #include <utils/qtcassert.h>
49 
50 using namespace CPlusPlus;
51 using namespace ClangBackEnd;
52 using namespace TextEditor;
53 using namespace Utils;
54 
55 namespace ClangCodeModel {
56 namespace Internal {
57 
prematurelyApplies(const QChar & typedCharacter) const58 bool ClangAssistProposalItem::prematurelyApplies(const QChar &typedCharacter) const
59 {
60     bool applies = false;
61 
62     if (m_completionOperator == T_SIGNAL || m_completionOperator == T_SLOT)
63         applies = QString::fromLatin1("(,").contains(typedCharacter);
64     else if (m_completionOperator == T_STRING_LITERAL || m_completionOperator == T_ANGLE_STRING_LITERAL)
65         applies = (typedCharacter == QLatin1Char('/')) && text().endsWith(QLatin1Char('/'));
66     else if (firstCodeCompletion().completionKind == CodeCompletion::ObjCMessageCompletionKind)
67         applies = QString::fromLatin1(";.,").contains(typedCharacter);
68     else
69         applies = QString::fromLatin1(";.,:(").contains(typedCharacter);
70 
71     if (applies)
72         m_typedCharacter = typedCharacter;
73 
74     return applies;
75 }
76 
implicitlyApplies() const77 bool ClangAssistProposalItem::implicitlyApplies() const
78 {
79     return true;
80 }
81 
textUntilPreviousStatement(TextDocumentManipulatorInterface & manipulator,int startPosition)82 static QString textUntilPreviousStatement(TextDocumentManipulatorInterface &manipulator,
83                                           int startPosition)
84 {
85     static const QString stopCharacters(";{}#");
86 
87     int endPosition = 0;
88     for (int i = startPosition; i >= 0 ; --i) {
89         if (stopCharacters.contains(manipulator.characterAt(i))) {
90             endPosition = i + 1;
91             break;
92         }
93     }
94 
95     return manipulator.textAt(endPosition, startPosition - endPosition);
96 }
97 
98 // 7.3.3: using typename(opt) nested-name-specifier unqualified-id ;
isAtUsingDeclaration(TextDocumentManipulatorInterface & manipulator,int basePosition)99 static bool isAtUsingDeclaration(TextDocumentManipulatorInterface &manipulator,
100                                  int basePosition)
101 {
102     SimpleLexer lexer;
103     lexer.setLanguageFeatures(LanguageFeatures::defaultFeatures());
104     const QString textToLex = textUntilPreviousStatement(manipulator, basePosition);
105     const Tokens tokens = lexer(textToLex);
106     if (tokens.empty())
107         return false;
108 
109     // The nested-name-specifier always ends with "::", so check for this first.
110     const Token lastToken = tokens[tokens.size() - 1];
111     if (lastToken.kind() != T_COLON_COLON)
112         return false;
113 
114     return contains(tokens, [](const Token &token) { return token.kind() == T_USING; });
115 }
116 
methodDefinitionParameters(const CodeCompletionChunks & chunks)117 static QString methodDefinitionParameters(const CodeCompletionChunks &chunks)
118 {
119     QString result;
120 
121     auto typedTextChunkIt = std::find_if(chunks.cbegin(), chunks.cend(),
122                                          [](const CodeCompletionChunk &chunk) {
123         return chunk.kind == CodeCompletionChunk::TypedText;
124     });
125     if (typedTextChunkIt == chunks.cend())
126         return result;
127 
128     std::for_each(++typedTextChunkIt, chunks.cend(), [&result](const CodeCompletionChunk &chunk) {
129         if (chunk.kind == CodeCompletionChunk::Placeholder && chunk.text.contains('=')) {
130             Utf8String text = chunk.text.mid(0, chunk.text.indexOf('='));
131             if (text.endsWith(' '))
132                 text.chop(1);
133             result += text;
134         } else {
135             result += chunk.text;
136         }
137     });
138 
139     return result;
140 }
141 
skipParenForFunctionLikeSnippet(const std::vector<int> & placeholderPositions,const QString & text,int position)142 static bool skipParenForFunctionLikeSnippet(const std::vector<int> &placeholderPositions,
143                                             const QString &text,
144                                             int position)
145 {
146     return placeholderPositions.size() == 1
147            && position > 0
148            && text[position - 1] == '('
149            && text[position] == ')'
150            && position + 1 == text.size();
151 }
152 
isFuncDeclAsSingleTypedText(const CodeCompletion & completion)153 static bool isFuncDeclAsSingleTypedText(const CodeCompletion &completion)
154 {
155     // There is no libclang API to tell function call items from declaration items apart.
156     // However, the chunks differ for these items (c-index-test -code-completion-at=...):
157     //   An (override) declaration (available in derived class scope):
158     //     CXXMethod:{TypedText void hello() override} (40)
159     //   A function call:
160     //     CXXMethod:{ResultType void}{TypedText hello}{LeftParen (}{RightParen )} (36)
161     return completion.completionKind == CodeCompletion::FunctionDefinitionCompletionKind
162            && completion.chunks.size() == 1
163            && completion.chunks[0].kind == CodeCompletionChunk::TypedText;
164 }
165 
apply(TextDocumentManipulatorInterface & manipulator,int basePosition) const166 void ClangAssistProposalItem::apply(TextDocumentManipulatorInterface &manipulator,
167                                     int basePosition) const
168 {
169     const CodeCompletion ccr = firstCodeCompletion();
170 
171     if (!ccr.requiredFixIts.empty()) {
172         // Important: Calculate shift before changing the document.
173         basePosition += fixItsShift(manipulator);
174 
175         ClangFixItOperation fixItOperation(Utf8String(), ccr.requiredFixIts);
176         fixItOperation.perform();
177     }
178 
179     QString textToBeInserted = m_text;
180     QString extraCharacters;
181     int extraLength = 0;
182     int cursorOffset = 0;
183     bool setAutoCompleteSkipPos = false;
184     int currentPosition = manipulator.currentPosition();
185 
186     if (m_completionOperator == T_SIGNAL || m_completionOperator == T_SLOT) {
187         extraCharacters += QLatin1Char(')');
188         if (m_typedCharacter == QLatin1Char('(')) // Eat the opening parenthesis
189             m_typedCharacter = QChar();
190     } else if (ccr.completionKind == CodeCompletion::KeywordCompletionKind) {
191         CompletionChunksToTextConverter converter;
192         converter.setupForKeywords();
193         converter.parseChunks(ccr.chunks);
194 
195         textToBeInserted = converter.text();
196 
197         if (converter.hasPlaceholderPositions()) {
198             const std::vector<int> &placeholderPositions = converter.placeholderPositions();
199             const int position = placeholderPositions[0];
200             cursorOffset = position - converter.text().size();
201             // If the snippet looks like a function call, e.g. "sizeof(<PLACEHOLDER>)",
202             // ensure that we can "overtype" ')' after inserting it.
203             setAutoCompleteSkipPos = skipParenForFunctionLikeSnippet(placeholderPositions,
204                                                                      textToBeInserted,
205                                                                      position);
206         }
207     } else if (ccr.completionKind == CodeCompletion::NamespaceCompletionKind) {
208         CompletionChunksToTextConverter converter;
209         converter.parseChunks(ccr.chunks); // Appends "::" after name space name
210         textToBeInserted = converter.text();
211 
212         // Clang does not provide the "::" chunk consistently for namespaces, e.g.
213         //
214         //    namespace a { namespace b { namespace c {} } }
215         //    <CURSOR> // complete "a" ==> "a::"
216         //    a::<CURSOR> // complete "b" ==> "b", not "b::"
217         //
218         // Remove "::" to avoid any confusion for now.
219         if (textToBeInserted.endsWith("::"))
220             textToBeInserted.chop(2);
221     } else if (!ccr.text.isEmpty()) {
222         const CompletionSettings &completionSettings =
223                 TextEditorSettings::completionSettings();
224         const bool autoInsertBrackets = completionSettings.m_autoInsertBrackets;
225 
226         if (autoInsertBrackets &&
227                 (ccr.completionKind == CodeCompletion::FunctionCompletionKind
228                  || ccr.completionKind == CodeCompletion::FunctionDefinitionCompletionKind
229                  || ccr.completionKind == CodeCompletion::DestructorCompletionKind
230                  || ccr.completionKind == CodeCompletion::ConstructorCompletionKind
231                  || ccr.completionKind == CodeCompletion::SignalCompletionKind
232                  || ccr.completionKind == CodeCompletion::SlotCompletionKind)) {
233             // When the user typed the opening parenthesis, he'll likely also type the closing one,
234             // in which case it would be annoying if we put the cursor after the already automatically
235             // inserted closing parenthesis.
236             const bool skipClosingParenthesis = m_typedCharacter != QLatin1Char('(');
237             QTextCursor cursor = manipulator.textCursorAt(basePosition);
238 
239             bool abandonParen = false;
240             if (matchPreviousWord(manipulator, cursor, "&")) {
241                 moveToPreviousWord(manipulator, cursor);
242                 moveToPreviousChar(manipulator, cursor);
243                 const QChar prevChar = manipulator.characterAt(cursor.position());
244                 cursor.setPosition(basePosition);
245                 abandonParen = QString("(;,{}").contains(prevChar);
246             }
247             if (!abandonParen) {
248                 const bool isFullDecl = isFuncDeclAsSingleTypedText(ccr);
249                 if (isFullDecl)
250                     extraCharacters += QLatin1Char(';');
251                 abandonParen = isAtUsingDeclaration(manipulator, basePosition) || isFullDecl;
252             }
253 
254             if (!abandonParen && ccr.completionKind == CodeCompletion::FunctionDefinitionCompletionKind) {
255                 const CodeCompletionChunk resultType = ccr.chunks.first();
256                 if (resultType.kind == CodeCompletionChunk::ResultType) {
257                     if (matchPreviousWord(manipulator, cursor, resultType.text.toString())) {
258                         extraCharacters += methodDefinitionParameters(ccr.chunks);
259                         // To skip the next block.
260                         abandonParen = true;
261                     }
262                 } else {
263                     // Do nothing becasue it's not a function definition.
264 
265                     // It's a clang bug that the function might miss a ResultType chunk
266                     // when the base class method is called from the overriding method
267                     // of the derived class. For example:
268                     // void Derived::foo() override { Base::<complete here> }
269                 }
270             }
271             if (!abandonParen) {
272                 if (completionSettings.m_spaceAfterFunctionName)
273                     extraCharacters += QLatin1Char(' ');
274                 extraCharacters += QLatin1Char('(');
275                 if (m_typedCharacter == QLatin1Char('('))
276                     m_typedCharacter = QChar();
277 
278                 // If the function doesn't return anything, automatically place the semicolon,
279                 // unless we're doing a scope completion (then it might be function definition).
280                 const QChar characterAtCursor = manipulator.characterAt(currentPosition);
281                 bool endWithSemicolon = m_typedCharacter == QLatin1Char(';')/*
282                                                 || (function->returnType()->isVoidType() && m_completionOperator != T_COLON_COLON)*/; //###
283                 const QChar semicolon = m_typedCharacter.isNull() ? QLatin1Char(';') : m_typedCharacter;
284 
285                 if (endWithSemicolon && characterAtCursor == semicolon) {
286                     endWithSemicolon = false;
287                     m_typedCharacter = QChar();
288                 }
289 
290                 // If the function takes no arguments, automatically place the closing parenthesis
291                 if (!hasOverloadsWithParameters() && !ccr.hasParameters && skipClosingParenthesis) {
292                     extraCharacters += QLatin1Char(')');
293                     if (endWithSemicolon) {
294                         extraCharacters += semicolon;
295                         m_typedCharacter = QChar();
296                     }
297                 } else {
298                     const QChar lookAhead = manipulator.characterAt(manipulator.currentPosition() + 1);
299                     if (MatchingText::shouldInsertMatchingText(lookAhead)) {
300                         extraCharacters += QLatin1Char(')');
301                         --cursorOffset;
302                         setAutoCompleteSkipPos = true;
303                         if (endWithSemicolon) {
304                             extraCharacters += semicolon;
305                             --cursorOffset;
306                             m_typedCharacter = QChar();
307                         }
308                     }
309                 }
310             }
311         }
312     }
313 
314     // Append an unhandled typed character, adjusting cursor offset when it had been adjusted before
315     if (!m_typedCharacter.isNull()) {
316         extraCharacters += m_typedCharacter;
317         if (cursorOffset != 0)
318             --cursorOffset;
319     }
320 
321     // Avoid inserting characters that are already there
322     QTextCursor cursor = manipulator.textCursorAt(basePosition);
323     cursor.movePosition(QTextCursor::EndOfWord);
324     const QString textAfterCursor = manipulator.textAt(currentPosition,
325                                                        cursor.position() - currentPosition);
326 
327     if (textToBeInserted != textAfterCursor
328             && textToBeInserted.indexOf(textAfterCursor, currentPosition - basePosition) >= 0) {
329         currentPosition = cursor.position();
330     }
331 
332     for (int i = 0; i < extraCharacters.length(); ++i) {
333         const QChar a = extraCharacters.at(i);
334         const QChar b = manipulator.characterAt(currentPosition + i);
335         if (a == b)
336             ++extraLength;
337         else
338             break;
339     }
340 
341     textToBeInserted += extraCharacters;
342 
343     const int length = currentPosition - basePosition + extraLength;
344 
345     const bool isReplaced = manipulator.replace(basePosition, length, textToBeInserted);
346     manipulator.setCursorPosition(basePosition + textToBeInserted.length());
347     if (isReplaced) {
348         if (cursorOffset)
349             manipulator.setCursorPosition(manipulator.currentPosition() + cursorOffset);
350         if (setAutoCompleteSkipPos)
351             manipulator.setAutoCompleteSkipPosition(manipulator.currentPosition());
352 
353         if (ccr.completionKind == CodeCompletion::KeywordCompletionKind)
354             manipulator.autoIndent(basePosition, textToBeInserted.size());
355     }
356 }
357 
setText(const QString & text)358 void ClangAssistProposalItem::setText(const QString &text)
359 {
360     m_text = text;
361 }
362 
text() const363 QString ClangAssistProposalItem::text() const
364 {
365     return m_text;
366 }
367 
firstCompletionFixIts() const368 const QVector<ClangBackEnd::FixItContainer> &ClangAssistProposalItem::firstCompletionFixIts() const
369 {
370     return firstCodeCompletion().requiredFixIts;
371 }
372 
fixItPositionsRange(const FixItContainer & fixIt,const QTextCursor & cursor)373 std::pair<int, int> fixItPositionsRange(const FixItContainer &fixIt, const QTextCursor &cursor)
374 {
375     const QTextBlock startLine = cursor.document()->findBlockByNumber(fixIt.range.start.line - 1);
376     const QTextBlock endLine = cursor.document()->findBlockByNumber(fixIt.range.end.line - 1);
377 
378     const int fixItStartPos = Text::positionInText(
379                 cursor.document(),
380                 fixIt.range.start.line,
381                 cppEditorColumn(startLine, fixIt.range.start.column));
382     const int fixItEndPos = Text::positionInText(
383                 cursor.document(),
384                 fixIt.range.end.line,
385                 cppEditorColumn(endLine, fixIt.range.end.column));
386     return std::make_pair(fixItStartPos, fixItEndPos);
387 }
388 
textReplacedByFixit(const FixItContainer & fixIt)389 static QString textReplacedByFixit(const FixItContainer &fixIt)
390 {
391     TextEditorWidget *textEditorWidget = TextEditorWidget::currentTextEditorWidget();
392     if (!textEditorWidget)
393         return QString();
394     const std::pair<int, int> fixItPosRange = fixItPositionsRange(fixIt,
395                                                                   textEditorWidget->textCursor());
396     return textEditorWidget->textAt(fixItPosRange.first,
397                                     fixItPosRange.second - fixItPosRange.first);
398 }
399 
fixItText() const400 QString ClangAssistProposalItem::fixItText() const
401 {
402     const FixItContainer &fixIt = firstCompletionFixIts().first();
403     return QCoreApplication::translate("ClangCodeModel::ClangAssistProposalItem",
404                                        "Requires changing \"%1\" to \"%2\"")
405             .arg(textReplacedByFixit(fixIt), fixIt.text.toString());
406 }
407 
fixItsShift(const TextDocumentManipulatorInterface & manipulator) const408 int ClangAssistProposalItem::fixItsShift(const TextDocumentManipulatorInterface &manipulator) const
409 {
410     const QVector<ClangBackEnd::FixItContainer> &requiredFixIts = firstCompletionFixIts();
411     if (requiredFixIts.empty())
412         return 0;
413 
414     int shift = 0;
415     const QTextCursor cursor = manipulator.textCursorAt(0);
416     for (const FixItContainer &fixIt : requiredFixIts) {
417         const std::pair<int, int> fixItPosRange = fixItPositionsRange(fixIt, cursor);
418         shift += fixIt.text.toString().length() - (fixItPosRange.second - fixItPosRange.first);
419     }
420     return shift;
421 }
422 
icon() const423 QIcon ClangAssistProposalItem::icon() const
424 {
425     using namespace CPlusPlus::Icons;
426     static const char SNIPPET_ICON_PATH[] = ":/texteditor/images/snippet.png";
427     static const QIcon snippetIcon = QIcon(QLatin1String(SNIPPET_ICON_PATH));
428 
429     const ClangBackEnd::CodeCompletion &completion = firstCodeCompletion();
430     switch (completion.completionKind) {
431         case CodeCompletion::ClassCompletionKind:
432         case CodeCompletion::TemplateClassCompletionKind:
433         case CodeCompletion::TypeAliasCompletionKind:
434             return CodeModelIcon::iconForType(CodeModelIcon::Class);
435         case CodeCompletion::EnumerationCompletionKind:
436             return CodeModelIcon::iconForType(CodeModelIcon::Enum);
437         case CodeCompletion::EnumeratorCompletionKind:
438             return CodeModelIcon::iconForType(CodeModelIcon::Enumerator);
439         case CodeCompletion::ConstructorCompletionKind:
440         case CodeCompletion::DestructorCompletionKind:
441         case CodeCompletion::FunctionCompletionKind:
442         case CodeCompletion::FunctionDefinitionCompletionKind:
443         case CodeCompletion::TemplateFunctionCompletionKind:
444         case CodeCompletion::ObjCMessageCompletionKind:
445             switch (completion.availability) {
446                 case CodeCompletion::Available:
447                 case CodeCompletion::Deprecated:
448                     return CodeModelIcon::iconForType(CodeModelIcon::FuncPublic);
449                 default:
450                     return CodeModelIcon::iconForType(CodeModelIcon::FuncPrivate);
451             }
452         case CodeCompletion::SignalCompletionKind:
453             return CodeModelIcon::iconForType(CodeModelIcon::Signal);
454         case CodeCompletion::SlotCompletionKind:
455             switch (completion.availability) {
456                 case CodeCompletion::Available:
457                 case CodeCompletion::Deprecated:
458                     return CodeModelIcon::iconForType(CodeModelIcon::SlotPublic);
459                 case CodeCompletion::NotAccessible:
460                 case CodeCompletion::NotAvailable:
461                     return CodeModelIcon::iconForType(CodeModelIcon::SlotPrivate);
462             }
463             break;
464         case CodeCompletion::NamespaceCompletionKind:
465             return CodeModelIcon::iconForType(CodeModelIcon::Namespace);
466         case CodeCompletion::PreProcessorCompletionKind:
467             return CodeModelIcon::iconForType(CodeModelIcon::Macro);
468         case CodeCompletion::VariableCompletionKind:
469             switch (completion.availability) {
470                 case CodeCompletion::Available:
471                 case CodeCompletion::Deprecated:
472                     return CodeModelIcon::iconForType(CodeModelIcon::VarPublic);
473                 default:
474                     return CodeModelIcon::iconForType(CodeModelIcon::VarPrivate);
475             }
476         case CodeCompletion::KeywordCompletionKind:
477             return CodeModelIcon::iconForType(CodeModelIcon::Keyword);
478         case CodeCompletion::ClangSnippetKind:
479             return snippetIcon;
480         case CodeCompletion::Other:
481             return CodeModelIcon::iconForType(CodeModelIcon::Unknown);
482         default:
483             break;
484     }
485 
486     return QIcon();
487 }
488 
detail() const489 QString ClangAssistProposalItem::detail() const
490 {
491     QString detail;
492     for (const ClangBackEnd::CodeCompletion &codeCompletion : m_codeCompletions) {
493         if (!detail.isEmpty())
494             detail += "<br>";
495         detail += CompletionChunksToTextConverter::convertToToolTipWithHtml(
496                     codeCompletion.chunks, codeCompletion.completionKind);
497 
498         if (!codeCompletion.briefComment.isEmpty())
499             detail += "<br>" + codeCompletion.briefComment.toString();
500     }
501 
502     if (requiresFixIts())
503         detail += "<br><br><b>" + fixItText() + "</b>";
504 
505     return detail;
506 }
507 
isKeyword() const508 bool ClangAssistProposalItem::isKeyword() const
509 {
510     // KeywordCompletionKind includes real keywords but also "code patterns"/snippets.
511     return m_codeCompletions[0].completionKind == CodeCompletion::KeywordCompletionKind;
512 }
513 
detailFormat() const514 Qt::TextFormat ClangAssistProposalItem::detailFormat() const
515 {
516     return Qt::RichText;
517 }
518 
isSnippet() const519 bool ClangAssistProposalItem::isSnippet() const
520 {
521     return false;
522 }
523 
isValid() const524 bool ClangAssistProposalItem::isValid() const
525 {
526     return true;
527 }
528 
hash() const529 quint64 ClangAssistProposalItem::hash() const
530 {
531     return 0;
532 }
533 
requiresFixIts() const534 bool ClangAssistProposalItem::requiresFixIts() const
535 {
536     return !firstCompletionFixIts().empty();
537 }
538 
hasOverloadsWithParameters() const539 bool ClangAssistProposalItem::hasOverloadsWithParameters() const
540 {
541     return m_hasOverloadsWithParameters;
542 }
543 
setHasOverloadsWithParameters(bool hasOverloadsWithParameters)544 void ClangAssistProposalItem::setHasOverloadsWithParameters(bool hasOverloadsWithParameters)
545 {
546     m_hasOverloadsWithParameters = hasOverloadsWithParameters;
547 }
548 
keepCompletionOperator(unsigned compOp)549 void ClangAssistProposalItem::keepCompletionOperator(unsigned compOp)
550 {
551     m_completionOperator = compOp;
552 }
553 
appendCodeCompletion(const CodeCompletion & codeCompletion)554 void ClangAssistProposalItem::appendCodeCompletion(const CodeCompletion &codeCompletion)
555 {
556     m_codeCompletions.push_back(codeCompletion);
557 }
558 
firstCodeCompletion() const559 const ClangBackEnd::CodeCompletion &ClangAssistProposalItem::firstCodeCompletion() const
560 {
561     return m_codeCompletions.at(0);
562 }
563 
removeFirstCodeCompletion()564 void ClangAssistProposalItem::removeFirstCodeCompletion()
565 {
566     QTC_ASSERT(!m_codeCompletions.empty(), return;);
567     m_codeCompletions.erase(m_codeCompletions.begin());
568 }
569 
570 } // namespace Internal
571 } // namespace ClangCodeModel
572