[clangd] Add CodePatterns config option under Completion (#137613)

Allows enabling/disabling code pattern & snippet suggestions
during code completion.

Resolves https://github.com/clangd/clangd/discussions/1867
This commit is contained in:
Noustaa 2025-05-12 20:22:27 +02:00 committed by GitHub
parent a6c4ca8e93
commit bfd2ef7659
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 88 additions and 2 deletions

View File

@ -457,6 +457,7 @@ void ClangdServer::codeComplete(PathRef File, Position Pos,
CodeCompleteOpts.ArgumentLists = Config::current().Completion.ArgumentLists;
CodeCompleteOpts.InsertIncludes =
Config::current().Completion.HeaderInsertion;
CodeCompleteOpts.CodePatterns = Config::current().Completion.CodePatterns;
// FIXME(ibiryukov): even if Preamble is non-null, we may want to check
// both the old and the new version in case only one of them matches.
CodeCompleteResult Result = clangd::codeComplete(

View File

@ -926,7 +926,8 @@ struct CompletionRecorder : public CodeCompleteConsumer {
// FIXME: in case there is no future sema completion callback after the
// recovery mode, we might still want to provide some results (e.g. trivial
// identifier-based completion).
if (Context.getKind() == CodeCompletionContext::CCC_Recovery) {
CodeCompletionContext::Kind ContextKind = Context.getKind();
if (ContextKind == CodeCompletionContext::CCC_Recovery) {
log("Code complete: Ignoring sema code complete callback with Recovery "
"context.");
return;
@ -950,6 +951,12 @@ struct CompletionRecorder : public CodeCompleteConsumer {
// Retain the results we might want.
for (unsigned I = 0; I < NumResults; ++I) {
auto &Result = InResults[I];
if (Config::current().Completion.CodePatterns ==
Config::CodePatternsPolicy::None &&
Result.Kind == CodeCompletionResult::RK_Pattern &&
// keep allowing the include files autocomplete suggestions
ContextKind != CodeCompletionContext::CCC_IncludedFile)
continue;
// Class members that are shadowed by subclasses are usually noise.
if (Result.Hidden && Result.Declaration &&
Result.Declaration->isCXXClassMember())
@ -2153,7 +2160,8 @@ private:
clang::CodeCompleteOptions CodeCompleteOptions::getClangCompleteOpts() const {
clang::CodeCompleteOptions Result;
Result.IncludeCodePatterns = EnableSnippets;
Result.IncludeCodePatterns =
EnableSnippets && (CodePatterns != Config::CodePatternsPolicy::None);
Result.IncludeMacros = true;
Result.IncludeGlobals = true;
// We choose to include full comments and not do doxygen parsing in

View File

@ -111,6 +111,9 @@ struct CodeCompleteOptions {
Config::ArgumentListsPolicy ArgumentLists =
Config::ArgumentListsPolicy::FullPlaceholders;
/// Whether to suggest code patterns & snippets or not in completion
Config::CodePatternsPolicy CodePatterns = Config::CodePatternsPolicy::All;
/// Whether to use the clang parser, or fallback to text-based completion
/// (using identifiers in the current file and symbol indexes).
enum CodeCompletionParse {

View File

@ -152,6 +152,11 @@ struct Config {
NeverInsert // Never insert headers as part of code completion
};
enum class CodePatternsPolicy {
All, // Suggest all code patterns and snippets
None // Suggest none of the code patterns and snippets
};
/// Configures code completion feature.
struct {
/// Whether code completion includes results that are not visible in current
@ -161,6 +166,8 @@ struct Config {
ArgumentListsPolicy ArgumentLists = ArgumentListsPolicy::FullPlaceholders;
/// Controls if headers should be inserted when completions are accepted
HeaderInsertionPolicy HeaderInsertion = HeaderInsertionPolicy::IWYU;
/// Enables code patterns & snippets suggestions
CodePatternsPolicy CodePatterns = CodePatternsPolicy::All;
} Completion;
/// Configures hover feature.

View File

@ -707,6 +707,17 @@ struct FragmentCompiler {
C.Completion.HeaderInsertion = *Val;
});
}
if (F.CodePatterns) {
if (auto Val = compileEnum<Config::CodePatternsPolicy>("CodePatterns",
*F.CodePatterns)
.map("All", Config::CodePatternsPolicy::All)
.map("None", Config::CodePatternsPolicy::None)
.value())
Out.Apply.push_back([Val](const Params &, Config &C) {
C.Completion.CodePatterns = *Val;
});
}
}
void compile(Fragment::HoverBlock &&F) {

View File

@ -349,6 +349,11 @@ struct Fragment {
/// symbol is forward-declared
/// "Never": Never insert headers
std::optional<Located<std::string>> HeaderInsertion;
/// Will suggest code patterns & snippets.
/// Values are Config::CodePatternsPolicy:
/// All => enable all code patterns and snippets suggestion
/// None => disable all code patterns and snippets suggestion
std::optional<Located<std::string>> CodePatterns;
};
CompletionBlock Completion;

View File

@ -249,6 +249,10 @@ private:
if (auto HeaderInsertion = scalarValue(N, "HeaderInsertion"))
F.HeaderInsertion = *HeaderInsertion;
});
Dict.handle("CodePatterns", [&](Node &N) {
if (auto CodePatterns = scalarValue(N, "CodePatterns"))
F.CodePatterns = *CodePatterns;
});
Dict.parse(N);
}

View File

@ -3326,6 +3326,40 @@ TEST(CompletionTest, AllScopesCompletion) {
kind(CompletionItemKind::EnumMember))));
}
TEST(CompletionTest, NoCodePatternsIfDisabled) {
clangd::CodeCompleteOptions Opts = {};
Opts.EnableSnippets = true;
Opts.CodePatterns = Config::CodePatternsPolicy::None;
auto Results = completions(R"cpp(
void function() {
/// Trying to trigger "for (init-statement; condition; inc-expression)
/// {statements}~" code pattern
for^
}
)cpp",
{}, Opts);
EXPECT_THAT(Results.Completions,
Not(Contains(kind(CompletionItemKind::Snippet))));
}
TEST(CompletionTest, CompleteIncludeIfCodePatternsNone) {
clangd::CodeCompleteOptions Opts = {};
Opts.EnableSnippets = true;
Opts.CodePatterns = Config::CodePatternsPolicy::None;
Annotations Test(R"cpp(#include "^)cpp");
auto TU = TestTU::withCode(Test.code());
TU.AdditionalFiles["foo/bar.h"] = "";
TU.ExtraArgs.push_back("-I" + testPath("foo"));
auto Results = completions(TU, Test.point(), {}, Opts);
EXPECT_THAT(Results.Completions,
AllOf(has("foo/", CompletionItemKind::Folder),
has("bar.h\"", CompletionItemKind::File)));
}
TEST(CompletionTest, NoQualifierIfShadowed) {
clangd::CodeCompleteOptions Opts = {};
Opts.AllScopes = true;

View File

@ -217,6 +217,19 @@ Completion:
EXPECT_THAT(Results[0].Completion.AllScopes, testing::Eq(std::nullopt));
}
TEST(ParseYAML, CodePatterns) {
CapturedDiags Diags;
Annotations YAML(R"yaml(
Completion:
CodePatterns: None
)yaml");
auto Results =
Fragment::parseYAML(YAML.code(), "config.yaml", Diags.callback());
ASSERT_THAT(Diags.Diagnostics, IsEmpty());
ASSERT_EQ(Results.size(), 1u);
EXPECT_THAT(Results[0].Completion.CodePatterns, llvm::ValueIs(val("None")));
}
TEST(ParseYAML, ShowAKA) {
CapturedDiags Diags;
Annotations YAML(R"yaml(