#include "ClangdLSPServer.h"
#include "Diagnostics.h"
+#include "FormattedString.h"
#include "Protocol.h"
#include "SourceCode.h"
#include "Trace.h"
SupportsHierarchicalDocumentSymbol =
Params.capabilities.HierarchicalDocumentSymbol;
SupportFileStatus = Params.initializationOptions.FileStatus;
+ HoverContentFormat = Params.capabilities.HoverContentFormat;
llvm::json::Object Result{
{{"capabilities",
llvm::json::Object{
Callback<llvm::Optional<Hover>> Reply) {
Server->findHover(Params.textDocument.uri.file(), Params.position,
Bind(
- [](decltype(Reply) Reply,
- llvm::Expected<llvm::Optional<HoverInfo>> HIorErr) {
- if (!HIorErr)
- return Reply(HIorErr.takeError());
- const auto &HI = HIorErr.get();
- if (!HI)
+ [this](decltype(Reply) Reply,
+ llvm::Expected<llvm::Optional<HoverInfo>> H) {
+ if (!H)
+ return Reply(H.takeError());
+ if (!*H)
return Reply(llvm::None);
- Hover H;
- H.range = HI->SymRange;
- H.contents = HI->render();
- return Reply(H);
+
+ Hover R;
+ R.contents.kind = HoverContentFormat;
+ R.range = (*H)->SymRange;
+ switch (HoverContentFormat) {
+ case MarkupKind::PlainText:
+ R.contents.value =
+ (*H)->present().renderAsPlainText();
+ return Reply(std::move(R));
+ case MarkupKind::Markdown:
+ R.contents.value =
+ (*H)->present().renderAsMarkdown();
+ return Reply(std::move(R));
+ };
+ llvm_unreachable("unhandled MarkupKind");
},
std::move(Reply)));
}
bool SupportsHierarchicalDocumentSymbol = false;
/// Whether the client supports showing file status.
bool SupportFileStatus = false;
- // Store of the current versions of the open documents.
+ /// Which kind of markup should we use in textDocument/hover responses.
+ MarkupKind HoverContentFormat = MarkupKind::PlainText;
+
+ /// Store of the current versions of the open documents.
DraftStore DraftMgr;
// The CDB is created by the "initialize" LSP method.
#include "ClangdUnit.h"
#include "CodeComplete.h"
#include "FindSymbols.h"
+#include "FormattedString.h"
#include "Headers.h"
#include "Protocol.h"
#include "SourceCode.h"
#include "TUScheduler.h"
#include "Trace.h"
+#include "XRefs.h"
#include "index/CanonicalIncludes.h"
#include "index/FileIndex.h"
#include "index/Merge.h"
void ClangdServer::findHover(PathRef File, Position Pos,
Callback<llvm::Optional<HoverInfo>> CB) {
- auto Action = [Pos](Callback<llvm::Optional<HoverInfo>> CB, Path File,
+ auto Action = [Pos](decltype(CB) CB, Path File,
llvm::Expected<InputsAndAST> InpAST) {
if (!InpAST)
return CB(InpAST.takeError());
#include "ClangdUnit.h"
#include "CodeComplete.h"
#include "FSProvider.h"
+#include "FormattedString.h"
#include "Function.h"
#include "GlobalCompilationDatabase.h"
#include "Protocol.h"
#include "clang/Basic/CharInfo.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/ErrorHandling.h"
+#include "llvm/Support/FormatVariadic.h"
#include <cstddef>
#include <string>
R.pop_back();
return R;
}
+
+std::string FormattedString::renderForTests() const {
+ std::string R;
+ for (const auto &C : Chunks) {
+ switch (C.Kind) {
+ case ChunkKind::PlainText:
+ R += "text[" + C.Contents + "]";
+ break;
+ case ChunkKind::InlineCodeBlock:
+ R += "code[" + C.Contents + "]";
+ break;
+ case ChunkKind::CodeBlock:
+ if (!R.empty())
+ R += "\n";
+ R += llvm::formatv("codeblock({0}) [\n{1}\n]\n", C.Language, C.Contents);
+ break;
+ }
+ }
+ while (!R.empty() && isWhitespace(R.back()))
+ R.pop_back();
+ return R;
+}
} // namespace clangd
} // namespace clang
std::string renderAsMarkdown() const;
std::string renderAsPlainText() const;
+ std::string renderForTests() const;
private:
enum class ChunkKind {
DocumentSymbol->getBoolean("hierarchicalDocumentSymbolSupport"))
R.HierarchicalDocumentSymbol = *HierarchicalSupport;
}
+ if (auto *Hover = TextDocument->getObject("hover")) {
+ if (auto *ContentFormat = Hover->getArray("contentFormat")) {
+ for (const auto &Format : *ContentFormat) {
+ MarkupKind K = MarkupKind::PlainText;
+ if (fromJSON(Format, K)) {
+ R.HoverContentFormat = K;
+ break;
+ }
+ }
+ }
+ }
}
if (auto *Workspace = O->getObject("workspace")) {
if (auto *Symbol = Workspace->getObject("symbol")) {
llvm_unreachable("Invalid MarkupKind");
}
+bool fromJSON(const llvm::json::Value &V, MarkupKind &K) {
+ auto Str = V.getAsString();
+ if (!Str) {
+ elog("Failed to parse markup kind: expected a string");
+ return false;
+ }
+ if (*Str == "plaintext")
+ K = MarkupKind::PlainText;
+ else if (*Str == "markdown")
+ K = MarkupKind::Markdown;
+ else {
+ elog("Unknown markup kind: {0}", *Str);
+ return false;
+ }
+ return true;
+}
+
+llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, MarkupKind K) {
+ return OS << toTextKind(K);
+}
+
llvm::json::Value toJSON(const MarkupContent &MC) {
if (MC.value.empty())
return nullptr;
bool fromJSON(const llvm::json::Value &, OffsetEncoding &);
llvm::raw_ostream &operator<<(llvm::raw_ostream &, OffsetEncoding);
+// Describes the content type that a client supports in various result literals
+// like `Hover`, `ParameterInfo` or `CompletionItem`.
+enum class MarkupKind {
+ PlainText,
+ Markdown,
+};
+bool fromJSON(const llvm::json::Value &, MarkupKind &);
+llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, MarkupKind);
+
// 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.
/// Supported encodings for LSP character offsets. (clangd extension).
llvm::Optional<std::vector<OffsetEncoding>> offsetEncoding;
+
+ /// The content format that should be used for Hover requests.
+ MarkupKind HoverContentFormat = MarkupKind::PlainText;
};
bool fromJSON(const llvm::json::Value &, ClientCapabilities &);
};
bool fromJSON(const llvm::json::Value &, CompletionParams &);
-enum class MarkupKind {
- PlainText,
- Markdown,
-};
-
struct MarkupContent {
MarkupKind kind = MarkupKind::PlainText;
std::string value;
#include "AST.h"
#include "CodeCompletionStrings.h"
#include "FindSymbols.h"
+#include "FormattedString.h"
#include "Logger.h"
#include "Protocol.h"
#include "SourceCode.h"
return Result;
}
-MarkupContent HoverInfo::render() const {
- MarkupContent Content;
- Content.kind = MarkupKind::PlainText;
- std::vector<std::string> Output;
-
+FormattedString HoverInfo::present() const {
+ FormattedString Output;
if (NamespaceScope) {
- llvm::raw_string_ostream Out(Content.value);
- Out << "Declared in ";
+ Output.appendText("Declared in");
// Drop trailing "::".
if (!LocalScope.empty())
- Out << *NamespaceScope << llvm::StringRef(LocalScope).drop_back(2);
+ Output.appendInlineCode(llvm::StringRef(LocalScope).drop_back(2));
else if (NamespaceScope->empty())
- Out << "global namespace";
+ Output.appendInlineCode("global namespace");
else
- Out << llvm::StringRef(*NamespaceScope).drop_back(2);
- Out << "\n\n";
+ Output.appendInlineCode(llvm::StringRef(*NamespaceScope).drop_back(2));
}
if (!Definition.empty()) {
- Output.push_back(Definition);
+ Output.appendCodeBlock(Definition);
} else {
// Builtin types
- Output.push_back(Name);
+ Output.appendCodeBlock(Name);
}
- Content.value += llvm::join(Output, " ");
- return Content;
+ return Output;
}
llvm::raw_ostream &operator<<(llvm::raw_ostream &OS,
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_XREFS_H
#include "ClangdUnit.h"
+#include "FormattedString.h"
#include "Protocol.h"
#include "index/Index.h"
#include "index/SymbolLocation.h"
/// Set for all templates(function, class, variable).
llvm::Optional<std::vector<Param>> TemplateParameters;
- /// Lower to LSP struct.
- MarkupContent render() const;
+ /// Produce a user-readable information.
+ FormattedString present() const;
};
llvm::raw_ostream &operator<<(llvm::raw_ostream &, const HoverInfo::Param &);
inline bool operator==(const HoverInfo::Param &LHS,
int test1 = bonjour;
}
)cpp",
- "Declared in main\n\nint bonjour",
+ "text[Declared in]code[main]\n"
+ "codeblock(cpp) [\n"
+ "int bonjour\n"
+ "]",
},
{
R"cpp(// Local variable in method
}
};
)cpp",
- "Declared in s::method\n\nint bonjour",
+ "text[Declared in]code[s::method]\n"
+ "codeblock(cpp) [\n"
+ "int bonjour\n"
+ "]",
},
{
R"cpp(// Struct
ns1::My^Class* Params;
}
)cpp",
- "Declared in ns1\n\nstruct MyClass {}",
+ "text[Declared in]code[ns1]\n"
+ "codeblock(cpp) [\n"
+ "struct MyClass {}\n"
+ "]",
},
{
R"cpp(// Class
ns1::My^Class* Params;
}
)cpp",
- "Declared in ns1\n\nclass MyClass {}",
+ "text[Declared in]code[ns1]\n"
+ "codeblock(cpp) [\n"
+ "class MyClass {}\n"
+ "]",
},
{
R"cpp(// Union
ns1::My^Union Params;
}
)cpp",
- "Declared in ns1\n\nunion MyUnion {}",
+ "text[Declared in]code[ns1]\n"
+ "codeblock(cpp) [\n"
+ "union MyUnion {}\n"
+ "]",
},
{
R"cpp(// Function definition via pointer
auto *X = &^foo;
}
)cpp",
- "Declared in global namespace\n\nint foo(int)",
+ "text[Declared in]code[global namespace]\n"
+ "codeblock(cpp) [\n"
+ "int foo(int)\n"
+ "]",
},
{
R"cpp(// Function declaration via call
return ^foo(42);
}
)cpp",
- "Declared in global namespace\n\nint foo(int)",
+ "text[Declared in]code[global namespace]\n"
+ "codeblock(cpp) [\n"
+ "int foo(int)\n"
+ "]",
},
{
R"cpp(// Field
bar.^x;
}
)cpp",
- "Declared in Foo\n\nint x",
+ "text[Declared in]code[Foo]\n"
+ "codeblock(cpp) [\n"
+ "int x\n"
+ "]",
},
{
R"cpp(// Field with initialization
bar.^x;
}
)cpp",
- "Declared in Foo\n\nint x = 5",
+ "text[Declared in]code[Foo]\n"
+ "codeblock(cpp) [\n"
+ "int x = 5\n"
+ "]",
},
{
R"cpp(// Static field
Foo::^x;
}
)cpp",
- "Declared in Foo\n\nstatic int x",
+ "text[Declared in]code[Foo]\n"
+ "codeblock(cpp) [\n"
+ "static int x\n"
+ "]",
},
{
R"cpp(// Field, member initializer
Foo() : ^x(0) {}
};
)cpp",
- "Declared in Foo\n\nint x",
+ "text[Declared in]code[Foo]\n"
+ "codeblock(cpp) [\n"
+ "int x\n"
+ "]",
},
{
R"cpp(// Field, GNU old-style field designator
Foo bar = { ^x : 1 };
}
)cpp",
- "Declared in Foo\n\nint x",
+ "text[Declared in]code[Foo]\n"
+ "codeblock(cpp) [\n"
+ "int x\n"
+ "]",
},
{
R"cpp(// Field, field designator
Foo bar = { .^x = 2 };
}
)cpp",
- "Declared in Foo\n\nint x",
+ "text[Declared in]code[Foo]\n"
+ "codeblock(cpp) [\n"
+ "int x\n"
+ "]",
},
{
R"cpp(// Method call
bar.^x();
}
)cpp",
- "Declared in Foo\n\nint x()",
+ "text[Declared in]code[Foo]\n"
+ "codeblock(cpp) [\n"
+ "int x()\n"
+ "]",
},
{
R"cpp(// Static method call
Foo::^x();
}
)cpp",
- "Declared in Foo\n\nstatic int x()",
+ "text[Declared in]code[Foo]\n"
+ "codeblock(cpp) [\n"
+ "static int x()\n"
+ "]",
},
{
R"cpp(// Typedef
^Foo bar;
}
)cpp",
- "Declared in global namespace\n\ntypedef int Foo",
+ "text[Declared in]code[global namespace]\n"
+ "codeblock(cpp) [\n"
+ "typedef int Foo\n"
+ "]",
},
{
R"cpp(// Namespace
} // namespace ns
int main() { ^ns::Foo::bar(); }
)cpp",
- "Declared in global namespace\n\nnamespace ns {}",
+ "text[Declared in]code[global namespace]\n"
+ "codeblock(cpp) [\n"
+ "namespace ns {}\n"
+ "]",
},
{
R"cpp(// Anonymous namespace
} // namespace ns
int main() { ns::f^oo++; }
)cpp",
- "Declared in ns::(anonymous)\n\nint foo",
+ "text[Declared in]code[ns::(anonymous)]\n"
+ "codeblock(cpp) [\n"
+ "int foo\n"
+ "]",
},
{
R"cpp(// Macro
#define MACRO 2
#undef macro
)cpp",
- "#define MACRO 1",
+ "codeblock(cpp) [\n"
+ "#define MACRO 1\n"
+ "]",
},
{
R"cpp(// Macro
#define MACRO 0
#define MACRO2 ^MACRO
)cpp",
- "#define MACRO 0",
+ "codeblock(cpp) [\n"
+ "#define MACRO 0\n"
+ "]",
},
{
R"cpp(// Macro
}
int main() ^MACRO
)cpp",
- "#define MACRO "
- " \\\n { return 0; }",
+ R"cpp(codeblock(cpp) [
+#define MACRO \
+ { return 0; }
+])cpp",
},
{
R"cpp(// Forward class declaration
class Foo {};
F^oo* foo();
)cpp",
- "Declared in global namespace\n\nclass Foo {}",
+ "text[Declared in]code[global namespace]\n"
+ "codeblock(cpp) [\n"
+ "class Foo {}\n"
+ "]",
},
{
R"cpp(// Function declaration
void g() { f^oo(); }
void foo() {}
)cpp",
- "Declared in global namespace\n\nvoid foo()",
+ "text[Declared in]code[global namespace]\n"
+ "codeblock(cpp) [\n"
+ "void foo()\n"
+ "]",
},
{
R"cpp(// Enum declaration
Hel^lo hello = ONE;
}
)cpp",
- "Declared in global namespace\n\nenum Hello {}",
+ "text[Declared in]code[global namespace]\n"
+ "codeblock(cpp) [\n"
+ "enum Hello {}\n"
+ "]",
},
{
R"cpp(// Enumerator
Hello hello = O^NE;
}
)cpp",
- "Declared in Hello\n\nONE",
+ "text[Declared in]code[Hello]\n"
+ "codeblock(cpp) [\n"
+ "ONE\n"
+ "]",
},
{
R"cpp(// Enumerator in anonymous enum
int hello = O^NE;
}
)cpp",
- "Declared in global namespace\n\nONE",
+ "text[Declared in]code[global namespace]\n"
+ "codeblock(cpp) [\n"
+ "ONE\n"
+ "]",
},
{
R"cpp(// Global variable
he^y++;
}
)cpp",
- "Declared in global namespace\n\nstatic int hey = 10",
+ "text[Declared in]code[global namespace]\n"
+ "codeblock(cpp) [\n"
+ "static int hey = 10\n"
+ "]",
},
{
R"cpp(// Global variable in namespace
ns1::he^y++;
}
)cpp",
- "Declared in ns1\n\nstatic int hey = 10",
+ "text[Declared in]code[ns1]\n"
+ "codeblock(cpp) [\n"
+ "static int hey = 10\n"
+ "]",
},
{
R"cpp(// Field in anonymous struct
s.he^llo++;
}
)cpp",
- "Declared in (anonymous struct)\n\nint hello",
+ "text[Declared in]code[(anonymous struct)]\n"
+ "codeblock(cpp) [\n"
+ "int hello\n"
+ "]",
},
{
R"cpp(// Templated function
}
void g() { auto x = f^oo<int>(); }
)cpp",
- "Declared in global namespace\n\ntemplate <typename T> T foo()",
+ "text[Declared in]code[global namespace]\n"
+ "codeblock(cpp) [\n"
+ "template <typename T> T foo()\n"
+ "]",
},
{
R"cpp(// Anonymous union
};
void g() { struct outer o; o.v.d^ef++; }
)cpp",
- "Declared in outer::(anonymous union)\n\nint def",
+ "text[Declared in]code[outer::(anonymous union)]\n"
+ "codeblock(cpp) [\n"
+ "int def\n"
+ "]",
},
{
R"cpp(// Nothing
^auto i = 1;
}
)cpp",
- "int",
+ "codeblock(cpp) [\n"
+ "int\n"
+ "]",
},
{
R"cpp(// Simple initialization with const auto
const ^auto i = 1;
}
)cpp",
- "int",
+ "codeblock(cpp) [\n"
+ "int\n"
+ "]",
},
{
R"cpp(// Simple initialization with const auto&
const ^auto& i = 1;
}
)cpp",
- "int",
+ "codeblock(cpp) [\n"
+ "int\n"
+ "]",
},
{
R"cpp(// Simple initialization with auto&
^auto& i = 1;
}
)cpp",
- "int",
+ "codeblock(cpp) [\n"
+ "int\n"
+ "]",
},
{
R"cpp(// Simple initialization with auto*
^auto* i = &a;
}
)cpp",
- "int",
+ "codeblock(cpp) [\n"
+ "int\n"
+ "]",
},
{
R"cpp(// Auto with initializer list.
^auto i = {1,2};
}
)cpp",
- "class std::initializer_list<int>",
+ "codeblock(cpp) [\n"
+ "class std::initializer_list<int>\n"
+ "]",
},
{
R"cpp(// User defined conversion to auto
operator ^auto() const { return 10; }
};
)cpp",
- "int",
+ "codeblock(cpp) [\n"
+ "int\n"
+ "]",
},
{
R"cpp(// Simple initialization with decltype(auto)
^decltype(auto) i = 1;
}
)cpp",
- "int",
+ "codeblock(cpp) [\n"
+ "int\n"
+ "]",
},
{
R"cpp(// Simple initialization with const decltype(auto)
^decltype(auto) i = j;
}
)cpp",
- "const int",
+ "codeblock(cpp) [\n"
+ "const int\n"
+ "]",
},
{
R"cpp(// Simple initialization with const& decltype(auto)
^decltype(auto) i = j;
}
)cpp",
- "const int &",
+ "codeblock(cpp) [\n"
+ "const int &\n"
+ "]",
},
{
R"cpp(// Simple initialization with & decltype(auto)
^decltype(auto) i = j;
}
)cpp",
- "int &",
+ "codeblock(cpp) [\n"
+ "int &\n"
+ "]",
},
{
R"cpp(// decltype with initializer list: nothing
return 0;
}
)cpp",
- "int",
+ "codeblock(cpp) [\n"
+ "int\n"
+ "]",
},
{
R"cpp(// auto function return with trailing type
return Bar();
}
)cpp",
- "struct Bar",
+ "codeblock(cpp) [\n"
+ "struct Bar\n"
+ "]",
},
{
R"cpp(// trailing return type
return Bar();
}
)cpp",
- "struct Bar",
+ "codeblock(cpp) [\n"
+ "struct Bar\n"
+ "]",
},
{
R"cpp(// auto in function return
return Bar();
}
)cpp",
- "struct Bar",
+ "codeblock(cpp) [\n"
+ "struct Bar\n"
+ "]",
},
{
R"cpp(// auto& in function return
return Bar();
}
)cpp",
- "struct Bar",
+ "codeblock(cpp) [\n"
+ "struct Bar\n"
+ "]",
},
{
R"cpp(// auto* in function return
return bar;
}
)cpp",
- "struct Bar",
+ "codeblock(cpp) [\n"
+ "struct Bar\n"
+ "]",
},
{
R"cpp(// const auto& in function return
return Bar();
}
)cpp",
- "struct Bar",
+ "codeblock(cpp) [\n"
+ "struct Bar\n"
+ "]",
},
{
R"cpp(// decltype(auto) in function return
return Bar();
}
)cpp",
- "struct Bar",
+ "codeblock(cpp) [\n"
+ "struct Bar\n"
+ "]",
},
{
R"cpp(// decltype(auto) reference in function return
return (a);
}
)cpp",
- "int &",
+ "codeblock(cpp) [\n"
+ "int &\n"
+ "]",
},
{
R"cpp(// decltype lvalue reference
^decltype(I) J = I;
}
)cpp",
- "int",
+ "codeblock(cpp) [\n"
+ "int\n"
+ "]",
},
{
R"cpp(// decltype lvalue reference
^decltype(K) J = I;
}
)cpp",
- "int &",
+ "codeblock(cpp) [\n"
+ "int &\n"
+ "]",
},
{
R"cpp(// decltype lvalue reference parenthesis
^decltype((I)) J = I;
}
)cpp",
- "int &",
+ "codeblock(cpp) [\n"
+ "int &\n"
+ "]",
},
{
R"cpp(// decltype rvalue reference
^decltype(static_cast<int&&>(I)) J = static_cast<int&&>(I);
}
)cpp",
- "int &&",
+ "codeblock(cpp) [\n"
+ "int &&\n"
+ "]",
},
{
R"cpp(// decltype rvalue reference function call
^decltype(bar()) J = bar();
}
)cpp",
- "int &&",
+ "codeblock(cpp) [\n"
+ "int &&\n"
+ "]",
},
{
R"cpp(// decltype of function with trailing return type.
^decltype(test()) i = test();
}
)cpp",
- "struct Bar",
+ "codeblock(cpp) [\n"
+ "struct Bar\n"
+ "]",
},
{
R"cpp(// decltype of var with decltype.
^decltype(J) K = J;
}
)cpp",
- "int",
+ "codeblock(cpp) [\n"
+ "int\n"
+ "]",
},
{
R"cpp(// structured binding. Not supported yet
int bar();
^auto (*foo)() = bar;
)cpp",
- "int",
+ "codeblock(cpp) [\n"
+ "int\n"
+ "]",
},
};
auto AST = TU.build();
if (auto H = getHover(AST, T.point(), format::getLLVMStyle())) {
EXPECT_NE("", Test.ExpectedHover) << Test.Input;
- EXPECT_EQ(H->render().value, Test.ExpectedHover.str()) << Test.Input;
+ EXPECT_EQ(H->present().renderForTests(), Test.ExpectedHover.str())
+ << Test.Input;
} else
EXPECT_EQ("", Test.ExpectedHover.str()) << Test.Input;
}