void getSignature(const CodeCompletionString &CCS, std::string *Signature,
std::string *Snippet, std::string *RequiredQualifiers) {
unsigned ArgCount = 0;
+ bool HadObjCArguments = false;
for (const auto &Chunk : CCS) {
// Informative qualifier chunks only clutter completion results, skip
// them.
switch (Chunk.Kind) {
case CodeCompletionString::CK_TypedText:
// The typed-text chunk is the actual name. We don't record this chunk.
- // In general our string looks like <qualifiers><name><signature>.
- // So once we see the name, any text we recorded so far should be
- // reclassified as qualifiers.
- if (RequiredQualifiers)
- *RequiredQualifiers = std::move(*Signature);
- Signature->clear();
- Snippet->clear();
+ // C++:
+ // In general our string looks like <qualifiers><name><signature>.
+ // So once we see the name, any text we recorded so far should be
+ // reclassified as qualifiers.
+ //
+ // Objective-C:
+ // Objective-C methods may have multiple typed-text chunks, so we must
+ // treat them carefully. For Objective-C methods, all typed-text chunks
+ // will end in ':' (unless there are no arguments, in which case we
+ // can safely treat them as C++).
+ if (!StringRef(Chunk.Text).endswith(":")) { // Treat as C++.
+ if (RequiredQualifiers)
+ *RequiredQualifiers = std::move(*Signature);
+ Signature->clear();
+ Snippet->clear();
+ } else { // Objective-C method with args.
+ // If this is the first TypedText to the Objective-C method, discard any
+ // text that we've previously seen (such as previous parameter selector,
+ // which will be marked as Informative text).
+ //
+ // TODO: Make previous parameters part of the signature for Objective-C
+ // methods.
+ if (!HadObjCArguments) {
+ HadObjCArguments = true;
+ Signature->clear();
+ } else { // Subsequent argument, considered part of snippet/signature.
+ *Signature += Chunk.Text;
+ *Snippet += Chunk.Text;
+ }
+ }
break;
case CodeCompletionString::CK_Text:
*Signature += Chunk.Text;
}
MATCHER_P(SnippetSuffix, Text, "") { return arg.SnippetSuffix == Text; }
MATCHER_P(Origin, OriginSet, "") { return arg.Origin == OriginSet; }
+MATCHER_P(Signature, S, "") { return arg.Signature == S; }
// Shorthand for Contains(Named(Name)).
Matcher<const std::vector<CodeCompletion> &> Has(std::string Name) {
CodeCompleteResult completions(ClangdServer &Server, StringRef Text,
std::vector<Symbol> IndexSymbols = {},
- clangd::CodeCompleteOptions Opts = {}) {
+ clangd::CodeCompleteOptions Opts = {},
+ PathRef FilePath = "foo.cpp") {
std::unique_ptr<SymbolIndex> OverrideIndex;
if (!IndexSymbols.empty()) {
assert(!Opts.Index && "both Index and IndexSymbols given!");
Opts.Index = OverrideIndex.get();
}
- auto File = testPath("foo.cpp");
+ auto File = testPath(FilePath);
Annotations Test(Text);
runAddDocument(Server, File, Test.code());
auto CompletionList =
// If IndexSymbols is non-empty, an index will be built and passed to opts.
CodeCompleteResult completions(StringRef Text,
std::vector<Symbol> IndexSymbols = {},
- clangd::CodeCompleteOptions Opts = {}) {
+ clangd::CodeCompleteOptions Opts = {},
+ PathRef FilePath = "foo.cpp") {
MockFSProvider FS;
MockCompilationDatabase CDB;
IgnoreDiagnostics DiagConsumer;
ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest());
- return completions(Server, Text, std::move(IndexSymbols), std::move(Opts));
+ return completions(Server, Text, std::move(IndexSymbols), std::move(Opts),
+ FilePath);
}
std::string replace(StringRef Haystack, StringRef Needle, StringRef Repl) {
{cls("naber"), cls("nx::naber")}, Opts);
EXPECT_THAT(Results.Completions, UnorderedElementsAre());
}
+
+TEST(CompletionTest, ObjectiveCMethodNoArguments) {
+ auto Results = completions(R"objc(
+ @interface Foo
+ @property(nonatomic, setter=setXToIgnoreComplete:) int value;
+ @end
+ Foo *foo = [Foo new]; int y = [foo v^]
+ )objc",
+ /*IndexSymbols=*/{},
+ /*Opts=*/{},
+ "Foo.m");
+
+ auto C = Results.Completions;
+ EXPECT_THAT(C, ElementsAre(Named("value")));
+ EXPECT_THAT(C, ElementsAre(Kind(CompletionItemKind::Method)));
+ EXPECT_THAT(C, ElementsAre(ReturnType("int")));
+ EXPECT_THAT(C, ElementsAre(Signature("")));
+ EXPECT_THAT(C, ElementsAre(SnippetSuffix("")));
+}
+
+TEST(CompletionTest, ObjectiveCMethodOneArgument) {
+ auto Results = completions(R"objc(
+ @interface Foo
+ - (int)valueForCharacter:(char)c;
+ @end
+ Foo *foo = [Foo new]; int y = [foo v^]
+ )objc",
+ /*IndexSymbols=*/{},
+ /*Opts=*/{},
+ "Foo.m");
+
+ auto C = Results.Completions;
+ EXPECT_THAT(C, ElementsAre(Named("valueForCharacter:")));
+ EXPECT_THAT(C, ElementsAre(Kind(CompletionItemKind::Method)));
+ EXPECT_THAT(C, ElementsAre(ReturnType("int")));
+ EXPECT_THAT(C, ElementsAre(Signature("(char)")));
+ EXPECT_THAT(C, ElementsAre(SnippetSuffix("${1:(char)}")));
+}
+
+TEST(CompletionTest, ObjectiveCMethodTwoArgumentsFromBeginning) {
+ auto Results = completions(R"objc(
+ @interface Foo
+ + (id)fooWithValue:(int)value fooey:(unsigned int)fooey;
+ @end
+ id val = [Foo foo^]
+ )objc",
+ /*IndexSymbols=*/{},
+ /*Opts=*/{},
+ "Foo.m");
+
+ auto C = Results.Completions;
+ EXPECT_THAT(C, ElementsAre(Named("fooWithValue:")));
+ EXPECT_THAT(C, ElementsAre(Kind(CompletionItemKind::Method)));
+ EXPECT_THAT(C, ElementsAre(ReturnType("id")));
+ EXPECT_THAT(C, ElementsAre(Signature("(int) fooey:(unsigned int)")));
+ EXPECT_THAT(C,
+ ElementsAre(SnippetSuffix("${1:(int)} fooey:${2:(unsigned int)}")));
+}
+
+TEST(CompletionTest, ObjectiveCMethodTwoArgumentsFromMiddle) {
+ auto Results = completions(R"objc(
+ @interface Foo
+ + (id)fooWithValue:(int)value fooey:(unsigned int)fooey;
+ @end
+ id val = [Foo fooWithValue:10 f^]
+ )objc",
+ /*IndexSymbols=*/{},
+ /*Opts=*/{},
+ "Foo.m");
+
+ auto C = Results.Completions;
+ EXPECT_THAT(C, ElementsAre(Named("fooey:")));
+ EXPECT_THAT(C, ElementsAre(Kind(CompletionItemKind::Method)));
+ EXPECT_THAT(C, ElementsAre(ReturnType("id")));
+ EXPECT_THAT(C, ElementsAre(Signature("(unsigned int)")));
+ EXPECT_THAT(C, ElementsAre(SnippetSuffix("${1:(unsigned int)}")));
+}
+
} // namespace
} // namespace clangd
} // namespace clang
EXPECT_EQ(Snippet, "");
}
+TEST_F(CompletionStringTest, ObjectiveCMethodNoArguments) {
+ Builder.AddResultTypeChunk("void");
+ Builder.AddTypedTextChunk("methodName");
+
+ auto *CCS = Builder.TakeString();
+ computeSignature(*CCS);
+ EXPECT_EQ(Signature, "");
+ EXPECT_EQ(Snippet, "");
+}
+
+TEST_F(CompletionStringTest, ObjectiveCMethodOneArgument) {
+ Builder.AddResultTypeChunk("void");
+ Builder.AddTypedTextChunk("methodWithArg:");
+ Builder.AddPlaceholderChunk("(type)");
+
+ auto *CCS = Builder.TakeString();
+ computeSignature(*CCS);
+ EXPECT_EQ(Signature, "(type)");
+ EXPECT_EQ(Snippet, "${1:(type)}");
+}
+
+TEST_F(CompletionStringTest, ObjectiveCMethodTwoArgumentsFromBeginning) {
+ Builder.AddResultTypeChunk("int");
+ Builder.AddTypedTextChunk("withFoo:");
+ Builder.AddPlaceholderChunk("(type)");
+ Builder.AddChunk(CodeCompletionString::CK_HorizontalSpace);
+ Builder.AddTypedTextChunk("bar:");
+ Builder.AddPlaceholderChunk("(type2)");
+
+ auto *CCS = Builder.TakeString();
+ computeSignature(*CCS);
+ EXPECT_EQ(Signature, "(type) bar:(type2)");
+ EXPECT_EQ(Snippet, "${1:(type)} bar:${2:(type2)}");
+}
+
+TEST_F(CompletionStringTest, ObjectiveCMethodTwoArgumentsFromMiddle) {
+ Builder.AddResultTypeChunk("int");
+ Builder.AddInformativeChunk("withFoo:");
+ Builder.AddTypedTextChunk("bar:");
+ Builder.AddPlaceholderChunk("(type2)");
+
+ auto *CCS = Builder.TakeString();
+ computeSignature(*CCS);
+ EXPECT_EQ(Signature, "(type2)");
+ EXPECT_EQ(Snippet, "${1:(type2)}");
+}
+
} // namespace
} // namespace clangd
} // namespace clang