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