mirror of
https://github.com/llvm/llvm-project.git
synced 2025-04-16 12:56:35 +00:00
[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
This commit is contained in:
parent
7dfb139554
commit
8fbac4e88a
@ -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{
|
||||
|
@ -1098,6 +1098,50 @@ private:
|
||||
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("\"") ||
|
||||
|
@ -48,7 +48,8 @@
|
||||
# CHECK-NEXT: ">",
|
||||
# CHECK-NEXT: ":",
|
||||
# CHECK-NEXT: "\"",
|
||||
# CHECK-NEXT: "/"
|
||||
# CHECK-NEXT: "/",
|
||||
# CHECK-NEXT: "*"
|
||||
# CHECK-NEXT: ]
|
||||
# CHECK-NEXT: },
|
||||
# CHECK-NEXT: "declarationProvider": true,
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user