Skip to content

[clangd] Add CodePatterns config option under Completion #137613

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
May 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions clang-tools-extra/clangd/ClangdServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
12 changes: 10 additions & 2 deletions clang-tools-extra/clangd/CodeComplete.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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())
Expand Down Expand Up @@ -2153,7 +2160,8 @@ class CodeCompleteFlow {

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
Expand Down
3 changes: 3 additions & 0 deletions clang-tools-extra/clangd/CodeComplete.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
7 changes: 7 additions & 0 deletions clang-tools-extra/clangd/Config.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Expand Down
11 changes: 11 additions & 0 deletions clang-tools-extra/clangd/ConfigCompile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
5 changes: 5 additions & 0 deletions clang-tools-extra/clangd/ConfigFragment.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
4 changes: 4 additions & 0 deletions clang-tools-extra/clangd/ConfigYAML.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,10 @@ class Parser {
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);
}

Expand Down
34 changes: 34 additions & 0 deletions clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
13 changes: 13 additions & 0 deletions clang-tools-extra/clangd/unittests/ConfigYAMLTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,19 @@ TEST(ParseYAML, AllScopesWarn) {
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(
Expand Down