diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp --- a/clang-tools-extra/clangd/ClangdLSPServer.cpp +++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp @@ -542,7 +542,7 @@ "^", "&", "#", "?", ".", "=", "\"", "'", "|"}}, {"resolveProvider", false}, // We do extra checks, e.g. that > is part of ->. - {"triggerCharacters", {".", "<", ">", ":", "\"", "/"}}, + {"triggerCharacters", {".", "<", ">", ":", "\"", "/", "*"}}, }}, {"semanticTokensProvider", llvm::json::Object{ diff --git a/clang-tools-extra/clangd/CodeComplete.cpp b/clang-tools-extra/clangd/CodeComplete.cpp --- a/clang-tools-extra/clangd/CodeComplete.cpp +++ b/clang-tools-extra/clangd/CodeComplete.cpp @@ -1098,6 +1098,50 @@ const SymbolIndex *Index; }; // SignatureHelpCollector +// Used only for completion of C-style comments in function call (i.e. +// /*foo=*/7). Similar to SignatureHelpCollector, but needs to do less work. +class ParamNameCollector final : public CodeCompleteConsumer { +public: + ParamNameCollector(const clang::CodeCompleteOptions &CodeCompleteOpts, + std::set &ParamNames) + : CodeCompleteConsumer(CodeCompleteOpts), + Allocator(std::make_shared()), + CCTUInfo(Allocator), ParamNames(ParamNames) {} + + void ProcessOverloadCandidates(Sema &S, unsigned CurrentArg, + OverloadCandidate *Candidates, + unsigned NumCandidates, + SourceLocation OpenParLoc) override { + assert(CurrentArg <= (unsigned)std::numeric_limits::max() && + "too many arguments"); + + for (unsigned I = 0; I < NumCandidates; ++I) { + OverloadCandidate Candidate = Candidates[I]; + auto *Func = Candidate.getFunction(); + if (!Func || Func->getNumParams() <= CurrentArg) + continue; + auto *PVD = Func->getParamDecl(CurrentArg); + if (!PVD) + continue; + auto *Ident = PVD->getIdentifier(); + if (!Ident) + continue; + auto Name = Ident->getName(); + if (!Name.empty()) + ParamNames.insert(Name.str()); + } + } + +private: + GlobalCodeCompletionAllocator &getAllocator() override { return *Allocator; } + + CodeCompletionTUInfo &getCodeCompletionTUInfo() override { return CCTUInfo; } + + std::shared_ptr Allocator; + CodeCompletionTUInfo CCTUInfo; + std::set &ParamNames; +}; + struct SemaCompleteInput { PathRef FileName; size_t Offset; @@ -1860,6 +1904,59 @@ return Result; } +// Code complete the argument name on "/*" inside function call. +// Offset should be pointing to the start of the comment, i.e.: +// foo(^/*, rather than foo(/*^) where the cursor probably is. +CodeCompleteResult codeCompleteComment(PathRef FileName, unsigned Offset, + llvm::StringRef Prefix, + const PreambleData *Preamble, + const ParseInputs &ParseInput) { + if (Preamble == nullptr) // Can't run without Sema. + return CodeCompleteResult(); + + clang::CodeCompleteOptions Options; + Options.IncludeGlobals = false; + Options.IncludeMacros = false; + Options.IncludeCodePatterns = false; + Options.IncludeBriefComments = false; + std::set ParamNames; + // We want to see signatures coming from newly introduced includes, hence a + // full patch. + semaCodeComplete( + std::make_unique(Options, ParamNames), Options, + {FileName, Offset, *Preamble, + PreamblePatch::createFullPatch(FileName, ParseInput, *Preamble), + ParseInput}); + if (ParamNames.empty()) + return CodeCompleteResult(); + + CodeCompleteResult Result; + Result.Context = CodeCompletionContext::CCC_NaturalLanguage; + for (llvm::StringRef Name : ParamNames) { + if (!Name.startswith(Prefix)) + continue; + CodeCompletion Item; + Item.Name = Name.str() + "="; + Item.Kind = CompletionItemKind::Text; + Result.Completions.push_back(Item); + } + + return Result; +} + +// If Offset is inside what looks like argument comment (e.g. +// "/*^" or "/* foo^"), returns new offset pointing to the start of the /* +// (place where semaCodeComplete should run). +llvm::Optional +maybeFunctionArgumentCommentStart(llvm::StringRef Content) { + while (!Content.empty() && isAsciiIdentifierContinue(Content.back())) + Content = Content.drop_back(); + Content = Content.rtrim(); + if (Content.endswith("/*")) + return Content.size() - 2; + return None; +} + CodeCompleteResult codeComplete(PathRef FileName, Position Pos, const PreambleData *Preamble, const ParseInputs &ParseInput, @@ -1870,6 +1967,19 @@ elog("Code completion position was invalid {0}", Offset.takeError()); return CodeCompleteResult(); } + + auto Content = llvm::StringRef(ParseInput.Contents).take_front(*Offset); + if (auto OffsetBeforeComment = maybeFunctionArgumentCommentStart(Content)) { + // We are doing code completion of a comment, where we currently only + // support completing param names in function calls. To do this, we + // require information from Sema, but Sema's comment completion stops at + // parsing, so we must move back the position before running it, extract + // information we need and construct completion items ourselves. + auto CommentPrefix = Content.substr(*OffsetBeforeComment + 2).trim(); + return codeCompleteComment(FileName, *OffsetBeforeComment, CommentPrefix, + Preamble, ParseInput); + } + auto Flow = CodeCompleteFlow( FileName, Preamble ? Preamble->Includes : IncludeStructure(), SpecFuzzyFind, Opts); @@ -2053,7 +2163,8 @@ Content = Content.substr(Pos + 1); // Complete after scope operators. - if (Content.endswith(".") || Content.endswith("->") || Content.endswith("::")) + if (Content.endswith(".") || Content.endswith("->") || + Content.endswith("::") || Content.endswith("/*")) return true; // Complete after `#include <` and #include `", # CHECK-NEXT: ":", # CHECK-NEXT: "\"", -# CHECK-NEXT: "/" +# CHECK-NEXT: "/", +# CHECK-NEXT: "*" # CHECK-NEXT: ] # CHECK-NEXT: }, # CHECK-NEXT: "declarationProvider": true, diff --git a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp --- a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp +++ b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp @@ -3029,7 +3029,7 @@ // Sema doesn't trigger at all here, while the no-sema completion runs // heuristics as normal and reports a range. It'd be nice to be consistent. - const char *NoCompletion = "/* [[]]^ */"; + const char *NoCompletion = "/* foo [[]]^ */"; Completions = completions(NoCompletion); EXPECT_EQ(Completions.CompletionRange, llvm::None); Completions = completionsNoCompile(NoCompletion); @@ -3279,6 +3279,35 @@ EXPECT_THAT(Result.Completions, Not(testing::IsEmpty())); } +TEST(CompletionTest, CommentParamName) { + clangd::CodeCompleteOptions Opts; + const std::string Code = R"cpp( + void fun(int foo, int bar); + void overloaded(int param_int); + void overloaded(int param_int, int param_other); + void overloaded(char param_char); + int main() { + )cpp"; + + EXPECT_THAT(completions(Code + "fun(/*^", {}, Opts).Completions, + UnorderedElementsAre(Labeled("foo="))); + EXPECT_THAT(completions(Code + "fun(1, /*^", {}, Opts).Completions, + UnorderedElementsAre(Labeled("bar="))); + EXPECT_THAT(completions(Code + "/*^", {}, Opts).Completions, IsEmpty()); + // Test de-duplication. + EXPECT_THAT( + completions(Code + "overloaded(/*^", {}, Opts).Completions, + UnorderedElementsAre(Labeled("param_int="), Labeled("param_char="))); + // Comment already has some text in it. + EXPECT_THAT(completions(Code + "fun(/* ^", {}, Opts).Completions, + UnorderedElementsAre(Labeled("foo="))); + EXPECT_THAT(completions(Code + "fun(/* f^", {}, Opts).Completions, + UnorderedElementsAre(Labeled("foo="))); + EXPECT_THAT(completions(Code + "fun(/* x^", {}, Opts).Completions, IsEmpty()); + EXPECT_THAT(completions(Code + "fun(/* f ^", {}, Opts).Completions, + IsEmpty()); +} + } // namespace } // namespace clangd } // namespace clang