Index: clangd/ClangdLSPServer.h =================================================================== --- clangd/ClangdLSPServer.h +++ clangd/ClangdLSPServer.h @@ -146,6 +146,9 @@ bool SupportsHierarchicalDocumentSymbol = false; /// Whether the client supports showing file status. bool SupportFileStatus = false; + /// From capabilities of textDocument/hover. + MarkupKindBitset HoverMarkupKinds; + // Store of the current versions of the open documents. DraftStore DraftMgr; Index: clangd/ClangdLSPServer.cpp =================================================================== --- clangd/ClangdLSPServer.cpp +++ clangd/ClangdLSPServer.cpp @@ -331,6 +331,9 @@ SupportsHierarchicalDocumentSymbol = Params.capabilities.HierarchicalDocumentSymbol; SupportFileStatus = Params.initializationOptions.FileStatus; + if (Params.capabilities.HoverMarkupKinds) + HoverMarkupKinds |= *Params.capabilities.HoverMarkupKinds; + Reply(llvm::json::Object{ {{"capabilities", llvm::json::Object{ @@ -802,8 +805,27 @@ void ClangdLSPServer::onHover(const TextDocumentPositionParams &Params, Callback> Reply) { - Server->findHover(Params.textDocument.uri.file(), Params.position, - std::move(Reply)); + + Server->findHover( + Params.textDocument.uri.file(), Params.position, + Bind( + [this](decltype(Reply) Reply, Expected> H) { + if (!H) + return Reply(H.takeError()); + + // If the client supports Markdown, convert from plaintext here. + const size_t MarkDownVal = + static_cast(MarkupKind::Markdown); + if (*H && MarkDownVal < HoverMarkupKinds.size() && + HoverMarkupKinds[MarkDownVal]) { + (*H)->contents.kind = MarkupKind::Markdown; + (*H)->contents.value = + llvm::formatv("```C++\n{0}\n```", (*H)->contents.value); + } + + Reply(std::move(*H)); + }, + std::move(Reply))); } void ClangdLSPServer::applyConfiguration( Index: clangd/Protocol.h =================================================================== --- clangd/Protocol.h +++ clangd/Protocol.h @@ -331,6 +331,16 @@ SymbolKind adjustKindToCapability(SymbolKind Kind, SymbolKindBitset &supportedSymbolKinds); +enum class MarkupKind { + PlainText, + Markdown, +}; +bool fromJSON(const llvm::json::Value &, MarkupKind &); +constexpr auto MarkupKindMin = static_cast(MarkupKind::PlainText); +constexpr auto MarkupKindMax = static_cast(MarkupKind::Markdown); +using MarkupKindBitset = std::bitset; +bool fromJSON(const llvm::json::Value &, MarkupKindBitset &); + // This struct doesn't mirror LSP! // The protocol defines deeply nested structures for client capabilities. // Instead of mapping them all, this just parses out the bits we care about. @@ -362,6 +372,10 @@ /// Client supports CodeAction return value for textDocument/codeAction. /// textDocument.codeAction.codeActionLiteralSupport. bool CodeActionStructure = false; + + /// The supported set of MarkupKinds for hover. + /// textDocument.hover.contentFormat. + llvm::Optional HoverMarkupKinds; }; bool fromJSON(const llvm::json::Value &, ClientCapabilities &); @@ -818,11 +832,6 @@ }; bool fromJSON(const llvm::json::Value &, CompletionParams &); -enum class MarkupKind { - PlainText, - Markdown, -}; - struct MarkupContent { MarkupKind kind = MarkupKind::PlainText; std::string value; Index: clangd/Protocol.cpp =================================================================== --- clangd/Protocol.cpp +++ clangd/Protocol.cpp @@ -245,6 +245,13 @@ DocumentSymbol->getBoolean("hierarchicalDocumentSymbolSupport")) R.HierarchicalDocumentSymbol = *HierarchicalSupport; } + if (auto *Hover = TextDocument->getObject("hover")) { + if (auto HoverMarkupKinds = Hover->get("contentFormat")) { + R.HoverMarkupKinds.emplace(); + if (!fromJSON(*HoverMarkupKinds, *R.HoverMarkupKinds)) + return false; + } + } } if (auto *Workspace = O->getObject("workspace")) { if (auto *Symbol = Workspace->getObject("symbol")) { @@ -615,6 +622,30 @@ {"value", MC.value}, }; } +bool fromJSON(const llvm::json::Value &E, MarkupKind &Out) { + if (auto T = E.getAsString()) { + if (*T == "plaintext") + Out = MarkupKind::PlainText; + else if (*T == "markdown") + Out = MarkupKind::Markdown; + else + return false; + return true; + } + return false; +} + +bool fromJSON(const llvm::json::Value &E, MarkupKindBitset &Out) { + if (auto *A = E.getAsArray()) { + for (size_t I = 0; I < A->size(); ++I) { + MarkupKind KindOut; + if (fromJSON((*A)[I], KindOut)) + Out.set(size_t(KindOut)); + } + return true; + } + return false; +} llvm::json::Value toJSON(const Hover &H) { llvm::json::Object Result{{"contents", toJSON(H.contents)}}; Index: clangd/XRefs.cpp =================================================================== --- clangd/XRefs.cpp +++ clangd/XRefs.cpp @@ -537,13 +537,30 @@ return H; } -/// Generate a \p Hover object given the macro \p MacroInf. -static Hover getHoverContents(llvm::StringRef MacroName) { - Hover H; - - H.contents.value = "#define "; - H.contents.value += MacroName; +/// Generate a \p Hover object given the macro \p MacroDecl. +static Hover getHoverContents(MacroDecl Decl, ParsedAST &AST) { + SourceManager &SM = AST.getASTContext().getSourceManager(); + std::string Definition = Decl.Name; + + // Try to get the full definition, not just the name + SourceLocation StartLoc = Decl.Info->getDefinitionLoc(); + SourceLocation EndLoc = Decl.Info->getDefinitionEndLoc(); + if (EndLoc.isValid()) { + EndLoc = Lexer::getLocForEndOfToken(EndLoc, 0, SM, + AST.getASTContext().getLangOpts()); + bool Invalid; + StringRef Buffer = SM.getBufferData(SM.getFileID(StartLoc), &Invalid); + if (!Invalid) { + unsigned StartOffset = SM.getFileOffset(StartLoc); + unsigned EndOffset = SM.getFileOffset(EndLoc); + if (EndOffset <= Buffer.size() && StartOffset < EndOffset) + Definition = Buffer.substr(StartOffset, EndOffset - StartOffset).str(); + } + } + Hover H; + H.contents.kind = MarkupKind::PlainText; + H.contents.value = "#define " + Definition; return H; } @@ -667,7 +684,7 @@ auto Symbols = getSymbolAtPosition(AST, SourceLocationBeg); if (!Symbols.Macros.empty()) - return getHoverContents(Symbols.Macros[0].Name); + return getHoverContents(Symbols.Macros[0], AST); if (!Symbols.Decls.empty()) return getHoverContents(Symbols.Decls[0].D); Index: unittests/clangd/XRefsTests.cpp =================================================================== --- unittests/clangd/XRefsTests.cpp +++ unittests/clangd/XRefsTests.cpp @@ -648,7 +648,25 @@ #define MACRO 2 #undef macro )cpp", - "#define MACRO", + "#define MACRO 1", + }, + { + R"cpp(// Macro + #define MACRO 0 + #define MACRO2 ^MACRO + )cpp", + "#define MACRO 0", + }, + { + R"cpp(// Macro + #define MACRO {\ + return 0;\ + } + int main() ^MACRO + )cpp", + R"cpp(#define MACRO {\ + return 0;\ + })cpp", }, { R"cpp(// Forward class declaration