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 "clangactivationsequenceprocessor.h"
29 #include "clangassistproposalmodel.h"
30 #include "clangcompletionassistprocessor.h"
31 #include "clangcompletioncontextanalyzer.h"
32 #include "clangfixitoperation.h"
33 #include "clangfunctionhintmodel.h"
34 #include "clangcompletionchunkstotextconverter.h"
35 #include "clangpreprocessorassistproposalitem.h"
36 
37 #include <cpptools/cppdoxygen.h>
38 #include <cpptools/cppmodelmanager.h>
39 #include <cpptools/cpptoolsbridge.h>
40 #include <cpptools/editordocumenthandle.h>
41 
42 #include <texteditor/codeassist/assistproposalitem.h>
43 #include <texteditor/codeassist/functionhintproposal.h>
44 #include <texteditor/codeassist/genericproposal.h>
45 #include <texteditor/codeassist/ifunctionhintproposalmodel.h>
46 #include <texteditor/texteditorsettings.h>
47 
48 #include <cplusplus/BackwardsScanner.h>
49 #include <cplusplus/ExpressionUnderCursor.h>
50 #include <cplusplus/Icons.h>
51 #include <cplusplus/SimpleLexer.h>
52 
53 #include <clangsupport/filecontainer.h>
54 
55 #include <utils/algorithm.h>
56 #include <utils/mimetypes/mimedatabase.h>
57 #include <utils/optional.h>
58 #include <utils/porting.h>
59 #include <utils/qtcassert.h>
60 #include <utils/textutils.h>
61 
62 #include <QDirIterator>
63 #include <QPair>
64 #include <QTextDocument>
65 
66 namespace ClangCodeModel {
67 namespace Internal {
68 
69 using ClangBackEnd::CodeCompletion;
70 using TextEditor::AssistProposalItemInterface;
71 
addAssistProposalItem(QList<AssistProposalItemInterface * > & items,const CodeCompletion & codeCompletion,const QString & name)72 static void addAssistProposalItem(QList<AssistProposalItemInterface *> &items,
73                                   const CodeCompletion &codeCompletion,
74                                   const QString &name)
75 {
76     auto item = new ClangAssistProposalItem;
77     items.push_back(item);
78 
79     item->setText(name);
80     item->setOrder(int(codeCompletion.priority));
81     item->appendCodeCompletion(codeCompletion);
82 }
83 
84 // Add the next CXXMethod or CXXConstructor which is the overload for another existing item.
addFunctionOverloadAssistProposalItem(QList<AssistProposalItemInterface * > & items,AssistProposalItemInterface * sameItem,const ClangCompletionAssistInterface * interface,const CodeCompletion & codeCompletion,const QString & name)85 static void addFunctionOverloadAssistProposalItem(QList<AssistProposalItemInterface *> &items,
86                                                   AssistProposalItemInterface *sameItem,
87                                                   const ClangCompletionAssistInterface *interface,
88                                                   const CodeCompletion &codeCompletion,
89                                                   const QString &name)
90 {
91     auto *item = static_cast<ClangAssistProposalItem *>(sameItem);
92     item->setHasOverloadsWithParameters(codeCompletion.hasParameters);
93     if (codeCompletion.completionKind == CodeCompletion::ConstructorCompletionKind) {
94         // It's the constructor, currently constructor definitions do not lead here.
95         // CLANG-UPGRADE-CHECK: Can we get here with constructor definition?
96         item->appendCodeCompletion(codeCompletion);
97         return;
98     }
99 
100     QTextCursor cursor = interface->textEditorWidget()->textCursor();
101     cursor.setPosition(interface->position());
102     cursor.movePosition(QTextCursor::StartOfWord);
103 
104     const ClangBackEnd::CodeCompletionChunk resultType = codeCompletion.chunks.first();
105     if (matchPreviousWord(*interface->textEditorWidget(), cursor, resultType.text.toString())) {
106         // Function definition completion - do not merge completions together.
107         addAssistProposalItem(items, codeCompletion, name);
108     } else {
109         item->appendCodeCompletion(codeCompletion);
110     }
111 }
112 
113 // Check if they are both CXXMethod or CXXConstructor.
isTheSameFunctionOverload(const CodeCompletion & completion,const QString & name,ClangAssistProposalItem * lastItem)114 static bool isTheSameFunctionOverload(const CodeCompletion &completion,
115                                       const QString &name,
116                                       ClangAssistProposalItem *lastItem)
117 {
118     return completion.completionKind == lastItem->firstCodeCompletion().completionKind
119             && lastItem->text() == name;
120 }
121 
toAssistProposalItems(const CodeCompletions & completions) const122 QList<AssistProposalItemInterface *> ClangCompletionAssistProcessor::toAssistProposalItems(
123         const CodeCompletions &completions) const
124 {
125     // TODO: Handle Qt4's SIGNAL/SLOT
126     //   Possibly check for m_completionOperator == T_SIGNAL
127     //   Possibly check for codeCompletion.completionKind == CodeCompletion::SignalCompletionKind
128 
129     QList<AssistProposalItemInterface *> items;
130     items.reserve(completions.size());
131 
132     // If there are signals among the candidates, we employ the built-in code model to find out
133     // whether the cursor was on the second argument of a (dis)connect() call.
134     // If so, we offer only signals, as nothing else makes sense in that context.
135     bool considerOnlySignals = false;
136     if (m_position != -1 && Utils::anyOf(completions, [](const CodeCompletion &c) {
137         return c.completionKind == CodeCompletion::SignalCompletionKind;
138     })) {
139         considerOnlySignals = CppTools::CppModelManager::instance()
140                 ->positionRequiresSignal(m_interface->filePath().toString(), m_content, m_position);
141     }
142     for (const CodeCompletion &codeCompletion : completions) {
143         if (codeCompletion.text.isEmpty())
144             continue; // It's an OverloadCandidate which has text but no typedText.
145 
146         if (considerOnlySignals
147                 && codeCompletion.completionKind != CodeCompletion::ClassCompletionKind
148                 && codeCompletion.completionKind != CodeCompletion::NamespaceCompletionKind
149                 && codeCompletion.completionKind != CodeCompletion::SignalCompletionKind) {
150                 continue;
151         }
152 
153         // Don't offer symbols that are not accessible here.
154         if (codeCompletion.availability == CodeCompletion::NotAvailable
155                 || codeCompletion.availability == CodeCompletion::NotAccessible) {
156             continue;
157         }
158 
159         const QString name = codeCompletion.completionKind == CodeCompletion::KeywordCompletionKind
160                 ? CompletionChunksToTextConverter::convertToName(codeCompletion.chunks)
161                 : codeCompletion.text.toString();
162 
163         if (items.empty()) {
164             addAssistProposalItem(items, codeCompletion, name);
165         } else {
166             auto *lastItem = static_cast<ClangAssistProposalItem *>(items.last());
167             if (isTheSameFunctionOverload(codeCompletion, name, lastItem)) {
168                 addFunctionOverloadAssistProposalItem(items, items.back(), m_interface.data(),
169                                                       codeCompletion, name);
170             } else {
171                 addAssistProposalItem(items, codeCompletion, name);
172             }
173         }
174     }
175 
176     return items;
177 }
178 
179 using namespace CPlusPlus;
180 using namespace TextEditor;
181 
ClangCompletionAssistProcessor()182 ClangCompletionAssistProcessor::ClangCompletionAssistProcessor()
183     : CppCompletionAssistProcessor(100)
184     , m_completionOperator(T_EOF_SYMBOL)
185 {
186 }
187 
188 ClangCompletionAssistProcessor::~ClangCompletionAssistProcessor() = default;
189 
perform(const AssistInterface * interface)190 IAssistProposal *ClangCompletionAssistProcessor::perform(const AssistInterface *interface)
191 {
192     m_interface.reset(static_cast<const ClangCompletionAssistInterface *>(interface));
193 
194     if (interface->reason() != ExplicitlyInvoked && !accepts()) {
195         m_requestSent = false;
196         return nullptr;
197     }
198 
199     return startCompletionHelper(); // == 0 if results are calculated asynchronously
200 }
201 
202 // All completions require fix-it, apply this fix-it now.
applyCompletionFixIt(const CodeCompletions & completions)203 CodeCompletions ClangCompletionAssistProcessor::applyCompletionFixIt(const CodeCompletions &completions)
204 {
205     // CLANG-UPGRADE-CHECK: Currently we rely on fact that there are only 2 possible fix-it types:
206     // 'dot to arrow' and 'arrow to dot' and they can't appear for the same item.
207     // However if we get multiple options which trigger for the same code we need to improve this
208     // algorithm. Check comments to FixIts field of CodeCompletionResult and which fix-its are used
209     // to construct results in SemaCodeComplete.cpp.
210     const CodeCompletion &completion = completions.front();
211     const ClangBackEnd::FixItContainer &fixIt = completion.requiredFixIts.front();
212 
213     ClangFixItOperation fixItOperation(Utf8String(), completion.requiredFixIts);
214     fixItOperation.perform();
215 
216     const int fixItLength = fixIt.range.end.column - fixIt.range.start.column;
217     const QString fixItText = fixIt.text.toString();
218     m_positionForProposal += fixItText.length() - fixItLength;
219 
220     CodeCompletions completionsWithoutFixIts;
221     completionsWithoutFixIts.reserve(completions.size());
222     for (const CodeCompletion &completion : completions) {
223         CodeCompletion completionCopy = completion;
224         completionCopy.requiredFixIts.clear();
225         completionsWithoutFixIts.push_back(completionCopy);
226     }
227 
228     return completionsWithoutFixIts;
229 }
230 
handleAvailableCompletions(const CodeCompletions & completions)231 void ClangCompletionAssistProcessor::handleAvailableCompletions(const CodeCompletions &completions)
232 {
233     QTC_CHECK(m_completions.isEmpty());
234 
235     if (m_sentRequestType == FunctionHintCompletion && !completions.isEmpty()) {
236         const CodeCompletion &firstCompletion = completions.front();
237         if (firstCompletion.completionKind == CodeCompletion::FunctionOverloadCompletionKind) {
238             setAsyncProposalAvailable(createFunctionHintProposal(completions));
239             return;
240         }
241 
242         if (!m_fallbackToNormalCompletion) {
243             // We must report back to the code assistant under all circumstances
244             setAsyncProposalAvailable(nullptr);
245             return;
246         }
247         // else: Proceed with a normal completion in case:
248         // 1) it was not a function call, but e.g. a function declaration like "void f("
249         // 2) '{' meant not a constructor call.
250     }
251 
252     //m_sentRequestType == NormalCompletion or function signatures were empty
253 
254     // Completions are sorted the way that all items with fix-its come after all items without them
255     // therefore it's enough to check only the first one.
256     if (!completions.isEmpty() && !completions.front().requiredFixIts.isEmpty())
257         m_completions = toAssistProposalItems(applyCompletionFixIt(completions));
258     else
259         m_completions = toAssistProposalItems(completions);
260 
261     if (m_addSnippets && !m_completions.isEmpty())
262         addSnippets();
263 
264     setAsyncProposalAvailable(createProposal());
265 }
266 
textEditorWidget() const267 const TextEditorWidget *ClangCompletionAssistProcessor::textEditorWidget() const
268 {
269     return m_interface->textEditorWidget();
270 }
271 
272 /// Seach backwards in the document starting from pos to find the first opening
273 /// parenthesis. Nested parenthesis are skipped.
findOpenParen(QTextDocument * document,int start)274 static int findOpenParen(QTextDocument *document, int start)
275 {
276     unsigned parenCount = 1;
277     for (int position = start; position >= 0; --position) {
278         const QChar ch = document->characterAt(position);
279         if (ch == QLatin1Char('(')) {
280             --parenCount;
281             if (parenCount == 0)
282                 return position;
283         } else if (ch == QLatin1Char(')')) {
284             ++parenCount;
285         }
286     }
287     return -1;
288 }
289 
modifyInput(QTextDocument * doc,int endOfExpression)290 static QByteArray modifyInput(QTextDocument *doc, int endOfExpression) {
291     int comma = endOfExpression;
292     while (comma > 0) {
293         const QChar ch = doc->characterAt(comma);
294         if (ch == QLatin1Char(','))
295             break;
296         if (ch == QLatin1Char(';') || ch == QLatin1Char('{') || ch == QLatin1Char('}')) {
297             // Safety net: we don't seem to have "connect(pointer, SIGNAL(" as
298             // input, so stop searching.
299             comma = -1;
300             break;
301         }
302         --comma;
303     }
304     if (comma < 0)
305         return QByteArray();
306     const int openBrace = findOpenParen(doc, comma);
307     if (openBrace < 0)
308         return QByteArray();
309 
310     QByteArray modifiedInput = doc->toPlainText().toUtf8();
311     const int len = endOfExpression - comma;
312     QByteArray replacement(len - 4, ' ');
313     replacement.append(")->");
314     modifiedInput.replace(comma, len, replacement);
315     modifiedInput.insert(openBrace, '(');
316     return modifiedInput;
317 }
318 
lastPrecedingNonWhitespaceChar(const ClangCompletionAssistInterface * interface)319 static QChar lastPrecedingNonWhitespaceChar(const ClangCompletionAssistInterface *interface)
320 {
321     int pos = interface->position();
322     while (pos >= 0 && interface->characterAt(pos).isSpace())
323         --pos;
324     return pos >= 0 ? interface->characterAt(pos) : QChar::Null;
325 }
326 
startCompletionHelper()327 IAssistProposal *ClangCompletionAssistProcessor::startCompletionHelper()
328 {
329     ClangCompletionContextAnalyzer analyzer(m_interface.data(), m_interface->languageFeatures());
330     analyzer.analyze();
331     m_completionOperator = analyzer.completionOperator();
332     m_positionForProposal = analyzer.positionForProposal();
333     m_addSnippets = analyzer.addSnippets();
334 
335     QByteArray modifiedFileContent;
336 
337     const ClangCompletionContextAnalyzer::CompletionAction action = analyzer.completionAction();
338     switch (action) {
339     case ClangCompletionContextAnalyzer::CompleteDoxygenKeyword:
340         if (completeDoxygenKeywords())
341             return createProposal();
342         break;
343     case ClangCompletionContextAnalyzer::CompleteIncludePath:
344         if (completeInclude(analyzer.positionEndOfExpression()))
345             return createProposal();
346         break;
347     case ClangCompletionContextAnalyzer::CompletePreprocessorDirective:
348         if (completePreprocessorDirectives())
349             return createProposal();
350         break;
351     case ClangCompletionContextAnalyzer::CompleteSignal:
352     case ClangCompletionContextAnalyzer::CompleteSlot:
353         modifiedFileContent = modifyInput(m_interface->textDocument(),
354                                           analyzer.positionEndOfExpression());
355         Q_FALLTHROUGH();
356     case ClangCompletionContextAnalyzer::PassThroughToLibClang: {
357         m_sentRequestType = NormalCompletion;
358         m_requestSent = sendCompletionRequest(analyzer.positionForClang(),
359                                               modifiedFileContent);
360         break;
361     }
362     case ClangCompletionContextAnalyzer::PassThroughToLibClangAfterLeftParen: {
363         m_sentRequestType = FunctionHintCompletion;
364         if (lastPrecedingNonWhitespaceChar(m_interface.data()) == ',')
365             m_fallbackToNormalCompletion = false;
366         m_requestSent = sendCompletionRequest(analyzer.positionForClang(), QByteArray(),
367                                               analyzer.functionNameStart());
368         break;
369     }
370     case ClangCompletionContextAnalyzer::CompleteNone:
371     default:
372         break;
373     }
374 
375     return nullptr;
376 }
377 
startOfOperator(int positionInDocument,unsigned * kind,bool wantFunctionCall) const378 int ClangCompletionAssistProcessor::startOfOperator(int positionInDocument,
379                                                     unsigned *kind,
380                                                     bool wantFunctionCall) const
381 {
382     auto activationSequence = m_interface->textAt(positionInDocument - 3, 3);
383     ActivationSequenceProcessor activationSequenceProcessor(activationSequence,
384                                                             positionInDocument,
385                                                             wantFunctionCall);
386 
387     *kind = activationSequenceProcessor.completionKind();
388     int start = activationSequenceProcessor.operatorStartPosition();
389 
390     CppCompletionAssistProcessor::startOfOperator(m_interface->textDocument(),
391                                                   positionInDocument,
392                                                   kind,
393                                                   start,
394                                                   m_interface->languageFeatures());
395 
396     return start;
397 }
398 
findStartOfName(int pos) const399 int ClangCompletionAssistProcessor::findStartOfName(int pos) const
400 {
401     if (pos == -1)
402         pos = m_interface->position();
403     QChar chr;
404 
405     // Skip to the start of a name
406     do {
407         chr = m_interface->characterAt(--pos);
408     } while (chr.isLetterOrNumber() || chr == QLatin1Char('_'));
409 
410     return pos + 1;
411 }
412 
accepts() const413 bool ClangCompletionAssistProcessor::accepts() const
414 {
415     const int pos = m_interface->position();
416     unsigned token = T_EOF_SYMBOL;
417 
418     const int start = startOfOperator(pos, &token, /*want function call=*/ true);
419     if (start != pos) {
420         if (token == T_POUND) {
421             const int column = pos - m_interface->textDocument()->findBlock(start).position();
422             if (column != 1)
423                 return false;
424         }
425 
426         return true;
427     } else {
428         // Trigger completion after n characters of a name have been typed, when not editing an existing name
429         QChar characterUnderCursor = m_interface->characterAt(pos);
430         if (!characterUnderCursor.isLetterOrNumber() && characterUnderCursor != QLatin1Char('_')) {
431             const int startOfName = findStartOfName(pos);
432             if (pos - startOfName >= TextEditorSettings::completionSettings().m_characterThreshold) {
433                 const QChar firstCharacter = m_interface->characterAt(startOfName);
434                 if (firstCharacter.isLetter() || firstCharacter == QLatin1Char('_')) {
435                     // Finally check that we're not inside a comment or string (code copied from startOfOperator)
436                     QTextCursor tc(m_interface->textDocument());
437                     tc.setPosition(pos);
438 
439                     SimpleLexer tokenize;
440                     LanguageFeatures lf = tokenize.languageFeatures();
441                     lf.qtMocRunEnabled = true;
442                     lf.objCEnabled = true;
443                     tokenize.setLanguageFeatures(lf);
444                     tokenize.setSkipComments(false);
445                     const Tokens &tokens = tokenize(tc.block().text(), BackwardsScanner::previousBlockState(tc.block()));
446                     const int tokenIdx = SimpleLexer::tokenBefore(tokens, qMax(0, tc.positionInBlock() - 1));
447                     const Token tk = (tokenIdx == -1) ? Token() : tokens.at(tokenIdx);
448 
449                     if (!tk.isComment() && !tk.isLiteral()) {
450                         return true;
451                     } else if (tk.isLiteral()
452                                && tokens.size() == 3
453                                && tokens.at(0).kind() == T_POUND
454                                && tokens.at(1).kind() == T_IDENTIFIER) {
455                         const QString &line = tc.block().text();
456                         const Token &idToken = tokens.at(1);
457                         QStringView identifier = Utils::midView(line,
458                                                                        idToken.utf16charsBegin(),
459                                                                        idToken.utf16chars());
460                         if (identifier == QLatin1String("include")
461                                 || identifier == QLatin1String("include_next")
462                                 || (m_interface->objcEnabled() && identifier == QLatin1String("import"))) {
463                             return true;
464                         }
465                     }
466                 }
467             }
468         }
469     }
470 
471     return false;
472 }
473 
474 /**
475  * @brief Creates completion proposals for #include and given cursor
476  * @param cursor - cursor placed after opening bracked or quote
477  * @return false if completions list is empty
478  */
completeInclude(const QTextCursor & cursor)479 bool ClangCompletionAssistProcessor::completeInclude(const QTextCursor &cursor)
480 {
481     QString directoryPrefix;
482     if (m_completionOperator == T_SLASH) {
483         QTextCursor c = cursor;
484         c.movePosition(QTextCursor::StartOfLine, QTextCursor::KeepAnchor);
485         QString sel = c.selectedText();
486         int startCharPos = sel.indexOf(QLatin1Char('"'));
487         if (startCharPos == -1) {
488             startCharPos = sel.indexOf(QLatin1Char('<'));
489             m_completionOperator = T_ANGLE_STRING_LITERAL;
490         } else {
491             m_completionOperator = T_STRING_LITERAL;
492         }
493         if (startCharPos != -1)
494             directoryPrefix = sel.mid(startCharPos + 1, sel.length() - 1);
495     }
496 
497     // Make completion for all relevant includes
498     ProjectExplorer::HeaderPaths headerPaths = m_interface->headerPaths();
499     const ProjectExplorer::HeaderPath currentFilePath(m_interface->filePath().toFileInfo().path(),
500                                                       ProjectExplorer::HeaderPathType::User);
501     if (!headerPaths.contains(currentFilePath))
502         headerPaths.append(currentFilePath);
503 
504     const ::Utils::MimeType mimeType = ::Utils::mimeTypeForName("text/x-c++hdr");
505     const QStringList suffixes = mimeType.suffixes();
506 
507     foreach (const ProjectExplorer::HeaderPath &headerPath, headerPaths) {
508         QString realPath = headerPath.path;
509         if (!directoryPrefix.isEmpty()) {
510             realPath += QLatin1Char('/');
511             realPath += directoryPrefix;
512             if (headerPath.type == ProjectExplorer::HeaderPathType::Framework)
513                 realPath += QLatin1String(".framework/Headers");
514         }
515         completeIncludePath(realPath, suffixes);
516     }
517 
518     QList<QPair<AssistProposalItemInterface *, QString>> completionsForSorting;
519     for (AssistProposalItemInterface * const item : qAsConst(m_completions)) {
520         QString s = item->text();
521         s.replace('/', QChar(0)); // The dir separator should compare less than anything else.
522         completionsForSorting << qMakePair(item, s);
523     }
524     Utils::sort(completionsForSorting, [](const auto &left, const auto &right) {
525         return left.second < right.second;
526     });
527     for (int i = 0; i < completionsForSorting.count(); ++i)
528         m_completions[i] = completionsForSorting[i].first;
529 
530     return !m_completions.isEmpty();
531 }
532 
completeInclude(int position)533 bool ClangCompletionAssistProcessor::completeInclude(int position)
534 {
535     QTextCursor textCursor(m_interface->textDocument()); // TODO: Simplify, move into function
536     textCursor.setPosition(position);
537     return completeInclude(textCursor);
538 }
539 
540 /**
541  * @brief Adds #include completion proposals using given include path
542  * @param realPath - one of directories where compiler searches includes
543  * @param suffixes - file suffixes for C/C++ header files
544  */
completeIncludePath(const QString & realPath,const QStringList & suffixes)545 void ClangCompletionAssistProcessor::completeIncludePath(const QString &realPath,
546                                                          const QStringList &suffixes)
547 {
548     QDirIterator i(realPath, QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
549     //: Parent folder for proposed #include completion
550     const QString hint = tr("Location: %1").arg(QDir::toNativeSeparators(QDir::cleanPath(realPath)));
551     while (i.hasNext()) {
552         const QString fileName = i.next();
553         const QFileInfo fileInfo = i.fileInfo();
554         const QString suffix = fileInfo.suffix();
555         if (suffix.isEmpty() || suffixes.contains(suffix)) {
556             QString text = fileName.mid(realPath.length() + 1);
557             if (fileInfo.isDir())
558                 text += QLatin1Char('/');
559 
560             auto *item = new ClangPreprocessorAssistProposalItem;
561             item->setText(text);
562             item->setDetail(hint);
563             item->setIcon(CPlusPlus::Icons::keywordIcon());
564             item->setCompletionOperator(m_completionOperator);
565             m_completions.append(item);
566         }
567     }
568 }
569 
completePreprocessorDirectives()570 bool ClangCompletionAssistProcessor::completePreprocessorDirectives()
571 {
572     foreach (const QString &preprocessorCompletion, m_preprocessorCompletions)
573         addCompletionItem(preprocessorCompletion,
574                           Utils::CodeModelIcon::iconForType(Utils::CodeModelIcon::Macro));
575 
576     if (m_interface->objcEnabled())
577         addCompletionItem(QLatin1String("import"),
578                           Utils::CodeModelIcon::iconForType(Utils::CodeModelIcon::Macro));
579 
580     return !m_completions.isEmpty();
581 }
582 
completeDoxygenKeywords()583 bool ClangCompletionAssistProcessor::completeDoxygenKeywords()
584 {
585     for (int i = 1; i < CppTools::T_DOXY_LAST_TAG; ++i)
586         addCompletionItem(QString::fromLatin1(CppTools::doxygenTagSpell(i)), CPlusPlus::Icons::keywordIcon());
587     return !m_completions.isEmpty();
588 }
589 
addCompletionItem(const QString & text,const QIcon & icon,int order)590 void ClangCompletionAssistProcessor::addCompletionItem(const QString &text,
591                                                        const QIcon &icon,
592                                                        int order)
593 {
594     auto *item = new ClangPreprocessorAssistProposalItem;
595     item->setText(text);
596     item->setIcon(icon);
597     item->setOrder(order);
598     item->setCompletionOperator(m_completionOperator);
599     m_completions.append(item);
600 }
601 
602 ClangCompletionAssistProcessor::UnsavedFileContentInfo
unsavedFileContent(const QByteArray & customFileContent) const603 ClangCompletionAssistProcessor::unsavedFileContent(const QByteArray &customFileContent) const
604 {
605     const bool hasCustomModification = !customFileContent.isEmpty();
606 
607     UnsavedFileContentInfo info;
608     info.isDocumentModified = hasCustomModification || m_interface->textDocument()->isModified();
609     info.unsavedContent = hasCustomModification
610                         ? customFileContent
611                         : m_interface->textDocument()->toPlainText().toUtf8();
612     return info;
613 }
614 
sendFileContent(const QByteArray & customFileContent)615 void ClangCompletionAssistProcessor::sendFileContent(const QByteArray &customFileContent)
616 {
617     // TODO: Revert custom modification after the completions
618     const UnsavedFileContentInfo info = unsavedFileContent(customFileContent);
619 
620     BackendCommunicator &communicator = m_interface->communicator();
621     communicator.documentsChanged({{m_interface->filePath().toString(),
622                                     Utf8String::fromByteArray(info.unsavedContent),
623                                     info.isDocumentModified,
624                                     uint(m_interface->textDocument()->revision())}});
625 }
626 namespace {
shouldSendDocumentForCompletion(const QString & filePath,int completionPosition)627 bool shouldSendDocumentForCompletion(const QString &filePath,
628                                      int completionPosition)
629 {
630     CppTools::CppEditorDocumentHandle *document = cppDocument(filePath);
631 
632     if (document) {
633         auto &sendTracker = document->sendTracker();
634         return sendTracker.shouldSendRevisionWithCompletionPosition(int(document->revision()),
635                                                                     completionPosition);
636     }
637 
638     return true;
639 }
640 
shouldSendCodeCompletion(const QString & filePath,int completionPosition)641 bool shouldSendCodeCompletion(const QString &filePath,
642                               int completionPosition)
643 {
644     CppTools::CppEditorDocumentHandle *document = cppDocument(filePath);
645 
646     if (document) {
647         auto &sendTracker = document->sendTracker();
648         return sendTracker.shouldSendCompletion(completionPosition);
649     }
650 
651     return true;
652 }
653 
setLastDocumentRevision(const QString & filePath)654 void setLastDocumentRevision(const QString &filePath)
655 {
656     CppTools::CppEditorDocumentHandle *document = cppDocument(filePath);
657 
658     if (document)
659         document->sendTracker().setLastSentRevision(int(document->revision()));
660 }
661 
setLastCompletionPosition(const QString & filePath,int completionPosition)662 void setLastCompletionPosition(const QString &filePath,
663                                int completionPosition)
664 {
665     CppTools::CppEditorDocumentHandle *document = cppDocument(filePath);
666 
667     if (document)
668         document->sendTracker().setLastCompletionPosition(completionPosition);
669 }
670 
671 }
672 
673 ClangCompletionAssistProcessor::Position
extractLineColumn(int position)674 ClangCompletionAssistProcessor::extractLineColumn(int position)
675 {
676     if (position < 0)
677         return {-1, -1};
678 
679     int line = -1, column = -1;
680     Utils::Text::convertPosition(m_interface->textDocument(), position, &line, &column);
681 
682     column = clangColumn(m_interface->textDocument()->findBlock(position), column);
683     return {line, column};
684 }
685 
sendCompletionRequest(int position,const QByteArray & customFileContent,int functionNameStartPosition)686 bool ClangCompletionAssistProcessor::sendCompletionRequest(int position,
687                                                            const QByteArray &customFileContent,
688                                                            int functionNameStartPosition)
689 {
690     const QString filePath = m_interface->filePath().toString();
691 
692     auto &communicator = m_interface->communicator();
693 
694     if (shouldSendCodeCompletion(filePath, position) || communicator.isNotWaitingForCompletion()) {
695         if (shouldSendDocumentForCompletion(filePath, position)) {
696             sendFileContent(customFileContent);
697             setLastDocumentRevision(filePath);
698         }
699 
700         const Position cursorPosition = extractLineColumn(position);
701         const Position functionNameStart = extractLineColumn(functionNameStartPosition);
702         communicator.requestCompletions(this,
703                                         filePath,
704                                         uint(cursorPosition.line),
705                                         uint(cursorPosition.column),
706                                         functionNameStart.line,
707                                         functionNameStart.column);
708         setLastCompletionPosition(filePath, position);
709         if (m_sentRequestType == NormalCompletion) {
710             if (!customFileContent.isEmpty())
711                 m_content = customFileContent;
712             else if (const CppTools::CppEditorDocumentHandle * const doc = cppDocument(filePath))
713                 m_content = doc->contents();
714             m_position = position;
715         }
716         return true;
717     }
718 
719     return false;
720 }
721 
createProposal()722 TextEditor::IAssistProposal *ClangCompletionAssistProcessor::createProposal()
723 {
724     m_requestSent = false;
725     TextEditor::GenericProposalModelPtr model(new ClangAssistProposalModel());
726     model->loadContent(m_completions);
727     return new GenericProposal(m_positionForProposal, model);
728 }
729 
createFunctionHintProposal(const ClangBackEnd::CodeCompletions & completions)730 IAssistProposal *ClangCompletionAssistProcessor::createFunctionHintProposal(
731         const ClangBackEnd::CodeCompletions &completions)
732 {
733     m_requestSent = false;
734     TextEditor::FunctionHintProposalModelPtr model(new ClangFunctionHintModel(completions));
735     return new FunctionHintProposal(m_positionForProposal, model);
736 }
737 
cancel()738 void ClangCompletionAssistProcessor::cancel()
739 {
740     m_interface->communicator().cancelCompletions(this);
741 }
742 
743 } // namespace Internal
744 } // namespace ClangCodeModel
745