Commit d3ee49d9 authored by Kevin Funk's avatar Kevin Funk

Multiple fixes to code completion

- ClangCodeCompletionContext is now able to filter out some of its results
- Better "Best Match" quality
- More tests
parent 01486717
......@@ -48,7 +48,9 @@ using namespace KDevelop;
namespace {
/// Completion results with priority below this value will be shown in "Best Matches" group
const int MAX_PRIORITY_FOR_BEST_MATCHES = 15;
/// See http://clang.llvm.org/doxygen/CodeCompleteConsumer_8h_source.html
/// We currently treat CCP_SuperCompletion = 20 as the highest priority that still gives a "Best Match"
const unsigned int MAX_PRIORITY_FOR_BEST_MATCHES = 20;
/// Maximum return-type string length in completion items
const int MAX_RETURN_TYPE_STRING_LENGTH = 20;
......@@ -155,8 +157,8 @@ public:
QVariant data(const QModelIndex& index, int role, const CodeCompletionModel* model) const override
{
if (role == CodeCompletionModel::MatchQuality && m_bestMatchQuality) {
return m_bestMatchQuality;
if (role == CodeCompletionModel::MatchQuality && m_matchQuality) {
return m_matchQuality;
}
auto ret = CompletionItem<NormalDeclarationCompletionItem>::data(index, role, model);
......@@ -197,14 +199,19 @@ public:
return new ClangNavigationWidget(m_declaration);
}
int matchQuality() const
{
return m_matchQuality;
}
///Sets match quality from 0 to 10. 10 is the best fit.
void setBestMatchQuality(int value)
void setMatchQuality(int value)
{
m_bestMatchQuality = value;
m_matchQuality = value;
}
private:
int m_bestMatchQuality = 0;
int m_matchQuality = 0;
};
/**
......@@ -254,6 +261,17 @@ QString& elideStringRight(QString& str, int length)
return str;
}
/**
* @return Value suited for @ref CodeCompletionModel::MatchQuality in the range [0.0, 10.0]
*
* See http://clang.llvm.org/doxygen/CodeCompleteConsumer_8h_source.html for list of priorities
* They (currently) have a range from [-3, 80] (the lower, the better)
*/
int codeCompletionPriorityToMatchQuality(unsigned int completionPriority)
{
return qBound(0u, completionPriority, 80u) / 8;
}
}
ClangCodeCompletionContext::ClangCodeCompletionContext(const DUContextPointer& context,
......@@ -267,6 +285,8 @@ ClangCodeCompletionContext::ClangCodeCompletionContext(const DUContextPointer& c
{
ClangString file(clang_getFileName(session.file()));
const unsigned int completeOptions = clang_defaultCodeCompleteOptions();
if (!m_text.isEmpty()) {
kDebug() << "Unsaved contents found for file" << file << "- creating CXUnsavedFile";
......@@ -279,12 +299,12 @@ ClangCodeCompletionContext::ClangCodeCompletionContext(const DUContextPointer& c
m_results.reset(clang_codeCompleteAt(session.unit(), file.c_str(),
position.line + 1, position.column + 1,
&unsaved, 1u,
clang_defaultCodeCompleteOptions()));
completeOptions));
} else {
m_results.reset(clang_codeCompleteAt(session.unit(), file.c_str(),
position.line + 1, position.column + 1,
nullptr, 0u,
clang_defaultCodeCompleteOptions()));
completeOptions));
}
if (!m_results) {
......@@ -331,6 +351,11 @@ QList<CompletionTreeItemPointer> ClangCodeCompletionContext::completionItems(boo
for (uint i = 0; i < m_results->NumResults; ++i) {
auto result = m_results->Results[i];
const bool isMacroDefinition = result.CursorKind == CXCursor_MacroDefinition;
if (isMacroDefinition && m_filters & NoMacros) {
continue;
}
const uint chunks = clang_getNumCompletionChunks(result.CompletionString);
// the string that would be neede to type, usually the identifier of something
......@@ -360,6 +385,7 @@ QList<CompletionTreeItemPointer> ClangCodeCompletionContext::completionItems(boo
if (kind == CXCompletionChunk_CurrentParameter || kind == CXCompletionChunk_Optional) {
continue;
}
const QString string = ClangString(clang_getCompletionChunkText(result.CompletionString, j)).toString();
switch (kind) {
case CXCompletionChunk_Informative:
......@@ -402,10 +428,20 @@ QList<CompletionTreeItemPointer> ClangCodeCompletionContext::completionItems(boo
}
}
const bool isBuiltin = (result.CursorKind == CXCursor_NotImplemented);
if (isBuiltin && m_filters & NoBuiltins) {
continue;
}
// ellide text to the right for overly long result types (templates especially)
elideStringRight(resultType, MAX_RETURN_TYPE_STRING_LENGTH);
if (result.CursorKind != CXCursor_MacroDefinition && result.CursorKind != CXCursor_NotImplemented) {
const bool isDeclaration = !isMacroDefinition && !isBuiltin;
if (isDeclaration && m_filters & NoDeclarations) {
continue;
}
if (isDeclaration) {
const Identifier id(typed);
QualifiedIdentifier qid;
ClangString parent(clang_getCompletionParent(result.CompletionString, nullptr));
......@@ -426,11 +462,13 @@ QList<CompletionTreeItemPointer> ClangCodeCompletionContext::completionItems(boo
if (found) {
auto item = new DeclarationItem(found, display, resultType, replacement);
bool bestMatch = clang_getCompletionPriority(result.CompletionString) < MAX_PRIORITY_FOR_BEST_MATCHES;
const unsigned int completionPriority = clang_getCompletionPriority(result.CompletionString);
const bool bestMatch = completionPriority <= MAX_PRIORITY_FOR_BEST_MATCHES;
//don't set best match property for internal identifiers, also prefer declarations from current file
if (bestMatch && !found->indexedIdentifier().identifier().toString().startsWith("__") ) {
item->setBestMatchQuality(found->context()->url() == ctx->url() ? 10 : 9 );
const int matchQuality = codeCompletionPriorityToMatchQuality(completionPriority);
item->setMatchQuality(matchQuality);
}
items << CompletionTreeItemPointer(item);
......@@ -506,3 +544,13 @@ QList<CompletionTreeElementPointer> ClangCodeCompletionContext::ungroupedElement
{
return m_ungrouped;
}
ClangCodeCompletionContext::ContextFilters ClangCodeCompletionContext::filters() const
{
return m_filters;
}
void ClangCodeCompletionContext::setFilters(const ClangCodeCompletionContext::ContextFilters& filters)
{
m_filters = filters;
}
......@@ -35,6 +35,16 @@
class ClangCodeCompletionContext : public KDevelop::CodeCompletionContext
{
public:
enum ContextFilter
{
NoFilter = 0, ///< Show everything
NoBuiltins = 1 << 0, ///< Hide builtin completion items
NoMacros = 1 << 1, ///< Hide macro completion items
NoDeclarations = 1 << 2, ///< Hide declaration completion items
NoClangCompletion = NoBuiltins | NoMacros | NoDeclarations
};
Q_DECLARE_FLAGS(ContextFilters, ContextFilter)
ClangCodeCompletionContext(const KDevelop::DUContextPointer& context,
const ParseSession& session,
const KDevelop::SimpleCursor& position,
......@@ -45,6 +55,9 @@ public:
QList<KDevelop::CompletionTreeElementPointer> ungroupedElements() override;
ContextFilters filters() const;
void setFilters(const ContextFilters& filters);
private:
void addOverwritableItems();
void addImplementationHelperItems();
......@@ -59,6 +72,7 @@ private:
QList<KDevelop::CompletionTreeElementPointer> m_ungrouped;
CompletionHelper m_completionHelper;
ParseSession m_parseSession;
ContextFilters m_filters = NoFilter;
};
#endif // CLANGCODECOMPLETIONCONTEXT_H
......@@ -76,7 +76,10 @@ ParseSessionData::Ptr sessionData(const TestFile& file, ClangIndex* index)
return ParseSessionData::Ptr(new ParseSessionData(file.url(), file.fileContents().toUtf8(), index));
}
void executeCompletionTest(const QString& code, const CompletionItemsList& expectedCompletionItems)
void executeCompletionTest(const QString& code, const CompletionItemsList& expectedCompletionItems,
const ClangCodeCompletionContext::ContextFilters& filters = ClangCodeCompletionContext::ContextFilters(
ClangCodeCompletionContext::NoBuiltins |
ClangCodeCompletionContext::NoMacros))
{
TestFile file(code, "cpp");
QVERIFY(file.parseAndWait(TopDUContext::AllDeclarationsContextsUsesAndAST));
......@@ -89,8 +92,10 @@ void executeCompletionTest(const QString& code, const CompletionItemsList& expec
foreach(CompletionItems items, expectedCompletionItems) {
// TODO: We should not need to pass 'session' to the context, should just use the base class ctor
auto context = new ClangCodeCompletionContext(DUContextPointer(top), session, items.position, QString());
context->setFilters(filters);
auto tester = ClangCodeCompletionItemTester(KSharedPtr<ClangCodeCompletionContext>(context));
tester.names.sort();
QCOMPARE(tester.names, items.completions);
}
}
......@@ -110,14 +115,35 @@ void TestCodeCompletion::testClangCodeCompletion_data()
QTest::addColumn<QString>("code");
QTest::addColumn<CompletionItemsList>("expectedItems");
QTest::newRow("basic")
<< "class Foo { public: void foo() {} }; int main() { Foo f; \nf. }"
QTest::newRow("assignment")
<< "int foo = 5; \nint bar = "
<< CompletionItemsList{{{1,9}, {
"bar",
"foo",
}}};
QTest::newRow("dotmemberaccess")
<< "class Foo { public: void foo() {} }; int main() { Foo f; \nf. "
<< CompletionItemsList{{{1, 2}, {
"Foo",
"foo()",
"operator=(Foo &&)",
"operator=(const Foo &)",
"~Foo()",
}}};
QTest::newRow("arrowmemberaccess")
<< "class Foo { public: void foo() {} }; int main() { Foo* f = new Foo; \nf-> }"
<< CompletionItemsList{{{1, 3}, {
"Foo",
"foo()",
"operator=(Foo &&)",
"operator=(const Foo &)",
"operator=(Foo &&)"
"~Foo()",
}}};
QTest::newRow("enum-case")
<< "int main() { enum Foo { foo, bar } e; switch (e) {\ncase "
<< CompletionItemsList{{{1,4}, {
"bar",
"foo",
}}};
}
......@@ -126,7 +152,7 @@ void TestCodeCompletion::testVirtualOverride()
QFETCH(QString, code);
QFETCH(CompletionItemsList, expectedItems);
executeCompletionTest(code, expectedItems);
executeCompletionTest(code, expectedItems, ClangCodeCompletionContext::NoClangCompletion);
}
void TestCodeCompletion::testVirtualOverride_data()
......@@ -154,7 +180,7 @@ void TestCodeCompletion::testVirtualOverride_data()
<< "class Foo { virtual int foo(int i); };\n"
"class Baz { virtual char baz(char c); };\n"
"class Bar : Foo, Baz \n{\n}"
<< CompletionItemsList{{{4, 1}, {"foo(int i)", "baz(char c)"}}};
<< CompletionItemsList{{{4, 1}, {"baz(char c)", "foo(int i)"}}};
QTest::newRow("deep")
<< "class Foo { virtual int foo(int i); };\n"
......@@ -178,7 +204,7 @@ void TestCodeCompletion::testImplement()
QFETCH(QString, code);
QFETCH(CompletionItemsList, expectedItems);
executeCompletionTest(code, expectedItems);
executeCompletionTest(code, expectedItems, ClangCodeCompletionContext::NoClangCompletion);
}
void TestCodeCompletion::testImplement_data()
......@@ -271,7 +297,7 @@ void TestCodeCompletion::testImplement_data()
QTest::newRow("variadic")
<< "int foo(...); int bar(int i, ...); \n"
<< CompletionItemsList{{{1, 1}, {"foo(...)", "bar(int i, ...)"}}};
<< CompletionItemsList{{{1, 1}, {"bar(int i, ...)", "foo(...)"}}};
QTest::newRow("const")
<< "class Foo { int bar() const; };"
......@@ -292,9 +318,6 @@ void TestCodeCompletion::testInvalidCompletions_data()
QTest::addColumn<QString>("code");
QTest::addColumn<CompletionItemsList>("expectedItems");
QTest::newRow("invalid-context-infunction")
<< "class Foo { int bar() const; };\nint somefunc() {\n}"
<< CompletionItemsList{{{2, 0}, {}}};
QTest::newRow("invalid-context-incomment")
<< "class Foo { int bar() const; };\n/*\n*/"
<< CompletionItemsList{{{2, 0}, {}}};
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment