/**************************************************************************** ** ** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** 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 Digia. For licensing terms and ** conditions see http://www.qt.io/licensing. 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, Digia gives you certain additional ** rights. These rights are described in the Digia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ****************************************************************************/ #include "completionproposalsbuilder.h" #include "utils_p.h" #include #include enum PriorityFixes { PriorityFix_ExplicitDestructorCall = 10 }; namespace ClangCodeModel { namespace { struct ObjCMessagePart { QString text; int signatureLen; // length of "setScale:" in "setScale: 13" ObjCMessagePart() : signatureLen(0) {} ObjCMessagePart(const QString &signature, int &indentBonus) : text(signature) , signatureLen(signature.length() + indentBonus) { indentBonus = 0; } void addToSignature(const QString &signature) { text += signature; signatureLen += signature.length(); } }; } // anonymous namespace /** * @class ClangCodeModel::CompletionProposalsBuilder * @brief Captures completion lists and than processes sequence of completion chunks * * Can produce several completion proposals for one CXCompletionResult, if and * only if result contains chunks with kind 'Optional' * Different proposals can have the same text, it's normal behavior. * * @note Unit tests are in \a clangcompletion_test.cpp * * @note Unresolved problems: * Function hint not appear after space: "foo(1, "; * Slot can have optional arguments, that produces 2 slots. * */ CompletionProposalsBuilder::CompletionProposalsBuilder(QList &results, quint64 contexts, bool isSignalSlotCompletion) : m_results(results) , m_contexts(contexts) , m_isSignalSlotCompletion(isSignalSlotCompletion) { } void CompletionProposalsBuilder::operator ()(const CXCompletionResult &cxResult) { resetWithResult(cxResult); #if defined(CINDEX_VERSION) && (CINDEX_VERSION > 5) const QString brief = Internal::getQString(clang_getCompletionBriefComment(cxResult.CompletionString)); if (!brief.isEmpty()) m_comment += QLatin1String("Brief: ") + Qt::escape(brief); #endif if (m_resultAvailability == CodeCompletionResult::Deprecated) { m_comment += QLatin1String("@note "); //: deprecated C++ symbol m_comment += tr("Is deprecated"); } m_hint = QLatin1String("

"); switch (m_resultKind) { case CodeCompletionResult::ObjCMessageCompletionKind: concatChunksForObjectiveCMessage(cxResult); break; case CodeCompletionResult::ClassCompletionKind: case CodeCompletionResult::NamespaceCompletionKind: case CodeCompletionResult::EnumeratorCompletionKind: concatChunksForNestedName(cxResult.CompletionString); break; case CodeCompletionResult::ClangSnippetKind: concatChunksAsSnippet(cxResult.CompletionString); break; case CodeCompletionResult::SlotCompletionKind: case CodeCompletionResult::SignalCompletionKind: if (m_isSignalSlotCompletion) concatSlotSignalSignature(cxResult.CompletionString); else concatChunksOnlyTypedText(cxResult.CompletionString); break; default: concatChunksOnlyTypedText(cxResult.CompletionString); break; } m_hint += QLatin1String("

"); m_hint += m_comment; finalize(); foreach (const OptionalChunk &chunk, m_optionalChunks) { m_hint.insert(chunk.positionInHint, chunk.hint); finalize(); } } /** * @return Internal ClangCodeModel's completion kind, that affects further postprocessing */ CodeCompletionResult::Kind CompletionProposalsBuilder::getKind(const CXCompletionResult &cxResult) { CXCompletionString complStr = cxResult.CompletionString; CXCursorKind cursorKind = cxResult.CursorKind; switch (cursorKind) { case CXCursor_Constructor: return CodeCompletionResult::ConstructorCompletionKind; case CXCursor_Destructor: return CodeCompletionResult::DestructorCompletionKind; case CXCursor_CXXMethod: { const unsigned numAnnotations = clang_getCompletionNumAnnotations(complStr); bool isSignal = false, isSlot = false; for (unsigned i = 0; i < numAnnotations && !isSignal && !isSlot; ++i) { CXString cxAnn = clang_getCompletionAnnotation(complStr, i); QString ann = Internal::getQString(cxAnn); isSignal = ann == QLatin1String("qt_signal"); isSlot = ann == QLatin1String("qt_slot"); } if (isSignal) return CodeCompletionResult::SignalCompletionKind; if (isSlot) return CodeCompletionResult::SlotCompletionKind; } // intentional fall-through! case CXCursor_ConversionFunction: case CXCursor_FunctionDecl: case CXCursor_FunctionTemplate: case CXCursor_MemberRef: case CXCursor_MemberRefExpr: return CodeCompletionResult::FunctionCompletionKind; break; case CXCursor_FieldDecl: case CXCursor_VarDecl: case CXCursor_ParmDecl: case CXCursor_ObjCIvarDecl: case CXCursor_ObjCPropertyDecl: case CXCursor_ObjCSynthesizeDecl: case CXCursor_NonTypeTemplateParameter: return CodeCompletionResult::VariableCompletionKind; case CXCursor_Namespace: case CXCursor_NamespaceAlias: case CXCursor_NamespaceRef: return CodeCompletionResult::NamespaceCompletionKind; case CXCursor_StructDecl: case CXCursor_UnionDecl: case CXCursor_ClassDecl: case CXCursor_TypeRef: case CXCursor_TemplateRef: case CXCursor_TypedefDecl: case CXCursor_ClassTemplate: case CXCursor_ClassTemplatePartialSpecialization: case CXCursor_ObjCClassRef: case CXCursor_ObjCInterfaceDecl: case CXCursor_ObjCImplementationDecl: case CXCursor_ObjCCategoryDecl: case CXCursor_ObjCCategoryImplDecl: case CXCursor_ObjCProtocolDecl: case CXCursor_ObjCProtocolRef: case CXCursor_TemplateTypeParameter: case CXCursor_TemplateTemplateParameter: return CodeCompletionResult::ClassCompletionKind; case CXCursor_EnumConstantDecl: return CodeCompletionResult::EnumeratorCompletionKind; case CXCursor_EnumDecl: return CodeCompletionResult::EnumCompletionKind; case CXCursor_MacroDefinition: { const unsigned numChunks = clang_getNumCompletionChunks(complStr); for (unsigned i = 0; i < numChunks; ++i) { CXCompletionChunkKind kind = clang_getCompletionChunkKind(complStr, i); if (kind == CXCompletionChunk_Placeholder) { return CodeCompletionResult::FunctionCompletionKind; } } return CodeCompletionResult::PreProcessorCompletionKind; } case CXCursor_PreprocessingDirective: case CXCursor_MacroExpansion: case CXCursor_InclusionDirective: return CodeCompletionResult::PreProcessorCompletionKind; case CXCursor_ObjCClassMethodDecl: case CXCursor_ObjCInstanceMethodDecl: return CodeCompletionResult::ObjCMessageCompletionKind; case CXCursor_NotImplemented: { const unsigned numChunks = clang_getNumCompletionChunks(complStr); for (unsigned i = 0; i < numChunks; ++i) { CXCompletionChunkKind kind = clang_getCompletionChunkKind(complStr, i); if (kind == CXCompletionChunk_Placeholder) { return CodeCompletionResult::ClangSnippetKind; } } return CodeCompletionResult::KeywordCompletionKind; } default: break; } return CodeCompletionResult::Other; } /** * @return Symbol availability, which is almost unused */ CodeCompletionResult::Availability CompletionProposalsBuilder::getAvailability(const CXCompletionResult &cxResult) { CXCompletionString complStr = cxResult.CompletionString; switch (clang_getCompletionAvailability(complStr)) { case CXAvailability_Deprecated: return CodeCompletionResult::Deprecated; case CXAvailability_NotAvailable: return CodeCompletionResult::NotAvailable; case CXAvailability_NotAccessible: return CodeCompletionResult::NotAccessible; default: return CodeCompletionResult::Available; } } /** * @return Start index of name, which is unused in Qt signal/slot signature * @param text Text of Placeholder completion string chunk */ int CompletionProposalsBuilder::findNameInPlaceholder(const QString &text) { bool firstIdPassed = false; bool isInIdentifier = false; int bracesCounter = 0; int idStart = 0; for (int i = 0, n = text.size(); i < n; ++i) { const QChar ch = text[i]; if (ch == QLatin1Char(':')) { firstIdPassed = false; isInIdentifier = false; } if (ch == QLatin1Char('<') || ch == QLatin1Char('(')) { if (isInIdentifier && text.mid(idStart, i - idStart) == QLatin1String("const")) firstIdPassed = false; ++bracesCounter; isInIdentifier = false; } else if (ch == QLatin1Char('>') || ch == QLatin1Char(')')) { if (isInIdentifier && text.mid(idStart, i - idStart) == QLatin1String("const")) firstIdPassed = false; --bracesCounter; isInIdentifier = false; } else if (bracesCounter == 0) { if (isInIdentifier) { isInIdentifier = ch.isLetterOrNumber() || (ch == QLatin1Char('_')); if (!isInIdentifier && text.mid(idStart, i - idStart) == QLatin1String("const")) firstIdPassed = false; } else if (ch.isLetter() || (ch == QLatin1Char('_'))) { if (firstIdPassed) return i; isInIdentifier = true; idStart = i; firstIdPassed = true; } } } return text.size(); } void CompletionProposalsBuilder::resetWithResult(const CXCompletionResult &cxResult) { m_priority = clang_getCompletionPriority(cxResult.CompletionString); m_resultKind = getKind(cxResult); m_resultAvailability = getAvailability(cxResult); m_hasParameters = false; m_hint.clear(); m_text.clear(); m_snippet.clear(); m_comment.clear(); m_optionalChunks.clear(); } /** * @brief Appends completion proposal initialized with collected data */ void CompletionProposalsBuilder::finalize() { // Fixes code completion: operator and destructor cases if (m_contexts & (CXCompletionContext_DotMemberAccess | CXCompletionContext_ArrowMemberAccess | CXCompletionContext_AnyValue)) { if (m_resultKind == CodeCompletionResult::DestructorCompletionKind) m_priority *= PriorityFix_ExplicitDestructorCall; else if (m_resultKind == CodeCompletionResult::FunctionCompletionKind && m_text.startsWith(QLatin1String("operator"))) return; } CodeCompletionResult ccr(m_priority); ccr.setCompletionKind(m_resultKind); ccr.setAvailability(m_resultAvailability); ccr.setHasParameters(m_hasParameters); ccr.setHint(m_hint); ccr.setText(m_text); ccr.setSnippet(m_snippet); m_results.append(ccr); } /** * @brief Creates text, hint and snippet * * Text is just signature, e.g. 'length' for [NSString length] or 'respondsToSelector:' * for [id respondsToSelector:(SEL)sel]. * Snippet is actual text, where any message parameter becames snippet part: * 'respondsToSelector:$(SEL)sel$'. * Hint consists of snippet preview and doxygen 'return' entry with returned type. */ void CompletionProposalsBuilder::concatChunksForObjectiveCMessage(const CXCompletionResult &cxResult) { CXCompletionString cxString = cxResult.CompletionString; const unsigned count = clang_getNumCompletionChunks(cxString); unsigned index = 0; QString hintPrefix; if (cxResult.CursorKind == CXCursor_ObjCClassMethodDecl) hintPrefix += QLatin1Char('+'); else hintPrefix += QLatin1Char('-'); int indentBonus = 1; bool addSpaceAtPrefixEnd = true; for (; index < count; ++index) { CXCompletionChunkKind chunkKind = clang_getCompletionChunkKind(cxString, index); if (chunkKind == CXCompletionChunk_TypedText || chunkKind == CXCompletionChunk_Informative) { break; } const QString text = Internal::getQString(clang_getCompletionChunkText(cxString, index), false); if (chunkKind == CXCompletionChunk_ResultType) { hintPrefix += QLatin1String("("); hintPrefix += Qt::escape(text); hintPrefix += QLatin1String(") "); indentBonus += 3 + text.length(); addSpaceAtPrefixEnd = false; } else { hintPrefix += Qt::escape(text); indentBonus += text.length(); m_snippet += text; } } if (addSpaceAtPrefixEnd) { m_snippet += QLatin1Char(' '); hintPrefix += QLatin1Char(' '); indentBonus += 1; } m_hint += hintPrefix; QList parts; bool previousWasTypedText = false; for (; index < count; ++index) { CXCompletionChunkKind chunkKind = clang_getCompletionChunkKind(cxString, index); const QString text = Internal::getQString(clang_getCompletionChunkText(cxString, index), false); switch (chunkKind) { case CXCompletionChunk_TypedText: if (previousWasTypedText) parts.back().addToSignature(text); else parts.append(ObjCMessagePart(text, indentBonus)); m_snippet += text; m_text += text; break; case CXCompletionChunk_Informative: parts.append(ObjCMessagePart(text, indentBonus)); break; case CXCompletionChunk_Text: case CXCompletionChunk_LeftParen: case CXCompletionChunk_RightParen: case CXCompletionChunk_Comma: case CXCompletionChunk_HorizontalSpace: m_snippet += text; parts.back().text += Qt::escape(text); break; case CXCompletionChunk_Placeholder: appendSnippet(text); parts.back().text += QLatin1String(""); parts.back().text += Qt::escape(text); parts.back().text += QLatin1String(""); break; case CXCompletionChunk_LeftAngle: m_snippet += text; parts.back().text += QLatin1String("<"); break; case CXCompletionChunk_RightAngle: m_snippet += text; parts.back().text += QLatin1String(">"); break; default: break; } previousWasTypedText = (chunkKind == CXCompletionChunk_TypedText); } int indent = 0; foreach (const ObjCMessagePart &part, parts) indent = qMax(indent, part.signatureLen); bool isFirstPart = true; foreach (const ObjCMessagePart &part, parts) { if (!isFirstPart) m_hint += QLatin1String("
"); isFirstPart = false; for (int i = 0; i < indent - part.signatureLen; ++i) m_hint += QLatin1String(" "); m_hint += part.text; } } /** * @brief Creates entries like 'MyClass', 'MyNamespace::', 'MyEnumClass::Value1' */ void CompletionProposalsBuilder::concatChunksForNestedName(const CXCompletionString &cxString) { bool hasPlaceholder = false; unsigned count = clang_getNumCompletionChunks(cxString); for (unsigned i = 0; i < count; ++i) { CXCompletionChunkKind chunkKind = clang_getCompletionChunkKind(cxString, i); QString text = Internal::getQString(clang_getCompletionChunkText(cxString, i), false); switch (chunkKind) { case CXCompletionChunk_TypedText: case CXCompletionChunk_Text: m_text += text; m_snippet += text; m_hint += text; break; case CXCompletionChunk_LeftAngle: case CXCompletionChunk_RightAngle: case CXCompletionChunk_Comma: case CXCompletionChunk_HorizontalSpace: m_snippet += text; m_hint += Qt::escape(text); break; case CXCompletionChunk_Placeholder: hasPlaceholder = true; appendSnippet(text); appendHintBold(text); break; default: break; } } if (!hasPlaceholder) m_snippet.clear(); } /** * @brief Creates text, snippet and hint for snippet preview * * Text is copy of snippet without '$' marks. * Hint also have 'return' doxygen entry if applicable (e.g. 'typeid...') */ void CompletionProposalsBuilder::concatChunksAsSnippet(const CXCompletionString &cxString) { unsigned count = clang_getNumCompletionChunks(cxString); for (unsigned i = 0; i < count; ++i) { CXCompletionChunkKind chunkKind = clang_getCompletionChunkKind(cxString, i); const QString text = Internal::getQString(clang_getCompletionChunkText(cxString, i), false); switch (chunkKind) { case CXCompletionChunk_ResultType: attachResultTypeToComment(text); break; case CXCompletionChunk_Placeholder: m_text += text; appendSnippet(text); appendHintBold(text); break; case CXCompletionChunk_LeftAngle: m_snippet += text; m_text += text; m_hint += QLatin1String("<"); break; case CXCompletionChunk_RightAngle: m_snippet += text; m_text += text; m_hint += QLatin1String(">"); break; case CXCompletionChunk_VerticalSpace: m_snippet += QLatin1Char('\n'); m_text += QLatin1Char(' '); m_hint += QLatin1String("
"); break; default: m_snippet += text; m_text += text; m_hint += text; break; } } } /** * @brief Creates short text and hint with details * * Text is just function or variable name. Hint also contains function signature * or variable type. */ void CompletionProposalsBuilder::concatChunksOnlyTypedText(const CXCompletionString &cxString) { bool previousChunkWasLParen = false; bool isInsideTemplateSpec = false; unsigned count = clang_getNumCompletionChunks(cxString); for (unsigned i = 0; i < count; ++i) { CXCompletionChunkKind chunkKind = clang_getCompletionChunkKind(cxString, i); QString text = Internal::getQString(clang_getCompletionChunkText(cxString, i), false); switch (chunkKind) { case CXCompletionChunk_LeftParen: case CXCompletionChunk_RightParen: case CXCompletionChunk_Text: case CXCompletionChunk_LeftAngle: case CXCompletionChunk_RightAngle: m_hint += Qt::escape(text); break; case CXCompletionChunk_HorizontalSpace: case CXCompletionChunk_Comma: if (isInsideTemplateSpec) { m_snippet += text; } m_hint += Qt::escape(text); break; case CXCompletionChunk_Placeholder: if (isInsideTemplateSpec) { appendSnippet(text); } m_hint += Qt::escape(text); break; case CXCompletionChunk_TypedText: m_text = text; appendHintBold(text); break; case CXCompletionChunk_ResultType: { m_hint += Qt::escape(text); QChar last = text[text.size() - 1]; if (last != QLatin1Char('*') && last != QLatin1Char('&')) m_hint += QLatin1Char(' '); } break; case CXCompletionChunk_Informative: if (text == QLatin1String(" const")) m_hint += text; break; case CXCompletionChunk_Optional: appendOptionalChunks(clang_getCompletionChunkCompletionString(cxString, i), m_hint.size()); break; default: break; } if (chunkKind == CXCompletionChunk_RightParen && previousChunkWasLParen) m_hasParameters = false; if (chunkKind == CXCompletionChunk_LeftParen) { previousChunkWasLParen = true; m_hasParameters = true; } else { previousChunkWasLParen = false; } if (chunkKind == CXCompletionChunk_LeftAngle) { m_snippet = m_text; m_snippet += text; isInsideTemplateSpec = true; } else if (chunkKind == CXCompletionChunk_RightAngle) { isInsideTemplateSpec = false; m_snippet += text; } } } /** * @brief Produces signal/slot signatures for 'connect' methods family */ void CompletionProposalsBuilder::concatSlotSignalSignature(const CXCompletionString &cxString) { QString resultType; unsigned count = clang_getNumCompletionChunks(cxString); for (unsigned i = 0; i < count; ++i) { CXCompletionChunkKind chunkKind = clang_getCompletionChunkKind(cxString, i); QString text = Internal::getQString(clang_getCompletionChunkText(cxString, i), false); switch (chunkKind) { case CXCompletionChunk_Placeholder: text.truncate(findNameInPlaceholder(text)); // fall-through case CXCompletionChunk_TypedText: case CXCompletionChunk_LeftParen: case CXCompletionChunk_RightParen: case CXCompletionChunk_Comma: case CXCompletionChunk_Text: m_text += text; break; case CXCompletionChunk_ResultType: resultType += text; resultType += QLatin1Char(' '); break; default: break; } } const QString parent = Internal::getQString(clang_getCompletionParent(cxString, NULL)); if (m_resultKind == CodeCompletionResult::SlotCompletionKind) m_hint += tr("Slot of %1, returns %2").arg(parent, resultType); else m_hint += tr("Signal of %1, returns %2").arg(parent, resultType); } /** * @brief Stores optional part for further postprocessing in \a finalize() * @param insertionIndex Index where to insert optional chunk into hint */ void CompletionProposalsBuilder::appendOptionalChunks(const CXCompletionString &cxString, int insertionIndex) { OptionalChunk chunk; chunk.positionInHint = insertionIndex; unsigned count = clang_getNumCompletionChunks(cxString); for (unsigned i = 0; i < count; ++i) { CXCompletionChunkKind chunkKind = clang_getCompletionChunkKind(cxString, i); QString text = Internal::getQString(clang_getCompletionChunkText(cxString, i), false); switch (chunkKind) { case CXCompletionChunk_Placeholder: chunk.hint += Qt::escape(text); break; case CXCompletionChunk_Comma: chunk.hint += text; chunk.hint += QLatin1Char(' '); break; case CXCompletionChunk_Optional: m_optionalChunks.append(chunk); appendOptionalChunks(clang_getCompletionChunkCompletionString(cxString, i), insertionIndex + chunk.hint.size()); return; default: break; } } m_optionalChunks.append(chunk); } void CompletionProposalsBuilder::attachResultTypeToComment(const QString &resultType) { if (resultType.isEmpty()) return; if (!m_comment.isEmpty()) m_comment += QLatin1String("
"); m_comment += QLatin1String("@return "); m_comment += resultType; } void CompletionProposalsBuilder::appendSnippet(const QString &text) { m_snippet += QLatin1Char('$'); m_snippet += text; m_snippet += QLatin1Char('$'); } void CompletionProposalsBuilder::appendHintBold(const QString &text) { m_hint += QLatin1String(""); m_hint += Qt::escape(text); m_hint += QLatin1String(""); } } // namespace ClangCodeModel