[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
This commit is contained in:
David Goldman 2022-04-28 15:13:21 -04:00
parent 1ca772ed95
commit 322e2a3b40
5 changed files with 59 additions and 4 deletions

View file

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

View file

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

View file

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

View file

@ -606,9 +606,12 @@ public:
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; }

View file

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