Clang: Correct member access operator if possible

1 struct Foo { int member; };
 2 void f(Foo *foo)
 3 {
 4     foo.<REQUEST COMPLETION> // correct '.' to '->' and provide results
 5 }

The preferred approach would be to check if "foo" in line 4 is of
pointer type, but there is no suitable cursor (only CompoundStmt) at
that position since the code is usually not yet parsed and thus invalid.

Thus, just run the completion as is. If there are not any results for a
dot completion, re-run the completion with "." exchanged by "->". This
approach is inherently slower than the preferred approach implemented in
the built-in code model.

The following rare cases are not handled:

 1) Requesting completion after white space:
      Works: foo.<COMPLETE HERE>
      Fails: foo. <COMPLETE HERE>

 2) Opening a file and requesting completion (ctrl+space) without prior
    editing. No editing before triggering completion means that no
    unsaved file is generated on the backend side, which is a
    requirement for the correction.

Task-number: QTCREATORBUG-11581
Change-Id: I6bc8e8594778774ab342755fdb01a8a3e5c52ba0
Reviewed-by: Marco Bubke <marco.bubke@theqtcompany.com>
This commit is contained in:
Nikolai Kosjar
2016-01-11 15:20:04 +01:00
parent 43800a6aa8
commit 8d6549fa74
40 changed files with 747 additions and 37 deletions

View File

@@ -78,5 +78,11 @@ enum class HighlightingType
OutputArgument
};
enum class CompletionCorrection
{
NoCorrection,
DotToArrowCorrection
};
}
#endif // CLANGBACKENDIPC_GLOBAL_H

View File

@@ -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<quint32&>(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,6 +87,7 @@ QDataStream &operator>>(QDataStream &in, CodeCompletedMessage &message)
bool operator==(const CodeCompletedMessage &first, const CodeCompletedMessage &second)
{
return first.ticketNumber_ == second.ticketNumber_
&& first.neededCorrection_ == second.neededCorrection_
&& first.codeCompletions_ == second.codeCompletions_;
}
@@ -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() << ")";

View File

@@ -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);

View File

@@ -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;

View File

@@ -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();

View File

@@ -42,7 +42,10 @@ ClangAssistProposal::ClangAssistProposal(int cursorPos, TextEditor::GenericPropo
bool ClangAssistProposal::isCorrective() const
{
return false;
auto clangAssistProposalModel = static_cast<ClangAssistProposalModel*>(model());
return clangAssistProposalModel->neededCorrection()
== ClangBackEnd::CompletionCorrection::DotToArrowCorrection;
}
void ClangAssistProposal::makeCorrection(TextEditor::TextEditorWidget *editorWidget)

View File

@@ -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

View File

@@ -35,14 +35,23 @@
#include <texteditor/codeassist/genericproposalmodel.h>
#include <clangbackendipc/clangbackendipc_global.h>
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

View File

@@ -163,7 +163,9 @@ void IpcReceiver::codeCompleted(const CodeCompletedMessage &message)
const quint64 ticket = message.ticketNumber();
QScopedPointer<ClangCompletionAssistProcessor> 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();
}

View File

@@ -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(

View File

@@ -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:

View File

@@ -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");

View File

@@ -53,6 +53,8 @@ private slots:
void testCompleteFunctions();
void testCompleteConstructorAndFallbackToGlobalCompletion();
void testCompleteWithDotToArrowCorrection();
void testCompleteProjectDependingCode();
void testCompleteProjectDependingCodeAfterChangingProject();
void testCompleteProjectDependingCodeInGeneratedUiFile();

View File

@@ -21,5 +21,6 @@
<file>objc_messages_2.mm</file>
<file>objc_messages_3.mm</file>
<file>preprocessorKeywordsCompletion.cpp</file>
<file>dotToArrowCorrection.cpp</file>
</qresource>
</RCC>

View File

@@ -0,0 +1,5 @@
struct Bar { int member; };
void f(Bar *bar)
{
bar /* COMPLETE HERE */
}

View File

@@ -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

View File

@@ -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;

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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<CXUnsavedFile> &TranslationUnit::cxUnsavedFilesVector() const
{
return unsavedFiles().cxUnsavedFileVector();
}
TranslationUnit::~TranslationUnit() = default;
TranslationUnit::TranslationUnit(const TranslationUnit &) = default;

View File

@@ -99,6 +99,8 @@ public:
CXTranslationUnit cxTranslationUnit() const;
CXTranslationUnit cxTranslationUnitWithoutReparsing() const;
CXUnsavedFile * cxUnsavedFiles() const;
const std::vector<CXUnsavedFile> &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;

View File

@@ -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 <clang-c/Index.h>
#include <QDebug>
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(),
neededCorrection_ = CompletionCorrection::NoCorrection;
ClangCodeCompleteResults results = complete(line,
column,
translationUnit.cxUnsavedFiles(),
translationUnit.unsavedFilesCount());
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,
translationUnit.cxUnsavedFiles(),
translationUnit.unsavedFilesCount(),
CXCodeComplete_IncludeMacros | CXCodeComplete_IncludeCodePatterns));
unsavedFiles,
unsavedFileCount,
options);
}
CodeCompletionsExtractor extractor(completeResults.data());
ClangCodeCompleteResults CodeCompleter::completeWithArrowInsteadOfDot(uint line, uint column)
{
TemporaryModifiedUnsavedFiles modifiedUnsavedFiles(translationUnit.cxUnsavedFilesVector());
const SourceLocation location = translationUnit.sourceLocationAtWithoutReparsing(line,
column - 1);
return extractor.extractAll();
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

View File

@@ -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

View File

@@ -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)

View File

@@ -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 <cstring>
namespace ClangBackEnd {
TemporaryModifiedUnsavedFiles::TemporaryModifiedUnsavedFiles(
const std::vector<CXUnsavedFile> &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

View File

@@ -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 <clang-c/Index.h>
#include <utf8string.h>
#include <vector>
namespace ClangBackEnd {
class TemporaryModifiedUnsavedFiles
{
public:
TemporaryModifiedUnsavedFiles(const std::vector<CXUnsavedFile> &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<CXUnsavedFile> m_unsavedFileVector;
std::vector<Utf8String> m_modifiedContents;
};
} // namespace ClangBackEnd
#endif // CLANGBACKEND_TEMPORARYMODIFIEDUNSAVEDFILES_H

View File

@@ -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);

View File

@@ -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));
}
}

View File

@@ -0,0 +1,6 @@
struct Foo { int member; };
void g(Foo *foo)
{
foo->
}

View File

@@ -0,0 +1,6 @@
struct Foo { int member; };
void g(Foo *foo)
{
foo.
}

View File

@@ -0,0 +1,7 @@
struct Foo { int member; };
void g(Foo *foo)
{
foo->.
}

View File

@@ -0,0 +1,7 @@
struct Foo { int member; };
void g(Foo *foo)
{
foo..
}

View File

@@ -0,0 +1,4 @@
void f()
{
float pi = 3.
}

View File

@@ -0,0 +1,6 @@
struct Foo { int member; };
void g(Foo foo)
{
foo.
}

View File

@@ -0,0 +1,9 @@
struct Foo {
Foo *operator->();
int member;
};
void g(Foo foo)
{
foo.
}

View File

@@ -0,0 +1,7 @@
struct Foo { int member; };
void g(Foo *foo)
{
.
}

View File

@@ -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);

View File

@@ -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 <filecontainer.h>
#include <temporarymodifiedunsavedfiles.h>
#include <unsavedfiles.h>
#include <gmock/gmock.h>
#include <gmock/gmock-matchers.h>
#include <gtest/gtest.h>
#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

View File

@@ -59,7 +59,8 @@ SOURCES += \
highlightinginformationstest.cpp \
skippedsourcerangestest.cpp \
highlightingmarksreportertest.cpp \
chunksreportedmonitor.cpp
chunksreportedmonitor.cpp \
temporarymodifiedunsavedfilestest.cpp
HEADERS += \
gtest-qt-printing.h \

View File

@@ -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"));