[clang-tools-extra] 8fbac4e - [clangd] Add code completion of param name on /* inside function calls.
Adam Czachorowski via cfe-commits
cfe-commits at lists.llvm.org
Tue Oct 19 04:04:31 PDT 2021
Author: Adam Czachorowski
Date: 2021-10-19T12:49:46+02:00
New Revision: 8fbac4e88ac3dde30310bb63b234045075cd338b
URL: https://github.com/llvm/llvm-project/commit/8fbac4e88ac3dde30310bb63b234045075cd338b
DIFF: https://github.com/llvm/llvm-project/commit/8fbac4e88ac3dde30310bb63b234045075cd338b.diff
LOG: [clangd] Add code completion of param name on /* inside function calls.
For example, if you have:
void foo(int bar);
foo(/*^
it should auto-complete to "bar=".
Because Sema callbacks for code completion in comments happen before we
have an AST we need to cheat in clangd by detecting completion on /*
before, moving cursor back by two characters, then running a simplified
verion of SignatureHelp to extract argument name(s) from possible
overloads.
Differential Revision: https://reviews.llvm.org/D110823
Added:
Modified:
clang-tools-extra/clangd/ClangdLSPServer.cpp
clang-tools-extra/clangd/CodeComplete.cpp
clang-tools-extra/clangd/test/initialize-params.test
clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
Removed:
################################################################################
diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp
index a9debfd2a6ed5..c01a4286884b8 100644
--- a/clang-tools-extra/clangd/ClangdLSPServer.cpp
+++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp
@@ -542,7 +542,7 @@ void ClangdLSPServer::onInitialize(const InitializeParams &Params,
"^", "&", "#", "?", ".", "=", "\"", "'", "|"}},
{"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
index 2d01a163431e2..8c88884b292d4 100644
--- a/clang-tools-extra/clangd/CodeComplete.cpp
+++ b/clang-tools-extra/clangd/CodeComplete.cpp
@@ -1098,6 +1098,50 @@ class SignatureHelpCollector final : public CodeCompleteConsumer {
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<std::string> &ParamNames)
+ : CodeCompleteConsumer(CodeCompleteOpts),
+ Allocator(std::make_shared<clang::GlobalCodeCompletionAllocator>()),
+ CCTUInfo(Allocator), ParamNames(ParamNames) {}
+
+ void ProcessOverloadCandidates(Sema &S, unsigned CurrentArg,
+ OverloadCandidate *Candidates,
+ unsigned NumCandidates,
+ SourceLocation OpenParLoc) override {
+ assert(CurrentArg <= (unsigned)std::numeric_limits<int>::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<clang::GlobalCodeCompletionAllocator> Allocator;
+ CodeCompletionTUInfo CCTUInfo;
+ std::set<std::string> &ParamNames;
+};
+
struct SemaCompleteInput {
PathRef FileName;
size_t Offset;
@@ -1860,6 +1904,59 @@ CompletionPrefix guessCompletionPrefix(llvm::StringRef Content,
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<std::string> ParamNames;
+ // We want to see signatures coming from newly introduced includes, hence a
+ // full patch.
+ semaCodeComplete(
+ std::make_unique<ParamNameCollector>(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<unsigned>
+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 @@ CodeCompleteResult codeComplete(PathRef FileName, Position Pos,
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 @@ bool allowImplicitCompletion(llvm::StringRef Content, unsigned Offset) {
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 `<foo/`.
if ((Content.endswith("<") || Content.endswith("\"") ||
diff --git a/clang-tools-extra/clangd/test/initialize-params.test b/clang-tools-extra/clangd/test/initialize-params.test
index d356c2e0f7652..0e33bcd74903d 100644
--- a/clang-tools-extra/clangd/test/initialize-params.test
+++ b/clang-tools-extra/clangd/test/initialize-params.test
@@ -48,7 +48,8 @@
# CHECK-NEXT: ">",
# 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
index 8c0d3d34c75db..c63eecbe50913 100644
--- a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
+++ b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
@@ -3029,7 +3029,7 @@ TEST(CompletionTest, CompletionRange) {
// 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 @@ TEST(CompletionTest, PreambleCodeComplete) {
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
More information about the cfe-commits
mailing list