Index: clangd/ClangdLSPServer.cpp =================================================================== --- clangd/ClangdLSPServer.cpp +++ clangd/ClangdLSPServer.cpp @@ -8,6 +8,7 @@ //===---------------------------------------------------------------------===// #include "ClangdLSPServer.h" +#include "clang/Basic/SourceManager.h" #include "JSONRPCDispatcher.h" #include "ProtocolHandlers.h" @@ -70,7 +71,10 @@ void onCompletion(TextDocumentPositionParams Params, StringRef ID, JSONOutput &Out) override; void onGoToDefinition(TextDocumentPositionParams Params, StringRef ID, - JSONOutput &Out) override; + JSONOutput &Out) override; + void onCodeHover(TextDocumentPositionParams Params, StringRef ID, + JSONOutput &Out) override; + private: ClangdLSPServer &LangServer; @@ -87,7 +91,8 @@ "documentOnTypeFormattingProvider": {"firstTriggerCharacter":"}","moreTriggerCharacter":[]}, "codeActionProvider": true, "completionProvider": {"resolveProvider": false, "triggerCharacters": [".",">"]}, - "definitionProvider": true + "definitionProvider": true, + "hoverProvider": true }}})"); } @@ -185,6 +190,7 @@ Params.textDocument.uri.file, Position{Params.position.line, Params.position.character}).Value; + std::string Completions; for (const auto &Item : Items) { Completions += CompletionItem::unparse(Item); @@ -200,8 +206,7 @@ void ClangdLSPServer::LSPProtocolCallbacks::onGoToDefinition( TextDocumentPositionParams Params, StringRef ID, JSONOutput &Out) { - auto Items = LangServer.Server.findDefinitions( - Params.textDocument.uri.file, + auto Items = LangServer.Server.findDefinitions(Params.textDocument.uri.file, Position{Params.position.line, Params.position.character}).Value; std::string Locations; @@ -216,6 +221,17 @@ 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. @@ -190,7 +192,7 @@ std::vector formatOnType(PathRef File, Position Pos); /// Gets current document contents for \p File. \p File must point to a - /// currently tracked file. + /// currently tracked file /// FIXME(ibiryukov): This function is here to allow offset-to-Position /// conversions in outside code, maybe there's a way to get rid of it. std::string getDocument(PathRef File); @@ -200,7 +202,7 @@ /// \p File. \p File must be in the list of added documents. std::string dumpAST(PathRef File); -private: + private: GlobalCompilationDatabase &CDB; DiagnosticsConsumer &DiagConsumer; FileSystemProvider &FSProvider; 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" @@ -16,11 +17,17 @@ #include "llvm/ADT/ArrayRef.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/raw_ostream.h" +#include #include using namespace clang; using namespace clang::clangd; +const char* const DEFAULT_SOURCE_EXTENSIONS [] = { ".cpp", ".c", ".cc", ".cxx", + ".c++", ".C", ".m", ".mm" }; +const char* const DEFAULT_HEADER_EXTENSIONS [] = { ".h", ".hh", ".hpp", ".hxx", + ".inc" }; + namespace { std::vector formatCode(StringRef Code, StringRef Filename, @@ -273,16 +280,41 @@ return DumpFuture.get(); } -Tagged> -ClangdServer::findDefinitions(PathRef File, Position Pos) { +Tagged> ClangdServer::findDefinitions(PathRef File, + Position Pos) { auto FileContents = DraftMgr.getDraft(File); - assert(FileContents.Draft && "findDefinitions is called for non-added document"); + assert(FileContents.Draft && + "findDefinitions is called for non-added document"); std::vector Result; auto TaggedFS = FSProvider.getTaggedFileSystem(File); - Units.runOnUnit(File, *FileContents.Draft, ResourceDir, CDB, PCHs, - TaggedFS.Value, [&](ClangdUnit &Unit) { - Result = Unit.findDefinitions(Pos); - }); + Units.runOnUnit( + File, *FileContents.Draft, ResourceDir, CDB, PCHs, TaggedFS.Value, + [&](ClangdUnit &Unit) { Result = Unit.findDefinitions(Pos); }); 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,10 +69,29 @@ /// 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->getSourceManager().getFileEntryForID(L.unitFileID); + StringRef ref = Unit->getSourceManager().getBufferData(L.unitFileID); + 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)); + //return ref; + 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; std::shared_ptr PCHs; + SourceLocation getBeginningOfIdentifier(const Position& Pos, const FileEntry* FE) const; }; Index: clangd/ClangdUnit.cpp =================================================================== --- clangd/ClangdUnit.cpp +++ clangd/ClangdUnit.cpp @@ -13,8 +13,8 @@ #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/CompilerInvocation.h" #include "clang/Frontend/Utils.h" -#include "clang/Index/IndexingAction.h" #include "clang/Index/IndexDataConsumer.h" +#include "clang/Index/IndexingAction.h" #include "clang/Lex/Lexer.h" #include "clang/Lex/MacroInfo.h" #include "clang/Lex/Preprocessor.h" @@ -275,10 +275,12 @@ std::vector DeclarationLocations; const SourceLocation &SearchedLocation; ASTUnit &Unit; + public: DeclarationLocationsFinder(raw_ostream &OS, - const SourceLocation &SearchedLocation, ASTUnit &Unit) : - SearchedLocation(SearchedLocation), Unit(Unit) {} + const SourceLocation &SearchedLocation, + ASTUnit &Unit) + : SearchedLocation(SearchedLocation), Unit(Unit) {} std::vector takeLocations() { // Don't keep the same location multiple times. @@ -290,10 +292,11 @@ return std::move(DeclarationLocations); } - bool handleDeclOccurence(const Decl* D, index::SymbolRoleSet Roles, - ArrayRef Relations, FileID FID, unsigned Offset, - index::IndexDataConsumer::ASTNodeInfo ASTNode) override - { + bool + handleDeclOccurence(const Decl *D, index::SymbolRoleSet Roles, + ArrayRef Relations, FileID FID, + unsigned Offset, + index::IndexDataConsumer::ASTNodeInfo ASTNode) override { if (isSearchedLocation(FID, Offset)) { addDeclarationLocation(D->getSourceRange()); } @@ -303,16 +306,19 @@ private: bool isSearchedLocation(FileID FID, unsigned Offset) const { const SourceManager &SourceMgr = Unit.getSourceManager(); - return SourceMgr.getFileOffset(SearchedLocation) == Offset - && SourceMgr.getFileID(SearchedLocation) == FID; + + return SourceMgr.getFileOffset(SearchedLocation) == Offset && + SourceMgr.getFileID(SearchedLocation) == FID; } - void addDeclarationLocation(const SourceRange& ValSourceRange) { - const SourceManager& SourceMgr = Unit.getSourceManager(); - const LangOptions& LangOpts = Unit.getLangOpts(); + void addDeclarationLocation(const SourceRange &ValSourceRange) { + const SourceManager &SourceMgr = Unit.getSourceManager(); + const LangOptions &LangOpts = Unit.getLangOpts(); SourceLocation LocStart = ValSourceRange.getBegin(); SourceLocation LocEnd = Lexer::getLocForEndOfToken(ValSourceRange.getEnd(), - 0, SourceMgr, LangOpts); + 0, SourceMgr, LangOpts); + + Position Begin; Begin.line = SourceMgr.getSpellingLineNumber(LocStart) - 1; Begin.character = SourceMgr.getSpellingColumnNumber(LocStart) - 1; @@ -321,6 +327,7 @@ End.character = SourceMgr.getSpellingColumnNumber(LocEnd) - 1; Range R = {Begin, End}; Location L; + L.unitFileID = SourceMgr.getFileID(LocStart); L.uri = URI::fromFile( SourceMgr.getFilename(SourceMgr.getSpellingLoc(LocStart))); L.range = R; @@ -331,11 +338,11 @@ // Also handle possible macro at the searched location. Token Result; if (!Lexer::getRawToken(SearchedLocation, Result, Unit.getSourceManager(), - Unit.getASTContext().getLangOpts(), false)) { + Unit.getASTContext().getLangOpts(), false)) { if (Result.is(tok::raw_identifier)) { Unit.getPreprocessor().LookUpIdentifierInfo(Result); } - IdentifierInfo* IdentifierInfo = Result.getIdentifierInfo(); + IdentifierInfo *IdentifierInfo = Result.getIdentifierInfo(); if (IdentifierInfo && IdentifierInfo->hadMacroDefinition()) { std::pair DecLoc = Unit.getSourceManager().getDecomposedExpansionLoc(SearchedLocation); @@ -345,13 +352,12 @@ Unit.getSourceManager().getFileEntryForID(DecLoc.first), DecLoc.second - 1); MacroDefinition MacroDef = - Unit.getPreprocessor().getMacroDefinitionAtLoc(IdentifierInfo, - BeforeSearchedLocation); - MacroInfo* MacroInf = MacroDef.getMacroInfo(); + Unit.getPreprocessor().getMacroDefinitionAtLoc( + IdentifierInfo, BeforeSearchedLocation); + MacroInfo *MacroInf = MacroDef.getMacroInfo(); if (MacroInf) { - addDeclarationLocation( - SourceRange(MacroInf->getDefinitionLoc(), - MacroInf->getDefinitionEndLoc())); + addDeclarationLocation(SourceRange(MacroInf->getDefinitionLoc(), + MacroInf->getDefinitionEndLoc())); } } } @@ -378,11 +384,11 @@ } SourceLocation ClangdUnit::getBeginningOfIdentifier(const Position &Pos, - const FileEntry *FE) const { + const FileEntry *FE) const { // The language server protocol uses zero-based line and column numbers. // Clang uses one-based numbers. - SourceLocation InputLocation = Unit->getLocation(FE, Pos.line + 1, - Pos.character + 1); + SourceLocation InputLocation = + Unit->getLocation(FE, Pos.line + 1, Pos.character + 1); if (Pos.character == 0) { return InputLocation; @@ -396,8 +402,8 @@ // token. If so, Take the beginning of this token. // (It should be the same identifier because you can't have two adjacent // identifiers without another token in between.) - SourceLocation PeekBeforeLocation = Unit->getLocation(FE, Pos.line + 1, - Pos.character); + SourceLocation PeekBeforeLocation = + Unit->getLocation(FE, Pos.line + 1, Pos.character); const SourceManager &SourceMgr = Unit->getSourceManager(); Token Result; if (Lexer::getRawToken(PeekBeforeLocation, Result, SourceMgr, @@ -408,7 +414,7 @@ if (Result.is(tok::raw_identifier)) { return Lexer::GetBeginningOfToken(PeekBeforeLocation, SourceMgr, - Unit->getASTContext().getLangOpts()); + Unit->getASTContext().getLangOpts()); } return InputLocation; 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 @@ -102,6 +103,7 @@ /// The text document's URI. URI uri; Range range; + FileID unitFileID; friend bool operator==(const Location &LHS, const Location &RHS) { return LHS.uri == RHS.uri && LHS.range == RHS.range; @@ -304,7 +306,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 @@ -47,7 +47,10 @@ virtual void onCompletion(TextDocumentPositionParams Params, StringRef ID, JSONOutput &Out) = 0; virtual void onGoToDefinition(TextDocumentPositionParams Params, StringRef ID, - JSONOutput &Out) = 0; + 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 @@ -196,7 +196,6 @@ Output.log("Failed to decode TextDocumentPositionParams!\n"); return; } - Callbacks.onGoToDefinition(*TDPP, ID, Output); } @@ -204,6 +203,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, @@ -236,7 +253,13 @@ llvm::make_unique(Out, Callbacks)); Dispatcher.registerHandler( "textDocument/completion", + // } llvm::make_unique(Out, Callbacks)); - Dispatcher.registerHandler("textDocument/definition", + Dispatcher.registerHandler( + "textDocument/definition", llvm::make_unique(Out, Callbacks)); + Dispatcher.registerHandler( + "textDocument/hover", + llvm::make_unique(Out, Callbacks)); + } Index: clangd/clients/clangd-vscode/.vscode/launch.json =================================================================== --- clangd/clients/clangd-vscode/.vscode/launch.json +++ clangd/clients/clangd-vscode/.vscode/launch.json @@ -2,6 +2,14 @@ { "version": "0.1.0", "configurations": [ + { + "name": "(gdb) Attach", + "type": "cppdbg", + "request": "attach", + "program": "/home/ewilenr/theiaEclipse/eclipse/git/llvm/build/bin/clangd", + "processId": "${command:pickProcess}", + "MIMode": "gdb" + }, { "name": "Launch Extension", "type": "extensionHost", 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 @@ -54,6 +55,7 @@ console.log('Clang Language Server is now active!'); + clangdClient.trace = vscodejsonrpc.Trace.Verbose; const disposable = clangdClient.start(); context.subscriptions.push(disposable, vscode.commands.registerCommand('clangd.applyFix', applyTextEdits)); 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"}