Index: clangd/ClangdLSPServer.cpp =================================================================== --- clangd/ClangdLSPServer.cpp +++ clangd/ClangdLSPServer.cpp @@ -71,6 +71,8 @@ JSONOutput &Out) override; void onGoToDefinition(TextDocumentPositionParams Params, StringRef ID, JSONOutput &Out) override; + void onCodeHover(TextDocumentPositionParams Params, StringRef ID, + JSONOutput &Out) override; private: ClangdLSPServer &LangServer; @@ -87,7 +89,8 @@ "documentOnTypeFormattingProvider": {"firstTriggerCharacter":"}","moreTriggerCharacter":[]}, "codeActionProvider": true, "completionProvider": {"resolveProvider": false, "triggerCharacters": [".",">"]}, - "definitionProvider": true + "definitionProvider": true, + "hoverProvider": true }}})"); } @@ -216,6 +219,18 @@ R"(,"result":[)" + Locations + R"(]})"); } +void ClangdLSPServer::LSPProtocolCallbacks::onCodeHover( + TextDocumentPositionParams Params, StringRef ID, JSONOutput &Out) { + + Hover H = LangServer.Server.findHover( + Params.textDocument.uri.file, + Position{Params.position.line, Params.position.character}) .Value; + + Out.writeMessage( + R"({"jsonrpc":"2.0","id":)" + ID.str() + + R"(,"result":)" + Hover::unparse(H) + R"(})"); +} + ClangdLSPServer::ClangdLSPServer(JSONOutput &Out, bool RunSynchronously) : Out(Out), DiagConsumer(*this), Server(CDB, DiagConsumer, FSProvider, RunSynchronously) {} Index: clangd/ClangdServer.h =================================================================== --- clangd/ClangdServer.h +++ clangd/ClangdServer.h @@ -182,6 +182,8 @@ /// Get definition of symbol at a specified \p Line and \p Column in \p File. Tagged> findDefinitions(PathRef File, Position Pos); + Tagged findHover(PathRef File, Position Pos); + /// Run formatting for \p Rng inside \p File. std::vector formatRange(PathRef File, Range Rng); /// Run formatting for the whole \p File. Index: clangd/ClangdServer.cpp =================================================================== --- clangd/ClangdServer.cpp +++ clangd/ClangdServer.cpp @@ -8,6 +8,7 @@ //===-------------------------------------------------------------------===// #include "ClangdServer.h" +#include "Protocol.h" #include "clang/Format/Format.h" #include "clang/Frontend/ASTUnit.h" #include "clang/Frontend/CompilerInstance.h" @@ -286,3 +287,28 @@ }); return make_tagged(std::move(Result), TaggedFS.Tag); } + +Tagged ClangdServer::findHover(PathRef File, Position Pos) { + auto FileContents = DraftMgr.getDraft(File); + assert(FileContents.Draft && + "findHover is called for non-added document"); + + + MarkedString MS = MarkedString("", ""); + Range R; + Hover FinalHover(MS, R); + auto TaggedFS = FSProvider.getTaggedFileSystem(File); + Units.runOnUnit(File, *FileContents.Draft, ResourceDir, CDB, PCHs, + TaggedFS.Value, [&](ClangdUnit &Unit) { + std::vector Result = Unit.findDefinitions(Pos); + if (!Result.empty()) { + Location FoundLocation = Result[0]; + FinalHover = Unit.getHover(FoundLocation); + } + }); + + + return make_tagged(std::move(FinalHover), TaggedFS.Tag); +} + + Index: clangd/ClangdUnit.h =================================================================== --- clangd/ClangdUnit.h +++ clangd/ClangdUnit.h @@ -69,6 +69,24 @@ /// unserialized Decls, so use with care. void dumpAST(llvm::raw_ostream &OS) const; + Hover getHover(Location L){ + + MarkedString MS = MarkedString("", ""); + Range R; + const FileEntry *FE = Unit->getFileManager().getFile(L.uri.file); + FileID id = Unit->getSourceManager().translateFile(FE); + StringRef ref = Unit->getSourceManager().getBufferData(id); + start = Unit->getSourceManager().getFileOffset(Unit->getSourceManager().translateFileLineCol(FE, L.range.start.line + 1, L.range.start.character + 1)); + end = Unit->getSourceManager().getFileOffset(Unit->getSourceManager().translateFileLineCol(FE, L.range.end.line + 1, L.range.end.character + 1)); + ref = ref.slice(start, end); + MS = MarkedString("C++", ref); + R = L.range; + Hover H(MS, R); + return H; + } + unsigned start; + unsigned end; + private: Path FileName; std::unique_ptr Unit; Index: clangd/Protocol.h =================================================================== --- clangd/Protocol.h +++ clangd/Protocol.h @@ -23,6 +23,7 @@ #include "llvm/ADT/Optional.h" #include "llvm/Support/YAMLParser.h" +#include "clang/Basic/SourceLocation.h" #include #include @@ -304,7 +305,59 @@ parse(llvm::yaml::MappingNode *Params); }; -/// The kind of a completion entry. +struct MarkedString { + /** + * MarkedString can be used to render human readable text. It is either a + * markdown string + * or a code-block that provides a language and a code snippet. The language + * identifier + * is sematically equal to the optional language identifier in fenced code + * blocks in GitHub + * issues. See + * https://help.github.com/articles/creating-and-highlighting-code-blocks/#syntax-highlighting + * + * The pair of a language and a value is an equivalent to markdown: + * ```${language} + * ${value} + * ``` + * + * Note that markdown strings will be sanitized - that means html will be + * escaped. + */ + + MarkedString(std::string markdown) + : markdownString(markdown), codeBlockLanguage(""), codeBlockValue("") {} + + MarkedString(std::string blockLanguage, std::string blockValue) + : markdownString(""), codeBlockLanguage(blockLanguage), + codeBlockValue(blockValue) {} + + std::string markdownString; + std::string codeBlockLanguage; + std::string codeBlockValue; + + static std::string unparse(const MarkedString &MS); +}; + +struct Hover { + + Hover(MarkedString ms, Range r) : contents(ms), range(r) {} + + /** + * The hover's content + */ + MarkedString contents; + + /** + * An optional range is a range inside a text document + * that is used to visualize a hover, e.g. by changing the background color. + */ + Range range; + + static std::string unparse(const Hover &H); +}; + +/// The kind of a completion entry.std::string* enum class CompletionItemKind { Missing = 0, Text = 1, Index: clangd/Protocol.cpp =================================================================== --- clangd/Protocol.cpp +++ clangd/Protocol.cpp @@ -746,3 +746,26 @@ Result.back() = '}'; return Result; } + +std::string Hover::unparse(const Hover &H) { + std::string Result; + llvm::raw_string_ostream(Result) << llvm::format( + R"({"contents": %s, "range": %s})", MarkedString::unparse(H.contents).c_str(), + Range::unparse(H.range).c_str()); + return Result; +} + +std::string MarkedString::unparse(const MarkedString &MS) { + std::string Result; + if (MS.markdownString != "") + { + llvm::raw_string_ostream(Result) << llvm::format(R"({"markdown": "%s"})", MS.markdownString.c_str()); + } + else + { + + llvm::raw_string_ostream(Result) << llvm::format(R"({"language": "%s", "value": "%s"})", (llvm::yaml::escape(MS.codeBlockLanguage)).c_str(), (llvm::yaml::escape(MS.codeBlockValue)).c_str()); + } + + return Result; +} Index: clangd/ProtocolHandlers.h =================================================================== --- clangd/ProtocolHandlers.h +++ clangd/ProtocolHandlers.h @@ -48,6 +48,8 @@ JSONOutput &Out) = 0; virtual void onGoToDefinition(TextDocumentPositionParams Params, StringRef ID, JSONOutput &Out) = 0; + virtual void onCodeHover(TextDocumentPositionParams Params, StringRef ID, + JSONOutput &Out) = 0; }; void regiterCallbackHandlers(JSONRPCDispatcher &Dispatcher, JSONOutput &Out, Index: clangd/ProtocolHandlers.cpp =================================================================== --- clangd/ProtocolHandlers.cpp +++ clangd/ProtocolHandlers.cpp @@ -204,6 +204,24 @@ ProtocolCallbacks &Callbacks; }; +struct CodeHoverHandler : Handler { + CodeHoverHandler(JSONOutput &Output, ProtocolCallbacks &Callbacks) + : Handler(Output), Callbacks(Callbacks) {} + + void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override { + auto TDPP = TextDocumentPositionParams::parse(Params); + if (!TDPP) { + Output.log("Failed to decode TextDocumentPositionParams!\n"); + return; + } + + Callbacks.onCodeHover(*TDPP, ID, Output); + } + +private: + ProtocolCallbacks &Callbacks; +}; + } // namespace void clangd::regiterCallbackHandlers(JSONRPCDispatcher &Dispatcher, @@ -239,4 +257,7 @@ llvm::make_unique(Out, Callbacks)); Dispatcher.registerHandler("textDocument/definition", llvm::make_unique(Out, Callbacks)); + Dispatcher.registerHandler( + "textDocument/hover", + llvm::make_unique(Out, Callbacks)); } Index: clangd/clients/clangd-vscode/src/extension.ts =================================================================== --- clangd/clients/clangd-vscode/src/extension.ts +++ clangd/clients/clangd-vscode/src/extension.ts @@ -1,5 +1,6 @@ import * as vscode from 'vscode'; import * as vscodelc from 'vscode-languageclient'; +import * as vscodejsonrpc from 'vscode-jsonrpc'; /** * Method to get workspace configuration option Index: test/clangd/hover.test =================================================================== --- /dev/null +++ test/clangd/hover.test @@ -0,0 +1,26 @@ +# RUN: clangd -run-synchronously < %s | FileCheck %s +# It is absolutely vital that this file has CRLF line endings. +# +Content-Length: 125 + +{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}} + +Content-Length: 172 + +{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///main.cpp","languageId":"cpp","version":1,"text":"int main() {\nint a;\na;\n}\n"}}} + +Content-Length: 143 + +{"jsonrpc":"2.0","id":1,"method":"textDocument/hover","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":0,"character":5}}} +# Go to local variable +# CHECK: {"jsonrpc":"2.0","id":1,"result":{"contents": {"language": "C++", "value": "int main() {\nint a;\na;\n}"}, "range": {"start": {"line": 0, "character": 0}, "end": {"line": 3, "character": 1}}}} + +Content-Length: 143 + +{"jsonrpc":"2.0","id":1,"method":"textDocument/hover","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":1,"character":5}}} +# Go to local variable +# CHECK: {"jsonrpc":"2.0","id":1,"result":{"contents": {"language": "C++", "value": "int a"}, "range": {"start": {"line": 1, "character": 0}, "end": {"line": 1, "character": 5}}}} + +Content-Length: 44 + +{"jsonrpc":"2.0","id":3,"method":"shutdown"}