Author: David Goldman Date: 2022-05-20T11:49:16-04:00 New Revision: 322e2a3b40fa5f6bfcb868e827960c812b185710
URL: https://github.com/llvm/llvm-project/commit/322e2a3b40fa5f6bfcb868e827960c812b185710 DIFF: https://github.com/llvm/llvm-project/commit/322e2a3b40fa5f6bfcb868e827960c812b185710.diff LOG: [clangd][ObjC] Filter ObjC method completions on the remaining selector Previously, clangd would filter completions only on the first part of the selector (first typed chunk) instead of all remaining selector fragments (all typed chunks). Differential Revision: https://reviews.llvm.org/D124637 Added: Modified: clang-tools-extra/clangd/CodeComplete.cpp clang-tools-extra/clangd/CodeComplete.h clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp clang/include/clang/Sema/CodeCompleteConsumer.h clang/lib/Sema/CodeCompleteConsumer.cpp Removed: ################################################################################ diff --git a/clang-tools-extra/clangd/CodeComplete.cpp b/clang-tools-extra/clangd/CodeComplete.cpp index 920fb77f07dee..527b6cdc304db 100644 --- a/clang-tools-extra/clangd/CodeComplete.cpp +++ b/clang-tools-extra/clangd/CodeComplete.cpp @@ -303,6 +303,7 @@ struct CodeCompletionBuilder { assert(ASTCtx); Completion.Origin |= SymbolOrigin::AST; Completion.Name = std::string(llvm::StringRef(SemaCCS->getTypedText())); + Completion.FilterText = SemaCCS->getAllTypedText(); if (Completion.Scope.empty()) { if ((C.SemaResult->Kind == CodeCompletionResult::RK_Declaration) || (C.SemaResult->Kind == CodeCompletionResult::RK_Pattern)) @@ -335,6 +336,8 @@ struct CodeCompletionBuilder { Completion.Kind = toCompletionItemKind(C.IndexResult->SymInfo.Kind); if (Completion.Name.empty()) Completion.Name = std::string(C.IndexResult->Name); + if (Completion.FilterText.empty()) + Completion.FilterText = Completion.Name; // If the completion was visible to Sema, no qualifier is needed. This // avoids unneeded qualifiers in cases like with `using ns::X`. if (Completion.RequiredQualifier.empty() && !C.SemaResult) { @@ -352,6 +355,7 @@ struct CodeCompletionBuilder { Completion.Origin |= SymbolOrigin::Identifier; Completion.Kind = CompletionItemKind::Text; Completion.Name = std::string(C.IdentifierResult->Name); + Completion.FilterText = Completion.Name; } // Turn absolute path into a literal string that can be #included. @@ -860,7 +864,15 @@ struct CompletionRecorder : public CodeCompleteConsumer { return Result.Pattern->getTypedText(); } auto *CCS = codeCompletionString(Result); - return CCS->getTypedText(); + const CodeCompletionString::Chunk *OnlyText = nullptr; + for (auto &C : *CCS) { + if (C.Kind != CodeCompletionString::CK_TypedText) + continue; + if (OnlyText) + return CCAllocator->CopyString(CCS->getAllTypedText()); + OnlyText = &C; + } + return OnlyText ? OnlyText->Text : llvm::StringRef(); } // Build a CodeCompletion string for R, which must be from Results. @@ -1980,6 +1992,7 @@ CodeCompleteResult codeCompleteComment(PathRef FileName, unsigned Offset, continue; CodeCompletion Item; Item.Name = Name.str() + "="; + Item.FilterText = Item.Name; Item.Kind = CompletionItemKind::Text; Result.Completions.push_back(Item); } @@ -2118,8 +2131,8 @@ CompletionItem CodeCompletion::render(const CodeCompleteOptions &Opts) const { Doc.append(*Documentation); LSP.documentation = renderDoc(Doc, Opts.DocumentationFormat); } - LSP.sortText = sortText(Score.Total, Name); - LSP.filterText = Name; + LSP.sortText = sortText(Score.Total, FilterText); + LSP.filterText = FilterText; LSP.textEdit = {CompletionTokenRange, RequiredQualifier + Name}; // Merge continuous additionalTextEdits into main edit. The main motivation // behind this is to help LSP clients, it seems most of them are confused when diff --git a/clang-tools-extra/clangd/CodeComplete.h b/clang-tools-extra/clangd/CodeComplete.h index b76dd642943a9..73139ba40e765 100644 --- a/clang-tools-extra/clangd/CodeComplete.h +++ b/clang-tools-extra/clangd/CodeComplete.h @@ -155,6 +155,10 @@ struct CodeCompleteOptions { struct CodeCompletion { // The unqualified name of the symbol or other completion item. std::string Name; + // The name of the symbol for filtering and sorting purposes. Typically the + // same as `Name`, but may be diff erent e.g. for ObjC methods, `Name` is the + // first selector fragment but the `FilterText` is the entire selector. + std::string FilterText; // The scope qualifier for the symbol name. e.g. "ns1::ns2::" // Empty for non-symbol completions. Not inserted, but may be displayed. std::string Scope; diff --git a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp index 2e42786f76ad6..316fcb6293b12 100644 --- a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp +++ b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp @@ -58,6 +58,7 @@ MATCHER_P(scopeRefs, Refs, "") { return arg.ScopeRefsInFile == Refs; } MATCHER_P(nameStartsWith, Prefix, "") { return llvm::StringRef(arg.Name).startswith(Prefix); } +MATCHER_P(filterText, F, "") { return arg.FilterText == F; } MATCHER_P(scope, S, "") { return arg.Scope == S; } MATCHER_P(qualifier, Q, "") { return arg.RequiredQualifier == Q; } MATCHER_P(labeled, Label, "") { @@ -1918,6 +1919,7 @@ TEST(CompletionTest, QualifiedNames) { TEST(CompletionTest, Render) { CodeCompletion C; C.Name = "x"; + C.FilterText = "x"; C.Signature = "(bool) const"; C.SnippetSuffix = "(${0:bool})"; C.ReturnType = "int"; @@ -1950,6 +1952,11 @@ TEST(CompletionTest, Render) { EXPECT_FALSE(R.deprecated); EXPECT_EQ(R.score, .5f); + C.FilterText = "xtra"; + R = C.render(Opts); + EXPECT_EQ(R.filterText, "xtra"); + EXPECT_EQ(R.sortText, sortText(1.0, "xtra")); + Opts.EnableSnippets = true; R = C.render(Opts); EXPECT_EQ(R.insertText, "Foo::x(${0:bool})"); @@ -3051,6 +3058,25 @@ TEST(CompletionTest, ObjectiveCMethodTwoArgumentsFromMiddle) { EXPECT_THAT(C, ElementsAre(snippetSuffix("${1:(unsigned int)}"))); } +TEST(CompletionTest, ObjectiveCMethodFilterOnEntireSelector) { + auto Results = completions(R"objc( + @interface Foo + + (id)player:(id)player willRun:(id)run; + @end + id val = [Foo wi^] + )objc", + /*IndexSymbols=*/{}, + /*Opts=*/{}, "Foo.m"); + + auto C = Results.Completions; + EXPECT_THAT(C, ElementsAre(named("player:"))); + EXPECT_THAT(C, ElementsAre(filterText("player:willRun:"))); + EXPECT_THAT(C, ElementsAre(kind(CompletionItemKind::Method))); + EXPECT_THAT(C, ElementsAre(returnType("id"))); + EXPECT_THAT(C, ElementsAre(signature("(id) willRun:(id)"))); + EXPECT_THAT(C, ElementsAre(snippetSuffix("${1:(id)} willRun:${2:(id)}"))); +} + TEST(CompletionTest, ObjectiveCSimpleMethodDeclaration) { auto Results = completions(R"objc( @interface Foo diff --git a/clang/include/clang/Sema/CodeCompleteConsumer.h b/clang/include/clang/Sema/CodeCompleteConsumer.h index 41c495882b27b..3869fb5b83988 100644 --- a/clang/include/clang/Sema/CodeCompleteConsumer.h +++ b/clang/include/clang/Sema/CodeCompleteConsumer.h @@ -606,9 +606,12 @@ class CodeCompletionString { return begin()[I]; } - /// Returns the text in the TypedText chunk. + /// Returns the text in the first TypedText chunk. const char *getTypedText() const; + /// Returns the combined text from all TypedText chunks. + std::string getAllTypedText() const; + /// Retrieve the priority of this code completion result. unsigned getPriority() const { return Priority; } diff --git a/clang/lib/Sema/CodeCompleteConsumer.cpp b/clang/lib/Sema/CodeCompleteConsumer.cpp index 927b067797275..8e8a1be38c0f5 100644 --- a/clang/lib/Sema/CodeCompleteConsumer.cpp +++ b/clang/lib/Sema/CodeCompleteConsumer.cpp @@ -346,6 +346,15 @@ const char *CodeCompletionString::getTypedText() const { return nullptr; } +std::string CodeCompletionString::getAllTypedText() const { + std::string Res; + for (const Chunk &C : *this) + if (C.Kind == CK_TypedText) + Res += C.Text; + + return Res; +} + const char *CodeCompletionAllocator::CopyString(const Twine &String) { SmallString<128> Data; StringRef Ref = String.toStringRef(Data); _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits