forked from qt-creator/qt-creator
Clang: Fix dot-arrow-correction for not-yet-parsed unsaved content
The issue was re-producible with e.g.:
void g()
{
// Type 'foo.' as fast as possible in the next line
}
This led to "foo->" with completion results as if there was no "foo." at
all in that line.
We relied on a correct position for
translationUnit.sourceLocationAtWithoutReparsing(), but the just typed
characters were not yet reparsed. And we do not want to reparse at that
point since takes too long. We already determine the utf8 position for
the dot character, so simply use that instead.
This completes commit 17c1325cc4.
Change-Id: I669888b5c17ee63b2aec7b16c9921f9d79e281f9
Reviewed-by: David Schulz <david.schulz@theqtcompany.com>
This commit is contained in:
@@ -68,8 +68,7 @@ CodeCompletions CodeCompleter::complete(uint line, uint column)
|
|||||||
translationUnit.cxUnsavedFiles(),
|
translationUnit.cxUnsavedFiles(),
|
||||||
translationUnit.unsavedFilesCount());
|
translationUnit.unsavedFilesCount());
|
||||||
|
|
||||||
if (results.hasNoResultsForDotCompletion() && hasDotAt(line, column - 1))
|
tryDotArrowCorrectionIfNoResults(results, line, column);
|
||||||
results = completeWithArrowInsteadOfDot(line, column);
|
|
||||||
|
|
||||||
return toCodeCompletions(results);
|
return toCodeCompletions(results);
|
||||||
}
|
}
|
||||||
@@ -93,13 +92,6 @@ ClangCodeCompleteResults CodeCompleter::complete(uint line,
|
|||||||
defaultOptions());
|
defaultOptions());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CodeCompleter::hasDotAt(uint line, uint column) const
|
|
||||||
{
|
|
||||||
const UnsavedFile &unsavedFile = translationUnit.unsavedFile();
|
|
||||||
|
|
||||||
return unsavedFile.hasCharacterAt(line, column, '.');
|
|
||||||
}
|
|
||||||
|
|
||||||
uint CodeCompleter::defaultOptions() const
|
uint CodeCompleter::defaultOptions() const
|
||||||
{
|
{
|
||||||
uint options = CXCodeComplete_IncludeMacros
|
uint options = CXCodeComplete_IncludeMacros
|
||||||
@@ -111,12 +103,25 @@ uint CodeCompleter::defaultOptions() const
|
|||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
ClangCodeCompleteResults CodeCompleter::completeWithArrowInsteadOfDot(uint line, uint column)
|
void CodeCompleter::tryDotArrowCorrectionIfNoResults(ClangCodeCompleteResults &results,
|
||||||
|
uint line,
|
||||||
|
uint column)
|
||||||
|
{
|
||||||
|
if (results.hasNoResultsForDotCompletion()) {
|
||||||
|
const UnsavedFile &unsavedFile = translationUnit.unsavedFile();
|
||||||
|
bool positionIsOk = false;
|
||||||
|
const uint dotPosition = unsavedFile.toUtf8Position(line, column - 1, &positionIsOk);
|
||||||
|
if (positionIsOk && unsavedFile.hasCharacterAt(dotPosition, '.'))
|
||||||
|
results = completeWithArrowInsteadOfDot(line, column, dotPosition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ClangCodeCompleteResults CodeCompleter::completeWithArrowInsteadOfDot(uint line,
|
||||||
|
uint column,
|
||||||
|
uint dotPosition)
|
||||||
{
|
{
|
||||||
ClangCodeCompleteResults results;
|
ClangCodeCompleteResults results;
|
||||||
|
const bool replaced = translationUnit.unsavedFile().replaceAt(dotPosition,
|
||||||
const SourceLocation location = translationUnit.sourceLocationAtWithoutReparsing(line, column - 1);
|
|
||||||
const bool replaced = translationUnit.unsavedFile().replaceAt(location.offset(),
|
|
||||||
1,
|
1,
|
||||||
Utf8StringLiteral("->"));
|
Utf8StringLiteral("->"));
|
||||||
|
|
||||||
|
|||||||
@@ -46,18 +46,20 @@ public:
|
|||||||
|
|
||||||
CompletionCorrection neededCorrection() const;
|
CompletionCorrection neededCorrection() const;
|
||||||
|
|
||||||
public: // for tests
|
|
||||||
bool hasDotAt(uint line, uint column) const;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint defaultOptions() const;
|
uint defaultOptions() const;
|
||||||
|
|
||||||
|
void tryDotArrowCorrectionIfNoResults(ClangCodeCompleteResults &results,
|
||||||
|
uint line,
|
||||||
|
uint column);
|
||||||
|
|
||||||
ClangCodeCompleteResults complete(uint line,
|
ClangCodeCompleteResults complete(uint line,
|
||||||
uint column,
|
uint column,
|
||||||
CXUnsavedFile *unsavedFiles,
|
CXUnsavedFile *unsavedFiles,
|
||||||
unsigned unsavedFileCount);
|
unsigned unsavedFileCount);
|
||||||
|
ClangCodeCompleteResults completeWithArrowInsteadOfDot(uint line,
|
||||||
ClangCodeCompleteResults completeWithArrowInsteadOfDot(uint line, uint column);
|
uint column,
|
||||||
|
uint dotPosition);
|
||||||
|
|
||||||
Utf8String filePath() const;
|
Utf8String filePath() const;
|
||||||
static void checkCodeCompleteResult(CXCodeCompleteResults *completeResults);
|
static void checkCodeCompleteResult(CXCodeCompleteResults *completeResults);
|
||||||
|
|||||||
@@ -71,15 +71,24 @@ const char *UnsavedFile::filePath() const
|
|||||||
return cxUnsavedFile.Filename;
|
return cxUnsavedFile.Filename;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool UnsavedFile::hasCharacterAt(uint line, uint column, char character) const
|
uint UnsavedFile::toUtf8Position(uint line, uint column, bool *ok) const
|
||||||
{
|
{
|
||||||
Utf8PositionFromLineColumn converter(cxUnsavedFile.Contents);
|
Utf8PositionFromLineColumn converter(cxUnsavedFile.Contents);
|
||||||
if (converter.find(line, column)) {
|
if (converter.find(line, column)) {
|
||||||
const uint utf8Position = converter.position();
|
*ok = true;
|
||||||
return hasCharacterAt(utf8Position, character);
|
return converter.position();
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
*ok = false;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UnsavedFile::hasCharacterAt(uint line, uint column, char character) const
|
||||||
|
{
|
||||||
|
bool positionIsOk = false;
|
||||||
|
const uint utf8Position = toUtf8Position(line, column, &positionIsOk);
|
||||||
|
|
||||||
|
return positionIsOk && hasCharacterAt(utf8Position, character);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool UnsavedFile::hasCharacterAt(uint position, char character) const
|
bool UnsavedFile::hasCharacterAt(uint position, char character) const
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ public:
|
|||||||
const char *filePath() const;
|
const char *filePath() const;
|
||||||
|
|
||||||
// 1-based line and column
|
// 1-based line and column
|
||||||
|
uint toUtf8Position(uint line, uint column, bool *ok) const;
|
||||||
bool hasCharacterAt(uint line, uint column, char character) const;
|
bool hasCharacterAt(uint line, uint column, char character) const;
|
||||||
bool hasCharacterAt(uint position, char character) const;
|
bool hasCharacterAt(uint position, char character) const;
|
||||||
bool replaceAt(uint position, uint length, const Utf8String &replacement);
|
bool replaceAt(uint position, uint length, const Utf8String &replacement);
|
||||||
|
|||||||
@@ -112,6 +112,19 @@ protected:
|
|||||||
readFileContent(QStringLiteral("/complete_withDotArrowCorrectionForPointer.cpp")),
|
readFileContent(QStringLiteral("/complete_withDotArrowCorrectionForPointer.cpp")),
|
||||||
true
|
true
|
||||||
};
|
};
|
||||||
|
ClangBackEnd::FileContainer dotArrowCorrectionForPointerFileContainerBeforeTyping{
|
||||||
|
Utf8StringLiteral(TESTDATA_DIR"/complete_withDotArrowCorrectionForPointer.cpp"),
|
||||||
|
projectPart.projectPartId(),
|
||||||
|
readFileContent(QStringLiteral("/complete_withDotArrowCorrectionForPointer_beforeTyping.cpp")),
|
||||||
|
true
|
||||||
|
};
|
||||||
|
ClangBackEnd::FileContainer dotArrowCorrectionForPointerFileContainerAfterTyping{
|
||||||
|
Utf8StringLiteral(TESTDATA_DIR"/complete_withDotArrowCorrectionForPointer.cpp"),
|
||||||
|
projectPart.projectPartId(),
|
||||||
|
readFileContent(QStringLiteral("/complete_withDotArrowCorrectionForPointer_afterTyping.cpp")),
|
||||||
|
true
|
||||||
|
};
|
||||||
|
|
||||||
ClangBackEnd::FileContainer dotArrowCorrectionForPointerFileContainerInitial{
|
ClangBackEnd::FileContainer dotArrowCorrectionForPointerFileContainerInitial{
|
||||||
Utf8StringLiteral(TESTDATA_DIR"/complete_withDotArrowCorrectionForPointer.cpp"),
|
Utf8StringLiteral(TESTDATA_DIR"/complete_withDotArrowCorrectionForPointer.cpp"),
|
||||||
projectPart.projectPartId(),
|
projectPart.projectPartId(),
|
||||||
@@ -313,30 +326,6 @@ TEST_F(CodeCompleter, ArrowCompletion)
|
|||||||
ClangBackEnd::CompletionCorrection::NoCorrection);
|
ClangBackEnd::CompletionCorrection::NoCorrection);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(CodeCompleter, HasDotAt)
|
|
||||||
{
|
|
||||||
auto myCompleter = setupCompleter(dotArrowCorrectionForPointerFileContainer);
|
|
||||||
|
|
||||||
ASSERT_TRUE(myCompleter.hasDotAt(5, 8));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(CodeCompleter, HasDotAtWithUpdatedUnsavedFile)
|
|
||||||
{
|
|
||||||
auto myCompleter = setupCompleter(dotArrowCorrectionForPointerFileContainerInitial);
|
|
||||||
unsavedFiles.createOrUpdate({dotArrowCorrectionForPointerFileContainerUpdated});
|
|
||||||
|
|
||||||
ASSERT_TRUE(myCompleter.hasDotAt(5, 8));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(CodeCompleter, HasNoDotAtDueToMissingUnsavedFile)
|
|
||||||
{
|
|
||||||
const ClangBackEnd::FileContainer fileContainer = dotArrowCorrectionForPointerFileContainer;
|
|
||||||
translationUnits.create({fileContainer});
|
|
||||||
ClangBackEnd::CodeCompleter myCompleter(translationUnits.translationUnit(fileContainer));
|
|
||||||
|
|
||||||
ASSERT_FALSE(myCompleter.hasDotAt(5, 8));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(CodeCompleter, DotToArrowCompletionForPointer)
|
TEST_F(CodeCompleter, DotToArrowCompletionForPointer)
|
||||||
{
|
{
|
||||||
auto myCompleter = setupCompleter(dotArrowCorrectionForPointerFileContainer);
|
auto myCompleter = setupCompleter(dotArrowCorrectionForPointerFileContainer);
|
||||||
@@ -350,6 +339,24 @@ TEST_F(CodeCompleter, DotToArrowCompletionForPointer)
|
|||||||
ClangBackEnd::CompletionCorrection::DotToArrowCorrection);
|
ClangBackEnd::CompletionCorrection::DotToArrowCorrection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(CodeCompleter, DotToArrowCompletionForPointerInOutdatedTranslationUnit)
|
||||||
|
{
|
||||||
|
auto fileContainerBeforeTyping = dotArrowCorrectionForPointerFileContainerBeforeTyping;
|
||||||
|
auto myCompleter = setupCompleter(fileContainerBeforeTyping);
|
||||||
|
auto translationUnit = translationUnits.translationUnit(fileContainerBeforeTyping.filePath(),
|
||||||
|
fileContainerBeforeTyping.projectPartId());
|
||||||
|
translationUnit.cxTranslationUnit(); // Parse
|
||||||
|
unsavedFiles.createOrUpdate({dotArrowCorrectionForPointerFileContainerAfterTyping});
|
||||||
|
|
||||||
|
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)
|
TEST_F(CodeCompleter, NoDotToArrowCompletionForObject)
|
||||||
{
|
{
|
||||||
auto myCompleter = setupCompleter(noDotArrowCorrectionForObjectFileContainer);
|
auto myCompleter = setupCompleter(noDotArrowCorrectionForObjectFileContainer);
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
struct Foo { int member; };
|
||||||
|
|
||||||
|
void g(Foo *foo)
|
||||||
|
{
|
||||||
|
foo.
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
struct Foo { int member; };
|
||||||
|
|
||||||
|
void g(Foo *foo)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
@@ -34,6 +34,7 @@
|
|||||||
using ClangBackEnd::UnsavedFile;
|
using ClangBackEnd::UnsavedFile;
|
||||||
using ClangBackEnd::UnsavedFiles;
|
using ClangBackEnd::UnsavedFiles;
|
||||||
|
|
||||||
|
using ::testing::Eq;
|
||||||
using ::testing::PrintToString;
|
using ::testing::PrintToString;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@@ -150,6 +151,44 @@ TEST_F(UnsavedFile, Replace)
|
|||||||
ASSERT_THAT(unsavedFile, IsUnsavedFile(filePath, expectedContent, expectedContent.byteSize()));
|
ASSERT_THAT(unsavedFile, IsUnsavedFile(filePath, expectedContent, expectedContent.byteSize()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(UnsavedFile, ToUtf8PositionForValidLineColumn)
|
||||||
|
{
|
||||||
|
::UnsavedFile unsavedFile(filePath, fileContent);
|
||||||
|
bool ok = false;
|
||||||
|
|
||||||
|
const uint position = unsavedFile.toUtf8Position(1, 1, &ok);
|
||||||
|
|
||||||
|
ASSERT_TRUE(ok);
|
||||||
|
ASSERT_THAT(position, Eq(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(UnsavedFile, ToUtf8PositionForInValidLineColumn)
|
||||||
|
{
|
||||||
|
::UnsavedFile unsavedFile(filePath, fileContent);
|
||||||
|
bool ok = false;
|
||||||
|
|
||||||
|
unsavedFile.toUtf8Position(2, 1, &ok);
|
||||||
|
|
||||||
|
ASSERT_FALSE(ok);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(UnsavedFile, ToUtf8PositionForDefaultConstructedUnsavedFile)
|
||||||
|
{
|
||||||
|
::UnsavedFile unsavedFile;
|
||||||
|
bool ok = false;
|
||||||
|
|
||||||
|
unsavedFile.toUtf8Position(1, 1, &ok);
|
||||||
|
|
||||||
|
ASSERT_FALSE(ok);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(UnsavedFile, HasNoCharacterForDefaultConstructedUnsavedFile)
|
||||||
|
{
|
||||||
|
::UnsavedFile unsavedFile;
|
||||||
|
|
||||||
|
ASSERT_FALSE(unsavedFile.hasCharacterAt(0, 'x'));
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(UnsavedFile, HasNoCharacterForTooBigOffset)
|
TEST_F(UnsavedFile, HasNoCharacterForTooBigOffset)
|
||||||
{
|
{
|
||||||
::UnsavedFile unsavedFile(filePath, fileContent);
|
::UnsavedFile unsavedFile(filePath, fileContent);
|
||||||
|
|||||||
Reference in New Issue
Block a user