diff --git a/clang-tools-extra/clangd/SemanticSelection.cpp b/clang-tools-extra/clangd/SemanticSelection.cpp --- a/clang-tools-extra/clangd/SemanticSelection.cpp +++ b/clang-tools-extra/clangd/SemanticSelection.cpp @@ -5,6 +5,7 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// + #include "SemanticSelection.h" #include "FindSymbols.h" #include "ParsedAST.h" @@ -13,8 +14,10 @@ #include "SourceCode.h" #include "clang/AST/DeclBase.h" #include "clang/Basic/SourceLocation.h" +#include "clang/Basic/TokenKinds.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/Support/Error.h" +#include namespace clang { namespace clangd { @@ -41,6 +44,33 @@ collectFoldingRanges(Child, Result); } +void collectPreprocessorFoldingRanges(SourceManager &SM, + std::vector &Result) { + const syntax::TokenBuffer Buffer(SM); + const auto Tokens = Buffer.spelledTokens(SM.getMainFileID()); + std::stack FoldingRangeBeginnings; + for (size_t I = 1; I < Tokens.size(); ++I) { + const auto TokenText = Tokens[I].text(SM).str(); + if (tok::getPPKeywordFromSpelling(TokenText) == tok::PPKeywordKind::pp_if && + +Tokens[I - 1].kind() == tok::TokenKind::hash) { + FoldingRangeBeginnings.push(Tokens[I - 1].location()); + } + if (tok::getPPKeywordFromSpelling(TokenText) == + tok::PPKeywordKind::pp_endif && + +Tokens[I - 1].kind() == tok::TokenKind::hash) { + auto StartLoc = FoldingRangeBeginnings.top(); + FoldingRangeBeginnings.pop(); + auto EndLoc = Tokens[I].endLocation(); + FoldingRange NextRange; + NextRange.startLine = SM.getSpellingLineNumber(StartLoc); + NextRange.startCharacter = SM.getSpellingColumnNumber(StartLoc); + NextRange.endLine = SM.getSpellingLineNumber(EndLoc); + NextRange.endCharacter = SM.getSpellingColumnNumber(EndLoc); + Result.push_back(NextRange); + } + } +} + } // namespace llvm::Expected getSemanticRanges(ParsedAST &AST, Position Pos) { @@ -113,6 +143,7 @@ std::vector Result; for (const auto &Symbol : *DocumentSymbols) collectFoldingRanges(Symbol, Result); + collectPreprocessorFoldingRanges(AST.getSourceManager(), Result); return Result; } diff --git a/clang-tools-extra/clangd/unittests/SemanticSelectionTests.cpp b/clang-tools-extra/clangd/unittests/SemanticSelectionTests.cpp --- a/clang-tools-extra/clangd/unittests/SemanticSelectionTests.cpp +++ b/clang-tools-extra/clangd/unittests/SemanticSelectionTests.cpp @@ -234,6 +234,23 @@ } } +TEST(FoldingRanges, Preprocessor) { + const char *Tests[] = { + R"cpp( + [[#if true + void foo(); + #endif]] + )cpp", + }; + for (const char *Test : Tests) { + auto T = Annotations(Test); + auto AST = TestTU::withCode(T.code()).build(); + EXPECT_THAT(gatherFoldingRanges(llvm::cantFail(getFoldingRanges(AST))), + UnorderedElementsAreArray(T.ranges())) + << Test; + } +} + } // namespace } // namespace clangd } // namespace clang diff --git a/clang/include/clang/Basic/TokenKinds.h b/clang/include/clang/Basic/TokenKinds.h --- a/clang/include/clang/Basic/TokenKinds.h +++ b/clang/include/clang/Basic/TokenKinds.h @@ -96,6 +96,9 @@ /// Return true if this is an annotation token representing a pragma. bool isPragmaAnnotation(TokenKind K); +// NOTE: \p Spelling has to be null-terminated C string. +PPKeywordKind getPPKeywordFromSpelling(llvm::StringRef Spelling); + } // end namespace tok } // end namespace clang diff --git a/clang/lib/Basic/IdentifierTable.cpp b/clang/lib/Basic/IdentifierTable.cpp --- a/clang/lib/Basic/IdentifierTable.cpp +++ b/clang/lib/Basic/IdentifierTable.cpp @@ -271,54 +271,7 @@ } tok::PPKeywordKind IdentifierInfo::getPPKeywordID() const { - // We use a perfect hash function here involving the length of the keyword, - // the first and third character. For preprocessor ID's there are no - // collisions (if there were, the switch below would complain about duplicate - // case values). Note that this depends on 'if' being null terminated. - -#define HASH(LEN, FIRST, THIRD) \ - (LEN << 5) + (((FIRST-'a') + (THIRD-'a')) & 31) -#define CASE(LEN, FIRST, THIRD, NAME) \ - case HASH(LEN, FIRST, THIRD): \ - return memcmp(Name, #NAME, LEN) ? tok::pp_not_keyword : tok::pp_ ## NAME - - unsigned Len = getLength(); - if (Len < 2) return tok::pp_not_keyword; - const char *Name = getNameStart(); - switch (HASH(Len, Name[0], Name[2])) { - default: return tok::pp_not_keyword; - CASE( 2, 'i', '\0', if); - CASE( 4, 'e', 'i', elif); - CASE( 4, 'e', 's', else); - CASE( 4, 'l', 'n', line); - CASE( 4, 's', 'c', sccs); - CASE( 5, 'e', 'd', endif); - CASE( 5, 'e', 'r', error); - CASE( 5, 'i', 'e', ident); - CASE( 5, 'i', 'd', ifdef); - CASE( 5, 'u', 'd', undef); - - CASE( 6, 'a', 's', assert); - CASE( 6, 'd', 'f', define); - CASE( 6, 'i', 'n', ifndef); - CASE( 6, 'i', 'p', import); - CASE( 6, 'p', 'a', pragma); - - CASE( 7, 'd', 'f', defined); - CASE( 7, 'i', 'c', include); - CASE( 7, 'w', 'r', warning); - - CASE( 8, 'u', 'a', unassert); - CASE(12, 'i', 'c', include_next); - - CASE(14, '_', 'p', __public_macro); - - CASE(15, '_', 'p', __private_macro); - - CASE(16, '_', 'i', __include_macros); -#undef CASE -#undef HASH - } + return tok::getPPKeywordFromSpelling(getNameStart()); } //===----------------------------------------------------------------------===// diff --git a/clang/lib/Basic/TokenKinds.cpp b/clang/lib/Basic/TokenKinds.cpp --- a/clang/lib/Basic/TokenKinds.cpp +++ b/clang/lib/Basic/TokenKinds.cpp @@ -65,3 +65,58 @@ } return false; } + +tok::PPKeywordKind tok::getPPKeywordFromSpelling(llvm::StringRef Spelling) { + // We use a perfect hash function here involving the length of the keyword, + // the first and third character. For preprocessor ID's there are no + // collisions (if there were, the switch below would complain about duplicate + // case values). Note that this depends on 'if' being null terminated. + unsigned Len = Spelling.size(); + assert((*(Spelling.begin() + Len) == '\0') && + "Spelling has to be null-terminated string."); + +#define HASH(LEN, FIRST, THIRD) \ + (LEN << 5) + (((FIRST - 'a') + (THIRD - 'a')) & 31) +#define CASE(LEN, FIRST, THIRD, NAME) \ + case HASH(LEN, FIRST, THIRD): \ + return memcmp(Spelling.begin(), #NAME, LEN) ? tok::pp_not_keyword \ + : tok::pp_##NAME + + if (Len < 2) + return tok::pp_not_keyword; + switch (HASH(Len, Spelling[0], *(Spelling.begin() + 2))) { + default: + return tok::pp_not_keyword; + CASE(2, 'i', '\0', if); + CASE(4, 'e', 'i', elif); + CASE(4, 'e', 's', else); + CASE(4, 'l', 'n', line); + CASE(4, 's', 'c', sccs); + CASE(5, 'e', 'd', endif); + CASE(5, 'e', 'r', error); + CASE(5, 'i', 'e', ident); + CASE(5, 'i', 'd', ifdef); + CASE(5, 'u', 'd', undef); + + CASE(6, 'a', 's', assert); + CASE(6, 'd', 'f', define); + CASE(6, 'i', 'n', ifndef); + CASE(6, 'i', 'p', import); + CASE(6, 'p', 'a', pragma); + + CASE(7, 'd', 'f', defined); + CASE(7, 'i', 'c', include); + CASE(7, 'w', 'r', warning); + + CASE(8, 'u', 'a', unassert); + CASE(12, 'i', 'c', include_next); + + CASE(14, '_', 'p', __public_macro); + + CASE(15, '_', 'p', __private_macro); + + CASE(16, '_', 'i', __include_macros); +#undef CASE +#undef HASH + } +}