Skip to content
This repository was archived by the owner on Jan 26, 2024. It is now read-only.

Commit 8fbac4e

Browse files
committed
[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
1 parent 7dfb139 commit 8fbac4e

File tree

4 files changed

+145
-4
lines changed

4 files changed

+145
-4
lines changed

clang-tools-extra/clangd/ClangdLSPServer.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -542,7 +542,7 @@ void ClangdLSPServer::onInitialize(const InitializeParams &Params,
542542
"^", "&", "#", "?", ".", "=", "\"", "'", "|"}},
543543
{"resolveProvider", false},
544544
// We do extra checks, e.g. that > is part of ->.
545-
{"triggerCharacters", {".", "<", ">", ":", "\"", "/"}},
545+
{"triggerCharacters", {".", "<", ">", ":", "\"", "/", "*"}},
546546
}},
547547
{"semanticTokensProvider",
548548
llvm::json::Object{

clang-tools-extra/clangd/CodeComplete.cpp

+112-1
Original file line numberDiff line numberDiff line change
@@ -1098,6 +1098,50 @@ class SignatureHelpCollector final : public CodeCompleteConsumer {
10981098
const SymbolIndex *Index;
10991099
}; // SignatureHelpCollector
11001100

1101+
// Used only for completion of C-style comments in function call (i.e.
1102+
// /*foo=*/7). Similar to SignatureHelpCollector, but needs to do less work.
1103+
class ParamNameCollector final : public CodeCompleteConsumer {
1104+
public:
1105+
ParamNameCollector(const clang::CodeCompleteOptions &CodeCompleteOpts,
1106+
std::set<std::string> &ParamNames)
1107+
: CodeCompleteConsumer(CodeCompleteOpts),
1108+
Allocator(std::make_shared<clang::GlobalCodeCompletionAllocator>()),
1109+
CCTUInfo(Allocator), ParamNames(ParamNames) {}
1110+
1111+
void ProcessOverloadCandidates(Sema &S, unsigned CurrentArg,
1112+
OverloadCandidate *Candidates,
1113+
unsigned NumCandidates,
1114+
SourceLocation OpenParLoc) override {
1115+
assert(CurrentArg <= (unsigned)std::numeric_limits<int>::max() &&
1116+
"too many arguments");
1117+
1118+
for (unsigned I = 0; I < NumCandidates; ++I) {
1119+
OverloadCandidate Candidate = Candidates[I];
1120+
auto *Func = Candidate.getFunction();
1121+
if (!Func || Func->getNumParams() <= CurrentArg)
1122+
continue;
1123+
auto *PVD = Func->getParamDecl(CurrentArg);
1124+
if (!PVD)
1125+
continue;
1126+
auto *Ident = PVD->getIdentifier();
1127+
if (!Ident)
1128+
continue;
1129+
auto Name = Ident->getName();
1130+
if (!Name.empty())
1131+
ParamNames.insert(Name.str());
1132+
}
1133+
}
1134+
1135+
private:
1136+
GlobalCodeCompletionAllocator &getAllocator() override { return *Allocator; }
1137+
1138+
CodeCompletionTUInfo &getCodeCompletionTUInfo() override { return CCTUInfo; }
1139+
1140+
std::shared_ptr<clang::GlobalCodeCompletionAllocator> Allocator;
1141+
CodeCompletionTUInfo CCTUInfo;
1142+
std::set<std::string> &ParamNames;
1143+
};
1144+
11011145
struct SemaCompleteInput {
11021146
PathRef FileName;
11031147
size_t Offset;
@@ -1860,6 +1904,59 @@ CompletionPrefix guessCompletionPrefix(llvm::StringRef Content,
18601904
return Result;
18611905
}
18621906

1907+
// Code complete the argument name on "/*" inside function call.
1908+
// Offset should be pointing to the start of the comment, i.e.:
1909+
// foo(^/*, rather than foo(/*^) where the cursor probably is.
1910+
CodeCompleteResult codeCompleteComment(PathRef FileName, unsigned Offset,
1911+
llvm::StringRef Prefix,
1912+
const PreambleData *Preamble,
1913+
const ParseInputs &ParseInput) {
1914+
if (Preamble == nullptr) // Can't run without Sema.
1915+
return CodeCompleteResult();
1916+
1917+
clang::CodeCompleteOptions Options;
1918+
Options.IncludeGlobals = false;
1919+
Options.IncludeMacros = false;
1920+
Options.IncludeCodePatterns = false;
1921+
Options.IncludeBriefComments = false;
1922+
std::set<std::string> ParamNames;
1923+
// We want to see signatures coming from newly introduced includes, hence a
1924+
// full patch.
1925+
semaCodeComplete(
1926+
std::make_unique<ParamNameCollector>(Options, ParamNames), Options,
1927+
{FileName, Offset, *Preamble,
1928+
PreamblePatch::createFullPatch(FileName, ParseInput, *Preamble),
1929+
ParseInput});
1930+
if (ParamNames.empty())
1931+
return CodeCompleteResult();
1932+
1933+
CodeCompleteResult Result;
1934+
Result.Context = CodeCompletionContext::CCC_NaturalLanguage;
1935+
for (llvm::StringRef Name : ParamNames) {
1936+
if (!Name.startswith(Prefix))
1937+
continue;
1938+
CodeCompletion Item;
1939+
Item.Name = Name.str() + "=";
1940+
Item.Kind = CompletionItemKind::Text;
1941+
Result.Completions.push_back(Item);
1942+
}
1943+
1944+
return Result;
1945+
}
1946+
1947+
// If Offset is inside what looks like argument comment (e.g.
1948+
// "/*^" or "/* foo^"), returns new offset pointing to the start of the /*
1949+
// (place where semaCodeComplete should run).
1950+
llvm::Optional<unsigned>
1951+
maybeFunctionArgumentCommentStart(llvm::StringRef Content) {
1952+
while (!Content.empty() && isAsciiIdentifierContinue(Content.back()))
1953+
Content = Content.drop_back();
1954+
Content = Content.rtrim();
1955+
if (Content.endswith("/*"))
1956+
return Content.size() - 2;
1957+
return None;
1958+
}
1959+
18631960
CodeCompleteResult codeComplete(PathRef FileName, Position Pos,
18641961
const PreambleData *Preamble,
18651962
const ParseInputs &ParseInput,
@@ -1870,6 +1967,19 @@ CodeCompleteResult codeComplete(PathRef FileName, Position Pos,
18701967
elog("Code completion position was invalid {0}", Offset.takeError());
18711968
return CodeCompleteResult();
18721969
}
1970+
1971+
auto Content = llvm::StringRef(ParseInput.Contents).take_front(*Offset);
1972+
if (auto OffsetBeforeComment = maybeFunctionArgumentCommentStart(Content)) {
1973+
// We are doing code completion of a comment, where we currently only
1974+
// support completing param names in function calls. To do this, we
1975+
// require information from Sema, but Sema's comment completion stops at
1976+
// parsing, so we must move back the position before running it, extract
1977+
// information we need and construct completion items ourselves.
1978+
auto CommentPrefix = Content.substr(*OffsetBeforeComment + 2).trim();
1979+
return codeCompleteComment(FileName, *OffsetBeforeComment, CommentPrefix,
1980+
Preamble, ParseInput);
1981+
}
1982+
18731983
auto Flow = CodeCompleteFlow(
18741984
FileName, Preamble ? Preamble->Includes : IncludeStructure(),
18751985
SpecFuzzyFind, Opts);
@@ -2053,7 +2163,8 @@ bool allowImplicitCompletion(llvm::StringRef Content, unsigned Offset) {
20532163
Content = Content.substr(Pos + 1);
20542164

20552165
// Complete after scope operators.
2056-
if (Content.endswith(".") || Content.endswith("->") || Content.endswith("::"))
2166+
if (Content.endswith(".") || Content.endswith("->") ||
2167+
Content.endswith("::") || Content.endswith("/*"))
20572168
return true;
20582169
// Complete after `#include <` and #include `<foo/`.
20592170
if ((Content.endswith("<") || Content.endswith("\"") ||

clang-tools-extra/clangd/test/initialize-params.test

+2-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@
4848
# CHECK-NEXT: ">",
4949
# CHECK-NEXT: ":",
5050
# CHECK-NEXT: "\"",
51-
# CHECK-NEXT: "/"
51+
# CHECK-NEXT: "/",
52+
# CHECK-NEXT: "*"
5253
# CHECK-NEXT: ]
5354
# CHECK-NEXT: },
5455
# CHECK-NEXT: "declarationProvider": true,

clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp

+30-1
Original file line numberDiff line numberDiff line change
@@ -3029,7 +3029,7 @@ TEST(CompletionTest, CompletionRange) {
30293029

30303030
// Sema doesn't trigger at all here, while the no-sema completion runs
30313031
// heuristics as normal and reports a range. It'd be nice to be consistent.
3032-
const char *NoCompletion = "/* [[]]^ */";
3032+
const char *NoCompletion = "/* foo [[]]^ */";
30333033
Completions = completions(NoCompletion);
30343034
EXPECT_EQ(Completions.CompletionRange, llvm::None);
30353035
Completions = completionsNoCompile(NoCompletion);
@@ -3279,6 +3279,35 @@ TEST(CompletionTest, PreambleCodeComplete) {
32793279
EXPECT_THAT(Result.Completions, Not(testing::IsEmpty()));
32803280
}
32813281

3282+
TEST(CompletionTest, CommentParamName) {
3283+
clangd::CodeCompleteOptions Opts;
3284+
const std::string Code = R"cpp(
3285+
void fun(int foo, int bar);
3286+
void overloaded(int param_int);
3287+
void overloaded(int param_int, int param_other);
3288+
void overloaded(char param_char);
3289+
int main() {
3290+
)cpp";
3291+
3292+
EXPECT_THAT(completions(Code + "fun(/*^", {}, Opts).Completions,
3293+
UnorderedElementsAre(Labeled("foo=")));
3294+
EXPECT_THAT(completions(Code + "fun(1, /*^", {}, Opts).Completions,
3295+
UnorderedElementsAre(Labeled("bar=")));
3296+
EXPECT_THAT(completions(Code + "/*^", {}, Opts).Completions, IsEmpty());
3297+
// Test de-duplication.
3298+
EXPECT_THAT(
3299+
completions(Code + "overloaded(/*^", {}, Opts).Completions,
3300+
UnorderedElementsAre(Labeled("param_int="), Labeled("param_char=")));
3301+
// Comment already has some text in it.
3302+
EXPECT_THAT(completions(Code + "fun(/* ^", {}, Opts).Completions,
3303+
UnorderedElementsAre(Labeled("foo=")));
3304+
EXPECT_THAT(completions(Code + "fun(/* f^", {}, Opts).Completions,
3305+
UnorderedElementsAre(Labeled("foo=")));
3306+
EXPECT_THAT(completions(Code + "fun(/* x^", {}, Opts).Completions, IsEmpty());
3307+
EXPECT_THAT(completions(Code + "fun(/* f ^", {}, Opts).Completions,
3308+
IsEmpty());
3309+
}
3310+
32823311
} // namespace
32833312
} // namespace clangd
32843313
} // namespace clang

0 commit comments

Comments
 (0)