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,11 @@ void reparseOpenedFiles(); void applyConfiguration(const ConfigurationSettings &Settings); + /// Sends a "publishSemanticHighlighting" notification to the LSP client. + void + publishSemanticHighlighting(const URIForFile &File, + std::vector Highlightings); + /// 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 @@ -328,6 +328,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 +405,8 @@ {ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND, ExecuteCommandParams::CLANGD_APPLY_TWEAK}}, }}, + {"semanticHighlighting", + llvm::json::Object{{"scopes", getSemanticTextMateScopes()}}}, {"typeHierarchyProvider", true}, }}}}; if (NegotiatedOffsetEncoding) @@ -927,6 +931,16 @@ reparseOpenedFiles(); } +void ClangdLSPServer::publishSemanticHighlighting( + const URIForFile &File, + std::vector Highlightings) { + notify("textDocument/semanticHighlighting", + llvm::json::Object{ + {"textDocument", TextDocumentIdentifier{File}}, + {"lines", std::move(Highlightings)}, + }); +} + void ClangdLSPServer::publishDiagnostics( const URIForFile &File, std::vector Diagnostics) { // Publish diagnostics. @@ -1063,6 +1077,12 @@ return true; } +void ClangdLSPServer::onHighlightingsReady( + PathRef File, std::vector Highlightings) { + publishSemanticHighlighting(URIForFile::canonicalize(File, /*TUPath=*/File), + highlightingTokensToLines(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; 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; 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 @@ -27,10 +27,34 @@ bool operator==(const HighlightingToken &Lhs, const HighlightingToken &Rhs); +// Contains all highlightings in a single line. +struct LineHighlighting { + LineHighlighting(unsigned int Line = 0, + std::vector Tokens = {}) + : Line(Line), Tokens(Tokens) {} + unsigned int Line; + std::vector Tokens; +}; + +llvm::json::Value toJSON(const LineHighlighting &Highlighting); + // Returns all HighlightingTokens from an AST. Only generates highlights for the // 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 LineHighlightings. +// Assumes the HighlightingToken's vector is ordered in ascending order by line +// and secondarily by column of the token's start position. The returned +// LineHighlighting vector will be ordered in ascending order according to the +// line number. The tokens in a LineHighlighting are ordered in ascending order +// according to the token's start character. +std::vector +highlightingTokensToLines(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 @@ -63,8 +63,69 @@ } }; +// 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 +llvm::json::Value toJSON(const LineHighlighting &Highlight) { + llvm::SmallVector BinaryHighlights; + llvm::raw_svector_ostream OS(BinaryHighlights); + + for (size_t I = 0, End = Highlight.Tokens.size(); I < End; I++) { + // Each token should consists of 2 32 bit integers. The first integer is the + // start column of the token. The second integer's first 16 bits are the + // length of the token. The rest of the second integer is the scope index of + // the token. + HighlightingToken Token = Highlight.Tokens[I]; + write32be(Token.R.start.character, OS); + write16be(Token.R.end.character - Token.R.start.character, OS); + write16be(static_cast(Token.Kind), OS); + } + + return llvm::json::Object{{"line", Highlight.Line}, + {"tokens", encodeBase64(BinaryHighlights)}}; +} + bool operator==(const HighlightingToken &Lhs, const HighlightingToken &Rhs) { return Lhs.Kind == Rhs.Kind && Lhs.R == Rhs.R; } @@ -73,5 +134,35 @@ return HighlightingTokenCollector(AST).collectTokens(); } +std::vector +highlightingTokensToLines(llvm::ArrayRef Tokens) { + std::vector Lines; + int LastLine = -1; + // FIXME: Tokens might be multiple lines long (block comments) in this case + // this needs to add multiple lines for those tokens. + for (const auto &Token : Tokens) { + if (Token.R.start.line != LastLine) { + Lines.push_back(LineHighlighting(Token.R.start.line)); + LastLine = Token.R.start.line; + } + + Lines.back().Tokens.push_back(Token); + } + + 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: "(",