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 @@ -37,6 +37,7 @@ FileDistance.cpp FS.cpp FSProvider.cpp + FormattedString.cpp FuzzyMatch.cpp GlobalCompilationDatabase.cpp Headers.cpp 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 @@ -8,6 +8,7 @@ #include "ClangdLSPServer.h" #include "Diagnostics.h" +#include "FormattedString.h" #include "Protocol.h" #include "SourceCode.h" #include "Trace.h" @@ -803,7 +804,20 @@ void ClangdLSPServer::onHover(const TextDocumentPositionParams &Params, Callback> Reply) { Server->findHover(Params.textDocument.uri.file(), Params.position, - std::move(Reply)); + Bind( + [](decltype(Reply) Reply, + llvm::Expected> H) { + if (!H) + return Reply(H.takeError()); + if (!*H) + return Reply(llvm::None); + // FIXME: render as markdown if client supports it. + Hover R; + R.contents.kind = MarkupKind::PlainText; + R.contents.value = (*H)->renderAsPlainText(); + return Reply(std::move(R)); + }, + std::move(Reply))); } void ClangdLSPServer::applyConfiguration( diff --git a/clang-tools-extra/clangd/ClangdServer.h b/clang-tools-extra/clangd/ClangdServer.h --- a/clang-tools-extra/clangd/ClangdServer.h +++ b/clang-tools-extra/clangd/ClangdServer.h @@ -14,6 +14,7 @@ #include "ClangdUnit.h" #include "CodeComplete.h" #include "FSProvider.h" +#include "FormattedString.h" #include "Function.h" #include "GlobalCompilationDatabase.h" #include "Protocol.h" @@ -182,7 +183,7 @@ /// Get code hover for a given position. void findHover(PathRef File, Position Pos, - Callback> CB); + Callback> CB); /// Retrieve the top symbols from the workspace matching a query. void workspaceSymbols(StringRef Query, int Limit, diff --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp --- a/clang-tools-extra/clangd/ClangdServer.cpp +++ b/clang-tools-extra/clangd/ClangdServer.cpp @@ -10,6 +10,7 @@ #include "ClangdUnit.h" #include "CodeComplete.h" #include "FindSymbols.h" +#include "FormattedString.h" #include "Headers.h" #include "SourceCode.h" #include "Trace.h" @@ -512,14 +513,13 @@ } void ClangdServer::findHover(PathRef File, Position Pos, - Callback> CB) { - auto Action = [Pos](Callback> CB, + Callback> CB) { + auto Action = [Pos](Callback> CB, llvm::Expected InpAST) { if (!InpAST) return CB(InpAST.takeError()); CB(clangd::getHover(InpAST->AST, Pos)); }; - WorkScheduler.runWithAST("Hover", File, Bind(Action, std::move(CB))); } diff --git a/clang-tools-extra/clangd/FormattedString.h b/clang-tools-extra/clangd/FormattedString.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clangd/FormattedString.h @@ -0,0 +1,56 @@ +//===--- FormattedString.h ----------------------------------*- 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 +// +//===----------------------------------------------------------------------===// +// +// A simple intermediate representation of formatted text that could be +// converted to plaintext or markdown. +// +//===----------------------------------------------------------------------===// +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_FORMATTEDSTRING_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_FORMATTEDSTRING_H + +#include +#include + +namespace clang { +namespace clangd { + +/// A structured string representation that could be converted to markdown or +/// plaintext upon requrest. +class FormattedString { +public: + /// Append plain text to the end of the string. + void appendText(std::string Text); + /// Append a block of C++ code. This translates to a ``` block in markdown. + /// In a plain text representation, the code block will be surrounded by + /// newlines. + void appendCodeBlock(std::string Code); + /// Append an inline block of C++ code. This translates to the ` block in + /// markdown. + /// EXPECTS: Code does not contain newlines. + void appendInlineCode(std::string Code); + + std::string renderAsMarkdown() const; + std::string renderAsPlainText() const; + +private: + enum class ChunkKind { + PlainText, // A plain text paragraph. + CodeBlock, // A block of code. + InlineCodeBlock, // An inline block of code. + }; + struct Chunk { + ChunkKind Kind = ChunkKind::PlainText; + std::string Contents; + }; + std::vector Chunks; +}; + +} // namespace clangd +} // namespace clang + +#endif diff --git a/clang-tools-extra/clangd/FormattedString.cpp b/clang-tools-extra/clangd/FormattedString.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clangd/FormattedString.cpp @@ -0,0 +1,103 @@ +//===--- FormattedString.cpp --------------------------------*- 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 "FormattedString.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/ErrorHandling.h" +#include +#include + +namespace clang { +namespace clangd { + +namespace { +std::string escapeBackticks(llvm::StringRef Input) { + std::string R; + for (size_t From = 0; From < Input.size();) { + size_t Next = Input.find("`", From); + R += Input.substr(From, Next); + if (Next == llvm::StringRef::npos) + break; + R += "``"; // backticks should be doubled. + From = Next + 1; + } + return R; +} +} // namespace + +void FormattedString::appendText(std::string Text) { + if (Chunks.empty() || Chunks.back().Kind != ChunkKind::PlainText) { + Chunk C; + C.Kind = ChunkKind::PlainText; + Chunks.push_back(C); + } + Chunks.back().Contents += Text; +} + +void FormattedString::appendCodeBlock(std::string Code) { + Chunk C; + C.Kind = ChunkKind::CodeBlock; + C.Contents = std::move(Code); + Chunks.push_back(std::move(C)); +} + +void FormattedString::appendInlineCode(std::string Code) { + Chunk C; + C.Kind = ChunkKind::InlineCodeBlock; + C.Contents = std::move(Code); + Chunks.push_back(std::move(C)); +} + +std::string FormattedString::renderAsMarkdown() const { + std::string R; + for (const auto &C : Chunks) { + switch (C.Kind) { + case ChunkKind::PlainText: + R += C.Contents; + continue; + case ChunkKind::InlineCodeBlock: + R += " `"; + R += escapeBackticks(C.Contents); + R += "` "; + continue; + case ChunkKind::CodeBlock: + R += "\n```"; + R += escapeBackticks(C.Contents); + R += "\n```\n"; + continue; + } + llvm_unreachable("unhanlded ChunkKind"); + } + return R; +} + +std::string FormattedString::renderAsPlainText() const { + std::string R; + bool NeedsLineBreak = false; + for (const auto &C : Chunks) { + if (NeedsLineBreak) { + R += "\n"; + NeedsLineBreak = false; + } + switch (C.Kind) { + case ChunkKind::PlainText: + case ChunkKind::InlineCodeBlock: + R += C.Contents; + continue; + case ChunkKind::CodeBlock: + if (!R.empty()) + R += "\n"; + R += C.Contents; + NeedsLineBreak = true; + continue; + } + llvm_unreachable("unhanlded ChunkKind"); + } + return R; +} +} // namespace clangd +} // namespace clang diff --git a/clang-tools-extra/clangd/XRefs.h b/clang-tools-extra/clangd/XRefs.h --- a/clang-tools-extra/clangd/XRefs.h +++ b/clang-tools-extra/clangd/XRefs.h @@ -14,6 +14,7 @@ #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_XREFS_H #include "ClangdUnit.h" +#include "FormattedString.h" #include "Protocol.h" #include "index/Index.h" #include "llvm/ADT/Optional.h" @@ -47,7 +48,7 @@ Position Pos); /// Get the hover information when hovering at \p Pos. -llvm::Optional getHover(ParsedAST &AST, Position Pos); +llvm::Optional getHover(ParsedAST &AST, Position Pos); /// Returns reference locations of the symbol at a specified \p Pos. /// \p Limit limits the number of results returned (0 means no limit). diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp --- a/clang-tools-extra/clangd/XRefs.cpp +++ b/clang-tools-extra/clangd/XRefs.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "XRefs.h" #include "AST.h" +#include "FormattedString.h" #include "Logger.h" #include "SourceCode.h" #include "URI.h" @@ -515,17 +516,17 @@ } /// Generate a \p Hover object given the declaration \p D. -static Hover getHoverContents(const Decl *D) { - Hover H; +static FormattedString getHoverContents(const Decl *D) { + FormattedString R; llvm::Optional NamedScope = getScopeName(D); // Generate the "Declared in" section. if (NamedScope) { assert(!NamedScope->empty()); - H.contents.value += "Declared in "; - H.contents.value += *NamedScope; - H.contents.value += "\n\n"; + R.appendText("Declared in "); + R.appendText(*NamedScope); + R.appendText("\n"); } // We want to include the template in the Hover. @@ -537,35 +538,30 @@ PrintingPolicy Policy = printingPolicyForDecls(D->getASTContext().getPrintingPolicy()); - D->print(OS, Policy); - OS.flush(); - - H.contents.value += DeclText; - return H; + R.appendCodeBlock(OS.str()); + return R; } /// Generate a \p Hover object given the type \p T. -static Hover getHoverContents(QualType T, ASTContext &ASTCtx) { - Hover H; - std::string TypeText; - llvm::raw_string_ostream OS(TypeText); +static FormattedString getHoverContents(QualType T, ASTContext &ASTCtx) { + FormattedString R; + + std::string Code; + llvm::raw_string_ostream OS(Code); PrintingPolicy Policy = printingPolicyForDecls(ASTCtx.getPrintingPolicy()); T.print(OS, Policy); - OS.flush(); - H.contents.value += TypeText; - return H; + + R.appendCodeBlock(OS.str()); + return R; } /// 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; - - return H; +static FormattedString getHoverContents(llvm::StringRef MacroName) { + FormattedString S; + S.appendCodeBlock("#define " + MacroName.str()); + return S; } namespace { @@ -680,7 +676,7 @@ return V.getDeducedType(); } -llvm::Optional getHover(ParsedAST &AST, Position Pos) { +llvm::Optional getHover(ParsedAST &AST, Position Pos) { const SourceManager &SourceMgr = AST.getASTContext().getSourceManager(); SourceLocation SourceLocationBeg = getBeginningOfIdentifier(AST, Pos, SourceMgr.getMainFileID()); diff --git a/clang-tools-extra/unittests/clangd/XRefsTests.cpp b/clang-tools-extra/unittests/clangd/XRefsTests.cpp --- a/clang-tools-extra/unittests/clangd/XRefsTests.cpp +++ b/clang-tools-extra/unittests/clangd/XRefsTests.cpp @@ -1154,7 +1154,7 @@ auto AST = TU.build(); if (auto H = getHover(AST, T.point())) { EXPECT_NE("", Test.ExpectedHover) << Test.Input; - EXPECT_EQ(H->contents.value, Test.ExpectedHover.str()) << Test.Input; + EXPECT_EQ(H->renderAsPlainText(), Test.ExpectedHover.str()) << Test.Input; } else EXPECT_EQ("", Test.ExpectedHover.str()) << Test.Input; }