Nebiroth updated this revision to Diff 127413.
Nebiroth marked 3 inline comments as done.
Nebiroth added a comment.

  findHover properly returns an error
  Removed many cases of bad merging
  Separated getHover in two functions
  Minor indenting and NIT fixes


Repository:
  rCTE Clang Tools Extra

https://reviews.llvm.org/D35894

Files:
  clangd/ClangdLSPServer.cpp
  clangd/ClangdLSPServer.h
  clangd/ClangdServer.cpp
  clangd/ClangdServer.h
  clangd/ClangdUnit.cpp
  clangd/ClangdUnit.h
  clangd/Protocol.cpp
  clangd/Protocol.h
  clangd/ProtocolHandlers.cpp
  clangd/ProtocolHandlers.h
  test/clangd/hover.test
  test/clangd/initialize-params-invalid.test
  test/clangd/initialize-params.test

Index: test/clangd/initialize-params.test
===================================================================
--- test/clangd/initialize-params.test
+++ test/clangd/initialize-params.test
@@ -31,6 +31,7 @@
 # CHECK-NEXT:          "clangd.applyFix"
 # CHECK-NEXT:        ]
 # CHECK-NEXT:      },
+# CHECK-NEXT:      "hoverProvider": true,
 # CHECK-NEXT:      "renameProvider": true,
 # CHECK-NEXT:      "signatureHelpProvider": {
 # CHECK-NEXT:        "triggerCharacters": [
Index: test/clangd/initialize-params-invalid.test
===================================================================
--- test/clangd/initialize-params-invalid.test
+++ test/clangd/initialize-params-invalid.test
@@ -31,6 +31,7 @@
 # CHECK-NEXT:          "clangd.applyFix"
 # CHECK-NEXT:        ]
 # CHECK-NEXT:      },
+# CHECK-NEXT:      "hoverProvider": true,
 # CHECK-NEXT:      "renameProvider": true,
 # CHECK-NEXT:      "signatureHelpProvider": {
 # CHECK-NEXT:        "triggerCharacters": [
Index: test/clangd/hover.test
===================================================================
--- /dev/null
+++ test/clangd/hover.test
@@ -0,0 +1,56 @@
+# 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: 611
+
+{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///main.cpp","languageId":"cpp","version":1,"text":"#define MACRO 1\nnamespace ns1 {\nint test = 5;\nstruct MyClass {\nint xasd;\nvoid anotherOperation() {\n}\nstatic int foo(MyClass*) {\nreturn 0;\n}\n\n};}\n//comments test\ntemplate<class T>\nT templateTest(T foo) {\nreturn foo;}\ntemplate<classT>\nclass classTemplateTest {\npublic: T test;};\nint main() {\nint a;\na;\nint b = ns1::test;\nns1::MyClass* Params;\nParams->anotherOperation();\nMACRO;\nint temp = 5;\ntemplateTest(temp);classTemplateTest<int> test;}\n"}}}
+
+Content-Length: 144
+
+{"jsonrpc":"2.0","id":1,"method":"textDocument/hover","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":0,"character":12}}}
+# CHECK: {"id":1,"jsonrpc":"2.0","result":{"contents":[{"language":"C++","value":"#define MACRO 1"}],"range":{"end":{"character":15,"line":0},"start":{"character":8,"line":0}}}}
+
+Content-Length: 144
+
+{"jsonrpc":"2.0","id":1,"method":"textDocument/hover","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":21,"character":1}}}
+# CHECK: {"id":1,"jsonrpc":"2.0","result":{"contents":[{"language":"C++","value":"int a"}],"range":{"end":{"character":5,"line":20},"start":{"character":0,"line":20}}}}
+
+Content-Length: 145
+
+{"jsonrpc":"2.0","id":1,"method":"textDocument/hover","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":22,"character":15}}}
+# CHECK: {"id":1,"jsonrpc":"2.0","result":{"contents":[{"language":"C++","value":"In ns1"},{"language":"C++","value":"int test = 5"}],"range":{"end":{"character":12,"line":2},"start":{"character":0,"line":2}}}}
+
+Content-Length: 145
+
+{"jsonrpc":"2.0","id":1,"method":"textDocument/hover","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":23,"character":10}}}
+# CHECK: {"id":1,"jsonrpc":"2.0","result":{"contents":[{"language":"C++","value":"In ns1"},{"language":"C++","value":"struct MyClass {"}],"range":{"end":{"character":16,"line":3},"start":{"character":0,"line":3}}}}
+
+Content-Length: 145
+
+{"jsonrpc":"2.0","id":1,"method":"textDocument/hover","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":24,"character":13}}}
+# CHECK: {"id":1,"jsonrpc":"2.0","result":{"contents":[{"language":"C++","value":"In ns1::MyClass"},{"language":"C++","value":"void anotherOperation() {"}],"range":{"end":{"character":25,"line":5},"start":{"character":0,"line":5}}}}
+
+Content-Length: 144
+
+{"jsonrpc":"2.0","id":1,"method":"textDocument/hover","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":25,"character":1}}}
+# CHECK: {"id":1,"jsonrpc":"2.0","result":{"contents":[{"language":"C++","value":"#define MACRO 1"}],"range":{"end":{"character":15,"line":0},"start":{"character":8,"line":0}}}}
+
+Content-Length: 144
+
+{"jsonrpc":"2.0","id":1,"method":"textDocument/hover","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":26,"character":8}}}
+# CHECK: {"id":1,"jsonrpc":"2.0","result":{"contents":[{"language":"C++","value":"int temp = 5"}],"range":{"end":{"character":12,"line":26},"start":{"character":0,"line":26}}}}
+
+Content-Length: 144
+
+{"jsonrpc":"2.0","id":1,"method":"textDocument/hover","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":27,"character":1}}}
+# CHECK: {"id":1,"jsonrpc":"2.0","result":{"contents":[{"language":"C++","value":"//comments test\ntemplate<class T>\nT templateTest(T foo) {"}],"range":{"end":{"character":23,"line":14},"start":{"character":0,"line":12}}}}
+
+Content-Length: 44
+
+{"jsonrpc":"2.0","id":3,"method":"shutdown"}
+
+
Index: clangd/ProtocolHandlers.h
===================================================================
--- clangd/ProtocolHandlers.h
+++ clangd/ProtocolHandlers.h
@@ -57,6 +57,7 @@
   virtual void onRename(Ctx C, RenameParams &Parames) = 0;
   virtual void onDocumentHighlight(Ctx C,
                                    TextDocumentPositionParams &Params) = 0;
+  virtual void onCodeHover(Ctx C, TextDocumentPositionParams &Params) = 0;
 };
 
 void registerCallbackHandlers(JSONRPCDispatcher &Dispatcher, JSONOutput &Out,
Index: clangd/ProtocolHandlers.cpp
===================================================================
--- clangd/ProtocolHandlers.cpp
+++ clangd/ProtocolHandlers.cpp
@@ -69,6 +69,7 @@
   Register("textDocument/switchSourceHeader",
            &ProtocolCallbacks::onSwitchSourceHeader);
   Register("textDocument/rename", &ProtocolCallbacks::onRename);
+  Register("textDocument/hover", &ProtocolCallbacks::onCodeHover);
   Register("workspace/didChangeWatchedFiles", &ProtocolCallbacks::onFileEvent);
   Register("workspace/executeCommand", &ProtocolCallbacks::onCommand);
   Register("textDocument/documentHighlight",
Index: clangd/Protocol.h
===================================================================
--- clangd/Protocol.h
+++ clangd/Protocol.h
@@ -397,6 +397,41 @@
 };
 bool fromJSON(const json::Expr &, TextDocumentPositionParams &);
 
+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.
+
+  std::string markdownString;
+  std::string language;
+  std::string codeBlockValue;
+};
+json::Expr toJSON(const MarkedString &MS);
+
+struct Hover {
+  /// The hover's content
+  std::vector<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.
+  llvm::Optional<Range> range;
+};
+json::Expr toJSON(const Hover &H);
+
 /// The kind of a completion entry.
 enum class CompletionItemKind {
   Missing = 0,
Index: clangd/Protocol.cpp
===================================================================
--- clangd/Protocol.cpp
+++ clangd/Protocol.cpp
@@ -285,6 +285,27 @@
          O.map("position", R.position);
 }
 
+json::Expr toJSON(const MarkedString &MS) {
+  return json::obj{
+        {"language", MS.language},
+        {"value", MS.codeBlockValue},
+    };
+}
+
+
+json::Expr toJSON(const Hover &H) {
+  if (H.range.hasValue()) {
+    return json::obj{
+        {"contents", json::ary(H.contents)},
+        {"range", H.range.getValue()},
+    };
+  }
+
+  return json::obj{
+      {"contents", json::ary(H.contents)},
+  };
+}
+
 json::Expr toJSON(const CompletionItem &CI) {
   assert(!CI.label.empty() && "completion item label is required");
   json::obj Result{{"label", CI.label}};
Index: clangd/ClangdUnit.h
===================================================================
--- clangd/ClangdUnit.h
+++ clangd/ClangdUnit.h
@@ -265,6 +265,8 @@
 std::vector<DocumentHighlight>
 findDocumentHighlights(const Context &Ctx, ParsedAST &AST, Position Pos);
 
+Hover getHover(const Context &Ctx, ParsedAST &AST, Position Pos);
+
 /// For testing/debugging purposes. Note that this method deserializes all
 /// unserialized Decls, so use with care.
 void dumpAST(ParsedAST &AST, llvm::raw_ostream &OS);
Index: clangd/ClangdUnit.cpp
===================================================================
--- clangd/ClangdUnit.cpp
+++ clangd/ClangdUnit.cpp
@@ -26,6 +26,7 @@
 #include "llvm/ADT/ArrayRef.h"
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/Support/CrashRecoveryContext.h"
+#include "llvm/Support/Errc.h"
 #include "llvm/Support/Format.h"
 #include <algorithm>
 #include <chrono>
@@ -280,7 +281,7 @@
   return Mgr.getMacroArgExpandedLocation(InputLoc);
 }
 
-/// Finds declarations locations that a given source location refers to.
+/// Finds declarations and macros that a given source location refers to.
 class DeclarationAndMacrosFinder : public index::IndexDataConsumer {
   std::vector<const Decl *> Decls;
   std::vector<const MacroInfo *> MacroInfos;
@@ -308,7 +309,7 @@
     std::sort(MacroInfos.begin(), MacroInfos.end());
     auto Last = std::unique(MacroInfos.begin(), MacroInfos.end());
     MacroInfos.erase(Last, MacroInfos.end());
-    return std::move(MacroInfos);
+    return MacroInfos;
   }
 
   bool
@@ -424,18 +425,88 @@
   }
 };
 
+/// Returns a NamedDecl that could be used to print the qualifier.
+const Decl *getScopeOfDecl(const Decl *D) {
+  // Code from NamedDecl::printQualifiedName, we should probably move it into
+  const DeclContext *Ctx = D->getDeclContext();
+
+  // For ObjC methods, look through categories and use the interface as context.
+  if (auto *MD = dyn_cast<ObjCMethodDecl>(D))
+    if (auto *ID = MD->getClassInterface())
+      Ctx = ID;
+
+  if (Ctx->isFunctionOrMethod()) {
+    /// This is a function-local entity.
+    return nullptr;
+  }
+
+  return D;
+}
+
+std::string printScopeOfDecl(const Decl *Scope, const LangOptions &LangOpts) {
+  if (Scope) {
+    if (isa<TranslationUnitDecl>(Scope))
+      return "global namespace";
+
+    if (isa<NamedDecl>(Scope)) {
+      PrintingPolicy WithScopePP(LangOpts);
+      std::string WithScopeBuf;
+      llvm::raw_string_ostream WithScopeOS(WithScopeBuf);
+      dyn_cast<NamedDecl>(Scope)->printQualifiedName(WithScopeOS, WithScopePP);
+      std::string ResWithScope = WithScopeOS.str();
+      if (!ResWithScope.empty()) {
+        std::string Res;
+        // Get non-qualified name
+        std::string PrintedNameBuf;
+        llvm::raw_string_ostream PrintedNameOS(PrintedNameBuf);
+        dyn_cast<NamedDecl>(Scope)->printName(PrintedNameOS);
+        auto Last = ResWithScope.rfind(PrintedNameOS.str());
+        if (Last != std::string::npos) {
+          Res = ResWithScope.substr(0, Last);
+          if (Res.length() > 2 &&
+              Res.substr(Res.length() - 2, Res.length()) == "::")
+            Res = Res.substr(0, Res.length() - 2);
+        }
+        return Res;
+      }
+    }
+  }
+  return "";
+}
+
+/// Returns the file buffer data that the given SourceRange points to.
+llvm::Expected<StringRef> getBufferDataFromSourceRange(ParsedAST &AST,
+                                                       Location L) {
+  StringRef Ref;
+  const FileEntry *F =
+      AST.getASTContext().getSourceManager().getFileManager().getFile(
+          L.uri.file);
+  if (!F)
+    return "";
+
+  FileID FID = AST.getASTContext().getSourceManager().translateFile(F);
+  Ref = AST.getASTContext().getSourceManager().getBufferData(FID);
+  unsigned Start = AST.getASTContext().getSourceManager().getFileOffset(
+      AST.getASTContext().getSourceManager().translateFileLineCol(
+          F, L.range.start.line + 1, L.range.start.character + 1));
+  unsigned End = AST.getASTContext().getSourceManager().getFileOffset(
+      AST.getASTContext().getSourceManager().translateFileLineCol(
+          F, L.range.end.line + 1, L.range.end.character + 1));
+  Ref = Ref.slice(Start, End);
+  return Ref;
+}
 } // namespace
 
-llvm::Optional<Location>
+llvm::ErrorOr<Location>
 getDeclarationLocation(ParsedAST &AST, const SourceRange &ValSourceRange) {
   const SourceManager &SourceMgr = AST.getASTContext().getSourceManager();
   const LangOptions &LangOpts = AST.getASTContext().getLangOpts();
-  SourceLocation LocStart = ValSourceRange.getBegin();
-
+  SourceLocation LocStart =
+      SourceMgr.getExpansionLoc(ValSourceRange.getBegin());
   const FileEntry *F =
       SourceMgr.getFileEntryForID(SourceMgr.getFileID(LocStart));
   if (!F)
-    return llvm::None;
+    return llvm::errc::no_such_file_or_directory;
   SourceLocation LocEnd = Lexer::getLocForEndOfToken(ValSourceRange.getEnd(), 0,
                                                      SourceMgr, LangOpts);
   Position Begin;
@@ -529,6 +600,128 @@
   return DocHighlightsFinder->takeHighlights();
 }
 
+Hover getHoverContents(const Decl *D, ParsedAST &AST) {
+  const Decl *LocationDecl = D;
+  std::vector<MarkedString> HoverContents;
+
+  // Compute scope as the first "section" of the hover.
+  if (const NamedDecl *NamedD = dyn_cast<NamedDecl>(LocationDecl)) {
+    auto Scope = printScopeOfDecl(getScopeOfDecl(NamedD),
+                                  AST.getASTContext().getLangOpts());
+    if (!Scope.empty()) {
+      MarkedString Info = {"", "C++", "In " + Scope};
+      HoverContents.push_back(Info);
+    }
+  }
+
+  // Adjust beginning of hover text depending on the presence of templates and
+  // comments.
+  TemplateDecl *TDec = LocationDecl->getDescribedTemplate();
+  const Decl *BeginDecl = TDec ? TDec : LocationDecl;
+  SourceLocation BeginLocation = BeginDecl->getSourceRange().getBegin();
+  RawComment *RC = AST.getASTContext().getRawCommentForDeclNoCache(BeginDecl);
+  if (RC)
+    BeginLocation = RC->getLocStart();
+
+  // Adjust end of hover text for things that have braces/bodies. We don't
+  // want to show the whole definition of a function, class, etc.
+  SourceLocation EndLocation = LocationDecl->getSourceRange().getEnd();
+  if (auto FuncDecl = dyn_cast<FunctionDecl>(LocationDecl)) {
+    if (FuncDecl->isThisDeclarationADefinition() && FuncDecl->getBody())
+      EndLocation = FuncDecl->getBody()->getLocStart();
+  } else if (auto TagDeclaration = dyn_cast<TagDecl>(LocationDecl)) {
+    if (TagDeclaration->isThisDeclarationADefinition())
+      EndLocation = TagDeclaration->getBraceRange().getBegin();
+  } else if (dyn_cast<NamespaceDecl>(LocationDecl)) {
+    SourceLocation BeforeLBraceLoc = Lexer::getLocForEndOfToken(
+        LocationDecl->getLocation(), 0, AST.getASTContext().getSourceManager(),
+        AST.getASTContext().getLangOpts());
+    if (BeforeLBraceLoc.isValid())
+      EndLocation = BeforeLBraceLoc;
+  }
+
+  SourceRange SR(BeginLocation, EndLocation);
+  if (SR.isValid()) {
+    auto L = getDeclarationLocation(AST, SR);
+    if (L) {
+      auto Ref = getBufferDataFromSourceRange(AST, *L);
+      if (Ref) {
+        Hover H;
+        if (AST.getASTContext().getSourceManager().isInMainFile(BeginLocation))
+          H.range = L->range;
+        MarkedString MS = {"", "C++", *Ref};
+        HoverContents.push_back(MS);
+        H.contents = std::move(HoverContents);
+        return H;
+      }
+    }
+  }
+  return Hover();
+}
+
+Hover getHoverContents(const MacroInfo *MacroInf, ParsedAST &AST) {
+  const SourceManager &SourceMgr = AST.getASTContext().getSourceManager();
+  SourceRange SR(MacroInf->getDefinitionLoc(), MacroInf->getDefinitionEndLoc());
+  if (SR.isValid()) {
+    auto L = getDeclarationLocation(AST, SR);
+    if (L) {
+      auto Ref = getBufferDataFromSourceRange(AST, *L);
+      if (Ref) {
+        Hover H;
+        FileID FID =
+            SourceMgr.getFileID(SourceMgr.getExpansionLoc(SR.getBegin()));
+        bool IsInMainFile = FID.isValid() && SourceMgr.getMainFileID() == FID;
+        if (!IsInMainFile) {
+          // Special case when a macro is defined in a preamble, it could
+          // still be in the "main file", below the inclusions. The
+          // SourceManager considers the preamble and the main file as two
+          // different FileIDs but the FileEntries should match.
+          bool IsInPreamble = FID == SourceMgr.getPreambleFileID();
+          if (IsInPreamble)
+            IsInMainFile =
+                SourceMgr.getFileEntryForID(SourceMgr.getMainFileID()) ==
+                SourceMgr.getFileEntryForID(FID);
+        }
+        if (IsInMainFile)
+          H.range = L->range;
+        MarkedString MS = {"", "C++", "#define " + Ref->str()};
+        H.contents.push_back(MS);
+        return H;
+      }
+    }
+  }
+  return Hover();
+}
+
+Hover clangd::getHover(const Context &Ctx, ParsedAST &AST, Position Pos) {
+  const SourceManager &SourceMgr = AST.getASTContext().getSourceManager();
+  const FileEntry *FE = SourceMgr.getFileEntryForID(SourceMgr.getMainFileID());
+  if (!FE)
+    return Hover();
+
+  SourceLocation SourceLocationBeg = getBeginningOfIdentifier(AST, Pos, FE);
+
+  auto DeclMacrosFinder = std::make_shared<DeclarationAndMacrosFinder>(
+      llvm::errs(), SourceLocationBeg, AST.getASTContext(),
+      AST.getPreprocessor());
+  index::IndexingOptions IndexOpts;
+  IndexOpts.SystemSymbolFilter =
+      index::IndexingOptions::SystemSymbolFilterKind::All;
+  IndexOpts.IndexFunctionLocals = true;
+  indexTopLevelDecls(AST.getASTContext(), AST.getTopLevelDecls(),
+                     DeclMacrosFinder, IndexOpts);
+
+  auto Decls = DeclMacrosFinder->takeDecls();
+  if (!Decls.empty() && Decls[0])
+    return getHoverContents(Decls[0], AST);
+
+  auto MacroInfos = DeclMacrosFinder->takeMacroInfos();
+  if (!MacroInfos.empty() && MacroInfos[0])
+    return getHoverContents(MacroInfos[0], AST);
+
+  return Hover();
+}
+
 void ParsedAST::ensurePreambleDeclsDeserialized() {
   if (PreambleDeclsDeserialized || !Preamble)
     return;
@@ -745,6 +938,7 @@
                                               &IgnoreDiagnostics, false);
       CI =
           createInvocationFromCommandLine(ArgStrs, CommandLineDiagsEngine, VFS);
+      CI->LangOpts->CommentOpts.ParseAllComments = true;
       // createInvocationFromCommandLine sets DisableFree.
       CI->getFrontendOpts().DisableFree = false;
     }
Index: clangd/ClangdServer.h
===================================================================
--- clangd/ClangdServer.h
+++ clangd/ClangdServer.h
@@ -285,6 +285,9 @@
   llvm::Expected<Tagged<std::vector<DocumentHighlight>>>
   findDocumentHighlights(const Context &Ctx, PathRef File, Position Pos);
 
+  /// Get code hover for a given position.
+  llvm::Expected<Tagged<Hover>> findHover(const Context &Ctx, PathRef File, Position Pos);
+
   /// Run formatting for \p Rng inside \p File with content \p Code.
   llvm::Expected<tooling::Replacements> formatRange(StringRef Code,
                                                     PathRef File, Range Rng);
Index: clangd/ClangdServer.cpp
===================================================================
--- clangd/ClangdServer.cpp
+++ clangd/ClangdServer.cpp
@@ -428,6 +428,7 @@
   Resources->getAST().get()->runUnderLock([Pos, &Result, &Ctx](ParsedAST *AST) {
     if (!AST)
       return;
+
     Result = clangd::findDefinitions(Ctx, *AST, Pos);
   });
   return make_tagged(std::move(Result), TaggedFS.Tag);
@@ -508,89 +509,109 @@
 
 llvm::Expected<Tagged<std::vector<DocumentHighlight>>>
 ClangdServer::findDocumentHighlights(const Context &Ctx, PathRef File,
-                                     Position Pos) {
-  auto FileContents = DraftMgr.getDraft(File);
-  if (!FileContents.Draft)
-    return llvm::make_error<llvm::StringError>(
-        "findDocumentHighlights called on non-added file",
-        llvm::errc::invalid_argument);
+                                      Position Pos) {
+   auto FileContents = DraftMgr.getDraft(File);
+   if (!FileContents.Draft)
+     return llvm::make_error<llvm::StringError>(
+         "findDocumentHighlights called on non-added file",
+         llvm::errc::invalid_argument);
+
+   auto TaggedFS = FSProvider.getTaggedFileSystem(File);
+
+   std::shared_ptr<CppFile> Resources = Units.getFile(File);
+   if (!Resources)
+     return llvm::make_error<llvm::StringError>(
+         "findDocumentHighlights called on non-added file",
+         llvm::errc::invalid_argument);
+
+   std::vector<DocumentHighlight> Result;
+   llvm::Optional<llvm::Error> Err;
+   Resources->getAST().get()->runUnderLock([Pos, &Ctx, &Err,
+                                            &Result](ParsedAST *AST) {
+     if (!AST) {
+       Err = llvm::make_error<llvm::StringError>("Invalid AST",
+                                                 llvm::errc::invalid_argument);
+       return;
+     }
+     Result = clangd::findDocumentHighlights(Ctx, *AST, Pos);
+   });
+
+   if (Err)
+     return std::move(*Err);
+   return make_tagged(Result, TaggedFS.Tag);
+}
 
+llvm::Expected<Tagged<Hover>> ClangdServer::findHover(const Context &Ctx, PathRef File,
+                                                      Position Pos) {
+  Hover FinalHover;
   auto TaggedFS = FSProvider.getTaggedFileSystem(File);
 
   std::shared_ptr<CppFile> Resources = Units.getFile(File);
   if (!Resources)
     return llvm::make_error<llvm::StringError>(
-        "findDocumentHighlights called on non-added file",
+        "Calling findHover on non-added file",
         llvm::errc::invalid_argument);
-
-  std::vector<DocumentHighlight> Result;
-  llvm::Optional<llvm::Error> Err;
-  Resources->getAST().get()->runUnderLock([Pos, &Ctx, &Err,
-                                           &Result](ParsedAST *AST) {
-    if (!AST) {
-      Err = llvm::make_error<llvm::StringError>("Invalid AST",
-                                                llvm::errc::invalid_argument);
-      return;
-    }
-    Result = clangd::findDocumentHighlights(Ctx, *AST, Pos);
+  Resources->getAST().get()->runUnderLock(
+      [Pos, &FinalHover, &Ctx, this](ParsedAST *AST) {
+        if (!AST)
+          return;
+        FinalHover = clangd::getHover(Ctx, *AST, Pos);
   });
 
-  if (Err)
-    return std::move(*Err);
-  return make_tagged(Result, TaggedFS.Tag);
+  return make_tagged(std::move(FinalHover), TaggedFS.Tag);
 }
 
 std::future<Context> ClangdServer::scheduleReparseAndDiags(
-    Context Ctx, PathRef File, VersionedDraft Contents,
-    std::shared_ptr<CppFile> Resources,
-    Tagged<IntrusiveRefCntPtr<vfs::FileSystem>> TaggedFS) {
-
-  assert(Contents.Draft && "Draft must have contents");
-  UniqueFunction<llvm::Optional<std::vector<DiagWithFixIts>>(const Context &)>
-      DeferredRebuild =
-          Resources->deferRebuild(*Contents.Draft, TaggedFS.Value);
-  std::promise<Context> DonePromise;
-  std::future<Context> DoneFuture = DonePromise.get_future();
-
-  DocVersion Version = Contents.Version;
-  Path FileStr = File;
-  VFSTag Tag = TaggedFS.Tag;
-  auto ReparseAndPublishDiags =
-      [this, FileStr, Version,
-       Tag](UniqueFunction<llvm::Optional<std::vector<DiagWithFixIts>>(
-                const Context &)>
-                DeferredRebuild,
-            std::promise<Context> DonePromise, Context Ctx) -> void {
-    auto Guard = onScopeExit([&]() { DonePromise.set_value(std::move(Ctx)); });
-
-    auto CurrentVersion = DraftMgr.getVersion(FileStr);
-    if (CurrentVersion != Version)
-      return; // This request is outdated
-
-    auto Diags = DeferredRebuild(Ctx);
-    if (!Diags)
-      return; // A new reparse was requested before this one completed.
-
-    // We need to serialize access to resulting diagnostics to avoid calling
-    // `onDiagnosticsReady` in the wrong order.
-    std::lock_guard<std::mutex> DiagsLock(DiagnosticsMutex);
-    DocVersion &LastReportedDiagsVersion = ReportedDiagnosticVersions[FileStr];
-    // FIXME(ibiryukov): get rid of '<' comparison here. In the current
-    // implementation diagnostics will not be reported after version counters'
-    // overflow. This should not happen in practice, since DocVersion is a
-    // 64-bit unsigned integer.
-    if (Version < LastReportedDiagsVersion)
-      return;
-    LastReportedDiagsVersion = Version;
-
-    DiagConsumer.onDiagnosticsReady(FileStr,
-                                    make_tagged(std::move(*Diags), Tag));
-  };
-
-  WorkScheduler.addToFront(std::move(ReparseAndPublishDiags),
-                           std::move(DeferredRebuild), std::move(DonePromise),
-                           std::move(Ctx));
-  return DoneFuture;
+     Context Ctx, PathRef File, VersionedDraft Contents,
+     std::shared_ptr<CppFile> Resources,
+     Tagged<IntrusiveRefCntPtr<vfs::FileSystem>> TaggedFS) {
+
+   assert(Contents.Draft && "Draft must have contents");
+   UniqueFunction<llvm::Optional<std::vector<DiagWithFixIts>>(const Context &)>
+       DeferredRebuild =
+           Resources->deferRebuild(*Contents.Draft, TaggedFS.Value);
+   std::promise<Context> DonePromise;
+   std::future<Context> DoneFuture = DonePromise.get_future();
+
+   DocVersion Version = Contents.Version;
+   Path FileStr = File;
+   VFSTag Tag = TaggedFS.Tag;
+   auto ReparseAndPublishDiags =
+       [this, FileStr, Version,
+        Tag](UniqueFunction<llvm::Optional<std::vector<DiagWithFixIts>>(
+                 const Context &)>
+                 DeferredRebuild,
+             std::promise<Context> DonePromise, Context Ctx) -> void {
+     auto Guard = onScopeExit([&]() { DonePromise.set_value(std::move(Ctx)); });
+
+     auto CurrentVersion = DraftMgr.getVersion(FileStr);
+     if (CurrentVersion != Version)
+       return; // This request is outdated
+
+     auto Diags = DeferredRebuild(Ctx);
+     if (!Diags)
+       return; // A new reparse was requested before this one completed.
+
+     // We need to serialize access to resulting diagnostics to avoid calling
+     // `onDiagnosticsReady` in the wrong order.
+     std::lock_guard<std::mutex> DiagsLock(DiagnosticsMutex);
+     DocVersion &LastReportedDiagsVersion = ReportedDiagnosticVersions[FileStr];
+     // FIXME(ibiryukov): get rid of '<' comparison here. In the current
+     // implementation diagnostics will not be reported after version counters'
+     // overflow. This should not happen in practice, since DocVersion is a
+     // 64-bit unsigned integer.
+     if (Version < LastReportedDiagsVersion)
+       return;
+     LastReportedDiagsVersion = Version;
+
+     DiagConsumer.onDiagnosticsReady(FileStr,
+                                     make_tagged(std::move(*Diags), Tag));
+   };
+
+   WorkScheduler.addToFront(std::move(ReparseAndPublishDiags),
+                            std::move(DeferredRebuild), std::move(DonePromise),
+                            std::move(Ctx));
+   return DoneFuture;
 }
 
 std::future<Context>
Index: clangd/ClangdLSPServer.h
===================================================================
--- clangd/ClangdLSPServer.h
+++ clangd/ClangdLSPServer.h
@@ -73,6 +73,7 @@
   void onFileEvent(Ctx C, DidChangeWatchedFilesParams &Params) override;
   void onCommand(Ctx C, ExecuteCommandParams &Params) override;
   void onRename(Ctx C, RenameParams &Parames) override;
+  void onCodeHover(Ctx C, TextDocumentPositionParams &Params) override;
 
   std::vector<TextEdit> getFixIts(StringRef File, const clangd::Diagnostic &D);
 
Index: clangd/ClangdLSPServer.cpp
===================================================================
--- clangd/ClangdLSPServer.cpp
+++ clangd/ClangdLSPServer.cpp
@@ -68,6 +68,7 @@
              }},
             {"definitionProvider", true},
             {"documentHighlightProvider", true},
+            {"hoverProvider", true},
             {"renameProvider", true},
             {"executeCommandProvider",
              json::obj{
@@ -245,15 +246,16 @@
 
 void ClangdLSPServer::onGoToDefinition(Ctx C,
                                        TextDocumentPositionParams &Params) {
-  auto Items = Server.findDefinitions(
-      C, Params.textDocument.uri.file,
+  auto Items = Server.findDefinitions(C,
+      Params.textDocument.uri.file,
       Position{Params.position.line, Params.position.character});
   if (!Items)
     return replyError(C, ErrorCode::InvalidParams,
-                      llvm::toString(Items.takeError()));
+                        llvm::toString(Items.takeError()));
   reply(C, json::ary(Items->Value));
 }
 
+
 void ClangdLSPServer::onSwitchSourceHeader(Ctx C,
                                            TextDocumentIdentifier &Params) {
   llvm::Optional<Path> Result = Server.switchSourceHeader(Params.uri.file);
@@ -277,6 +279,19 @@
   reply(C, json::ary(Highlights->Value));
 }
 
+void ClangdLSPServer::onCodeHover(Ctx C, TextDocumentPositionParams &Params) {
+  auto H = Server.findHover(C,
+      Params.textDocument.uri.file,
+      Position{Params.position.line, Params.position.character});
+
+  if (!H) {
+    replyError(C, ErrorCode::InternalError, llvm::toString(H.takeError()));
+    return;
+  }
+
+  reply(C, H->Value);
+}
+
 ClangdLSPServer::ClangdLSPServer(JSONOutput &Out, unsigned AsyncThreadsCount,
                                  bool StorePreamblesInMemory,
                                  const clangd::CodeCompleteOptions &CCOpts,
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to