diff options
Diffstat (limited to 'src/plugins/clangcodemodel/clangcompletionassistprocessor.cpp')
-rw-r--r-- | src/plugins/clangcodemodel/clangcompletionassistprocessor.cpp | 788 |
1 files changed, 788 insertions, 0 deletions
diff --git a/src/plugins/clangcodemodel/clangcompletionassistprocessor.cpp b/src/plugins/clangcodemodel/clangcompletionassistprocessor.cpp new file mode 100644 index 0000000000..779c858975 --- /dev/null +++ b/src/plugins/clangcodemodel/clangcompletionassistprocessor.cpp @@ -0,0 +1,788 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms and +** conditions see http://www.qt.io/terms-conditions. For further information +** use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "clangassistproposalitem.h" + +#include "clangassistproposal.h" +#include "clangassistproposalmodel.h" +#include "clangcompletionassistprocessor.h" +#include "clangcompletioncontextanalyzer.h" +#include "clangfunctionhintmodel.h" +#include "clangutils.h" + +#include <utils/qtcassert.h> + +#include <texteditor/codeassist/assistproposalitem.h> +#include <texteditor/codeassist/functionhintproposal.h> +#include <texteditor/codeassist/ifunctionhintproposalmodel.h> +#include <texteditor/convenience.h> + +#include <cpptools/cppdoxygen.h> + +#include <cplusplus/BackwardsScanner.h> +#include <cplusplus/ExpressionUnderCursor.h> +#include <cplusplus/SimpleLexer.h> + +#include <QDirIterator> +#include <QTextBlock> +#include <filecontainer.h> + +#include <utils/mimetypes/mimedatabase.h> + +namespace ClangCodeModel { +namespace Internal { + +using TextEditor::AssistProposalItem; + +namespace { + +const char SNIPPET_ICON_PATH[] = ":/texteditor/images/snippet.png"; + +int activationSequenceChar(const QChar &ch, + const QChar &ch2, + const QChar &ch3, + unsigned *kind, + bool wantFunctionCall) +{ + using namespace CPlusPlus; + + int referencePosition = 0; + int completionKind = T_EOF_SYMBOL; + switch (ch.toLatin1()) { + case '.': + if (ch2 != QLatin1Char('.')) { + completionKind = T_DOT; + referencePosition = 1; + } + break; + case ',': + completionKind = T_COMMA; + referencePosition = 1; + break; + case '(': + if (wantFunctionCall) { + completionKind = T_LPAREN; + referencePosition = 1; + } + break; + case ':': + if (ch3 != QLatin1Char(':') && ch2 == QLatin1Char(':')) { + completionKind = T_COLON_COLON; + referencePosition = 2; + } + break; + case '>': + if (ch2 == QLatin1Char('-')) { + completionKind = T_ARROW; + referencePosition = 2; + } + break; + case '*': + if (ch2 == QLatin1Char('.')) { + completionKind = T_DOT_STAR; + referencePosition = 2; + } else if (ch3 == QLatin1Char('-') && ch2 == QLatin1Char('>')) { + completionKind = T_ARROW_STAR; + referencePosition = 3; + } + break; + case '\\': + case '@': + if (ch2.isNull() || ch2.isSpace()) { + completionKind = T_DOXY_COMMENT; + referencePosition = 1; + } + break; + case '<': + completionKind = T_ANGLE_STRING_LITERAL; + referencePosition = 1; + break; + case '"': + completionKind = T_STRING_LITERAL; + referencePosition = 1; + break; + case '/': + completionKind = T_SLASH; + referencePosition = 1; + break; + case '#': + completionKind = T_POUND; + referencePosition = 1; + break; + } + + if (kind) + *kind = completionKind; + + return referencePosition; +} + +QList<AssistProposalItem *> toAssistProposalItems(const CodeCompletions &completions) +{ + static CPlusPlus::Icons m_icons; // de-deduplicate + + QList<AssistProposalItem *> result; + + bool signalCompletion = false; // TODO + bool slotCompletion = false; // TODO + + const QIcon snippetIcon = QIcon(QLatin1String(SNIPPET_ICON_PATH)); + QHash<QString, ClangAssistProposalItem *> items; + foreach (const CodeCompletion &ccr, completions) { + if (ccr.text().isEmpty()) // TODO: Make isValid()? + continue; + if (signalCompletion && ccr.completionKind() != CodeCompletion::SignalCompletionKind) + continue; + if (slotCompletion && ccr.completionKind() != CodeCompletion::SlotCompletionKind) + continue; + + const QString txt(ccr.text().toString()); + ClangAssistProposalItem *item = items.value(txt, 0); + if (item) { + item->addOverload(ccr); + } else { + item = new ClangAssistProposalItem; + items.insert(txt, item); + item->setText(txt); + item->setDetail(ccr.hint().toString()); + item->setOrder(ccr.priority()); + + const QString snippet = ccr.snippet().toString(); + if (!snippet.isEmpty()) + item->setData(snippet); + else + item->setData(qVariantFromValue(ccr)); + } + + // FIXME: show the effective accessebility instead of availability + using ClangBackEnd::CodeCompletion; + using CPlusPlus::Icons; + + switch (ccr.completionKind()) { + case CodeCompletion::ClassCompletionKind: + case CodeCompletion::TemplateClassCompletionKind: + item->setIcon(m_icons.iconForType(Icons::ClassIconType)); break; + case CodeCompletion::EnumerationCompletionKind: item->setIcon(m_icons.iconForType(Icons::EnumIconType)); break; + case CodeCompletion::EnumeratorCompletionKind: item->setIcon(m_icons.iconForType(Icons::EnumeratorIconType)); break; + + case CodeCompletion::ConstructorCompletionKind: // fall through + case CodeCompletion::DestructorCompletionKind: // fall through + case CodeCompletion::FunctionCompletionKind: + case CodeCompletion::TemplateFunctionCompletionKind: + case CodeCompletion::ObjCMessageCompletionKind: + switch (ccr.availability()) { + case CodeCompletion::Available: + case CodeCompletion::Deprecated: + item->setIcon(m_icons.iconForType(Icons::FuncPublicIconType)); + break; + default: + item->setIcon(m_icons.iconForType(Icons::FuncPrivateIconType)); + break; + } + break; + + case CodeCompletion::SignalCompletionKind: + item->setIcon(m_icons.iconForType(Icons::SignalIconType)); + break; + + case CodeCompletion::SlotCompletionKind: + switch (ccr.availability()) { + case CodeCompletion::Available: + case CodeCompletion::Deprecated: + item->setIcon(m_icons.iconForType(Icons::SlotPublicIconType)); + break; + case CodeCompletion::NotAccessible: + case CodeCompletion::NotAvailable: + item->setIcon(m_icons.iconForType(Icons::SlotPrivateIconType)); + break; + } + break; + + case CodeCompletion::NamespaceCompletionKind: item->setIcon(m_icons.iconForType(Icons::NamespaceIconType)); break; + case CodeCompletion::PreProcessorCompletionKind: item->setIcon(m_icons.iconForType(Icons::MacroIconType)); break; + case CodeCompletion::VariableCompletionKind: + switch (ccr.availability()) { + case CodeCompletion::Available: + case CodeCompletion::Deprecated: + item->setIcon(m_icons.iconForType(Icons::VarPublicIconType)); + break; + default: + item->setIcon(m_icons.iconForType(Icons::VarPrivateIconType)); + break; + } + break; + + case CodeCompletion::KeywordCompletionKind: + item->setIcon(m_icons.iconForType(Icons::KeywordIconType)); + break; + + case CodeCompletion::ClangSnippetKind: + item->setIcon(snippetIcon); + break; + + case CodeCompletion::Other: + break; + } + } + + foreach (ClangAssistProposalItem *item, items.values()) + result.append(item); + + return result; +} + +bool isFunctionHintLikeCompletion(CodeCompletion::Kind kind) +{ + return kind == CodeCompletion::FunctionCompletionKind + || kind == CodeCompletion::ConstructorCompletionKind + || kind == CodeCompletion::DestructorCompletionKind + || kind == CodeCompletion::SignalCompletionKind + || kind == CodeCompletion::SlotCompletionKind; +} + +QVector<CodeCompletion> matchingFunctionCompletions(const QVector<CodeCompletion> completions, + const QString &functionName) +{ + QVector<CodeCompletion> matching; + + foreach (const CodeCompletion &completion, completions) { + if (isFunctionHintLikeCompletion(completion.completionKind()) + && completion.text().toString() == functionName) { + matching.append(completion); + } + } + + return matching; +} + +} // Anonymous + +using namespace CPlusPlus; +using namespace TextEditor; + +ClangCompletionAssistProcessor::ClangCompletionAssistProcessor() + : m_completionOperator(T_EOF_SYMBOL) +{ +} + +ClangCompletionAssistProcessor::~ClangCompletionAssistProcessor() +{ +} + +IAssistProposal *ClangCompletionAssistProcessor::perform(const AssistInterface *interface) +{ + m_interface.reset(static_cast<const ClangCompletionAssistInterface *>(interface)); + + if (interface->reason() != ExplicitlyInvoked && !accepts()) + return 0; + + return startCompletionHelper(); // == 0 if results are calculated asynchronously +} + +void ClangCompletionAssistProcessor::asyncCompletionsAvailable(const CodeCompletions &completions) +{ + switch (m_sentRequestType) { + case CompletionRequestType::NormalCompletion: + onCompletionsAvailable(completions); + break; + case CompletionRequestType::FunctionHintCompletion: + onFunctionHintCompletionsAvailable(completions); + break; + default: + QTC_CHECK(!"Unhandled ClangCompletionAssistProcessor::CompletionRequestType"); + break; + } +} + +const TextEditorWidget *ClangCompletionAssistProcessor::textEditorWidget() const +{ + return m_interface->textEditorWidget(); +} + +/// Seach backwards in the document starting from pos to find the first opening +/// parenthesis. Nested parenthesis are skipped. +static int findOpenParen(QTextDocument *document, int start) +{ + unsigned parenCount = 1; + for (int position = start; position >= 0; --position) { + const QChar ch = document->characterAt(position); + if (ch == QLatin1Char('(')) { + --parenCount; + if (parenCount == 0) + return position; + } else if (ch == QLatin1Char(')')) { + ++parenCount; + } + } + return -1; +} + +static QByteArray modifyInput(QTextDocument *doc, int endOfExpression) { + int comma = endOfExpression; + while (comma > 0) { + const QChar ch = doc->characterAt(comma); + if (ch == QLatin1Char(',')) + break; + if (ch == QLatin1Char(';') || ch == QLatin1Char('{') || ch == QLatin1Char('}')) { + // Safety net: we don't seem to have "connect(pointer, SIGNAL(" as + // input, so stop searching. + comma = -1; + break; + } + --comma; + } + if (comma < 0) + return QByteArray(); + const int openBrace = findOpenParen(doc, comma); + if (openBrace < 0) + return QByteArray(); + + QByteArray modifiedInput = doc->toPlainText().toUtf8(); + const int len = endOfExpression - comma; + QByteArray replacement(len - 4, ' '); + replacement.append(")->"); + modifiedInput.replace(comma, len, replacement); + modifiedInput.insert(openBrace, '('); + return modifiedInput; +} + +IAssistProposal *ClangCompletionAssistProcessor::startCompletionHelper() +{ + sendFileContent(Utils::projectFilePathForFile(m_interface->fileName()), QByteArray()); // TODO: Remoe + + ClangCompletionContextAnalyzer analyzer(m_interface.data(), m_interface->languageFeatures()); + analyzer.analyze(); + m_completionOperator = analyzer.completionOperator(); + m_positionForProposal = analyzer.positionForProposal(); + + QByteArray modifiedFileContent; + + const ClangCompletionContextAnalyzer::CompletionAction action = analyzer.completionAction(); + switch (action) { + case ClangCompletionContextAnalyzer::CompleteDoxygenKeyword: + if (completeDoxygenKeywords()) + return createProposal(); + break; + case ClangCompletionContextAnalyzer::CompleteIncludePath: + if (completeInclude(analyzer.positionEndOfExpression())) + return createProposal(); + break; + case ClangCompletionContextAnalyzer::CompletePreprocessorDirective: + if (completePreprocessorDirectives()) + return createProposal(); + break; + case ClangCompletionContextAnalyzer::CompleteSignal: + case ClangCompletionContextAnalyzer::CompleteSlot: + modifiedFileContent = modifyInput(m_interface->textDocument(), + analyzer.positionEndOfExpression()); + // Fall through! + case ClangCompletionContextAnalyzer::PassThroughToLibClang: { + m_addSnippets = m_completionOperator == T_EOF_SYMBOL; + m_sentRequestType = NormalCompletion; + sendCompletionRequest(analyzer.positionForClang(), modifiedFileContent); + break; + } + case ClangCompletionContextAnalyzer::PassThroughToLibClangAfterLeftParen: { + m_sentRequestType = FunctionHintCompletion; + m_functionName = analyzer.functionName(); + sendCompletionRequest(analyzer.positionForClang(), QByteArray()); + break; + } + default: + break; + } + + return 0; +} + +// TODO: Extract duplicated logic from InternalCppCompletionAssistProcessor::startOfOperator +int ClangCompletionAssistProcessor::startOfOperator(int pos, + unsigned *kind, + bool wantFunctionCall) const +{ + const QChar ch = pos > -1 ? m_interface->characterAt(pos - 1) : QChar(); + const QChar ch2 = pos > 0 ? m_interface->characterAt(pos - 2) : QChar(); + const QChar ch3 = pos > 1 ? m_interface->characterAt(pos - 3) : QChar(); + + int start = pos - activationSequenceChar(ch, ch2, ch3, kind, wantFunctionCall); + if (start != pos) { + QTextCursor tc(m_interface->textDocument()); + tc.setPosition(pos); + + // Include completion: make sure the quote character is the first one on the line + if (*kind == T_STRING_LITERAL) { + QTextCursor s = tc; + s.movePosition(QTextCursor::StartOfLine, QTextCursor::KeepAnchor); + QString sel = s.selectedText(); + if (sel.indexOf(QLatin1Char('"')) < sel.length() - 1) { + *kind = T_EOF_SYMBOL; + start = pos; + } + } + + if (*kind == T_COMMA) { + ExpressionUnderCursor expressionUnderCursor(m_interface->languageFeatures()); + if (expressionUnderCursor.startOfFunctionCall(tc) == -1) { + *kind = T_EOF_SYMBOL; + start = pos; + } + } + + SimpleLexer tokenize; + tokenize.setLanguageFeatures(m_interface->languageFeatures()); + tokenize.setSkipComments(false); + const Tokens &tokens = tokenize(tc.block().text(), BackwardsScanner::previousBlockState(tc.block())); + const int tokenIdx = SimpleLexer::tokenBefore(tokens, qMax(0, tc.positionInBlock() - 1)); // get the token at the left of the cursor + const Token tk = (tokenIdx == -1) ? Token() : tokens.at(tokenIdx); + + if (*kind == T_DOXY_COMMENT && !(tk.is(T_DOXY_COMMENT) || tk.is(T_CPP_DOXY_COMMENT))) { + *kind = T_EOF_SYMBOL; + start = pos; + } + // Don't complete in comments or strings, but still check for include completion + else if (tk.is(T_COMMENT) || tk.is(T_CPP_COMMENT) + || tk.is(T_CPP_DOXY_COMMENT) || tk.is(T_DOXY_COMMENT) + || (tk.isLiteral() && (*kind != T_STRING_LITERAL + && *kind != T_ANGLE_STRING_LITERAL + && *kind != T_SLASH))) { + *kind = T_EOF_SYMBOL; + start = pos; + } + // Include completion: can be triggered by slash, but only in a string + else if (*kind == T_SLASH && (tk.isNot(T_STRING_LITERAL) && tk.isNot(T_ANGLE_STRING_LITERAL))) { + *kind = T_EOF_SYMBOL; + start = pos; + } + else if (*kind == T_LPAREN) { + if (tokenIdx > 0) { + const Token &previousToken = tokens.at(tokenIdx - 1); // look at the token at the left of T_LPAREN + switch (previousToken.kind()) { + case T_IDENTIFIER: + case T_GREATER: + case T_SIGNAL: + case T_SLOT: + break; // good + + default: + // that's a bad token :) + *kind = T_EOF_SYMBOL; + start = pos; + } + } + } + // Check for include preprocessor directive + else if (*kind == T_STRING_LITERAL || *kind == T_ANGLE_STRING_LITERAL || *kind == T_SLASH) { + bool include = false; + if (tokens.size() >= 3) { + if (tokens.at(0).is(T_POUND) && tokens.at(1).is(T_IDENTIFIER) && (tokens.at(2).is(T_STRING_LITERAL) || + tokens.at(2).is(T_ANGLE_STRING_LITERAL))) { + const Token &directiveToken = tokens.at(1); + QString directive = tc.block().text().mid(directiveToken.bytesBegin(), + directiveToken.bytes()); + if (directive == QLatin1String("include") || + directive == QLatin1String("include_next") || + directive == QLatin1String("import")) { + include = true; + } + } + } + + if (!include) { + *kind = T_EOF_SYMBOL; + start = pos; + } + } + } + + return start; +} + +int ClangCompletionAssistProcessor::findStartOfName(int pos) const +{ + if (pos == -1) + pos = m_interface->position(); + QChar chr; + + // Skip to the start of a name + do { + chr = m_interface->characterAt(--pos); + } while (chr.isLetterOrNumber() || chr == QLatin1Char('_')); + + return pos + 1; +} + +bool ClangCompletionAssistProcessor::accepts() const +{ + const int pos = m_interface->position(); + unsigned token = T_EOF_SYMBOL; + + const int start = startOfOperator(pos, &token, /*want function call=*/ true); + if (start != pos) { + if (token == T_POUND) { + const int column = pos - m_interface->textDocument()->findBlock(start).position(); + if (column != 1) + return false; + } + + return true; + } else { + // Trigger completion after three characters of a name have been typed, when not editing an existing name + QChar characterUnderCursor = m_interface->characterAt(pos); + if (!characterUnderCursor.isLetterOrNumber() && characterUnderCursor != QLatin1Char('_')) { + const int startOfName = findStartOfName(pos); + if (pos - startOfName >= 3) { + const QChar firstCharacter = m_interface->characterAt(startOfName); + if (firstCharacter.isLetter() || firstCharacter == QLatin1Char('_')) { + // Finally check that we're not inside a comment or string (code copied from startOfOperator) + QTextCursor tc(m_interface->textDocument()); + tc.setPosition(pos); + + SimpleLexer tokenize; + LanguageFeatures lf = tokenize.languageFeatures(); + lf.qtMocRunEnabled = true; + lf.objCEnabled = true; + tokenize.setLanguageFeatures(lf); + tokenize.setSkipComments(false); + const Tokens &tokens = tokenize(tc.block().text(), BackwardsScanner::previousBlockState(tc.block())); + const int tokenIdx = SimpleLexer::tokenBefore(tokens, qMax(0, tc.positionInBlock() - 1)); + const Token tk = (tokenIdx == -1) ? Token() : tokens.at(tokenIdx); + + if (!tk.isComment() && !tk.isLiteral()) { + return true; + } else if (tk.isLiteral() + && tokens.size() == 3 + && tokens.at(0).kind() == T_POUND + && tokens.at(1).kind() == T_IDENTIFIER) { + const QString &line = tc.block().text(); + const Token &idToken = tokens.at(1); + const QStringRef &identifier = + line.midRef(idToken.bytesBegin(), + idToken.bytesEnd() - idToken.bytesBegin()); + if (identifier == QLatin1String("include") + || identifier == QLatin1String("include_next") + || (m_interface->objcEnabled() && identifier == QLatin1String("import"))) { + return true; + } + } + } + } + } + } + + return false; +} + +/** + * @brief Creates completion proposals for #include and given cursor + * @param cursor - cursor placed after opening bracked or quote + * @return false if completions list is empty + */ +bool ClangCompletionAssistProcessor::completeInclude(const QTextCursor &cursor) +{ + QString directoryPrefix; + if (m_completionOperator == T_SLASH) { + QTextCursor c = cursor; + c.movePosition(QTextCursor::StartOfLine, QTextCursor::KeepAnchor); + QString sel = c.selectedText(); + int startCharPos = sel.indexOf(QLatin1Char('"')); + if (startCharPos == -1) { + startCharPos = sel.indexOf(QLatin1Char('<')); + m_completionOperator = T_ANGLE_STRING_LITERAL; + } else { + m_completionOperator = T_STRING_LITERAL; + } + if (startCharPos != -1) + directoryPrefix = sel.mid(startCharPos + 1, sel.length() - 1); + } + + // Make completion for all relevant includes + CppTools::ProjectPart::HeaderPaths headerPaths = m_interface->headerPaths(); + const CppTools::ProjectPart::HeaderPath currentFilePath(QFileInfo(m_interface->fileName()).path(), + CppTools::ProjectPart::HeaderPath::IncludePath); + if (!headerPaths.contains(currentFilePath)) + headerPaths.append(currentFilePath); + + ::Utils::MimeDatabase mdb; + const ::Utils::MimeType mimeType = mdb.mimeTypeForName(QLatin1String("text/x-c++hdr")); + const QStringList suffixes = mimeType.suffixes(); + + foreach (const CppTools::ProjectPart::HeaderPath &headerPath, headerPaths) { + QString realPath = headerPath.path; + if (!directoryPrefix.isEmpty()) { + realPath += QLatin1Char('/'); + realPath += directoryPrefix; + if (headerPath.isFrameworkPath()) + realPath += QLatin1String(".framework/Headers"); + } + completeIncludePath(realPath, suffixes); + } + + return !m_completions.isEmpty(); +} + +bool ClangCompletionAssistProcessor::completeInclude(int position) +{ + QTextCursor textCursor(m_interface->textDocument()); // TODO: Simplify, move into function + textCursor.setPosition(position); + return completeInclude(textCursor); +} + +/** + * @brief Adds #include completion proposals using given include path + * @param realPath - one of directories where compiler searches includes + * @param suffixes - file suffixes for C/C++ header files + */ +void ClangCompletionAssistProcessor::completeIncludePath(const QString &realPath, + const QStringList &suffixes) +{ + QDirIterator i(realPath, QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot); + //: Parent folder for proposed #include completion + const QString hint = tr("Location: %1").arg(QDir::toNativeSeparators(QDir::cleanPath(realPath))); + while (i.hasNext()) { + const QString fileName = i.next(); + const QFileInfo fileInfo = i.fileInfo(); + const QString suffix = fileInfo.suffix(); + if (suffix.isEmpty() || suffixes.contains(suffix)) { + QString text = fileName.mid(realPath.length() + 1); + if (fileInfo.isDir()) + text += QLatin1Char('/'); + + ClangAssistProposalItem *item = new ClangAssistProposalItem; + item->setText(text); + item->setDetail(hint); + item->setIcon(m_icons.keywordIcon()); + item->keepCompletionOperator(m_completionOperator); + m_completions.append(item); + } + } +} + +bool ClangCompletionAssistProcessor::completePreprocessorDirectives() +{ + foreach (const QString &preprocessorCompletion, m_preprocessorCompletions) + addCompletionItem(preprocessorCompletion, + m_icons.iconForType(Icons::MacroIconType)); + + if (m_interface->objcEnabled()) + addCompletionItem(QLatin1String("import"), + m_icons.iconForType(Icons::MacroIconType)); + + return !m_completions.isEmpty(); +} + +bool ClangCompletionAssistProcessor::completeDoxygenKeywords() +{ + for (int i = 1; i < CppTools::T_DOXY_LAST_TAG; ++i) + addCompletionItem(QString::fromLatin1(CppTools::doxygenTagSpell(i)), m_icons.keywordIcon()); + return !m_completions.isEmpty(); +} + +void ClangCompletionAssistProcessor::addCompletionItem(const QString &text, + const QIcon &icon, + int order, + const QVariant &data) +{ + ClangAssistProposalItem *item = new ClangAssistProposalItem; + item->setText(text); + item->setIcon(icon); + item->setOrder(order); + item->setData(data); + item->keepCompletionOperator(m_completionOperator); + m_completions.append(item); +} + +void ClangCompletionAssistProcessor::sendFileContent(const QString &projectFilePath, + const QByteArray &modifiedFileContent) +{ + const QString filePath = m_interface->fileName(); + const QByteArray unsavedContent = modifiedFileContent.isEmpty() + ? m_interface->textDocument()->toPlainText().toUtf8() + : modifiedFileContent; + const bool hasUnsavedContent = true; // TODO + + IpcCommunicator &ipcCommunicator = m_interface->ipcCommunicator(); + ipcCommunicator.registerFilesForCodeCompletion( + {ClangBackEnd::FileContainer(filePath, + projectFilePath, + Utf8String::fromByteArray(unsavedContent), + hasUnsavedContent)}); +} + +void ClangCompletionAssistProcessor::sendCompletionRequest(int position, + const QByteArray &modifiedFileContent) +{ + int line, column; + TextEditor::Convenience::convertPosition(m_interface->textDocument(), position, &line, &column); + ++column; + + const QString filePath = m_interface->fileName(); + const QString projectFilePath = Utils::projectFilePathForFile(filePath); + sendFileContent(projectFilePath, modifiedFileContent); + m_interface->ipcCommunicator().completeCode(this, filePath, line, column, projectFilePath); +} + +TextEditor::IAssistProposal *ClangCompletionAssistProcessor::createProposal() const +{ + ClangAssistProposalModel *model = new ClangAssistProposalModel; + model->loadContent(m_completions); + return new ClangAssistProposal(m_positionForProposal, model); +} + +void ClangCompletionAssistProcessor::onCompletionsAvailable(const CodeCompletions &completions) +{ + QTC_CHECK(m_completions.isEmpty()); + + m_completions = toAssistProposalItems(completions); + if (m_addSnippets) + addSnippets(); + + setAsyncProposalAvailable(createProposal()); +} + +void ClangCompletionAssistProcessor::onFunctionHintCompletionsAvailable( + const CodeCompletions &completions) +{ + QTC_CHECK(!m_functionName.isEmpty()); + const auto relevantCompletions = matchingFunctionCompletions(completions, m_functionName); + + if (!relevantCompletions.isEmpty()) { + TextEditor::IFunctionHintProposalModel *model = new ClangFunctionHintModel(relevantCompletions); + TextEditor::FunctionHintProposal *proposal = new FunctionHintProposal(m_positionForProposal, model); + + setAsyncProposalAvailable(proposal); + } else { + QTC_CHECK(!"Function completion failed. Would fallback to global completion here..."); + // TODO: If we need this, the processor can't be deleted in IpcClient. + } +} + +} // namespace Internal +} // namespace ClangCodeModel + |