diff --git a/src/libs/clangbackendipc/clangbackendipc_global.h b/src/libs/clangbackendipc/clangbackendipc_global.h index 3efaf48c3c3..f3e97e5f32e 100644 --- a/src/libs/clangbackendipc/clangbackendipc_global.h +++ b/src/libs/clangbackendipc/clangbackendipc_global.h @@ -78,5 +78,11 @@ enum class HighlightingType OutputArgument }; +enum class CompletionCorrection +{ + NoCorrection, + DotToArrowCorrection +}; + } #endif // CLANGBACKENDIPC_GLOBAL_H diff --git a/src/libs/clangbackendipc/cmbcodecompletedmessage.cpp b/src/libs/clangbackendipc/cmbcodecompletedmessage.cpp index ca584306886..6b2d5b52ddc 100644 --- a/src/libs/clangbackendipc/cmbcodecompletedmessage.cpp +++ b/src/libs/clangbackendipc/cmbcodecompletedmessage.cpp @@ -37,9 +37,12 @@ namespace ClangBackEnd { -CodeCompletedMessage::CodeCompletedMessage(const CodeCompletions &codeCompletions, quint64 ticketNumber) +CodeCompletedMessage::CodeCompletedMessage(const CodeCompletions &codeCompletions, + CompletionCorrection neededCorrection, + quint64 ticketNumber) : codeCompletions_(codeCompletions), - ticketNumber_(ticketNumber) + ticketNumber_(ticketNumber), + neededCorrection_(neededCorrection) { } @@ -48,14 +51,25 @@ const CodeCompletions &CodeCompletedMessage::codeCompletions() const return codeCompletions_; } +CompletionCorrection CodeCompletedMessage::neededCorrection() const +{ + return neededCorrection_; +} + quint64 CodeCompletedMessage::ticketNumber() const { return ticketNumber_; } +quint32 &CodeCompletedMessage::neededCorrectionAsInt() +{ + return reinterpret_cast(neededCorrection_); +} + QDataStream &operator<<(QDataStream &out, const CodeCompletedMessage &message) { out << message.codeCompletions_; + out << quint32(message.neededCorrection_); out << message.ticketNumber_; return out; @@ -64,6 +78,7 @@ QDataStream &operator<<(QDataStream &out, const CodeCompletedMessage &message) QDataStream &operator>>(QDataStream &in, CodeCompletedMessage &message) { in >> message.codeCompletions_; + in >> message.neededCorrectionAsInt(); in >> message.ticketNumber_; return in; @@ -72,7 +87,8 @@ QDataStream &operator>>(QDataStream &in, CodeCompletedMessage &message) bool operator==(const CodeCompletedMessage &first, const CodeCompletedMessage &second) { return first.ticketNumber_ == second.ticketNumber_ - && first.codeCompletions_ == second.codeCompletions_; + && first.neededCorrection_ == second.neededCorrection_ + && first.codeCompletions_ == second.codeCompletions_; } bool operator<(const CodeCompletedMessage &first, const CodeCompletedMessage &second) @@ -80,11 +96,24 @@ bool operator<(const CodeCompletedMessage &first, const CodeCompletedMessage &se return first.ticketNumber_ < second.ticketNumber_; } +#define RETURN_TEXT_FOR_CASE(enumValue) case CompletionCorrection::enumValue: return #enumValue +static const char *completionCorrectionToText(CompletionCorrection correction) +{ + switch (correction) { + RETURN_TEXT_FOR_CASE(NoCorrection); + RETURN_TEXT_FOR_CASE(DotToArrowCorrection); + default: return "UnhandledCompletionCorrection"; + } +} +#undef RETURN_TEXT_FOR_CASE + QDebug operator<<(QDebug debug, const CodeCompletedMessage &message) { debug.nospace() << "CodeCompletedMessage("; - debug.nospace() << message.codeCompletions_ << ", " << message.ticketNumber_; + debug.nospace() << message.codeCompletions_ << ", " + << completionCorrectionToText(message.neededCorrection()) << ", " + << message.ticketNumber_; debug.nospace() << ")"; diff --git a/src/libs/clangbackendipc/cmbcodecompletedmessage.h b/src/libs/clangbackendipc/cmbcodecompletedmessage.h index 69d4e11bc1d..a215bc01cd2 100644 --- a/src/libs/clangbackendipc/cmbcodecompletedmessage.h +++ b/src/libs/clangbackendipc/cmbcodecompletedmessage.h @@ -48,15 +48,22 @@ class CMBIPC_EXPORT CodeCompletedMessage friend void PrintTo(const CodeCompletedMessage &message, ::std::ostream* os); public: CodeCompletedMessage() = default; - CodeCompletedMessage(const CodeCompletions &codeCompletions, quint64 ticketNumber); + CodeCompletedMessage(const CodeCompletions &codeCompletions, + CompletionCorrection neededCorrection, + quint64 ticketNumber); const CodeCompletions &codeCompletions() const; + CompletionCorrection neededCorrection() const; quint64 ticketNumber() const; +private: + quint32 &neededCorrectionAsInt(); + private: CodeCompletions codeCompletions_; quint64 ticketNumber_ = 0; + CompletionCorrection neededCorrection_ = CompletionCorrection::NoCorrection; }; CMBIPC_EXPORT QDataStream &operator<<(QDataStream &out, const CodeCompletedMessage &message); diff --git a/src/libs/sqlite/utf8string.cpp b/src/libs/sqlite/utf8string.cpp index 9fb92b5d39d..d032b775ea9 100644 --- a/src/libs/sqlite/utf8string.cpp +++ b/src/libs/sqlite/utf8string.cpp @@ -103,6 +103,11 @@ void Utf8String::replace(const Utf8String &before, const Utf8String &after) byteArray.replace(before.byteArray, after.byteArray); } +void Utf8String::replace(int position, int length, const Utf8String &after) +{ + byteArray.replace(position, length, after.byteArray); +} + Utf8StringVector Utf8String::split(char separator) const { Utf8StringVector utf8Vector; diff --git a/src/libs/sqlite/utf8string.h b/src/libs/sqlite/utf8string.h index 6158f8b690d..2d7ff32a772 100644 --- a/src/libs/sqlite/utf8string.h +++ b/src/libs/sqlite/utf8string.h @@ -76,6 +76,7 @@ public: Utf8String mid(int position, int length = -1) const; void replace(const Utf8String &before, const Utf8String &after); + void replace(int position, int length, const Utf8String &after); Utf8StringVector split(char separator) const; void clear(); diff --git a/src/plugins/clangcodemodel/clangassistproposal.cpp b/src/plugins/clangcodemodel/clangassistproposal.cpp index 6904f9abfcb..812dbd8313f 100644 --- a/src/plugins/clangcodemodel/clangassistproposal.cpp +++ b/src/plugins/clangcodemodel/clangassistproposal.cpp @@ -42,7 +42,10 @@ ClangAssistProposal::ClangAssistProposal(int cursorPos, TextEditor::GenericPropo bool ClangAssistProposal::isCorrective() const { - return false; + auto clangAssistProposalModel = static_cast(model()); + + return clangAssistProposalModel->neededCorrection() + == ClangBackEnd::CompletionCorrection::DotToArrowCorrection; } void ClangAssistProposal::makeCorrection(TextEditor::TextEditorWidget *editorWidget) diff --git a/src/plugins/clangcodemodel/clangassistproposalmodel.cpp b/src/plugins/clangcodemodel/clangassistproposalmodel.cpp index d4085b39298..1092f062b8d 100644 --- a/src/plugins/clangcodemodel/clangassistproposalmodel.cpp +++ b/src/plugins/clangcodemodel/clangassistproposalmodel.cpp @@ -39,6 +39,12 @@ namespace ClangCodeModel { namespace Internal { +ClangAssistProposalModel::ClangAssistProposalModel( + ClangBackEnd::CompletionCorrection neededCorrection) + : m_neededCorrection(neededCorrection) +{ +} + bool ClangAssistProposalModel::isSortable(const QString &/*prefix*/) const { return true; @@ -57,6 +63,11 @@ void ClangAssistProposalModel::sort(const QString &/*prefix*/) std::sort(m_currentItems.begin(), m_currentItems.end(), currentItemsCompare); } +ClangBackEnd::CompletionCorrection ClangAssistProposalModel::neededCorrection() const +{ + return m_neededCorrection; +} + } // namespace Internal } // namespace ClangCodeModel diff --git a/src/plugins/clangcodemodel/clangassistproposalmodel.h b/src/plugins/clangcodemodel/clangassistproposalmodel.h index 5f7b1f502a0..2e2db193a1f 100644 --- a/src/plugins/clangcodemodel/clangassistproposalmodel.h +++ b/src/plugins/clangcodemodel/clangassistproposalmodel.h @@ -35,14 +35,23 @@ #include +#include + namespace ClangCodeModel { namespace Internal { class ClangAssistProposalModel : public TextEditor::GenericProposalModel { public: + ClangAssistProposalModel(ClangBackEnd::CompletionCorrection neededCorrection); + bool isSortable(const QString &prefix) const override; void sort(const QString &prefix) override; + + ClangBackEnd::CompletionCorrection neededCorrection() const; + +private: + ClangBackEnd::CompletionCorrection m_neededCorrection; }; } // namespace Internal diff --git a/src/plugins/clangcodemodel/clangbackendipcintegration.cpp b/src/plugins/clangcodemodel/clangbackendipcintegration.cpp index cf4a5e1bb70..cc0935551ec 100644 --- a/src/plugins/clangcodemodel/clangbackendipcintegration.cpp +++ b/src/plugins/clangcodemodel/clangbackendipcintegration.cpp @@ -163,7 +163,9 @@ void IpcReceiver::codeCompleted(const CodeCompletedMessage &message) const quint64 ticket = message.ticketNumber(); QScopedPointer processor(m_assistProcessorsTable.take(ticket)); if (processor) { - const bool finished = processor->handleAvailableAsyncCompletions(message.codeCompletions()); + const bool finished = processor->handleAvailableAsyncCompletions( + message.codeCompletions(), + message.neededCorrection()); if (!finished) processor.take(); } diff --git a/src/plugins/clangcodemodel/clangcompletionassistprocessor.cpp b/src/plugins/clangcodemodel/clangcompletionassistprocessor.cpp index 284e9fe3d7e..ed01da7239f 100644 --- a/src/plugins/clangcodemodel/clangcompletionassistprocessor.cpp +++ b/src/plugins/clangcodemodel/clangcompletionassistprocessor.cpp @@ -240,13 +240,14 @@ IAssistProposal *ClangCompletionAssistProcessor::perform(const AssistInterface * } bool ClangCompletionAssistProcessor::handleAvailableAsyncCompletions( - const CodeCompletions &completions) + const CodeCompletions &completions, + CompletionCorrection neededCorrection) { bool handled = true; switch (m_sentRequestType) { case CompletionRequestType::NormalCompletion: - handleAvailableCompletions(completions); + handleAvailableCompletions(completions, neededCorrection); break; case CompletionRequestType::FunctionHintCompletion: handled = handleAvailableFunctionHintCompletions(completions); @@ -775,14 +776,17 @@ bool ClangCompletionAssistProcessor::sendCompletionRequest(int position, return false; } -TextEditor::IAssistProposal *ClangCompletionAssistProcessor::createProposal() const +TextEditor::IAssistProposal *ClangCompletionAssistProcessor::createProposal( + CompletionCorrection neededCorrection) const { - ClangAssistProposalModel *model = new ClangAssistProposalModel; + ClangAssistProposalModel *model = new ClangAssistProposalModel(neededCorrection); model->loadContent(m_completions); return new ClangAssistProposal(m_positionForProposal, model); } -void ClangCompletionAssistProcessor::handleAvailableCompletions(const CodeCompletions &completions) +void ClangCompletionAssistProcessor::handleAvailableCompletions( + const CodeCompletions &completions, + CompletionCorrection neededCorrection) { QTC_CHECK(m_completions.isEmpty()); @@ -790,7 +794,7 @@ void ClangCompletionAssistProcessor::handleAvailableCompletions(const CodeComple if (m_addSnippets) addSnippets(); - setAsyncProposalAvailable(createProposal()); + setAsyncProposalAvailable(createProposal(neededCorrection)); } bool ClangCompletionAssistProcessor::handleAvailableFunctionHintCompletions( diff --git a/src/plugins/clangcodemodel/clangcompletionassistprocessor.h b/src/plugins/clangcodemodel/clangcompletionassistprocessor.h index f4aee3618d0..bd4fd36d381 100644 --- a/src/plugins/clangcodemodel/clangcompletionassistprocessor.h +++ b/src/plugins/clangcodemodel/clangcompletionassistprocessor.h @@ -43,6 +43,7 @@ namespace ClangCodeModel { namespace Internal { using ClangBackEnd::CodeCompletions; +using ClangBackEnd::CompletionCorrection; class ClangCompletionAssistProcessor : public CppTools::CppCompletionAssistProcessor { @@ -54,7 +55,8 @@ public: TextEditor::IAssistProposal *perform(const TextEditor::AssistInterface *interface) override; - bool handleAvailableAsyncCompletions(const CodeCompletions &completions); + bool handleAvailableAsyncCompletions(const CodeCompletions &completions, + CompletionCorrection neededCorrection); const TextEditor::TextEditorWidget *textEditorWidget() const; @@ -64,7 +66,8 @@ private: int findStartOfName(int pos = -1) const; bool accepts() const; - TextEditor::IAssistProposal *createProposal() const; + TextEditor::IAssistProposal *createProposal( + CompletionCorrection neededCorrection = CompletionCorrection::NoCorrection) const; bool completeInclude(const QTextCursor &cursor); bool completeInclude(int position); @@ -85,7 +88,8 @@ private: void sendFileContent(const QByteArray &customFileContent); bool sendCompletionRequest(int position, const QByteArray &customFileContent); - void handleAvailableCompletions(const CodeCompletions &completions); + void handleAvailableCompletions(const CodeCompletions &completions, + CompletionCorrection neededCorrection); bool handleAvailableFunctionHintCompletions(const CodeCompletions &completions); private: diff --git a/src/plugins/clangcodemodel/test/clangcodecompletion_test.cpp b/src/plugins/clangcodemodel/test/clangcodecompletion_test.cpp index e1dd36ccbb3..27f5be09c58 100644 --- a/src/plugins/clangcodemodel/test/clangcodecompletion_test.cpp +++ b/src/plugins/clangcodemodel/test/clangcodecompletion_test.cpp @@ -689,6 +689,7 @@ class ProjectLessCompletionTest { public: ProjectLessCompletionTest(const QByteArray &testFileName, + const QString &textToInsert = QString(), const QStringList &includePaths = QStringList()) { CppTools::Tests::TestCase garbageCollectionGlobalSnapshot; @@ -697,8 +698,11 @@ public: const TestDocument testDocument(testFileName, globalTemporaryDir()); QVERIFY(testDocument.isCreatedAndHasValidCursorPosition()); OpenEditorAtCursorPosition openEditor(testDocument); - QVERIFY(openEditor.succeeded()); + + if (!textToInsert.isEmpty()) + openEditor.editor()->insert(textToInsert); + proposal = completionResults(openEditor.editor(), includePaths); } @@ -880,7 +884,9 @@ void ClangCodeCompletionTest::testCompletePreprocessorKeywords() void ClangCodeCompletionTest::testCompleteIncludeDirective() { CppTools::Tests::TemporaryCopiedDir testDir(qrcPath("exampleIncludeDir")); - ProjectLessCompletionTest t("includeDirectiveCompletion.cpp", QStringList(testDir.path())); + ProjectLessCompletionTest t("includeDirectiveCompletion.cpp", + QString(), + QStringList(testDir.path())); QVERIFY(hasItem(t.proposal, "file.h")); QVERIFY(hasItem(t.proposal, "otherFile.h")); @@ -931,6 +937,18 @@ void ClangCodeCompletionTest::testCompleteConstructorAndFallbackToGlobalCompleti QVERIFY(!hasSnippet(t.proposal, "class")); } +void ClangCodeCompletionTest::testCompleteWithDotToArrowCorrection() +{ + // Inserting the dot for this test is important since it will send the editor + // content to the backend and thus generate an unsaved file on the backend + // side. The unsaved file enables us to do the dot to arrow correction. + + ProjectLessCompletionTest t("dotToArrowCorrection.cpp", + QStringLiteral(".")); + + QVERIFY(hasItem(t.proposal, "member")); +} + void ClangCodeCompletionTest::testCompleteProjectDependingCode() { const TestDocument testDocument("completionWithProject.cpp"); diff --git a/src/plugins/clangcodemodel/test/clangcodecompletion_test.h b/src/plugins/clangcodemodel/test/clangcodecompletion_test.h index 3334475505d..3449fd32ff4 100644 --- a/src/plugins/clangcodemodel/test/clangcodecompletion_test.h +++ b/src/plugins/clangcodemodel/test/clangcodecompletion_test.h @@ -53,6 +53,8 @@ private slots: void testCompleteFunctions(); void testCompleteConstructorAndFallbackToGlobalCompletion(); + void testCompleteWithDotToArrowCorrection(); + void testCompleteProjectDependingCode(); void testCompleteProjectDependingCodeAfterChangingProject(); void testCompleteProjectDependingCodeInGeneratedUiFile(); diff --git a/src/plugins/clangcodemodel/test/data/clangtestdata.qrc b/src/plugins/clangcodemodel/test/data/clangtestdata.qrc index 429b59eae8d..c013ccd7f40 100644 --- a/src/plugins/clangcodemodel/test/data/clangtestdata.qrc +++ b/src/plugins/clangcodemodel/test/data/clangtestdata.qrc @@ -21,5 +21,6 @@ objc_messages_2.mm objc_messages_3.mm preprocessorKeywordsCompletion.cpp + dotToArrowCorrection.cpp diff --git a/src/plugins/clangcodemodel/test/data/dotToArrowCorrection.cpp b/src/plugins/clangcodemodel/test/data/dotToArrowCorrection.cpp new file mode 100644 index 00000000000..27993974381 --- /dev/null +++ b/src/plugins/clangcodemodel/test/data/dotToArrowCorrection.cpp @@ -0,0 +1,5 @@ +struct Bar { int member; }; +void f(Bar *bar) +{ + bar /* COMPLETE HERE */ +} diff --git a/src/tools/clangbackend/ipcsource/clangbackendclangipc-source.pri b/src/tools/clangbackend/ipcsource/clangbackendclangipc-source.pri index 1724c5799c2..632d687ccbd 100644 --- a/src/tools/clangbackend/ipcsource/clangbackendclangipc-source.pri +++ b/src/tools/clangbackend/ipcsource/clangbackendclangipc-source.pri @@ -31,7 +31,8 @@ HEADERS += $$PWD/clangipcserver.h \ $$PWD/highlightinginformationsiterator.h \ $$PWD/skippedsourceranges.h \ $$PWD/clangtranslationunit.h \ - $$PWD/clangtype.h + $$PWD/clangtype.h \ + $$PWD/temporarymodifiedunsavedfiles.h SOURCES += $$PWD/clangipcserver.cpp \ $$PWD/codecompleter.cpp \ @@ -62,4 +63,5 @@ SOURCES += $$PWD/clangipcserver.cpp \ $$PWD/highlightinginformation.cpp \ $$PWD/skippedsourceranges.cpp \ $$PWD/clangtranslationunit.cpp \ - $$PWD/clangtype.cpp + $$PWD/clangtype.cpp \ + $$PWD/temporarymodifiedunsavedfiles.cpp diff --git a/src/tools/clangbackend/ipcsource/clangcodecompleteresults.cpp b/src/tools/clangbackend/ipcsource/clangcodecompleteresults.cpp index 210a6a71992..98c5f8f1a3f 100644 --- a/src/tools/clangbackend/ipcsource/clangcodecompleteresults.cpp +++ b/src/tools/clangbackend/ipcsource/clangcodecompleteresults.cpp @@ -51,6 +51,28 @@ bool ClangCodeCompleteResults::isNull() const return cxCodeCompleteResults == nullptr; } +bool ClangCodeCompleteResults::isEmpty() const +{ + return cxCodeCompleteResults->NumResults == 0; +} + +bool ClangCodeCompleteResults::hasResults() const +{ + return !isNull() && !isEmpty(); +} + +bool ClangCodeCompleteResults::hasNoResultsForDotCompletion() const +{ + return !hasResults() && isDotCompletion(); +} + +bool ClangCodeCompleteResults::isDotCompletion() const +{ + const unsigned long long contexts = clang_codeCompleteGetContexts(cxCodeCompleteResults); + + return contexts & CXCompletionContext_DotMemberAccess; +} + CXCodeCompleteResults *ClangCodeCompleteResults::data() const { return cxCodeCompleteResults; diff --git a/src/tools/clangbackend/ipcsource/clangcodecompleteresults.h b/src/tools/clangbackend/ipcsource/clangcodecompleteresults.h index 12b38d84bfe..5afdc7af149 100644 --- a/src/tools/clangbackend/ipcsource/clangcodecompleteresults.h +++ b/src/tools/clangbackend/ipcsource/clangcodecompleteresults.h @@ -40,6 +40,8 @@ namespace ClangBackEnd { class ClangCodeCompleteResults { public: + ClangCodeCompleteResults() = default; + ClangCodeCompleteResults(CXCodeCompleteResults *cxCodeCompleteResults); ~ClangCodeCompleteResults(); @@ -50,6 +52,12 @@ public: ClangCodeCompleteResults &operator=(ClangCodeCompleteResults &&ClangCodeCompleteResults); bool isNull() const; + bool isEmpty() const; + + bool hasResults() const; + bool hasNoResultsForDotCompletion() const; + + bool isDotCompletion() const; CXCodeCompleteResults *data() const; diff --git a/src/tools/clangbackend/ipcsource/clangipcserver.cpp b/src/tools/clangbackend/ipcsource/clangipcserver.cpp index ac04f932030..476471b1cb8 100644 --- a/src/tools/clangbackend/ipcsource/clangipcserver.cpp +++ b/src/tools/clangbackend/ipcsource/clangipcserver.cpp @@ -253,7 +253,9 @@ void ClangIpcServer::completeCode(const ClangBackEnd::CompleteCodeMessage &messa const auto codeCompletions = codeCompleter.complete(message.line(), message.column()); - client()->codeCompleted(CodeCompletedMessage(codeCompletions, message.ticketNumber())); + client()->codeCompleted(CodeCompletedMessage(codeCompletions, + codeCompleter.neededCorrection(), + message.ticketNumber())); } catch (const TranslationUnitDoesNotExistException &exception) { client()->translationUnitDoesNotExist(TranslationUnitDoesNotExistMessage(exception.fileContainer())); } catch (const ProjectPartDoNotExistException &exception) { diff --git a/src/tools/clangbackend/ipcsource/clangtranslationunit.cpp b/src/tools/clangbackend/ipcsource/clangtranslationunit.cpp index 4954cd79048..4534ce81e08 100644 --- a/src/tools/clangbackend/ipcsource/clangtranslationunit.cpp +++ b/src/tools/clangbackend/ipcsource/clangtranslationunit.cpp @@ -480,6 +480,11 @@ CommandLineArguments TranslationUnit::commandLineArguments() const isVerboseModeEnabled()); } +SourceLocation TranslationUnit::sourceLocationAtWithoutReparsing(uint line, uint column) const +{ + return SourceLocation(cxTranslationUnitWithoutReparsing(), filePath(), line, column); +} + uint TranslationUnit::defaultOptions() { return CXTranslationUnit_CacheCompletionResults @@ -498,6 +503,11 @@ CXUnsavedFile *TranslationUnit::cxUnsavedFiles() const return unsavedFiles().cxUnsavedFiles(); } +const std::vector &TranslationUnit::cxUnsavedFilesVector() const +{ + return unsavedFiles().cxUnsavedFileVector(); +} + TranslationUnit::~TranslationUnit() = default; TranslationUnit::TranslationUnit(const TranslationUnit &) = default; diff --git a/src/tools/clangbackend/ipcsource/clangtranslationunit.h b/src/tools/clangbackend/ipcsource/clangtranslationunit.h index 016067dc369..e10f2bd39e6 100644 --- a/src/tools/clangbackend/ipcsource/clangtranslationunit.h +++ b/src/tools/clangbackend/ipcsource/clangtranslationunit.h @@ -99,6 +99,8 @@ public: CXTranslationUnit cxTranslationUnit() const; CXTranslationUnit cxTranslationUnitWithoutReparsing() const; CXUnsavedFile * cxUnsavedFiles() const; + const std::vector &cxUnsavedFilesVector() const; + uint unsavedFilesCount() const; const Utf8String &filePath() const; @@ -125,6 +127,7 @@ public: CommandLineArguments commandLineArguments() const; + SourceLocation sourceLocationAtWithoutReparsing(uint line, uint column) const; SourceLocation sourceLocationAt(uint line, uint column) const; SourceLocation sourceLocationAt(const Utf8String &filePath, uint line, uint column) const; diff --git a/src/tools/clangbackend/ipcsource/codecompleter.cpp b/src/tools/clangbackend/ipcsource/codecompleter.cpp index b628c51eb8f..45325fb5046 100644 --- a/src/tools/clangbackend/ipcsource/codecompleter.cpp +++ b/src/tools/clangbackend/ipcsource/codecompleter.cpp @@ -32,16 +32,33 @@ #include "clangcodecompleteresults.h" #include "clangstring.h" +#include "cursor.h" #include "codecompletefailedexception.h" #include "codecompletionsextractor.h" +#include "sourcelocation.h" +#include "temporarymodifiedunsavedfiles.h" #include "clangtranslationunit.h" +#include "sourcerange.h" #include -#include - namespace ClangBackEnd { +namespace { + +CodeCompletions toCodeCompletions(const ClangCodeCompleteResults &results) +{ + if (results.isNull()) + return CodeCompletions(); + + CodeCompletionsExtractor extractor(results.data()); + CodeCompletions codeCompletions = extractor.extractAll(); + + return codeCompletions; +} + +} // anonymous namespace + CodeCompleter::CodeCompleter(TranslationUnit translationUnit) : translationUnit(std::move(translationUnit)) { @@ -49,17 +66,64 @@ CodeCompleter::CodeCompleter(TranslationUnit translationUnit) CodeCompletions CodeCompleter::complete(uint line, uint column) { - ClangCodeCompleteResults completeResults(clang_codeCompleteAt(translationUnit.cxTranslationUnitWithoutReparsing(), - translationUnit.filePath().constData(), - line, - column, - translationUnit.cxUnsavedFiles(), - translationUnit.unsavedFilesCount(), - CXCodeComplete_IncludeMacros | CXCodeComplete_IncludeCodePatterns)); + neededCorrection_ = CompletionCorrection::NoCorrection; - CodeCompletionsExtractor extractor(completeResults.data()); + ClangCodeCompleteResults results = complete(line, + column, + translationUnit.cxUnsavedFiles(), + translationUnit.unsavedFilesCount()); - return extractor.extractAll(); + if (results.hasNoResultsForDotCompletion()) + results = completeWithArrowInsteadOfDot(line, column); + + return toCodeCompletions(results); +} + +CompletionCorrection CodeCompleter::neededCorrection() const +{ + return neededCorrection_; +} + +ClangCodeCompleteResults CodeCompleter::complete(uint line, + uint column, + CXUnsavedFile *unsavedFiles, + unsigned unsavedFileCount) +{ + const auto options = CXCodeComplete_IncludeMacros | CXCodeComplete_IncludeCodePatterns; + + return clang_codeCompleteAt(translationUnit.cxTranslationUnitWithoutReparsing(), + translationUnit.filePath().constData(), + line, + column, + unsavedFiles, + unsavedFileCount, + options); +} + +ClangCodeCompleteResults CodeCompleter::completeWithArrowInsteadOfDot(uint line, uint column) +{ + TemporaryModifiedUnsavedFiles modifiedUnsavedFiles(translationUnit.cxUnsavedFilesVector()); + const SourceLocation location = translationUnit.sourceLocationAtWithoutReparsing(line, + column - 1); + + const bool replaced = modifiedUnsavedFiles.replaceInFile(filePath(), + location.offset(), + 1, + Utf8StringLiteral("->")); + + ClangCodeCompleteResults results; + + if (replaced) { + results = complete(line, + column + 1, + modifiedUnsavedFiles.cxUnsavedFiles(), + modifiedUnsavedFiles.count()); + + if (results.hasResults()) + neededCorrection_ = CompletionCorrection::DotToArrowCorrection; + } + + return results; } Utf8String CodeCompleter::filePath() const diff --git a/src/tools/clangbackend/ipcsource/codecompleter.h b/src/tools/clangbackend/ipcsource/codecompleter.h index 971f209c361..20cfa301cb0 100644 --- a/src/tools/clangbackend/ipcsource/codecompleter.h +++ b/src/tools/clangbackend/ipcsource/codecompleter.h @@ -39,7 +39,7 @@ namespace ClangBackEnd { -class TranslationUnit; +class ClangCodeCompleteResults; class CodeCompleter { @@ -49,12 +49,22 @@ public: CodeCompletions complete(uint line, uint column); + CompletionCorrection neededCorrection() const; + private: + ClangCodeCompleteResults complete(uint line, + uint column, + CXUnsavedFile *unsavedFiles, + unsigned unsavedFileCount); + + ClangCodeCompleteResults completeWithArrowInsteadOfDot(uint line, uint column); + Utf8String filePath() const; static void checkCodeCompleteResult(CXCodeCompleteResults *completeResults); private: TranslationUnit translationUnit; + CompletionCorrection neededCorrection_ = CompletionCorrection::NoCorrection; }; } // namespace ClangBackEnd diff --git a/src/tools/clangbackend/ipcsource/sourcelocation.cpp b/src/tools/clangbackend/ipcsource/sourcelocation.cpp index 19794130b68..e274e25ee33 100644 --- a/src/tools/clangbackend/ipcsource/sourcelocation.cpp +++ b/src/tools/clangbackend/ipcsource/sourcelocation.cpp @@ -97,6 +97,7 @@ SourceLocation::SourceLocation(CXTranslationUnit cxTranslationUnit, line_(line), column_(column) { + clang_getFileLocation(cxSourceLocation, 0, 0, 0, &offset_); } bool operator==(const SourceLocation &first, const SourceLocation &second) diff --git a/src/tools/clangbackend/ipcsource/temporarymodifiedunsavedfiles.cpp b/src/tools/clangbackend/ipcsource/temporarymodifiedunsavedfiles.cpp new file mode 100644 index 00000000000..a22a04e57c1 --- /dev/null +++ b/src/tools/clangbackend/ipcsource/temporarymodifiedunsavedfiles.cpp @@ -0,0 +1,87 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "temporarymodifiedunsavedfiles.h" + +#include + +namespace ClangBackEnd { + +TemporaryModifiedUnsavedFiles::TemporaryModifiedUnsavedFiles( + const std::vector &unsavedFilesVector) + : m_unsavedFileVector(unsavedFilesVector) +{ +} + +bool TemporaryModifiedUnsavedFiles::replaceInFile(const Utf8String &filePath, + uint offset, + uint length, + const Utf8String &replacement) +{ + const auto isMatchingFile = [filePath] (const CXUnsavedFile &unsavedFile) { + return std::strcmp(unsavedFile.Filename, filePath.constData()) == 0; + }; + const auto unsavedFileIterator = std::find_if(m_unsavedFileVector.begin(), + m_unsavedFileVector.end(), + isMatchingFile); + + if (unsavedFileIterator == m_unsavedFileVector.end()) + return false; + + return replaceInFile_internal(*unsavedFileIterator, offset, length, replacement); +} + +CXUnsavedFile TemporaryModifiedUnsavedFiles::cxUnsavedFileAt(uint index) +{ + return m_unsavedFileVector[index]; +} + +CXUnsavedFile *TemporaryModifiedUnsavedFiles::cxUnsavedFiles() +{ + return m_unsavedFileVector.data(); +} + +uint TemporaryModifiedUnsavedFiles::count() +{ + return uint(m_unsavedFileVector.size()); +} + +bool TemporaryModifiedUnsavedFiles::replaceInFile_internal(CXUnsavedFile &unsavedFile, + uint offset, + uint length, + const Utf8String &replacement) +{ + auto modifiedContent = Utf8String::fromUtf8(unsavedFile.Contents); + modifiedContent.replace(int(offset), int(length), replacement); + + unsavedFile.Contents = modifiedContent.constData(); + unsavedFile.Length = uint(modifiedContent.byteSize() + 1); + + m_modifiedContents.push_back(modifiedContent); // Keep the modified copy. + + return true; +} + +} // namespace ClangBackEnd diff --git a/src/tools/clangbackend/ipcsource/temporarymodifiedunsavedfiles.h b/src/tools/clangbackend/ipcsource/temporarymodifiedunsavedfiles.h new file mode 100644 index 00000000000..959b9431505 --- /dev/null +++ b/src/tools/clangbackend/ipcsource/temporarymodifiedunsavedfiles.h @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#ifndef CLANGBACKEND_TEMPORARYMODIFIEDUNSAVEDFILES_H +#define CLANGBACKEND_TEMPORARYMODIFIEDUNSAVEDFILES_H + +#include + +#include + +#include + +namespace ClangBackEnd { + +class TemporaryModifiedUnsavedFiles +{ +public: + TemporaryModifiedUnsavedFiles(const std::vector &unsavedFilesVector); + + TemporaryModifiedUnsavedFiles(const TemporaryModifiedUnsavedFiles &) = delete; + + bool replaceInFile(const Utf8String &filePath, + uint offset, + uint length, + const Utf8String &replacement); + + CXUnsavedFile cxUnsavedFileAt(uint index); + CXUnsavedFile *cxUnsavedFiles(); + uint count(); + +private: + bool replaceInFile_internal(CXUnsavedFile &unsavedFile, + uint offset, + uint length, + const Utf8String &replacement); + +private: + std::vector m_unsavedFileVector; + std::vector m_modifiedContents; +}; + +} // namespace ClangBackEnd + +#endif // CLANGBACKEND_TEMPORARYMODIFIEDUNSAVEDFILES_H diff --git a/tests/unit/unittest/clientserverinprocesstest.cpp b/tests/unit/unittest/clientserverinprocesstest.cpp index c8f74551d43..7d31199aee6 100644 --- a/tests/unit/unittest/clientserverinprocesstest.cpp +++ b/tests/unit/unittest/clientserverinprocesstest.cpp @@ -217,7 +217,9 @@ TEST_F(ClientServerInProcess, SendRequestHighlightingMessage) TEST_F(ClientServerInProcess, SendCodeCompletedMessage) { ClangBackEnd::CodeCompletions codeCompletions({Utf8StringLiteral("newFunction()")}); - ClangBackEnd::CodeCompletedMessage message(codeCompletions, 1); + ClangBackEnd::CodeCompletedMessage message(codeCompletions, + ClangBackEnd::CompletionCorrection::NoCorrection, + 1); EXPECT_CALL(mockIpcClient, codeCompleted(message)) .Times(1); diff --git a/tests/unit/unittest/codecompletiontest.cpp b/tests/unit/unittest/codecompletiontest.cpp index efbf7ea5901..972a86c28ca 100644 --- a/tests/unit/unittest/codecompletiontest.cpp +++ b/tests/unit/unittest/codecompletiontest.cpp @@ -81,6 +81,7 @@ protected: void SetUp(); void copyTargetHeaderToTemporaryIncludeDirecory(); void copyChangedTargetHeaderToTemporaryIncludeDirecory(); + ClangBackEnd::CodeCompleter setupCompleter(const ClangBackEnd::FileContainer &fileContainer); static Utf8String readFileContent(const QString &fileName); protected: @@ -103,6 +104,55 @@ protected: projectPart.projectPartId(), readFileContent(QStringLiteral("/complete_target_header_unsaved.h")), true}; + + ClangBackEnd::FileContainer arrowFileContainer{ + Utf8StringLiteral(TESTDATA_DIR"/complete_arrow.cpp"), + projectPart.projectPartId(), + readFileContent(QStringLiteral("/complete_arrow.cpp")), + true + }; + ClangBackEnd::FileContainer dotArrowCorrectionForPointerFileContainer{ + Utf8StringLiteral(TESTDATA_DIR"/complete_withDotArrowCorrectionForPointer.cpp"), + projectPart.projectPartId(), + readFileContent(QStringLiteral("/complete_withDotArrowCorrectionForPointer.cpp")), + true + }; + ClangBackEnd::FileContainer noDotArrowCorrectionForObjectFileContainer{ + Utf8StringLiteral(TESTDATA_DIR"/complete_withNoDotArrowCorrectionForObject.cpp"), + projectPart.projectPartId(), + readFileContent(QStringLiteral("/complete_withNoDotArrowCorrectionForObject.cpp")), + true + }; + ClangBackEnd::FileContainer noDotArrowCorrectionForFloatFileContainer{ + Utf8StringLiteral(TESTDATA_DIR"/complete_withNoDotArrowCorrectionForFloat.cpp"), + projectPart.projectPartId(), + readFileContent(QStringLiteral("/complete_withNoDotArrowCorrectionForFloat.cpp")), + true + }; + ClangBackEnd::FileContainer noDotArrowCorrectionForObjectWithArrowOperatortFileContainer{ + Utf8StringLiteral(TESTDATA_DIR"/complete_withNoDotArrowCorrectionForObjectWithArrowOperator.cpp"), + projectPart.projectPartId(), + readFileContent(QStringLiteral("/complete_withNoDotArrowCorrectionForObjectWithArrowOperator.cpp")), + true + }; + ClangBackEnd::FileContainer noDotArrowCorrectionForDotDotFileContainer{ + Utf8StringLiteral(TESTDATA_DIR"/complete_withNoDotArrowCorrectionForDotDot.cpp"), + projectPart.projectPartId(), + readFileContent(QStringLiteral("/complete_withNoDotArrowCorrectionForDotDot.cpp")), + true + }; + ClangBackEnd::FileContainer noDotArrowCorrectionForArrowDotFileContainer{ + Utf8StringLiteral(TESTDATA_DIR"/complete_withNoDotArrowCorrectionForArrowDot.cpp"), + projectPart.projectPartId(), + readFileContent(QStringLiteral("/complete_withNoDotArrowCorrectionForArrowDot.cpp")), + true + }; + ClangBackEnd::FileContainer noDotArrowCorrectionForOnlyDotContainer{ + Utf8StringLiteral(TESTDATA_DIR"/complete_withNoDotArrowCorrectionForOnlyDot.cpp"), + projectPart.projectPartId(), + readFileContent(QStringLiteral("/complete_withNoDotArrowCorrectionForOnlyDot.cpp")), + true + }; }; Utf8String CodeCompleter::readFileContent(const QString &fileName) @@ -237,4 +287,105 @@ TEST_F(CodeCompleter, DISABLED_FunctionInChangedIncludedHeaderWithUnsavedContent CodeCompletion::FunctionCompletionKind))); } +TEST_F(CodeCompleter, ArrowCompletion) +{ + auto myCompleter = setupCompleter(arrowFileContainer); + + const ClangBackEnd::CodeCompletions completions = myCompleter.complete(5, 10); + + ASSERT_THAT(completions, + Contains(IsCodeCompletion(Utf8StringLiteral("member"), + CodeCompletion::VariableCompletionKind))); + ASSERT_THAT(myCompleter.neededCorrection(), + ClangBackEnd::CompletionCorrection::NoCorrection); +} + +TEST_F(CodeCompleter, DotToArrowCompletionForPointer) +{ + auto myCompleter = setupCompleter(dotArrowCorrectionForPointerFileContainer); + + const ClangBackEnd::CodeCompletions completions = myCompleter.complete(5, 9); + + ASSERT_THAT(completions, + Contains(IsCodeCompletion(Utf8StringLiteral("member"), + CodeCompletion::VariableCompletionKind))); + ASSERT_THAT(myCompleter.neededCorrection(), + ClangBackEnd::CompletionCorrection::DotToArrowCorrection); +} + +TEST_F(CodeCompleter, NoDotToArrowCompletionForObject) +{ + auto myCompleter = setupCompleter(noDotArrowCorrectionForObjectFileContainer); + + const ClangBackEnd::CodeCompletions completions = myCompleter.complete(5, 9); + + ASSERT_THAT(completions, + Contains(IsCodeCompletion(Utf8StringLiteral("member"), + CodeCompletion::VariableCompletionKind))); + ASSERT_THAT(myCompleter.neededCorrection(), ClangBackEnd::CompletionCorrection::NoCorrection); +} + +TEST_F(CodeCompleter, NoDotToArrowCompletionForFloat) +{ + auto myCompleter = setupCompleter(noDotArrowCorrectionForFloatFileContainer); + + const ClangBackEnd::CodeCompletions completions = myCompleter.complete(3, 18); + + ASSERT_TRUE(completions.isEmpty()); + ASSERT_THAT(myCompleter.neededCorrection(), ClangBackEnd::CompletionCorrection::NoCorrection); +} + +TEST_F(CodeCompleter, NoDotArrowCorrectionForObjectWithArrowOperator) +{ + auto myCompleter = setupCompleter(noDotArrowCorrectionForObjectWithArrowOperatortFileContainer); + + const ClangBackEnd::CodeCompletions completions = myCompleter.complete(8, 9); + + ASSERT_THAT(completions, + Contains(IsCodeCompletion(Utf8StringLiteral("member"), + CodeCompletion::VariableCompletionKind))); + ASSERT_THAT(myCompleter.neededCorrection(), ClangBackEnd::CompletionCorrection::NoCorrection); +} + +TEST_F(CodeCompleter, NoDotArrowCorrectionForDotDot) +{ + auto myCompleter = setupCompleter(noDotArrowCorrectionForDotDotFileContainer); + + const ClangBackEnd::CodeCompletions completions = myCompleter.complete(5, 10); + + ASSERT_TRUE(completions.isEmpty()); + ASSERT_THAT(myCompleter.neededCorrection(), ClangBackEnd::CompletionCorrection::NoCorrection); +} + +TEST_F(CodeCompleter, NoDotArrowCorrectionForArrowDot) +{ + auto myCompleter = setupCompleter(noDotArrowCorrectionForArrowDotFileContainer); + + const ClangBackEnd::CodeCompletions completions = myCompleter.complete(5, 11); + + ASSERT_TRUE(completions.isEmpty()); + ASSERT_THAT(myCompleter.neededCorrection(), ClangBackEnd::CompletionCorrection::NoCorrection); +} + +TEST_F(CodeCompleter, NoDotArrowCorrectionForOnlyDot) +{ + auto myCompleter = setupCompleter(noDotArrowCorrectionForOnlyDotContainer); + + const ClangBackEnd::CodeCompletions completions = myCompleter.complete(5, 6); + + ASSERT_THAT(completions, + Contains(IsCodeCompletion(Utf8StringLiteral("Foo"), + CodeCompletion::ClassCompletionKind))); + ASSERT_THAT(myCompleter.neededCorrection(), ClangBackEnd::CompletionCorrection::NoCorrection); +} + +ClangBackEnd::CodeCompleter CodeCompleter::setupCompleter( + const ClangBackEnd::FileContainer &fileContainer) +{ + translationUnits.create({fileContainer}); + unsavedFiles.createOrUpdate({fileContainer}); + + return ClangBackEnd::CodeCompleter(translationUnits.translationUnit(fileContainer)); +} + } diff --git a/tests/unit/unittest/data/complete_arrow.cpp b/tests/unit/unittest/data/complete_arrow.cpp new file mode 100644 index 00000000000..2d969e2bfbd --- /dev/null +++ b/tests/unit/unittest/data/complete_arrow.cpp @@ -0,0 +1,6 @@ +struct Foo { int member; }; + +void g(Foo *foo) +{ + foo-> +} diff --git a/tests/unit/unittest/data/complete_withDotArrowCorrectionForPointer.cpp b/tests/unit/unittest/data/complete_withDotArrowCorrectionForPointer.cpp new file mode 100644 index 00000000000..9f8d3645b2e --- /dev/null +++ b/tests/unit/unittest/data/complete_withDotArrowCorrectionForPointer.cpp @@ -0,0 +1,6 @@ +struct Foo { int member; }; + +void g(Foo *foo) +{ + foo. +} diff --git a/tests/unit/unittest/data/complete_withNoDotArrowCorrectionForArrowDot.cpp b/tests/unit/unittest/data/complete_withNoDotArrowCorrectionForArrowDot.cpp new file mode 100644 index 00000000000..304c17c05b6 --- /dev/null +++ b/tests/unit/unittest/data/complete_withNoDotArrowCorrectionForArrowDot.cpp @@ -0,0 +1,7 @@ +struct Foo { int member; }; + +void g(Foo *foo) +{ + foo->. +} + diff --git a/tests/unit/unittest/data/complete_withNoDotArrowCorrectionForDotDot.cpp b/tests/unit/unittest/data/complete_withNoDotArrowCorrectionForDotDot.cpp new file mode 100644 index 00000000000..8f77989da67 --- /dev/null +++ b/tests/unit/unittest/data/complete_withNoDotArrowCorrectionForDotDot.cpp @@ -0,0 +1,7 @@ +struct Foo { int member; }; + +void g(Foo *foo) +{ + foo.. +} + diff --git a/tests/unit/unittest/data/complete_withNoDotArrowCorrectionForFloat.cpp b/tests/unit/unittest/data/complete_withNoDotArrowCorrectionForFloat.cpp new file mode 100644 index 00000000000..c27a0ee8f08 --- /dev/null +++ b/tests/unit/unittest/data/complete_withNoDotArrowCorrectionForFloat.cpp @@ -0,0 +1,4 @@ +void f() +{ + float pi = 3. +} diff --git a/tests/unit/unittest/data/complete_withNoDotArrowCorrectionForObject.cpp b/tests/unit/unittest/data/complete_withNoDotArrowCorrectionForObject.cpp new file mode 100644 index 00000000000..d2e3b88da7c --- /dev/null +++ b/tests/unit/unittest/data/complete_withNoDotArrowCorrectionForObject.cpp @@ -0,0 +1,6 @@ +struct Foo { int member; }; + +void g(Foo foo) +{ + foo. +} diff --git a/tests/unit/unittest/data/complete_withNoDotArrowCorrectionForObjectWithArrowOperator.cpp b/tests/unit/unittest/data/complete_withNoDotArrowCorrectionForObjectWithArrowOperator.cpp new file mode 100644 index 00000000000..6a741603a18 --- /dev/null +++ b/tests/unit/unittest/data/complete_withNoDotArrowCorrectionForObjectWithArrowOperator.cpp @@ -0,0 +1,9 @@ +struct Foo { + Foo *operator->(); + int member; +}; + +void g(Foo foo) +{ + foo. +} diff --git a/tests/unit/unittest/data/complete_withNoDotArrowCorrectionForOnlyDot.cpp b/tests/unit/unittest/data/complete_withNoDotArrowCorrectionForOnlyDot.cpp new file mode 100644 index 00000000000..4a5d7894c4a --- /dev/null +++ b/tests/unit/unittest/data/complete_withNoDotArrowCorrectionForOnlyDot.cpp @@ -0,0 +1,7 @@ +struct Foo { int member; }; + +void g(Foo *foo) +{ + . +} + diff --git a/tests/unit/unittest/readandwritemessageblocktest.cpp b/tests/unit/unittest/readandwritemessageblocktest.cpp index ef958253dd2..f5f97946690 100644 --- a/tests/unit/unittest/readandwritemessageblocktest.cpp +++ b/tests/unit/unittest/readandwritemessageblocktest.cpp @@ -175,7 +175,11 @@ TEST_F(ReadAndWriteMessageBlock, CompareCodeCompletedMessage) { ClangBackEnd::CodeCompletions codeCompletions({Utf8StringLiteral("newFunction()")}); - CompareMessage(ClangBackEnd::CodeCompletedMessage(codeCompletions, 1)); + CompareMessage( + ClangBackEnd::CodeCompletedMessage(codeCompletions, + ClangBackEnd::CompletionCorrection::NoCorrection, + 1) + ); } TEST_F(ReadAndWriteMessageBlock, CompareDiagnosticsChangedMessage) @@ -244,7 +248,10 @@ TEST_F(ReadAndWriteMessageBlock, ReadMessageAfterInterruption) QVariant ReadAndWriteMessageBlock::writeCodeCompletedMessage() { - ClangBackEnd::CodeCompletedMessage message(ClangBackEnd::CodeCompletions({Utf8StringLiteral("newFunction()")}), 1); + ClangBackEnd::CodeCompletedMessage message( + ClangBackEnd::CodeCompletions({Utf8StringLiteral("newFunction()")}), + ClangBackEnd::CompletionCorrection::NoCorrection, + 1); const QVariant writeMessage = QVariant::fromValue(message); writeMessageBlock.write(writeMessage); diff --git a/tests/unit/unittest/temporarymodifiedunsavedfilestest.cpp b/tests/unit/unittest/temporarymodifiedunsavedfilestest.cpp new file mode 100644 index 00000000000..7fdd977e50d --- /dev/null +++ b/tests/unit/unittest/temporarymodifiedunsavedfilestest.cpp @@ -0,0 +1,106 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include +#include +#include + +#include +#include +#include +#include "gtest-qt-printing.h" + +using ClangBackEnd::FileContainer; +using ClangBackEnd::TemporaryModifiedUnsavedFiles; +using ClangBackEnd::UnsavedFiles; + +using testing::Eq; + +namespace { + +class TemporaryModifiedUnsavedFiles : public ::testing::Test +{ +protected: + ClangBackEnd::UnsavedFiles unsavedFiles; + + FileContainer fileContainer{Utf8StringLiteral("file1.h"), + Utf8StringLiteral("projectPartId"), + Utf8StringLiteral("void f() { foo. }"), + true}; + + Utf8String someNotExistingFilePath{Utf8StringLiteral("nonExistingFile.cpp")}; +}; + +TEST_F(TemporaryModifiedUnsavedFiles, HasZeroFilesOnCreation) +{ + ::TemporaryModifiedUnsavedFiles files(unsavedFiles.cxUnsavedFileVector()); + + ASSERT_THAT(files.count(), Eq(0L)); +} + +TEST_F(TemporaryModifiedUnsavedFiles, HasOneFileAfterCreatingOne) +{ + unsavedFiles.createOrUpdate({fileContainer}); + + ::TemporaryModifiedUnsavedFiles files(unsavedFiles.cxUnsavedFileVector()); + + ASSERT_THAT(files.count(), Eq(1L)); +} + +TEST_F(TemporaryModifiedUnsavedFiles, ReplaceIndicatesFailureOnNonExistingUnsavedFile) +{ + ::TemporaryModifiedUnsavedFiles files(unsavedFiles.cxUnsavedFileVector()); + const uint someOffset = 0; + const uint someLength = 1; + const auto someReplacement = Utf8StringLiteral("->"); + + const bool replaced = files.replaceInFile(someNotExistingFilePath, + someOffset, + someLength, + someReplacement); + + ASSERT_FALSE(replaced); +} + +TEST_F(TemporaryModifiedUnsavedFiles, ReplaceDotWithArrow) +{ + unsavedFiles.createOrUpdate({fileContainer}); + ::TemporaryModifiedUnsavedFiles files(unsavedFiles.cxUnsavedFileVector()); + const uint offset = 14; + const uint length = 1; + const auto replacement = Utf8StringLiteral("->"); + + const bool replacedSuccessfully = files.replaceInFile(fileContainer.filePath(), + offset, + length, + replacement); + + CXUnsavedFile cxUnsavedFile = files.cxUnsavedFileAt(0); + ASSERT_TRUE(replacedSuccessfully); + ASSERT_THAT(Utf8String::fromUtf8(cxUnsavedFile.Contents), + Eq(Utf8StringLiteral("void f() { foo-> }"))); +} + +} // anonymous diff --git a/tests/unit/unittest/unittest.pro b/tests/unit/unittest/unittest.pro index d3cefda9da0..69d0e8c8aab 100644 --- a/tests/unit/unittest/unittest.pro +++ b/tests/unit/unittest/unittest.pro @@ -59,7 +59,8 @@ SOURCES += \ highlightinginformationstest.cpp \ skippedsourcerangestest.cpp \ highlightingmarksreportertest.cpp \ - chunksreportedmonitor.cpp + chunksreportedmonitor.cpp \ + temporarymodifiedunsavedfilestest.cpp HEADERS += \ gtest-qt-printing.h \ diff --git a/tests/unit/unittest/utf8test.cpp b/tests/unit/unittest/utf8test.cpp index f3e4f94c6af..6053f56c0f1 100644 --- a/tests/unit/unittest/utf8test.cpp +++ b/tests/unit/unittest/utf8test.cpp @@ -164,6 +164,15 @@ TEST(Utf8, Replace) ASSERT_THAT(text, Utf8StringLiteral("any text")); } +TEST(Utf8, ReplaceNBytesFromIndexPosition) +{ + Utf8String text(Utf8StringLiteral("min")); + + text.replace(1, 1, Utf8StringLiteral("aa")); + + ASSERT_THAT(text, Utf8StringLiteral("maan")); +} + TEST(Utf8, StartsWith) { Utf8String text(Utf8StringLiteral("$column"));