diff --git a/src/plugins/clangcodemodel/clangassistproposalitem.cpp b/src/plugins/clangcodemodel/clangassistproposalitem.cpp index 697e5453bb6..41cb7702075 100644 --- a/src/plugins/clangcodemodel/clangassistproposalitem.cpp +++ b/src/plugins/clangcodemodel/clangassistproposalitem.cpp @@ -30,10 +30,13 @@ #include "clangassistproposalitem.h" +#include "completionchunkstotextconverter.h" + #include #include #include +#include #include #include @@ -64,6 +67,21 @@ bool ClangAssistProposalItem::prematurelyApplies(const QChar &typedChar) const return applies; } +static bool hasOnlyBlanksBeforeCursorInLine(QTextCursor textCursor) +{ + textCursor.movePosition(QTextCursor::StartOfLine, QTextCursor::KeepAnchor); + + const auto textBeforeCursor = textCursor.selectedText(); + + const auto nonSpace = std::find_if(textBeforeCursor.cbegin(), + textBeforeCursor.cend(), + [] (const QChar &signBeforeCursor) { + return !signBeforeCursor.isSpace(); + }); + + return nonSpace == textBeforeCursor.cend(); +} + void ClangAssistProposalItem::applyContextualContent(TextEditor::TextEditorWidget *editorWidget, int basePosition) const { @@ -86,6 +104,17 @@ void ClangAssistProposalItem::applyContextualContent(TextEditor::TextEditorWidge if (m_typedChar == QLatin1Char('/')) // Eat the slash m_typedChar = QChar(); } + } else if (ccr.completionKind() == CodeCompletion::KeywordCompletionKind) { + CompletionChunksToTextConverter converter; + converter.setAddPlaceHolderPositions(true); + converter.setAddSpaces(true); + converter.setAddExtraVerticalSpaceBetweenBraces(true); + + converter.parseChunks(ccr.chunks()); + + toInsert = converter.text(); + if (converter.hasPlaceholderPositions()) + cursorOffset = converter.placeholderPositions().at(0) - converter.text().size(); } else if (!ccr.text().isEmpty()) { const TextEditor::CompletionSettings &completionSettings = TextEditor::TextEditorSettings::instance()->completionSettings(); @@ -165,7 +194,7 @@ void ClangAssistProposalItem::applyContextualContent(TextEditor::TextEditorWidge const int endsPosition = editorWidget->position(TextEditor::EndOfLinePosition); const QString existingText = editorWidget->textAt(editorWidget->position(), endsPosition - editorWidget->position()); int existLength = 0; - if (!existingText.isEmpty()) { + if (!existingText.isEmpty() && ccr.completionKind() != CodeCompletion::KeywordCompletionKind) { // Calculate the exist length in front of the extra chars existLength = toInsert.length() - (editorWidget->position() - basePosition); while (!existingText.startsWith(toInsert.right(existLength))) { @@ -189,6 +218,18 @@ void ClangAssistProposalItem::applyContextualContent(TextEditor::TextEditorWidge editorWidget->replace(length, toInsert); if (cursorOffset) editorWidget->setCursorPosition(editorWidget->position() + cursorOffset); + + // indent the statement + if (ccr.completionKind() == CodeCompletion::KeywordCompletionKind) { + auto selectionCursor = editorWidget->textCursor(); + selectionCursor.setPosition(basePosition); + selectionCursor.setPosition(basePosition + toInsert.size(), QTextCursor::KeepAnchor); + + auto basePositionCursor = editorWidget->textCursor(); + basePositionCursor.setPosition(basePosition); + if (hasOnlyBlanksBeforeCursorInLine(basePositionCursor)) + editorWidget->textDocument()->autoIndent(selectionCursor); + } } void ClangAssistProposalItem::keepCompletionOperator(unsigned compOp) diff --git a/src/plugins/clangcodemodel/clangcompletionassistprocessor.cpp b/src/plugins/clangcodemodel/clangcompletionassistprocessor.cpp index 1062a3726b8..61c893601eb 100644 --- a/src/plugins/clangcodemodel/clangcompletionassistprocessor.cpp +++ b/src/plugins/clangcodemodel/clangcompletionassistprocessor.cpp @@ -37,6 +37,7 @@ #include "clangcompletioncontextanalyzer.h" #include "clangfunctionhintmodel.h" #include "clangutils.h" +#include "completionchunkstotextconverter.h" #include @@ -86,14 +87,19 @@ QList toAssistProposalItems(const CodeCompletions &complet if (slotCompletion && ccr.completionKind() != CodeCompletion::SlotCompletionKind) continue; - const QString txt(ccr.text().toString()); - ClangAssistProposalItem *item = items.value(txt, 0); + QString name; + if (ccr.completionKind() == CodeCompletion::KeywordCompletionKind) + name = CompletionChunksToTextConverter::convertToName(ccr.chunks()); + else + name = ccr.text().toString(); + + ClangAssistProposalItem *item = items.value(name, 0); if (item) { item->addOverload(ccr); } else { item = new ClangAssistProposalItem; - items.insert(txt, item); - item->setText(txt); + items.insert(name, item); + item->setText(name); item->setDetail(ccr.hint().toString()); item->setOrder(ccr.priority()); diff --git a/src/plugins/clangcodemodel/clangfunctionhintmodel.cpp b/src/plugins/clangcodemodel/clangfunctionhintmodel.cpp index 67e8048ee46..f950947448e 100644 --- a/src/plugins/clangcodemodel/clangfunctionhintmodel.cpp +++ b/src/plugins/clangcodemodel/clangfunctionhintmodel.cpp @@ -76,7 +76,7 @@ QString ClangFunctionHintModel::text(int index) const hintText += prettyMethod.mid(end).toHtmlEscaped()); return hintText; #endif - return CompletionChunksToTextConverter::convert(m_functionSymbols.at(index).chunks()); + return CompletionChunksToTextConverter::convertToFunctionSignature(m_functionSymbols.at(index).chunks()); } int ClangFunctionHintModel::activeArgument(const QString &prefix) const diff --git a/src/plugins/clangcodemodel/completionchunkstotextconverter.cpp b/src/plugins/clangcodemodel/completionchunkstotextconverter.cpp index 3027039603c..c9342bdccd4 100644 --- a/src/plugins/clangcodemodel/completionchunkstotextconverter.cpp +++ b/src/plugins/clangcodemodel/completionchunkstotextconverter.cpp @@ -30,15 +30,53 @@ #include "completionchunkstotextconverter.h" +#include +#include + namespace ClangCodeModel { namespace Internal { void CompletionChunksToTextConverter::parseChunks(const QVector &codeCompletionChunks) { m_text.clear(); + m_placeholderPositions.clear(); - for (const auto &codeCompletionChunk : codeCompletionChunks) - parse(codeCompletionChunk); + m_codeCompletionChunks = codeCompletionChunks; + + addExtraVerticalSpaceBetweenBraces(); + + std::for_each(m_codeCompletionChunks.cbegin(), + m_codeCompletionChunks.cend(), + [this] (const ClangBackEnd::CodeCompletionChunk &chunk) + { + parse(chunk); + m_previousCodeCompletionChunk = chunk; + }); +} + +void CompletionChunksToTextConverter::setAddPlaceHolderText(bool addPlaceHolderText) +{ + m_addPlaceHolderText = addPlaceHolderText; +} + +void CompletionChunksToTextConverter::setAddPlaceHolderPositions(bool addPlaceHolderPositions) +{ + m_addPlaceHolderPositions = addPlaceHolderPositions; +} + +void CompletionChunksToTextConverter::setAddResultType(bool addResultType) +{ + m_addResultType = addResultType; +} + +void CompletionChunksToTextConverter::setAddSpaces(bool addSpaces) +{ + m_addSpaces = addSpaces; +} + +void CompletionChunksToTextConverter::setAddExtraVerticalSpaceBetweenBraces(bool addExtraVerticalSpaceBetweenBraces) +{ + m_addExtraVerticalSpaceBetweenBraces = addExtraVerticalSpaceBetweenBraces; } const QString &CompletionChunksToTextConverter::text() const @@ -46,7 +84,28 @@ const QString &CompletionChunksToTextConverter::text() const return m_text; } -QString CompletionChunksToTextConverter::convert(const QVector &codeCompletionChunks) +const std::vector &CompletionChunksToTextConverter::placeholderPositions() const +{ + return m_placeholderPositions; +} + +bool CompletionChunksToTextConverter::hasPlaceholderPositions() const +{ + return m_placeholderPositions.size() > 0; +} + +QString CompletionChunksToTextConverter::convertToFunctionSignature(const QVector &codeCompletionChunks) +{ + CompletionChunksToTextConverter converter; + converter.setAddPlaceHolderText(true); + converter.setAddResultType(true); + + converter.parseChunks(codeCompletionChunks); + + return converter.text(); +} + +QString CompletionChunksToTextConverter::convertToName(const QVector &codeCompletionChunks) { CompletionChunksToTextConverter converter; @@ -62,17 +121,25 @@ void CompletionChunksToTextConverter::parse(const ClangBackEnd::CodeCompletionCh switch (codeCompletionChunk.kind()) { case CodeCompletionChunk::ResultType: parseResultType(codeCompletionChunk.text()); break; case CodeCompletionChunk::Optional: parseOptional(codeCompletionChunk); break; + case CodeCompletionChunk::Placeholder: parsePlaceHolder(codeCompletionChunk); break; + case CodeCompletionChunk::LeftParen: parseLeftParen(codeCompletionChunk); break; + case CodeCompletionChunk::LeftBrace: parseLeftBrace(codeCompletionChunk); break; default: parseText(codeCompletionChunk.text()); break; } } void CompletionChunksToTextConverter::parseResultType(const Utf8String &resultTypeText) { - m_text += resultTypeText.toString() + QChar(QChar::Space); + if (m_addResultType) + m_text += resultTypeText.toString() + QChar(QChar::Space); } void CompletionChunksToTextConverter::parseText(const Utf8String &text) { + if (m_addSpaces + && m_previousCodeCompletionChunk.kind() == ClangBackEnd::CodeCompletionChunk::RightBrace) + m_text += QChar(QChar::Space); + m_text += text.toString(); } @@ -80,11 +147,84 @@ void CompletionChunksToTextConverter::parseOptional(const ClangBackEnd::CodeComp { m_text += QStringLiteral(""); - m_text += convert(optionalCodeCompletionChunk.optionalChunks()); + m_text += convertToFunctionSignature(optionalCodeCompletionChunk.optionalChunks()); m_text += QStringLiteral(""); } +void CompletionChunksToTextConverter::parsePlaceHolder(const ClangBackEnd::CodeCompletionChunk &codeCompletionChunk) +{ + if (m_addPlaceHolderText) + m_text += codeCompletionChunk.text().toString(); + + if (m_addPlaceHolderPositions) + m_placeholderPositions.push_back(m_text.size()); +} + +void CompletionChunksToTextConverter::parseLeftParen(const ClangBackEnd::CodeCompletionChunk &codeCompletionChunk) +{ + if (m_addSpaces && m_previousCodeCompletionChunk.kind() != ClangBackEnd::CodeCompletionChunk::RightAngle) + m_text += QChar(QChar::Space); + + m_text += codeCompletionChunk.text().toString(); +} + +void CompletionChunksToTextConverter::parseLeftBrace(const ClangBackEnd::CodeCompletionChunk &codeCompletionChunk) +{ + if (m_addSpaces) + m_text += QChar(QChar::Space); + + m_text += codeCompletionChunk.text().toString(); +} + +void CompletionChunksToTextConverter::addExtraVerticalSpaceBetweenBraces() +{ + if (m_addExtraVerticalSpaceBetweenBraces) + addExtraVerticalSpaceBetweenBraces(m_codeCompletionChunks.begin()); +} + +void CompletionChunksToTextConverter::addExtraVerticalSpaceBetweenBraces(const QVector::iterator &begin) +{ + using ClangBackEnd::CodeCompletionChunk; + + const auto leftBraceCompare = [] (const CodeCompletionChunk &chunk) { + return chunk.kind() == CodeCompletionChunk::LeftBrace; + }; + + const auto rightBraceCompare = [] (const CodeCompletionChunk &chunk) { + return chunk.kind() == CodeCompletionChunk::RightBrace; + }; + + const auto verticalSpaceCompare = [] (const CodeCompletionChunk &chunk) { + return chunk.kind() == CodeCompletionChunk::VerticalSpace; + }; + + auto leftBrace = std::find_if(begin, m_codeCompletionChunks.end(), leftBraceCompare); + + if (leftBrace != m_codeCompletionChunks.end()) { + auto rightBrace = std::find_if(leftBrace, m_codeCompletionChunks.end(), rightBraceCompare); + + if (rightBrace != m_codeCompletionChunks.end()) { + auto verticalSpaceCount = std::count_if(leftBrace, rightBrace, verticalSpaceCompare); + + if (verticalSpaceCount <= 1) { + auto distance = std::distance(leftBrace, rightBrace); + CodeCompletionChunk verticalSpaceChunck(CodeCompletionChunk::VerticalSpace, + Utf8StringLiteral("\n")); + auto verticalSpace = m_codeCompletionChunks.insert(std::next(leftBrace), + verticalSpaceChunck); + std::advance(verticalSpace, distance); + rightBrace = verticalSpace; + } + + auto begin = std::next(rightBrace); + + if (begin != m_codeCompletionChunks.end()) + addExtraVerticalSpaceBetweenBraces(begin); + } + } +} + } // namespace Internal } // namespace ClangCodeModel diff --git a/src/plugins/clangcodemodel/completionchunkstotextconverter.h b/src/plugins/clangcodemodel/completionchunkstotextconverter.h index 20d4472b5ba..9e2f6d5c6d4 100644 --- a/src/plugins/clangcodemodel/completionchunkstotextconverter.h +++ b/src/plugins/clangcodemodel/completionchunkstotextconverter.h @@ -37,6 +37,8 @@ #include +#include + namespace ClangCodeModel { namespace Internal { @@ -45,18 +47,40 @@ class CompletionChunksToTextConverter public: void parseChunks(const QVector &codeCompletionChunks); - const QString &text() const; + void setAddPlaceHolderText(bool addPlaceHolderText); + void setAddPlaceHolderPositions(bool addPlaceHolderPositions); + void setAddResultType(bool addResultType); + void setAddSpaces(bool addSpaces); + void setAddExtraVerticalSpaceBetweenBraces(bool addExtraVerticalSpaceBetweenBraces); - static QString convert(const QVector &codeCompletionChunks); + const QString &text() const; + const std::vector &placeholderPositions() const; + bool hasPlaceholderPositions() const; + + static QString convertToFunctionSignature(const QVector &codeCompletionChunks); + static QString convertToName(const QVector &codeCompletionChunks); private: void parse(const ClangBackEnd::CodeCompletionChunk & codeCompletionChunk); void parseResultType(const Utf8String &text); void parseText(const Utf8String &text); - void parseOptional(const ClangBackEnd::CodeCompletionChunk & optionalCodeCompletionChunk); + void parseOptional(const ClangBackEnd::CodeCompletionChunk &optionalCodeCompletionChunk); + void parsePlaceHolder(const ClangBackEnd::CodeCompletionChunk &codeCompletionChunk); + void parseLeftParen(const ClangBackEnd::CodeCompletionChunk &codeCompletionChunk); + void parseLeftBrace(const ClangBackEnd::CodeCompletionChunk &codeCompletionChunk); + void addExtraVerticalSpaceBetweenBraces(); + void addExtraVerticalSpaceBetweenBraces(const QVector::iterator &); private: + std::vector m_placeholderPositions; + QVector m_codeCompletionChunks; + ClangBackEnd::CodeCompletionChunk m_previousCodeCompletionChunk; QString m_text; + bool m_addPlaceHolderText = false; + bool m_addPlaceHolderPositions = false; + bool m_addResultType = false; + bool m_addSpaces = false; + bool m_addExtraVerticalSpaceBetweenBraces = false; }; } // namespace Internal diff --git a/tests/unit/unittest/completionchunkstotextconvertertest.cpp b/tests/unit/unittest/completionchunkstotextconvertertest.cpp index 97f017c0695..0e51d63c735 100644 --- a/tests/unit/unittest/completionchunkstotextconvertertest.cpp +++ b/tests/unit/unittest/completionchunkstotextconvertertest.cpp @@ -39,11 +39,15 @@ namespace { using ClangBackEnd::CodeCompletionChunk; +using Converter = ClangCodeModel::Internal::CompletionChunksToTextConverter; class CompletionChunksToTextConverter : public ::testing::Test { protected: - ClangCodeModel::Internal::CompletionChunksToTextConverter converter; + void setupConverterForKeywords(); + +protected: + Converter converter; CodeCompletionChunk integerResultType{CodeCompletionChunk::ResultType, Utf8StringLiteral("int")}; CodeCompletionChunk enumerationResultType{CodeCompletionChunk::ResultType, Utf8StringLiteral("Enumeration")}; CodeCompletionChunk functionName{CodeCompletionChunk::TypedText, Utf8StringLiteral("Function")}; @@ -52,18 +56,33 @@ protected: CodeCompletionChunk enumerationName{CodeCompletionChunk::TypedText, Utf8StringLiteral("Enumeration")}; CodeCompletionChunk className{CodeCompletionChunk::TypedText, Utf8StringLiteral("Class")}; CodeCompletionChunk leftParen{CodeCompletionChunk::LeftParen, Utf8StringLiteral("(")}; - CodeCompletionChunk rightParen{CodeCompletionChunk::LeftParen, Utf8StringLiteral(")")}; + CodeCompletionChunk rightParen{CodeCompletionChunk::RightParen, Utf8StringLiteral(")")}; CodeCompletionChunk comma{CodeCompletionChunk::Comma, Utf8StringLiteral(", ")}; + CodeCompletionChunk semicolon{CodeCompletionChunk::SemiColon, Utf8StringLiteral(";")}; CodeCompletionChunk functionArgumentX{CodeCompletionChunk::Placeholder, Utf8StringLiteral("char x")}; CodeCompletionChunk functionArgumentY{CodeCompletionChunk::Placeholder, Utf8StringLiteral("int y")}; CodeCompletionChunk functionArgumentZ{CodeCompletionChunk::Placeholder, Utf8StringLiteral("int z")}; + CodeCompletionChunk switchName{CodeCompletionChunk::TypedText, Utf8StringLiteral("switch")}; + CodeCompletionChunk condition{CodeCompletionChunk::Placeholder, Utf8StringLiteral("condition")}; + CodeCompletionChunk leftBrace{CodeCompletionChunk::LeftBrace, Utf8StringLiteral("{")}; + CodeCompletionChunk rightBrace{CodeCompletionChunk::RightBrace, Utf8StringLiteral("}")}; + CodeCompletionChunk verticalSpace{CodeCompletionChunk::VerticalSpace, Utf8StringLiteral("\n")}; + CodeCompletionChunk throwName{CodeCompletionChunk::TypedText, Utf8StringLiteral("throw")}; + CodeCompletionChunk voidResultType{CodeCompletionChunk::ResultType, Utf8StringLiteral("void")}; + CodeCompletionChunk forName{CodeCompletionChunk::TypedText, Utf8StringLiteral("for")}; + CodeCompletionChunk initStatement{CodeCompletionChunk::Placeholder, Utf8StringLiteral("init-statement")}; + CodeCompletionChunk initExpression{CodeCompletionChunk::Placeholder, Utf8StringLiteral("init-expression")}; + CodeCompletionChunk statements{CodeCompletionChunk::Placeholder, Utf8StringLiteral("statements")}; + CodeCompletionChunk constCastName{CodeCompletionChunk::TypedText, Utf8StringLiteral("const_cast")}; + CodeCompletionChunk leftAngle{CodeCompletionChunk::LeftAngle, Utf8StringLiteral("<")}; + CodeCompletionChunk rightAngle{CodeCompletionChunk::RightAngle, Utf8StringLiteral(">")}; CodeCompletionChunk optional{CodeCompletionChunk::Optional, Utf8String(), {comma, functionArgumentY, comma, functionArgumentZ}}; }; TEST_F(CompletionChunksToTextConverter, ParseIsClearingText) { QVector completionChunks({integerResultType, functionName, leftParen, rightParen}); - converter.parseChunks(completionChunks); + converter.setAddResultType(true); converter.parseChunks(completionChunks); @@ -73,6 +92,7 @@ TEST_F(CompletionChunksToTextConverter, ParseIsClearingText) TEST_F(CompletionChunksToTextConverter, ConvertFunction) { QVector completionChunks({integerResultType, functionName, leftParen, rightParen}); + converter.setAddResultType(true); converter.parseChunks(completionChunks); @@ -82,6 +102,8 @@ TEST_F(CompletionChunksToTextConverter, ConvertFunction) TEST_F(CompletionChunksToTextConverter, ConvertFunctionWithParameters) { QVector completionChunks({integerResultType, functionName, leftParen, functionArgumentX,rightParen}); + converter.setAddResultType(true); + converter.setAddPlaceHolderText(true); converter.parseChunks(completionChunks); @@ -91,6 +113,8 @@ TEST_F(CompletionChunksToTextConverter, ConvertFunctionWithParameters) TEST_F(CompletionChunksToTextConverter, ConvertFunctionWithOptionalParameter) { QVector completionChunks({integerResultType, functionName, leftParen, functionArgumentX, optional,rightParen}); + converter.setAddResultType(true); + converter.setAddPlaceHolderText(true); converter.parseChunks(completionChunks); @@ -100,6 +124,7 @@ TEST_F(CompletionChunksToTextConverter, ConvertFunctionWithOptionalParameter) TEST_F(CompletionChunksToTextConverter, ConvertVariable) { QVector completionChunks({integerResultType, variableName}); + converter.setAddResultType(true); converter.parseChunks(completionChunks); @@ -109,6 +134,7 @@ TEST_F(CompletionChunksToTextConverter, ConvertVariable) TEST_F(CompletionChunksToTextConverter, Enumerator) { QVector completionChunks({enumerationResultType, enumeratorName}); + converter.setAddResultType(true); converter.parseChunks(completionChunks); @@ -124,4 +150,73 @@ TEST_F(CompletionChunksToTextConverter, Enumeration) ASSERT_THAT(converter.text(), QStringLiteral("Class")); } +TEST_F(CompletionChunksToTextConverter, Switch) +{ + QVector completionChunks({switchName, + leftParen, + condition, + rightParen, + leftBrace, + verticalSpace, + rightBrace}); + setupConverterForKeywords(); + + converter.parseChunks(completionChunks); + + ASSERT_THAT(converter.text(), QStringLiteral("switch () {\n\n}")); + ASSERT_THAT(converter.placeholderPositions().at(0), 8); +} + +TEST_F(CompletionChunksToTextConverter, For) +{ + QVector completionChunks({forName, + leftParen, + initStatement, + semicolon, + initExpression, + semicolon, + condition, + rightParen, + leftBrace, + verticalSpace, + statements, + verticalSpace, + rightBrace}); + setupConverterForKeywords(); + + converter.parseChunks(completionChunks); + + ASSERT_THAT(converter.text(), QStringLiteral("for (;;) {\n\n}")); +} + +TEST_F(CompletionChunksToTextConverter, const_cast) +{ + QVector completionChunks({constCastName, + leftAngle, + rightAngle, + leftParen, + rightParen}); + setupConverterForKeywords(); + + converter.parseChunks(completionChunks); + + ASSERT_THAT(converter.text(), QStringLiteral("const_cast<>()")); + +} + +TEST_F(CompletionChunksToTextConverter, Throw) +{ + QVector completionChunks({voidResultType, throwName}); + + auto completionName = Converter::convertToName(completionChunks); + + ASSERT_THAT(completionName, QStringLiteral("throw")); +} + +void CompletionChunksToTextConverter::setupConverterForKeywords() +{ + converter.setAddPlaceHolderPositions(true); + converter.setAddSpaces(true); + converter.setAddExtraVerticalSpaceBetweenBraces(true); +} }