[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:
parent
1ca772ed95
commit
322e2a3b40
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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; }
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue