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,55 @@ 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::vector &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]; + if (auto *Func = Candidate.getFunction()) { + if (Func->getNumParams() <= CurrentArg) + continue; + auto *PVD = Func->getParamDecl(CurrentArg); + if (PVD == nullptr) + continue; + auto *Ident = PVD->getIdentifier(); + if (Ident == nullptr) + continue; + auto Name = Ident->getName(); + if (Name.empty() || ParamNamesSeen.find(Name) != ParamNamesSeen.end()) + continue; + ParamNames.emplace_back(Name); + ParamNamesSeen.emplace(ParamNames.back()); + } + } + } + +private: + GlobalCodeCompletionAllocator &getAllocator() override { return *Allocator; } + + CodeCompletionTUInfo &getCodeCompletionTUInfo() override { return CCTUInfo; } + + std::shared_ptr Allocator; + CodeCompletionTUInfo CCTUInfo; + std::vector &ParamNames; + // For de-duplication only. StringRefs based on strings in ParamNames. + std::set ParamNamesSeen; +}; // SignatureHelpCollector + struct SemaCompleteInput { PathRef FileName; size_t Offset; @@ -1857,6 +1906,41 @@ 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, + 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::vector ParamNames; + 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 (const auto &Name : ParamNames) { + CodeCompletion Item; + Item.Name = Name + "="; + Item.Kind = CompletionItemKind::Text; + Result.Completions.push_back(Item); + } + + return Result; +} + CodeCompleteResult codeComplete(PathRef FileName, Position Pos, const PreambleData *Preamble, const ParseInputs &ParseInput, @@ -1867,6 +1951,19 @@ elog("Code completion position was invalid {0}", Offset.takeError()); return CodeCompleteResult(); } + + auto Content = llvm::StringRef(ParseInput.Contents).take_front(*Offset); + if (Content.endswith("/*")) { + // 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 OffsetBeforeComment = *Offset - 2; + return codeCompleteComment(FileName, OffsetBeforeComment, Preamble, + ParseInput); + } + auto Flow = CodeCompleteFlow( FileName, Preamble ? Preamble->Includes : IncludeStructure(), SpecFuzzyFind, Opts); @@ -2050,7 +2147,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 @@ -3279,6 +3279,27 @@ 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="))); +} + } // namespace } // namespace clangd } // namespace clang