diff --git a/clang-tools-extra/clangd/Config.h b/clang-tools-extra/clangd/Config.h --- a/clang-tools-extra/clangd/Config.h +++ b/clang-tools-extra/clangd/Config.h @@ -148,6 +148,13 @@ bool DeducedTypes = true; bool Designators = true; } InlayHints; + + struct { + /// Controls highlighting kinds that are disabled. + std::vector DisabledKinds; + /// Controls highlighting modifiers that are disabled. + std::vector DisabledModifiers; + } SemanticTokens; }; } // namespace clangd diff --git a/clang-tools-extra/clangd/ConfigCompile.cpp b/clang-tools-extra/clangd/ConfigCompile.cpp --- a/clang-tools-extra/clangd/ConfigCompile.cpp +++ b/clang-tools-extra/clangd/ConfigCompile.cpp @@ -196,6 +196,7 @@ compile(std::move(F.Completion)); compile(std::move(F.Hover)); compile(std::move(F.InlayHints)); + compile(std::move(F.SemanticTokens)); compile(std::move(F.Style)); } @@ -613,6 +614,37 @@ }); } + void compile(Fragment::SemanticTokensBlock &&F) { + if (!F.DisabledKinds.empty()) { + std::vector DisabledKinds; + for (auto &Kind : F.DisabledKinds) + DisabledKinds.push_back(std::move(*Kind)); + + Out.Apply.push_back( + [DisabledKinds(std::move(DisabledKinds))](const Params &, Config &C) { + for (auto &Kind : DisabledKinds) { + auto It = llvm::find(C.SemanticTokens.DisabledKinds, Kind); + if (It == C.SemanticTokens.DisabledKinds.end()) + C.SemanticTokens.DisabledKinds.push_back(std::move(Kind)); + } + }); + } + if (!F.DisabledModifiers.empty()) { + std::vector DisabledModifiers; + for (auto &Kind : F.DisabledModifiers) + DisabledModifiers.push_back(std::move(*Kind)); + + Out.Apply.push_back([DisabledModifiers(std::move(DisabledModifiers))]( + const Params &, Config &C) { + for (auto &Kind : DisabledModifiers) { + auto It = llvm::find(C.SemanticTokens.DisabledModifiers, Kind); + if (It == C.SemanticTokens.DisabledModifiers.end()) + C.SemanticTokens.DisabledModifiers.push_back(std::move(Kind)); + } + }); + } + } + constexpr static llvm::SourceMgr::DiagKind Error = llvm::SourceMgr::DK_Error; constexpr static llvm::SourceMgr::DiagKind Warning = llvm::SourceMgr::DK_Warning; diff --git a/clang-tools-extra/clangd/ConfigFragment.h b/clang-tools-extra/clangd/ConfigFragment.h --- a/clang-tools-extra/clangd/ConfigFragment.h +++ b/clang-tools-extra/clangd/ConfigFragment.h @@ -235,7 +235,6 @@ /// - None std::optional> UnusedIncludes; - /// Enable emitting diagnostics using stale preambles. std::optional> AllowStalePreamble; @@ -324,6 +323,15 @@ std::optional> Designators; }; InlayHintsBlock InlayHints; + + /// Configures semantic tokens that are produced by clangd. + struct SemanticTokensBlock { + /// Disables clangd to produce semantic tokens for the given kinds. + std::vector> DisabledKinds; + /// Disables clangd to assign semantic tokens with the given modifiers. + std::vector> DisabledModifiers; + }; + SemanticTokensBlock SemanticTokens; }; } // namespace config diff --git a/clang-tools-extra/clangd/ConfigYAML.cpp b/clang-tools-extra/clangd/ConfigYAML.cpp --- a/clang-tools-extra/clangd/ConfigYAML.cpp +++ b/clang-tools-extra/clangd/ConfigYAML.cpp @@ -68,6 +68,7 @@ Dict.handle("Completion", [&](Node &N) { parse(F.Completion, N); }); Dict.handle("Hover", [&](Node &N) { parse(F.Hover, N); }); Dict.handle("InlayHints", [&](Node &N) { parse(F.InlayHints, N); }); + Dict.handle("SemanticTokens", [&](Node &N) { parse(F.SemanticTokens, N); }); Dict.parse(N); return !(N.failed() || HadError); } @@ -257,6 +258,19 @@ Dict.parse(N); } + void parse(Fragment::SemanticTokensBlock &F, Node &N) { + DictParser Dict("SemanticTokens", this); + Dict.handle("DisabledKinds", [&](Node &N) { + if (auto Values = scalarValues(N)) + F.DisabledKinds = std::move(*Values); + }); + Dict.handle("DisabledModifiers", [&](Node &N) { + if (auto Values = scalarValues(N)) + F.DisabledModifiers = std::move(*Values); + }); + Dict.parse(N); + } + // Helper for parsing mapping nodes (dictionaries). // We don't use YamlIO as we want to control over unknown keys. class DictParser { diff --git a/clang-tools-extra/clangd/SemanticHighlighting.h b/clang-tools-extra/clangd/SemanticHighlighting.h --- a/clang-tools-extra/clangd/SemanticHighlighting.h +++ b/clang-tools-extra/clangd/SemanticHighlighting.h @@ -61,6 +61,8 @@ }; llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, HighlightingKind K); +std::optional +highlightingKindFromString(llvm::StringRef Name); enum class HighlightingModifier { Declaration, @@ -88,6 +90,8 @@ static_assert(static_cast(HighlightingModifier::LastModifier) < 32, "Increase width of modifiers bitfield!"); llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, HighlightingModifier K); +std::optional +highlightingModifierFromString(llvm::StringRef Name); // Contains all information needed for the highlighting a token. struct HighlightingToken { diff --git a/clang-tools-extra/clangd/SemanticHighlighting.cpp b/clang-tools-extra/clangd/SemanticHighlighting.cpp --- a/clang-tools-extra/clangd/SemanticHighlighting.cpp +++ b/clang-tools-extra/clangd/SemanticHighlighting.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "SemanticHighlighting.h" +#include "Config.h" #include "FindTarget.h" #include "HeuristicResolver.h" #include "ParsedAST.h" @@ -354,12 +355,58 @@ return Winner; } +/// Filter to remove particular kinds of highlighting tokens and modifiers from +/// the output. +class HighlightingFilter { +public: + HighlightingFilter() { + for (auto &Active : ActiveKindLookup) + Active = true; + + ActiveModifiersMask = ~0; + } + + void disableKind(HighlightingKind Kind) { + ActiveKindLookup[static_cast(Kind)] = false; + } + + void disableModifier(HighlightingModifier Modifier) { + ActiveModifiersMask &= ~(1 << static_cast(Modifier)); + } + + bool isHighlightKindActive(HighlightingKind Kind) const { + return ActiveKindLookup[static_cast(Kind)]; + } + + uint32_t maskModifiers(uint32_t Modifiers) const { + return Modifiers & ActiveModifiersMask; + } + + static HighlightingFilter fromCurrentConfig() { + const Config &C = Config::current(); + HighlightingFilter Filter; + for (const auto &Kind : C.SemanticTokens.DisabledKinds) + if (auto K = highlightingKindFromString(Kind)) + Filter.disableKind(*K); + for (const auto &Modifier : C.SemanticTokens.DisabledModifiers) + if (auto M = highlightingModifierFromString(Modifier)) + Filter.disableModifier(*M); + + return Filter; + } + +private: + bool ActiveKindLookup[static_cast(HighlightingKind::LastKind) + 1]; + uint32_t ActiveModifiersMask; +}; + /// Consumes source locations and maps them to text ranges for highlightings. class HighlightingsBuilder { public: - HighlightingsBuilder(const ParsedAST &AST, bool IncludeInactiveRegionTokens) + HighlightingsBuilder(const ParsedAST &AST, HighlightingFilter Filter, + bool IncludeInactiveRegionTokens) : TB(AST.getTokens()), SourceMgr(AST.getSourceManager()), - LangOpts(AST.getLangOpts()), + LangOpts(AST.getLangOpts()), Filter(Filter), IncludeInactiveRegionTokens(IncludeInactiveRegionTokens) {} HighlightingToken &addToken(SourceLocation Loc, HighlightingKind Kind) { @@ -412,6 +459,9 @@ } HighlightingToken &addToken(Range R, HighlightingKind Kind) { + if (!Filter.isHighlightKindActive(Kind)) + return InvalidHighlightingToken; + HighlightingToken HT; HT.R = std::move(R); HT.Kind = Kind; @@ -452,6 +502,7 @@ } } + Resolved->Modifiers = Filter.maskModifiers(Resolved->Modifiers); NonConflicting.push_back(*Resolved); } // TokRef[Conflicting.size()] is the next token with a different range (or @@ -459,7 +510,8 @@ TokRef = TokRef.drop_front(Conflicting.size()); } - if (!IncludeInactiveRegionTokens) + if (!IncludeInactiveRegionTokens || + !Filter.isHighlightKindActive(HighlightingKind::InactiveCode)) return NonConflicting; const auto &SM = AST.getSourceManager(); @@ -535,6 +587,7 @@ const syntax::TokenBuffer &TB; const SourceManager &SourceMgr; const LangOptions &LangOpts; + HighlightingFilter Filter; bool IncludeInactiveRegionTokens; std::vector Tokens; std::map> ExtraModifiers; @@ -1105,7 +1158,8 @@ getSemanticHighlightings(ParsedAST &AST, bool IncludeInactiveRegionTokens) { auto &C = AST.getASTContext(); // Add highlightings for AST nodes. - HighlightingsBuilder Builder(AST, IncludeInactiveRegionTokens); + HighlightingsBuilder Builder(AST, HighlightingFilter::fromCurrentConfig(), + IncludeInactiveRegionTokens); // Highlight 'decltype' and 'auto' as their underlying types. CollectExtraHighlightings(Builder).TraverseAST(C); // Highlight all decls and references coming from the AST. @@ -1224,6 +1278,38 @@ } llvm_unreachable("invalid HighlightingKind"); } +std::optional +highlightingKindFromString(llvm::StringRef Name) { + static llvm::StringMap Lookup = { + {"Variable", HighlightingKind::Variable}, + {"LocalVariable", HighlightingKind::LocalVariable}, + {"Parameter", HighlightingKind::Parameter}, + {"Function", HighlightingKind::Function}, + {"Method", HighlightingKind::Method}, + {"StaticMethod", HighlightingKind::StaticMethod}, + {"Field", HighlightingKind::Field}, + {"StaticField", HighlightingKind::StaticField}, + {"Class", HighlightingKind::Class}, + {"Interface", HighlightingKind::Interface}, + {"Enum", HighlightingKind::Enum}, + {"EnumConstant", HighlightingKind::EnumConstant}, + {"Typedef", HighlightingKind::Typedef}, + {"Type", HighlightingKind::Type}, + {"Unknown", HighlightingKind::Unknown}, + {"Namespace", HighlightingKind::Namespace}, + {"TemplateParameter", HighlightingKind::TemplateParameter}, + {"Concept", HighlightingKind::Concept}, + {"Primitive", HighlightingKind::Primitive}, + {"Macro", HighlightingKind::Macro}, + {"Modifier", HighlightingKind::Modifier}, + {"Operator", HighlightingKind::Operator}, + {"Bracket", HighlightingKind::Bracket}, + {"InactiveCode", HighlightingKind::InactiveCode}, + }; + + auto It = Lookup.find(Name); + return It != Lookup.end() ? std::make_optional(It->getValue()) : std::nullopt; +} llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, HighlightingModifier K) { switch (K) { case HighlightingModifier::Declaration: @@ -1236,6 +1322,33 @@ return OS << toSemanticTokenModifier(K); } } +std::optional +highlightingModifierFromString(llvm::StringRef Name) { + static llvm::StringMap Lookup = { + {"Declaration", HighlightingModifier::Declaration}, + {"Definition", HighlightingModifier::Definition}, + {"Deprecated", HighlightingModifier::Deprecated}, + {"Deduced", HighlightingModifier::Deduced}, + {"Readonly", HighlightingModifier::Readonly}, + {"Static", HighlightingModifier::Static}, + {"Abstract", HighlightingModifier::Abstract}, + {"Virtual", HighlightingModifier::Virtual}, + {"DependentName", HighlightingModifier::DependentName}, + {"DefaultLibrary", HighlightingModifier::DefaultLibrary}, + {"UsedAsMutableReference", HighlightingModifier::UsedAsMutableReference}, + {"UsedAsMutablePointer", HighlightingModifier::UsedAsMutablePointer}, + {"ConstructorOrDestructor", + HighlightingModifier::ConstructorOrDestructor}, + {"UserDefined", HighlightingModifier::UserDefined}, + {"FunctionScope", HighlightingModifier::FunctionScope}, + {"ClassScope", HighlightingModifier::ClassScope}, + {"FileScope", HighlightingModifier::FileScope}, + {"GlobalScope", HighlightingModifier::GlobalScope}, + }; + + auto It = Lookup.find(Name); + return It != Lookup.end() ? std::make_optional(It->getValue()) : std::nullopt; +} bool operator==(const HighlightingToken &L, const HighlightingToken &R) { return std::tie(L.R, L.Kind, L.Modifiers) == diff --git a/clang-tools-extra/clangd/unittests/ConfigYAMLTests.cpp b/clang-tools-extra/clangd/unittests/ConfigYAMLTests.cpp --- a/clang-tools-extra/clangd/unittests/ConfigYAMLTests.cpp +++ b/clang-tools-extra/clangd/unittests/ConfigYAMLTests.cpp @@ -246,6 +246,23 @@ EXPECT_EQ(Results[0].InlayHints.DeducedTypes, std::nullopt); } +TEST(ParseYAML, SemanticTokens) { + CapturedDiags Diags; + Annotations YAML(R"yaml( +SemanticTokens: + DisabledKinds: [ Operator, InactiveCode] + DisabledModifiers: Readonly + )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].SemanticTokens.DisabledKinds, + ElementsAre(val("Operator"), val("InactiveCode"))); + EXPECT_THAT(Results[0].SemanticTokens.DisabledModifiers, + ElementsAre(val("Readonly"))); +} + TEST(ParseYAML, IncludesIgnoreHeader) { CapturedDiags Diags; Annotations YAML(R"yaml( diff --git a/clang-tools-extra/clangd/unittests/SemanticHighlightingTests.cpp b/clang-tools-extra/clangd/unittests/SemanticHighlightingTests.cpp --- a/clang-tools-extra/clangd/unittests/SemanticHighlightingTests.cpp +++ b/clang-tools-extra/clangd/unittests/SemanticHighlightingTests.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "Annotations.h" +#include "Config.h" #include "Protocol.h" #include "SemanticHighlighting.h" #include "SourceCode.h" @@ -1260,6 +1261,17 @@ EXPECT_EQ(Toks[3].deltaStart, 2u); EXPECT_EQ(Toks[3].length, 3u); } + +TEST(SemanticHighlighting, WithHighlightingFilter) { + llvm::StringRef AnnotatedCode = R"cpp( +int *$Variable[[x]] = new int; +)cpp"; + Config Cfg; + Cfg.SemanticTokens.DisabledKinds = {"Operator"}; + Cfg.SemanticTokens.DisabledModifiers = {"Declaration", "Definition"}; + WithContextValue WithCfg(Config::Key, std::move(Cfg)); + checkHighlightings(AnnotatedCode, {}, ~ScopeModifierMask); +} } // namespace } // namespace clangd } // namespace clang