diff --git a/clang-tools-extra/clangd/CMakeLists.txt b/clang-tools-extra/clangd/CMakeLists.txt --- a/clang-tools-extra/clangd/CMakeLists.txt +++ b/clang-tools-extra/clangd/CMakeLists.txt @@ -62,6 +62,7 @@ Quality.cpp RIFF.cpp Selection.cpp + SemanticHighlight.cpp SourceCode.cpp Threading.cpp Trace.cpp diff --git a/clang-tools-extra/clangd/SemanticHighlight.h b/clang-tools-extra/clangd/SemanticHighlight.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clangd/SemanticHighlight.h @@ -0,0 +1,46 @@ +//==-- SemanticHighlight.h - Generating highlights from the AST--*- C++ -*-==// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// Code for collecting semantic symbols from the C++ AST using the +// RecursiveASTVisitor +// +//===----------------------------------------------------------------------===// +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_SEMANTICHIGHLIGHT_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_SEMANTICHIGHLIGHT_H + +#include "ClangdUnit.h" +#include "Protocol.h" + +namespace clang { +namespace clangd { + +enum class SemanticHighlightKind : int { + Variable = 0, + Function = 1, +}; + +// Contains all information needed for the highlighting a symbol. +struct SemanticToken { + SemanticToken() = default; + SemanticToken(SemanticHighlightKind Kind, Range R) : Kind(Kind), R(R) {} + SemanticHighlightKind Kind; + Range R; +}; + +bool operator==(const SemanticToken &Lhs, const SemanticToken &Rhs); +bool operator!=(const SemanticToken &Lhs, const SemanticToken &Rhs); + +// Returns semantic highlights for the AST. The vector is ordered in ascending +// order by the line number. Every symbol in LineHighlight is ordered in +// ascending order by their coumn number. +std::vector getSemanticHighlights(ParsedAST &AST); + +} // namespace clangd +} // namespace clang + +#endif diff --git a/clang-tools-extra/clangd/SemanticHighlight.cpp b/clang-tools-extra/clangd/SemanticHighlight.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clangd/SemanticHighlight.cpp @@ -0,0 +1,79 @@ +#include "SemanticHighlight.h" +#include "SourceCode.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/Lex/Lexer.h" + +namespace clang { +namespace clangd { +namespace { + +// Collects all semantic symbols in an ASTContext. Symbols on line i are always +// in front of symbols on line i+1 +class SemanticSymbolASTCollector + : public RecursiveASTVisitor { + std::vector Symbols; + const ASTContext * + const SourceManager &SM; + +public: + SemanticSymbolASTCollector(const ASTContext &AST) + : AST(AST), SM(AST.getSourceManager()) {} + + std::vector getSymbols() { return Symbols; } + + bool VisitVarDecl(VarDecl *Var) { + addSymbol(Var, SemanticHighlightKind::Variable); + return true; + } + + bool VisitFunctionDecl(FunctionDecl *Func) { + addSymbol(Func, SemanticHighlightKind::Function); + return true; + } + +private: + void addSymbol(Decl *D, SemanticHighlightKind Kind) { + unsigned int Len = clang::Lexer::MeasureTokenLength(D->getLocation(), SM, + AST.getLangOpts()); + if (Len == 0) { + // Don't add symbols that don't have any length. + return; + } + + if (D->getLocation().isMacroID()) { + // FIXME; This is inside a macro. For now skip the token + return; + } + + Position LSPLoc = sourceLocToPosition(SM, D->getLocation()); + + SemanticToken S; + S.R.start.character = LSPLoc.character; + S.R.start.line = LSPLoc.line; + S.R.end.character = LSPLoc.character + Len; + S.R.end.line = LSPLoc.line; + S.Kind = Kind; + + Symbols.push_back(S); + } +}; + +} // namespace + +bool operator==(const SemanticToken &Lhs, const SemanticToken &Rhs) { + return Lhs.Kind == Rhs.Kind && Lhs.R == Rhs.R; +} +bool operator!=(const SemanticToken &Lhs, const SemanticToken &Rhs) { + return !(Lhs == Rhs); +} + +std::vector getSemanticHighlights(ParsedAST &AST) { + ASTContext &Ctx = AST.getASTContext(); + Ctx.setTraversalScope(AST.getLocalTopLevelDecls()); + SemanticSymbolASTCollector Collector(Ctx); + Collector.TraverseAST(Ctx); + return Collector.getSymbols(); +} +} // namespace clangd +} // namespace clang diff --git a/clang-tools-extra/clangd/unittests/CMakeLists.txt b/clang-tools-extra/clangd/unittests/CMakeLists.txt --- a/clang-tools-extra/clangd/unittests/CMakeLists.txt +++ b/clang-tools-extra/clangd/unittests/CMakeLists.txt @@ -53,6 +53,7 @@ RenameTests.cpp RIFFTests.cpp SelectionTests.cpp + SemanticHighlightTests.cpp SerializationTests.cpp SourceCodeTests.cpp SymbolCollectorTests.cpp diff --git a/clang-tools-extra/clangd/unittests/SemanticHighlightTests.cpp b/clang-tools-extra/clangd/unittests/SemanticHighlightTests.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clangd/unittests/SemanticHighlightTests.cpp @@ -0,0 +1,55 @@ +//===- SemanticHighlightTest.cpp - SemanticHighlightTest tests-*- C++ -* -===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "Annotations.h" +#include "ClangdUnit.h" +#include "Protocol.h" +#include "SemanticHighlight.h" +#include "SourceCode.h" +#include "TestTU.h" +#include "llvm/Support/ScopedPrinter.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace clang { +namespace clangd { +namespace { + +using ::testing::ElementsAreArray; + +TEST(SemanticSymbolASTCollector, GetBeginningOfIdentifier) { + std::string Preamble = R"cpp( + struct A { + double SomeMember; + }; + void $foo[[foo]](int $a[[a]]) { + auto $vlvn[[VeryLongVariableName]] = 12312; + A $aa[[aa]]; + } + )cpp"; + + Annotations Test(Preamble); + auto Foo = Test.range("foo"); + auto A = Test.range("a"); + auto VeryLong = Test.range("vlvn"); + auto AA = Test.range("aa"); + std::vector CorrectTokens = std::vector{ + SemanticToken(SemanticHighlightKind::Function, Foo), + SemanticToken(SemanticHighlightKind::Variable, A), + SemanticToken(SemanticHighlightKind::Variable, VeryLong), + SemanticToken(SemanticHighlightKind::Variable, AA)}; + + auto AST = TestTU::withCode(Test.code()).build(); + auto Tokens = getSemanticHighlights(AST); + + EXPECT_THAT(Tokens, ElementsAreArray(CorrectTokens)); +} + +} // namespace +} // namespace clangd +} // namespace clang