diff --git a/clang-tools-extra/clangd/ClangdLSPServer.h b/clang-tools-extra/clangd/ClangdLSPServer.h --- a/clang-tools-extra/clangd/ClangdLSPServer.h +++ b/clang-tools-extra/clangd/ClangdLSPServer.h @@ -55,6 +55,8 @@ // Implement DiagnosticsConsumer. void onDiagnosticsReady(PathRef File, std::vector Diagnostics) override; void onFileUpdated(PathRef File, const TUStatus &Status) override; + void onHighlightingsReady(PathRef File, + std::vector Highlightings) override; // LSP methods. Notifications have signature void(const Params&). // Calls have signature void(const Params&, Callback). @@ -115,6 +117,9 @@ void reparseOpenedFiles(); void applyConfiguration(const ConfigurationSettings &Settings); + /// Sends a "publishSemanticHighlighting" notification to the LSP client. + void publishSemanticHighlighting(SemanticHighlightingParams Params); + /// Sends a "publishDiagnostics" notification to the LSP client. void publishDiagnostics(const URIForFile &File, std::vector Diagnostics); diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp --- a/clang-tools-extra/clangd/ClangdLSPServer.cpp +++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp @@ -11,6 +11,7 @@ #include "FormattedString.h" #include "GlobalCompilationDatabase.h" #include "Protocol.h" +#include "SemanticHighlighting.h" #include "SourceCode.h" #include "Trace.h" #include "URI.h" @@ -328,6 +329,8 @@ WithOffsetEncoding.emplace(kCurrentOffsetEncoding, *NegotiatedOffsetEncoding); + ClangdServerOpts.SemanticHighlighting = + Params.capabilities.SemanticHighlighting; if (Params.rootUri && *Params.rootUri) ClangdServerOpts.WorkspaceRoot = Params.rootUri->file(); else if (Params.rootPath && !Params.rootPath->empty()) @@ -403,6 +406,8 @@ {ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND, ExecuteCommandParams::CLANGD_APPLY_TWEAK}}, }}, + {"semanticHighlighting", + llvm::json::Object{{"scopes", getSemanticTextMateScopes()}}}, {"typeHierarchyProvider", true}, }}}}; if (NegotiatedOffsetEncoding) @@ -927,6 +932,11 @@ reparseOpenedFiles(); } +void ClangdLSPServer::publishSemanticHighlighting( + SemanticHighlightingParams Params) { + notify("textDocument/semanticHighlighting", Params); +} + void ClangdLSPServer::publishDiagnostics( const URIForFile &File, std::vector Diagnostics) { // Publish diagnostics. @@ -1063,6 +1073,13 @@ return true; } +void ClangdLSPServer::onHighlightingsReady( + PathRef File, std::vector Highlightings) { + publishSemanticHighlighting( + {{URIForFile::canonicalize(File, /*TUPath=*/File)}, + highlightingTokensToSemanticHighlightingInformation(Highlightings)}); +} + void ClangdLSPServer::onDiagnosticsReady(PathRef File, std::vector Diagnostics) { auto URI = URIForFile::canonicalize(File, /*TUPath=*/File); 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 @@ -406,6 +406,9 @@ /// textDocument.codeAction.codeActionLiteralSupport. bool CodeActionStructure = false; + /// Client supports semantic highlighting. + bool SemanticHighlighting = false; + /// Supported encodings for LSP character offsets. (clangd extension). llvm::Optional> offsetEncoding; @@ -1173,6 +1176,25 @@ }; llvm::json::Value toJSON(const FileStatus &FStatus); +// Contains all highlightings in a single line. +struct SemanticHighlightingInformation { + // The line these highlightings belong to. + unsigned int Line; + // The base64 encoded string of highlighting tokens. + std::string Tokens; +}; + +llvm::json::Value toJSON(const SemanticHighlightingInformation &Highlighting); + +struct SemanticHighlightingParams { + // The textdocument these highlightings belong to. + TextDocumentIdentifier TextDocument; + // The lines of highlightings that should be sent. + std::vector Lines; +}; + +llvm::json::Value toJSON(const SemanticHighlightingParams &Highlighting); + } // namespace clangd } // namespace clang 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 @@ -273,6 +273,12 @@ if (!O) return false; if (auto *TextDocument = O->getObject("textDocument")) { + if (auto *SemanticHighlighting = + TextDocument->getObject("semanticHighlightingCapabilities")) { + if (auto SemanticHighlightingSupport = + SemanticHighlighting->getBoolean("semanticHighlighting")) + R.SemanticHighlighting = *SemanticHighlightingSupport; + } if (auto *Diagnostics = TextDocument->getObject("publishDiagnostics")) { if (auto CategorySupport = Diagnostics->getBoolean("categorySupport")) R.DiagnosticCategory = *CategorySupport; @@ -1027,5 +1033,17 @@ return OS << toString(Enc); } +llvm::json::Value toJSON(const SemanticHighlightingInformation &Highlighting) { + return llvm::json::Object{{"line", Highlighting.Line}, + {"tokens", Highlighting.Tokens}}; +} + +llvm::json::Value toJSON(const SemanticHighlightingParams &Highlighting) { + return llvm::json::Object{ + {"textDocument", Highlighting.TextDocument}, + {"lines", std::move(Highlighting.Lines)}, + }; +} + } // namespace clangd } // namespace clang 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 @@ -10,6 +10,7 @@ #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_SEMANTICHIGHLIGHTING_H #include "ClangdUnit.h" +#include "Protocol.h" namespace clang { namespace clangd { @@ -31,6 +32,20 @@ // main AST. std::vector getSemanticHighlightings(ParsedAST &AST); +// Gets the TextMate scopes as a double nested array where the +// SemanticHighlightKind indexes correctly into this vector. +std::vector> getSemanticTextMateScopes(); + +// Converts a vector of HighlightingTokens to a vector of +// SemanticHighlightingInformation. Assumes the HighlightingToken's vector is +// ordered in ascending order by line and secondarily by column of the token's +// start position. The returned SemanticHighlightingInformation vector will be +// ordered in ascending order according to the line number. The token string in +// SemanticHighlightingInformation is base64 encoded. +std::vector +highlightingTokensToSemanticHighlightingInformation( + llvm::ArrayRef Tokens); + } // namespace clangd } // namespace clang 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 @@ -8,6 +8,7 @@ #include "SemanticHighlighting.h" #include "Logger.h" +#include "Protocol.h" #include "SourceCode.h" #include "clang/AST/ASTContext.h" #include "clang/AST/RecursiveASTVisitor.h" @@ -63,6 +64,48 @@ } }; +// Encode binary data into base64. +// This was copied from compiler-rt/lib/fuzzer/FuzzerUtil.cpp. +// FIXME: Factor this out into llvm/Support? +std::string encodeBase64(const llvm::SmallVectorImpl &U) { + static const char Table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + std::string Res; + size_t I; + for (I = 0; I + 2 < U.size(); I += 3) { + uint32_t X = (U[I] << 16) + (U[I + 1] << 8) + U[I + 2]; + Res += Table[(X >> 18) & 63]; + Res += Table[(X >> 12) & 63]; + Res += Table[(X >> 6) & 63]; + Res += Table[X & 63]; + } + if (I + 1 == U.size()) { + uint32_t X = (U[I] << 16); + Res += Table[(X >> 18) & 63]; + Res += Table[(X >> 12) & 63]; + Res += "=="; + } else if (I + 2 == U.size()) { + uint32_t X = (U[I] << 16) + (U[I + 1] << 8); + Res += Table[(X >> 18) & 63]; + Res += Table[(X >> 12) & 63]; + Res += Table[(X >> 6) & 63]; + Res += "="; + } + return Res; +} + +void write32be(uint32_t I, llvm::raw_ostream &OS) { + std::array Buf; + llvm::support::endian::write32be(Buf.data(), I); + OS.write(Buf.data(), Buf.size()); +} + +void write16be(uint16_t I, llvm::raw_ostream &OS) { + std::array Buf; + llvm::support::endian::write16be(Buf.data(), I); + OS.write(Buf.data(), Buf.size()); +} } // namespace bool operator==(const HighlightingToken &Lhs, const HighlightingToken &Rhs) { @@ -73,5 +116,48 @@ return HighlightingTokenCollector(AST).collectTokens(); } +std::vector +highlightingTokensToSemanticHighlightingInformation( + llvm::ArrayRef Tokens) { + if (Tokens.size() == 0) { + return {}; + } + + std::vector Lines; + int LastLine = Tokens.front().R.start.line; + // FIXME: Tokens might be multiple lines long (block comments) in this case + // this needs to add multiple lines for those tokens. + llvm::SmallVector LineHighlights; + llvm::raw_svector_ostream OS(LineHighlights); + for (const auto &Token : Tokens) { + if (Token.R.start.line != LastLine) { + Lines.push_back( + {static_cast(LastLine), encodeBase64(LineHighlights)}); + LastLine = Token.R.start.line; + LineHighlights.clear(); + } + + write32be(Token.R.start.character, OS); + write16be(Token.R.end.character - Token.R.start.character, OS); + write16be(static_cast(Token.Kind), OS); + } + + Lines.push_back( + {static_cast(LastLine), encodeBase64(LineHighlights)}); + return Lines; +} + +std::vector> getSemanticTextMateScopes() { + std::map> Scopes = { + {HighlightingKind::Variable, {"variable.cpp"}}, + {HighlightingKind::Function, {"entity.name.function.cpp"}}}; + std::vector> NestedScopes(Scopes.size()); + for (const auto &Scope : Scopes) { + NestedScopes[static_cast(Scope.first)] = Scope.second; + } + + return NestedScopes; +} + } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/test/initialize-params.test b/clang-tools-extra/clangd/test/initialize-params.test --- a/clang-tools-extra/clangd/test/initialize-params.test +++ b/clang-tools-extra/clangd/test/initialize-params.test @@ -33,6 +33,16 @@ # CHECK-NEXT: "hoverProvider": true, # CHECK-NEXT: "referencesProvider": true, # CHECK-NEXT: "renameProvider": true, +# CHECK-NEXT: "semanticHighlighting": { +# CHECK-NEXT: "scopes": [ +# CHECK-NEXT: [ +# CHECK-NEXT: "variable.cpp" +# CHECK-NEXT: ], +# CHECK-NEXT: [ +# CHECK-NEXT: "entity.name.function.cpp" +# CHECK-NEXT: ] +# CHECK-NEXT: ] +# CHECK-NEXT: }, # CHECK-NEXT: "signatureHelpProvider": { # CHECK-NEXT: "triggerCharacters": [ # CHECK-NEXT: "(", diff --git a/clang-tools-extra/clangd/test/semantic-highlighting.test b/clang-tools-extra/clangd/test/semantic-highlighting.test new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clangd/test/semantic-highlighting.test @@ -0,0 +1,21 @@ +# RUN: clangd -lit-test < %s | FileCheck -strict-whitespace %s +{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{"textDocument": {"semanticHighlightingCapabilities": {"semanticHighlighting": true}}},"trace":"off"}} +--- +{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo.cpp","languageId":"cpp","version":1,"text":"int x = 2;"}}} +# CHECK: "method": "textDocument/semanticHighlighting", +# CHECK-NEXT: "params": { +# CHECK-NEXT: "lines": [ +# CHECK-NEXT: { +# CHECK-NEXT: "line": 0, +# CHECK-NEXT: "tokens": "AAAABAABAAA=" +# CHECK-NEXT: } +# CHECK-NEXT: ], +# CHECK-NEXT: "textDocument": { +# CHECK-NEXT: "uri": "file:///clangd-test/foo.cpp" +# CHECK-NEXT: } +# CHECK-NEXT: } +# CHECK-NEXT:} +--- +{"jsonrpc":"2.0","id":3,"method":"shutdown"} +--- +{"jsonrpc":"2.0","method":"exit"}