diff --git a/clang-tools-extra/clangd/ParsedAST.h b/clang-tools-extra/clangd/ParsedAST.h --- a/clang-tools-extra/clangd/ParsedAST.h +++ b/clang-tools-extra/clangd/ParsedAST.h @@ -97,13 +97,16 @@ /// (!) does not have tokens from the preamble. const syntax::TokenBuffer &getTokens() const { return Tokens; } + llvm::ArrayRef getSkippedRanges() const { return SkippedRanges; } + private: ParsedAST(std::shared_ptr Preamble, std::unique_ptr Clang, std::unique_ptr Action, syntax::TokenBuffer Tokens, MainFileMacros Macros, std::vector LocalTopLevelDecls, std::vector Diags, IncludeStructure Includes, - CanonicalIncludes CanonIncludes); + CanonicalIncludes CanonIncludes, + std::vector SkippedRanges); // In-memory preambles must outlive the AST, it is important that this member // goes before Clang and Action. @@ -130,6 +133,8 @@ std::vector LocalTopLevelDecls; IncludeStructure Includes; CanonicalIncludes CanonIncludes; + // Ranges skipped during preprocessing. + std::vector SkippedRanges; }; /// Build an AST from provided user inputs. This function does not check if diff --git a/clang-tools-extra/clangd/ParsedAST.cpp b/clang-tools-extra/clangd/ParsedAST.cpp --- a/clang-tools-extra/clangd/ParsedAST.cpp +++ b/clang-tools-extra/clangd/ParsedAST.cpp @@ -208,6 +208,23 @@ const LangOptions &LangOpts; }; +class CollectSkippedRanges : public PPCallbacks { +public: + explicit CollectSkippedRanges(SourceManager &SM, + std::vector &SkippedRanges) + : SM(SM), SkippedRanges(SkippedRanges) {} + + void SourceRangeSkipped(SourceRange Range, SourceLocation EndifLoc) override { + if (isInsideMainFile(Range.getBegin(), SM)) { + SkippedRanges.push_back(Range); + } + } + +private: + SourceManager &SM; + std::vector &SkippedRanges; +}; + } // namespace void dumpAST(ParsedAST &AST, llvm::raw_ostream &OS) { @@ -348,6 +365,10 @@ Clang->getPreprocessor().addPPCallbacks( std::make_unique(Clang->getSourceManager(), Clang->getLangOpts(), Macros)); + std::vector SkippedRanges; + Clang->getPreprocessor().addPPCallbacks( + std::make_unique(Clang->getSourceManager(), + SkippedRanges)); // Copy over the includes from the preamble, then combine with the // non-preamble includes below. @@ -403,7 +424,7 @@ return ParsedAST(std::move(Preamble), std::move(Clang), std::move(Action), std::move(Tokens), std::move(Macros), std::move(ParsedDecls), std::move(Diags), std::move(Includes), - std::move(CanonIncludes)); + std::move(CanonIncludes), std::move(SkippedRanges)); } ParsedAST::ParsedAST(ParsedAST &&Other) = default; @@ -491,12 +512,14 @@ syntax::TokenBuffer Tokens, MainFileMacros Macros, std::vector LocalTopLevelDecls, std::vector Diags, IncludeStructure Includes, - CanonicalIncludes CanonIncludes) + CanonicalIncludes CanonIncludes, + std::vector SkippedRanges) : Preamble(std::move(Preamble)), Clang(std::move(Clang)), Action(std::move(Action)), Tokens(std::move(Tokens)), Macros(std::move(Macros)), Diags(std::move(Diags)), LocalTopLevelDecls(std::move(LocalTopLevelDecls)), - Includes(std::move(Includes)), CanonIncludes(std::move(CanonIncludes)) { + Includes(std::move(Includes)), CanonIncludes(std::move(CanonIncludes)), + SkippedRanges(std::move(SkippedRanges)) { assert(this->Clang); assert(this->Action); } diff --git a/clang-tools-extra/clangd/Protocol.h b/clang-tools-extra/clangd/Protocol.h --- a/clang-tools-extra/clangd/Protocol.h +++ b/clang-tools-extra/clangd/Protocol.h @@ -1209,6 +1209,9 @@ int Line = 0; /// The base64 encoded string of highlighting tokens. std::string Tokens; + /// Is the line in an inactive preprocessor branch? + /// This is a clangd extension. + bool IsInactive = false; }; bool operator==(const SemanticHighlightingInformation &Lhs, const SemanticHighlightingInformation &Rhs); diff --git a/clang-tools-extra/clangd/Protocol.cpp b/clang-tools-extra/clangd/Protocol.cpp --- a/clang-tools-extra/clangd/Protocol.cpp +++ b/clang-tools-extra/clangd/Protocol.cpp @@ -1063,7 +1063,8 @@ llvm::json::Value toJSON(const SemanticHighlightingInformation &Highlighting) { return llvm::json::Object{{"line", Highlighting.Line}, - {"tokens", Highlighting.Tokens}}; + {"tokens", Highlighting.Tokens}, + {"isInactive", Highlighting.IsInactive}}; } llvm::json::Value toJSON(const SemanticHighlightingParams &Highlighting) { 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 @@ -44,7 +44,11 @@ Primitive, Macro, - LastKind = Macro + // This one is different from the other kinds as it's a line style + // rather than a token style. + InactiveCode, + + LastKind = InactiveCode }; llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, HighlightingKind K); @@ -61,6 +65,7 @@ struct LineHighlightings { int Line; std::vector Tokens; + bool IsInactive; }; bool operator==(const LineHighlightings &L, const LineHighlightings &R); 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 @@ -158,6 +158,27 @@ // the end of the Tokens). TokRef = TokRef.drop_front(Conflicting.size()); } + // Add inactive highlighting tokens. + const SourceManager &SM = AST.getSourceManager(); + for (const SourceRange &R : AST.getSkippedRanges()) { + if (isInsideMainFile(R.getBegin(), SM)) { + // Create one token for each line in the skipped range, so it works + // with line-based diffing. + Position Begin = sourceLocToPosition(SM, R.getBegin()); + Position End = sourceLocToPosition(SM, R.getEnd()); + assert(Begin.line <= End.line); + for (int Line = Begin.line; Line < End.line; ++Line) { + // Don't bother computing the offset for the end of the line, just use + // zero. The client will treat this highlighting kind specially, and + // highlight the entire line visually (i.e. not just to where the text + // on the line ends, but to the end of the screen). + NonConflicting.push_back({HighlightingKind::InactiveCode, + {Position{Line, 0}, Position{Line, 0}}}); + } + } + } + // Re-sort the tokens because that's what the diffing expects. + llvm::sort(NonConflicting); return NonConflicting; } @@ -405,6 +426,8 @@ return OS << "Primitive"; case HighlightingKind::Macro: return OS << "Macro"; + case HighlightingKind::InactiveCode: + return OS << "InactiveCode"; } llvm_unreachable("invalid HighlightingKind"); } @@ -449,8 +472,24 @@ LineNumber = NextLineNumber()) { NewLine = takeLine(New, NewLine.end(), LineNumber); OldLine = takeLine(Old, OldLine.end(), LineNumber); - if (NewLine != OldLine) - DiffedLines.push_back({LineNumber, NewLine}); + if (NewLine != OldLine) { + DiffedLines.push_back({LineNumber, NewLine, /*IsInactive=*/false}); + + // Turn a HighlightingKind::InactiveCode token into the IsInactive flag. + // We do this by iterating over the tokens in the line, and if we find + // one with kind InactiveCode, removing that token and setting the + // IsInactive flag on the line instead. + auto &AddedLine = DiffedLines.back(); + for (auto Iter = AddedLine.Tokens.begin(); + Iter != AddedLine.Tokens.end();) { + if (Iter->Kind == HighlightingKind::InactiveCode) { + Iter = AddedLine.Tokens.erase(Iter); + AddedLine.IsInactive = true; + } else { + ++Iter; + } + } + } } return DiffedLines; @@ -493,7 +532,7 @@ write16be(static_cast(Token.Kind), OS); } - Lines.push_back({Line.Line, encodeBase64(LineByteTokens)}); + Lines.push_back({Line.Line, encodeBase64(LineByteTokens), Line.IsInactive}); } return Lines; @@ -538,6 +577,8 @@ return "storage.type.primitive.cpp"; case HighlightingKind::Macro: return "entity.name.function.preprocessor.cpp"; + case HighlightingKind::InactiveCode: + return "meta.disabled"; } llvm_unreachable("unhandled HighlightingKind"); } diff --git a/clang-tools-extra/clangd/test/semantic-highlighting.test b/clang-tools-extra/clangd/test/semantic-highlighting.test --- a/clang-tools-extra/clangd/test/semantic-highlighting.test +++ b/clang-tools-extra/clangd/test/semantic-highlighting.test @@ -57,6 +57,9 @@ # CHECK-NEXT: ], # CHECK-NEXT: [ # CHECK-NEXT: "entity.name.function.preprocessor.cpp" +# CHECK-NEXT: ], +# CHECK-NEXT: [ +# CHECK-NEXT: "meta.disabled" # CHECK-NEXT: ] # CHECK-NEXT: ] # CHECK-NEXT: }, @@ -66,6 +69,7 @@ # CHECK-NEXT: "params": { # CHECK-NEXT: "lines": [ # CHECK-NEXT: { +# CHECK-NEXT: "isInactive": false, # CHECK-NEXT: "line": 0, # CHECK-NEXT: "tokens": "AAAABAABAAA=" # CHECK-NEXT: } @@ -81,10 +85,12 @@ # CHECK-NEXT: "params": { # CHECK-NEXT: "lines": [ # CHECK-NEXT: { +# CHECK-NEXT: "isInactive": false, # CHECK-NEXT: "line": 0, # CHECK-NEXT: "tokens": "AAAABAABAAA=" # CHECK-NEXT: } # CHECK-NEXT: { +# CHECK-NEXT: "isInactive": false, # CHECK-NEXT: "line": 1, # CHECK-NEXT: "tokens": "AAAABAABAAA=" # CHECK-NEXT: } @@ -100,6 +106,7 @@ # CHECK-NEXT: "params": { # CHECK-NEXT: "lines": [ # CHECK-NEXT: { +# CHECK-NEXT: "isInactive": false, # CHECK-NEXT: "line": 1, # CHECK-NEXT: "tokens": "AAAABAABAAA=" # CHECK-NEXT: } @@ -115,6 +122,7 @@ # CHECK-NEXT: "params": { # CHECK-NEXT: "lines": [ # CHECK-NEXT: { +# CHECK-NEXT: "isInactive": false, # CHECK-NEXT: "line": 1, # CHECK-NEXT: "tokens": "" # CHECK-NEXT: } 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 @@ -140,7 +140,7 @@ } for (auto &LineTokens : ExpectedLines) ExpectedLinePairHighlighting.push_back( - {LineTokens.first, LineTokens.second}); + {LineTokens.first, LineTokens.second, /*IsInactive = */ false}); std::vector ActualDiffed = diffHighlightings(NewTokens, OldTokens); @@ -589,6 +589,33 @@ R"cpp( void $Function[[foo]](); using ::$Function[[foo]]; + )cpp", + // Inactive code highlighting + R"cpp( + // FIXME: In the preamble, no inactive code highlightings are produced. + #ifdef $Macro[[test]] + #endif + + // A declaration to cause the preamble to end. + int $Variable[[EndPreamble]]; + + // Code after the preamble. + // Inactive lines get an empty InactiveCode token at the beginning. + // Code inside inactive blocks does not get regular highlightings + // because it's not part of the AST. +$InactiveCode[[]] #ifdef $Macro[[test]] +$InactiveCode[[]] int Inactive1; +$InactiveCode[[]] #endif + + #ifndef $Macro[[test]] + int $Variable[[Active1]]; + #endif + +$InactiveCode[[]] #ifdef $Macro[[test]] +$InactiveCode[[]] int Inactive2; +$InactiveCode[[]] #else + int $Variable[[Active2]]; + #endif )cpp"}; for (const auto &TestCase : TestCases) { checkHighlightings(TestCase); @@ -656,10 +683,12 @@ {{HighlightingKind::Variable, Range{CreatePosition(3, 8), CreatePosition(3, 12)}}, {HighlightingKind::Function, - Range{CreatePosition(3, 4), CreatePosition(3, 7)}}}}, + Range{CreatePosition(3, 4), CreatePosition(3, 7)}}}, + /* IsInactive = */ false}, {1, {{HighlightingKind::Variable, - Range{CreatePosition(1, 1), CreatePosition(1, 5)}}}}}; + Range{CreatePosition(1, 1), CreatePosition(1, 5)}}}, + /* IsInactive = */ true}}; std::vector ActualResults = toSemanticHighlightingInformation(Tokens); std::vector ExpectedResults = {