ioeric updated this revision to Diff 125960.
ioeric added a comment.

- Use IncludeNamespaceLevelDecls option; fix some broken tests.


Repository:
  rCTE Clang Tools Extra

https://reviews.llvm.org/D40548

Files:
  clangd/CMakeLists.txt
  clangd/ClangdIndex.cpp
  clangd/ClangdIndex.h
  clangd/ClangdLSPServer.cpp
  clangd/ClangdLSPServer.h
  clangd/ClangdServer.cpp
  clangd/ClangdServer.h
  clangd/ClangdUnit.cpp
  clangd/ClangdUnit.h
  clangd/ClangdUnitStore.cpp
  clangd/ClangdUnitStore.h
  clangd/CodeComplete.cpp
  clangd/CodeComplete.h
  clangd/Symbol.cpp
  clangd/Symbol.h
  clangd/SymbolCompletionInfo.cpp
  clangd/SymbolCompletionInfo.h
  clangd/SymbolIndex.h
  clangd/tool/ClangdMain.cpp
  unittests/clangd/CMakeLists.txt
  unittests/clangd/SymbolCollectorTests.cpp

Index: unittests/clangd/SymbolCollectorTests.cpp
===================================================================
--- /dev/null
+++ unittests/clangd/SymbolCollectorTests.cpp
@@ -0,0 +1,110 @@
+//===-- SymbolCollectorTests.cpp  -------------------------------*- C++ -*-===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "Symbol.h"
+#include "clang/Index/IndexingAction.h"
+#include "clang/Basic/FileManager.h"
+#include "clang/Basic/FileSystemOptions.h"
+#include "clang/Basic/VirtualFileSystem.h"
+#include "clang/Frontend/CompilerInstance.h"
+#include "clang/Tooling/Tooling.h"
+#include "llvm/ADT/IntrusiveRefCntPtr.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/MemoryBuffer.h"
+#include "gtest/gtest.h"
+#include "gmock/gmock.h"
+
+#include <memory>
+#include <string>
+
+using testing::ElementsAre;
+using testing::Eq;
+using testing::Field;
+
+namespace clang {
+namespace clangd {
+
+namespace {
+class SymbolIndexActionFactory : public tooling::FrontendActionFactory {
+ public:
+  SymbolIndexActionFactory() = default;
+
+  clang::FrontendAction *create() override {
+    index::IndexingOptions IndexOpts;
+    IndexOpts.SystemSymbolFilter =
+        index::IndexingOptions::SystemSymbolFilterKind::All;
+    IndexOpts.IndexFunctionLocals = false;
+    Collector = std::make_shared<SymbolCollector>();
+    FrontendAction *Action =
+        index::createIndexingAction(Collector, IndexOpts, nullptr).release();
+    return Action;
+  }
+
+  std::shared_ptr<SymbolCollector> Collector;
+};
+
+class SymbolCollectorTest : public ::testing::Test {
+public:
+  bool runSymbolCollector(StringRef HeaderCode, StringRef MainCode) {
+    llvm::IntrusiveRefCntPtr<vfs::InMemoryFileSystem> InMemoryFileSystem(
+        new vfs::InMemoryFileSystem);
+    llvm::IntrusiveRefCntPtr<FileManager> Files(
+        new FileManager(FileSystemOptions(), InMemoryFileSystem));
+
+    const std::string FileName = "symbol.cc";
+    const std::string HeaderName = "symbols.h";
+    auto Factory = llvm::make_unique<SymbolIndexActionFactory>();
+
+    tooling::ToolInvocation Invocation(
+        {"symbol_collector", "-fsyntax-only", "-std=c++11", FileName},
+        Factory->create(), Files.get(),
+        std::make_shared<PCHContainerOperations>());
+
+    InMemoryFileSystem->addFile(HeaderName, 0,
+                                llvm::MemoryBuffer::getMemBuffer(HeaderCode));
+
+    std::string Content = "#include\"" + std::string(HeaderName) + "\"";
+    Content += "\n" + MainCode.str();
+    InMemoryFileSystem->addFile(FileName, 0,
+                                llvm::MemoryBuffer::getMemBuffer(Content));
+    Invocation.run();
+    Symbols = Factory->Collector->getSymbols();
+    return true;
+  }
+
+protected:
+  std::set<Symbol> Symbols;
+};
+
+TEST_F(SymbolCollectorTest, CollectSymbol) {
+  const std::string Header = R"(
+    class Foo {
+     void f();
+    };
+    void f1();
+    inline void f2() {}
+  )";
+  const std::string Main = R"(
+    namespace {
+    void ff() {} // ignore
+    }
+    void f1() {}
+  )";
+  runSymbolCollector(Header, Main);
+  EXPECT_THAT(Symbols,
+              UnorderedElementsAre(Field(&Symbol::QualifiedName, Eq("Foo")),
+                                   Field(&Symbol::QualifiedName, Eq("Foo::f")),
+                                   Field(&Symbol::QualifiedName, Eq("f1")),
+                                   Field(&Symbol::QualifiedName, Eq("f2"))));
+}
+
+} // namespace
+} // namespace clangd
+} // namespace clang
Index: unittests/clangd/CMakeLists.txt
===================================================================
--- unittests/clangd/CMakeLists.txt
+++ unittests/clangd/CMakeLists.txt
@@ -15,6 +15,7 @@
   JSONExprTests.cpp
   TestFS.cpp
   TraceTests.cpp
+  SymbolCollectorTests.cpp
   )
 
 target_link_libraries(ClangdTests
Index: clangd/tool/ClangdMain.cpp
===================================================================
--- clangd/tool/ClangdMain.cpp
+++ clangd/tool/ClangdMain.cpp
@@ -90,6 +90,15 @@
         "Trace internal events and timestamps in chrome://tracing JSON format"),
     llvm::cl::init(""), llvm::cl::Hidden);
 
+static llvm::cl::opt<bool> EnableIndexBasedCompletion(
+    "enable-index-based-completion",
+    llvm::cl::desc(
+        "Enable index-based global code completion (experimental). Clangd will "
+        "use symbols built from ASTs of opened files and additional indexes "
+        "(e.g. offline built codebase-wide symbol table) to complete partial "
+        "symbols."),
+    llvm::cl::init(false));
+
 int main(int argc, char *argv[]) {
   llvm::cl::ParseCommandLineOptions(argc, argv, "clangd");
 
@@ -170,10 +179,15 @@
   clangd::CodeCompleteOptions CCOpts;
   CCOpts.EnableSnippets = EnableSnippets;
   CCOpts.IncludeIneligibleResults = IncludeIneligibleResults;
+  if (EnableIndexBasedCompletion) {
+    // Disable sema code completion for qualified code completion and use global
+    // symbol index instead.
+    CCOpts.IncludeNamespaceLevelDecls = false;
+  }
   // Initialize and run ClangdLSPServer.
   ClangdLSPServer LSPServer(Out, WorkerThreadsCount, StorePreamblesInMemory,
-                            CCOpts, ResourceDirRef,
-                            CompileCommandsDirPath);
+                            CCOpts, ResourceDirRef, CompileCommandsDirPath,
+                            EnableIndexBasedCompletion, {});
   constexpr int NoShutdownRequestErrorCode = 1;
   llvm::set_thread_name("clangd.main");
   return LSPServer.run(std::cin) ? 0 : NoShutdownRequestErrorCode;
Index: clangd/SymbolIndex.h
===================================================================
--- /dev/null
+++ clangd/SymbolIndex.h
@@ -0,0 +1,60 @@
+//===--- CompletionIndex.h - Index for code completion -----------*- C++-*-===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_SYMBOLINDEX_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_SYMBOLINDEX_H
+
+#include "SymbolCompletionInfo.h"
+#include "llvm/Support/Error.h"
+
+namespace clang {
+namespace clangd {
+
+struct CompletionRequest {
+  std::string Query;
+  size_t MaxCandidateCount = UINT_MAX;
+};
+
+/// \brief Signals for scoring completion candidates.
+struct ScoreSignals {
+  // FIXME: add score signals like cross-reference count.
+};
+
+struct CompletionSymbol {
+  ScoreSignals Signals;
+  float SymbolScore;
+
+  std::string USR;
+  index::SymbolKind Kind;
+  std::string QualifiedName;
+
+  SymbolCompletionInfo CompletionInfo;
+};
+
+struct CompletionResult {
+  std::vector<CompletionSymbol> Symbols;
+  bool AllMatched;
+};
+
+class SymbolIndex {
+public:
+  virtual ~SymbolIndex() = default;
+
+  virtual llvm::Expected<CompletionResult>
+  complete(const CompletionRequest &Req) const = 0;
+
+  // FIXME: add interfaces for more index use cases:
+  //  - Symbol getSymbolInfo(llvm::StringRef USR);
+  //  - getAllOccurrences(llvm::StringRef USR);
+};
+
+} // namespace clangd
+} // namespace clang
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANGD_SYMBOLINDEX_H
Index: clangd/SymbolCompletionInfo.h
===================================================================
--- /dev/null
+++ clangd/SymbolCompletionInfo.h
@@ -0,0 +1,31 @@
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_SYMBOLCOMPLETIONINFO_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_SYMBOLCOMPLETIONINFO_H
+
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/Decl.h"
+#include "clang/Basic/SourceManager.h"
+#include "clang/Index/IndexSymbol.h"
+#include "clang/Lex/Preprocessor.h"
+
+namespace clang {
+namespace clangd {
+
+struct SymbolCompletionInfo {
+  static SymbolCompletionInfo create(ASTContext &Ctx, Preprocessor &PP,
+                                     const NamedDecl *ND);
+
+  SymbolCompletionInfo() = default;
+  /// Label that can be be displayed in the completion list.
+  std::string Label;
+  /// Symbol annotation and/or comment for the symbol declaration.
+  std::string Documentation;
+  /// Detail about the symbol like result type.
+  std::string Detail;
+  /// Function/method parameters. Useful for snippets.
+  std::vector<std::string> Params;
+};
+
+}  // clangd
+}  // clang
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANGD_SYMBOLCOMPLETIONINFO_H
Index: clangd/SymbolCompletionInfo.cpp
===================================================================
--- /dev/null
+++ clangd/SymbolCompletionInfo.cpp
@@ -0,0 +1,116 @@
+#include "SymbolCompletionInfo.h"
+#include "clang/Sema/CodeCompleteConsumer.h"
+#include "llvm/Support/YAMLTraits.h"
+#include <memory>
+
+namespace clang {
+namespace clangd {
+namespace {
+
+std::string getDocumentation(const CodeCompletionString &CCS) {
+  // Things like __attribute__((nonnull(1,3))) and [[noreturn]]. Present this
+  // information in the documentation field.
+  std::string Result;
+  const unsigned AnnotationCount = CCS.getAnnotationCount();
+  if (AnnotationCount > 0) {
+    Result += "Annotation";
+    if (AnnotationCount == 1) {
+      Result += ": ";
+    } else /* AnnotationCount > 1 */ {
+      Result += "s: ";
+    }
+    for (unsigned I = 0; I < AnnotationCount; ++I) {
+      Result += CCS.getAnnotation(I);
+      Result.push_back(I == AnnotationCount - 1 ? '\n' : ' ');
+    }
+  }
+  // Add brief documentation (if there is any).
+  if (CCS.getBriefComment() != nullptr) {
+    if (!Result.empty()) {
+      // This means we previously added annotations. Add an extra newline
+      // character to make the annotations stand out.
+      Result.push_back('\n');
+    }
+    Result += CCS.getBriefComment();
+  }
+  return Result;
+}
+
+void ProcessChunks(const CodeCompletionString &CCS,
+                   SymbolCompletionInfo *Info) {
+  for (const auto &Chunk : CCS) {
+    // Informative qualifier chunks only clutter completion results, skip
+    // them.
+    if (Chunk.Kind == CodeCompletionString::CK_Informative &&
+        StringRef(Chunk.Text).endswith("::"))
+      continue;
+
+    switch (Chunk.Kind) {
+    case CodeCompletionString::CK_TypedText:
+      // There's always exactly one CK_TypedText chunk.
+      Info->Label += Chunk.Text;
+      break;
+    case CodeCompletionString::CK_ResultType:
+      Info->Detail = Chunk.Text;
+      break;
+    case CodeCompletionString::CK_Optional:
+      break;
+    case CodeCompletionString::CK_Placeholder:
+      // A string that acts as a placeholder for, e.g., a function call
+      // argument.
+      Info->Params.push_back(Chunk.Text);
+      LLVM_FALLTHROUGH;
+    default:
+      Info->Label += Chunk.Text;
+      break;
+    }
+  }
+}
+
+inline std::string
+joinNamespaces(const llvm::SmallVectorImpl<StringRef> &Namespaces) {
+  if (Namespaces.empty())
+    return "";
+  std::string Result = Namespaces.front();
+  for (auto I = Namespaces.begin() + 1, E = Namespaces.end(); I != E; ++I)
+    Result += ("::" + *I).str();
+  return Result;
+}
+
+// Given "a::b::c", returns {"a", "b", "c"}.
+llvm::SmallVector<llvm::StringRef, 4> splitSymbolName(llvm::StringRef Name) {
+  llvm::SmallVector<llvm::StringRef, 4> Splitted;
+  Name.split(Splitted, "::", /*MaxSplit=*/-1,
+             /*KeepEmpty=*/false);
+  return Splitted;
+}
+
+} // namespace
+
+SymbolCompletionInfo SymbolCompletionInfo::create(ASTContext &Ctx,
+                                                  Preprocessor &PP,
+                                                  const NamedDecl *ND) {
+  CodeCompletionResult SymbolCompletion(ND, 0);
+  auto Allocator = std::make_shared<GlobalCodeCompletionAllocator>();
+  CodeCompletionTUInfo TUInfo(Allocator);
+  const auto *CCS = SymbolCompletion.CreateCodeCompletionString(
+      Ctx, PP, CodeCompletionContext::CCC_Name, *Allocator, TUInfo,
+      /*IncludeBriefComments*/ true);
+  SymbolCompletionInfo Info;
+  Info.Documentation = getDocumentation(*CCS);
+
+  ProcessChunks(*CCS, &Info);
+  // Since symbol names in CCS labels are not qualified, we prepend a namespace
+  // qualfifier.
+  std::string QualifiedName = ND->getQualifiedNameAsString();
+  auto SplittedNames = splitSymbolName(QualifiedName);
+  if (SplittedNames.size() > 1) {
+    std::string LabelPrefix = joinNamespaces(SmallVector<llvm::StringRef, 4>(
+        SplittedNames.begin(), SplittedNames.end() - 1));
+    Info.Label = LabelPrefix + "::" + Info.Label;
+  }
+  return Info;
+}
+
+} // namespace clangd
+} // namespace clang
Index: clangd/Symbol.h
===================================================================
--- /dev/null
+++ clangd/Symbol.h
@@ -0,0 +1,91 @@
+//===--- Symbol.h -----------------------------------------------*- C++-*-===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===---------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_SYMBOL_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_SYMBOL_H
+
+#include "SymbolCompletionInfo.h"
+#include "clang/Index/IndexDataConsumer.h"
+#include "clang/Index/IndexSymbol.h"
+
+#include <set>
+
+namespace clang {
+namespace clangd {
+
+struct SymbolLocation {
+  // The path of the source file where a symbol occurs.
+  std::string FilePath;
+  // The offset to the first character of the symbol from the beginning of the
+  // source file.
+  unsigned StartOffset;
+  // The offset to the last character of the symbol from the beginning of the
+  // source file.
+  unsigned EndOffset;
+};
+
+// The class presents a C++ symbol, e.g. class, function.
+struct Symbol {
+  // The symbol identifier, using USR.
+  std::string Identifier;
+  // The qualified name of the symbol, e.g. Foo::bar.
+  std::string QualifiedName;
+  // The symbol information, like symbol kind.
+  index::SymbolInfo SymInfo;
+  // The canonical location of the symbol.
+  //
+  // A C++ symbol might have multiple locations (e.g. a function is declared in
+  // ".h" file, and is defined in ".cc" file).
+  //   * For non-inline functions and class methods, the canonical location is
+  //     where they are declared.
+  //   * For classes, the canonical location is where they are defined.
+  SymbolLocation CanonicalLocation;
+  // Information for code completion.
+  SymbolCompletionInfo CompletionInfo;
+
+  bool operator<(const Symbol& S) const {
+    return Identifier < S.Identifier;
+  }
+
+  // FIXME: add extra fields for index scoring signals.
+  // FIXME: add all occurrences of the symbol.
+};
+
+// Collect all symbols from an AST.
+//
+// Clients (e.g. clangd) can use SymbolCollector together with
+// index::indexTopLevelDecls to retrieve all symbols when the source file is
+// changed.
+class SymbolCollector : public index::IndexDataConsumer {
+public:
+  SymbolCollector() = default;
+
+  void setPreprocessor(std::shared_ptr<Preprocessor> PP) override {
+    this->PP = std::move(PP);
+  }
+  void initialize(ASTContext &Ctx) override { ASTCtx = &Ctx; }
+
+  const std::set<Symbol> &getSymbols() const { return Symbols; }
+
+  bool
+  handleDeclOccurence(const Decl *D, index::SymbolRoleSet Roles,
+                      ArrayRef<index::SymbolRelation> Relations, FileID FID,
+                      unsigned Offset,
+                      index::IndexDataConsumer::ASTNodeInfo ASTNode) override;
+
+private:
+  std::set<Symbol> Symbols;
+  std::shared_ptr<Preprocessor> PP;
+  ASTContext *ASTCtx;
+};
+
+} // namespace clangd
+} // namespace clang
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANGD_SYMBOL_H
Index: clangd/Symbol.cpp
===================================================================
--- /dev/null
+++ clangd/Symbol.cpp
@@ -0,0 +1,63 @@
+//===--- Symbol.cpp ---------------------------------------------*- C++-*-===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===---------------------------------------------------------------------===//
+
+#include "Symbol.h"
+
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/Decl.h"
+#include "clang/Basic/SourceManager.h"
+#include "clang/Index/IndexSymbol.h"
+#include "clang/Index/USRGeneration.h"
+#include "llvm/Support/MemoryBuffer.h"
+
+namespace clang {
+namespace clangd {
+
+bool SymbolCollector::handleDeclOccurence(
+    const Decl *D, index::SymbolRoleSet Roles,
+    ArrayRef<index::SymbolRelation> Relations, FileID FID, unsigned Offset,
+    index::IndexDataConsumer::ASTNodeInfo ASTNode) {
+  // FIXME: collect all symbol references.
+  if (!(Roles & static_cast<unsigned>(index::SymbolRole::Declaration) ||
+        Roles & static_cast<unsigned>(index::SymbolRole::Definition)))
+    return true;
+
+  if (const NamedDecl *ND = llvm::dyn_cast<NamedDecl>(D)) {
+    // FIXME: Should we include the internal linkage symbols?
+    if (!ND->hasExternalFormalLinkage() || ND->isInAnonymousNamespace())
+      return true;
+
+    llvm::SmallVector<char, 128> Buff;
+    if (index::generateUSRForDecl(ND, Buff))
+      return true;
+
+    std::string ID(Buff.data(), Buff.size());
+    auto It = Symbols.find({ID});
+    if (It != Symbols.end()) {
+      return true;
+    }
+
+    auto &SM = ND->getASTContext().getSourceManager();
+    SymbolLocation Location = {SM.getFilename(D->getLocation()),
+                               SM.getFileOffset(D->getLocStart()),
+                               SM.getFileOffset(D->getLocEnd())};
+    assert(ASTCtx && "ASTContext must be set.");
+    assert(PP.get() && "Preprocessor must be set.");
+    SymbolCompletionInfo CompleteInfo =
+        SymbolCompletionInfo::create(*ASTCtx, *PP, ND);
+    Symbols.insert({std::move(ID), ND->getQualifiedNameAsString(),
+                    index::getSymbolInfo(D), std::move(Location),
+                    std::move(CompleteInfo)});
+  }
+
+  return true;
+}
+
+}  // clangd
+}  // clang
Index: clangd/CodeComplete.h
===================================================================
--- clangd/CodeComplete.h
+++ clangd/CodeComplete.h
@@ -18,6 +18,7 @@
 #include "Logger.h"
 #include "Path.h"
 #include "Protocol.h"
+#include "SymbolIndex.h"
 #include "clang/Frontend/PrecompiledPreamble.h"
 #include "clang/Sema/CodeCompleteOptions.h"
 #include "clang/Tooling/CompilationDatabase.h"
@@ -46,9 +47,15 @@
   /// Add globals to code completion results.
   bool IncludeGlobals = true;
 
+  /// Add symbols in namespace context (including global namespace) to code
+  /// completion.
+  /// FIXME(ioeric): this only affects qualified-id code completion at this
+  /// point.
+  unsigned IncludeNamespaceLevelDecls = true;
+
   /// Add brief comments to completion items, if available.
-  /// FIXME(ibiryukov): it looks like turning this option on significantly slows
-  /// down completion, investigate if it can be made faster.
+  /// FIXME(ibiryukov): it looks like turning this option on significantly
+  /// slows down completion, investigate if it can be made faster.
   bool IncludeBriefComments = true;
 
   /// Include results that are not legal completions in the current context.
@@ -61,13 +68,16 @@
 };
 
 /// Get code completions at a specified \p Pos in \p FileName.
+/// If `Index` is not nullptr, this will use the index for global code
+/// completion; otherwise, use sema code completion by default.
 CompletionList codeComplete(PathRef FileName,
                             const tooling::CompileCommand &Command,
                             PrecompiledPreamble const *Preamble,
                             StringRef Contents, Position Pos,
                             IntrusiveRefCntPtr<vfs::FileSystem> VFS,
                             std::shared_ptr<PCHContainerOperations> PCHs,
-                            CodeCompleteOptions Opts, Logger &Logger);
+                            CodeCompleteOptions Opts, Logger &Logger,
+                            const SymbolIndex *Index);
 
 /// Get signature help at a specified \p Pos in \p FileName.
 SignatureHelp
Index: clangd/CodeComplete.cpp
===================================================================
--- clangd/CodeComplete.cpp
+++ clangd/CodeComplete.cpp
@@ -15,6 +15,7 @@
 //===---------------------------------------------------------------------===//
 
 #include "CodeComplete.h"
+#include "ClangdServer.h"
 #include "Compiler.h"
 #include "clang/Frontend/CompilerInstance.h"
 #include "clang/Frontend/FrontendActions.h"
@@ -85,6 +86,53 @@
   llvm_unreachable("Unhandled CodeCompletionResult::ResultKind.");
 }
 
+CompletionItemKind getKindOfSymbol(index::SymbolKind Kind) {
+  using SK = index::SymbolKind;
+  switch (Kind){
+    case SK::Unknown:
+      return CompletionItemKind::Missing;
+    case SK::Module:
+    case SK::Namespace:
+    case SK::NamespaceAlias:
+      return CompletionItemKind::Module;
+    case SK::Macro:
+      return CompletionItemKind::Text;
+    case SK::Enum:
+      return CompletionItemKind::Enum;
+    case SK::Struct:
+    case SK::Class:
+    case SK::Protocol:
+    case SK::Extension:
+    case SK::Union:
+      return CompletionItemKind::Class;
+    case SK::TypeAlias:
+    case SK::Using:
+      return CompletionItemKind::Reference;
+    case SK::Function:
+    case SK::ConversionFunction:
+      return CompletionItemKind::Function;
+    case SK::Variable:
+    case SK::Parameter:
+      return CompletionItemKind::Variable;
+    case SK::Field:
+      return CompletionItemKind::Field;
+    case SK::EnumConstant:
+      return CompletionItemKind::Value;
+    case SK::InstanceMethod:
+    case SK::ClassMethod:
+    case SK::StaticMethod:
+      return CompletionItemKind::Method;
+    case SK::InstanceProperty:
+    case SK::ClassProperty:
+    case SK::StaticProperty:
+      return CompletionItemKind::Property;
+    case SK::Constructor:
+    case SK::Destructor:
+      return CompletionItemKind::Constructor;
+  }
+  llvm_unreachable("Unhandled clang::index::SymbolKind.");
+}
+
 std::string escapeSnippet(const llvm::StringRef Text) {
   std::string Result;
   Result.reserve(Text.size()); // Assume '$', '}' and '\\' are rare.
@@ -163,6 +211,22 @@
   return Result;
 }
 
+// Produces an integer that sorts in the same order as F.
+// That is: a < b <==> encodeFloat(a) < encodeFloat(b).
+static uint32_t encodeFloat(float F) {
+  static_assert(std::numeric_limits<float>::is_iec559, "");
+  static_assert(sizeof(float) == sizeof(uint32_t), "");
+  constexpr uint32_t TopBit = ~(~uint32_t{0} >> 1);
+
+  // Get the bits of the float. Endianness is the same as for integers.
+  uint32_t U;
+  memcpy(&U, &F, sizeof(float));
+  // IEEE 754 floats compare like sign-magnitude integers.
+  if (U & TopBit)    // Negative float.
+    return 0 - U;    // Map onto the low half of integers, order reversed.
+  return U + TopBit; // Positive floats map onto the high half of integers.
+}
+
 /// A scored code completion result.
 /// It may be promoted to a CompletionItem if it's among the top-ranked results.
 struct CompletionCandidate {
@@ -210,38 +274,110 @@
     }
     return Score;
   }
+};
+
+CompletionItem
+completionSymbolToCompletionItem(const CompletionSymbol &Sym,
+                                 bool FullyQualified, SourceManager &SM,
+                                 SourceRange SpecifierRange) {
+  CompletionItem Item;
+  if (FullyQualified)
+    Item.label = "::";
+  Item.label += Sym.CompletionInfo.Label.empty() ? Sym.QualifiedName
+                                                 : Sym.CompletionInfo.Label;
+  Item.kind = getKindOfSymbol(Sym.Kind);
+  Item.detail = Sym.CompletionInfo.Detail;
+  Item.documentation = Sym.CompletionInfo.Documentation;
+  Item.insertTextFormat = InsertTextFormat::PlainText;
+  Item.sortText = encodeFloat(Sym.SymbolScore);
+
+
+  TextEdit Edit;
+  Edit.newText =
+      FullyQualified ? ("::" + Sym.QualifiedName) : Sym.QualifiedName;
+  // Get the range to be replaced. We simply replace the entire range of
+  // specifier.
+  FileID FID = SM.getFileID(SpecifierRange.getBegin());
+  const auto *FE = SM.getFileEntryForID(FID);
+  llvm::MemoryBuffer *Buffer = SM.getMemoryBufferForFile(FE);
+  llvm::StringRef Code = Buffer->getBuffer();
+  Edit.range.start =
+      offsetToPosition(Code, SM.getFileOffset(SpecifierRange.getBegin()));
+  Edit.range.end =
+      offsetToPosition(Code, SM.getFileOffset(SpecifierRange.getEnd()));
+  Item.textEdit = std::move(Edit);
+  return Item;
+}
 
-  // Produces an integer that sorts in the same order as F.
-  // That is: a < b <==> encodeFloat(a) < encodeFloat(b).
-  static uint32_t encodeFloat(float F) {
-    static_assert(std::numeric_limits<float>::is_iec559, "");
-    static_assert(sizeof(float) == sizeof(uint32_t), "");
-    constexpr uint32_t TopBit = ~(~uint32_t{0} >> 1);
-
-    // Get the bits of the float. Endianness is the same as for integers.
-    uint32_t U;
-    memcpy(&U, &F, sizeof(float));
-    // IEEE 754 floats compare like sign-magnitude integers.
-    if (U & TopBit)    // Negative float.
-      return 0 - U;    // Map onto the low half of integers, order reversed.
-    return U + TopBit; // Positive floats map onto the high half of integers.
+void qualifiedIdCompletionWithIndex(const SymbolIndex &Index, Sema &S,
+                                    const CXXScopeSpec &SS,
+                                    CompletionList *Items) {
+  std::string WrittenSS =
+      Lexer::getSourceText(CharSourceRange::getCharRange(SS.getRange()),
+                           S.getSourceManager(), clang::LangOptions());
+  std::string RecognizedSpecifier;
+  if (SS.isValid()) {
+    DeclContext *DC = S.computeDeclContext(SS);
+    if (auto *NS = llvm::dyn_cast<NamespaceDecl>(DC)) {
+      RecognizedSpecifier = NS->getQualifiedNameAsString();
+    } else if (auto *TU = llvm::dyn_cast<TranslationUnitDecl>(DC)) {
+      RecognizedSpecifier = "::";
+      // Sema does not include the suffix "::" in the range of SS, so we add it
+      // back here.
+      WrittenSS = "::";
+    }
   }
-};
+
+  CompletionRequest Req;
+  Req.Query = RecognizedSpecifier.empty() ? WrittenSS : RecognizedSpecifier;
+  auto Filter = S.getPreprocessor().getCodeCompletionFilter();
+  llvm::errs() << "  Specifier: [" << Req.Query << "], "
+               << "Filter: [" << Filter << "],  Written: [" << WrittenSS
+               << "]\n";
+  // FIXME: for now we simply cancatenate specifier with the typed filter. We
+  // might want to fix the specifier prefix if it is a recognized context (e.g.
+  // a known namespace in the AST).
+  if (!Filter.empty()) {
+    if (!Req.Query.empty())
+      Req.Query += "::";
+    Req.Query += Filter;
+  }
+
+  llvm::Expected<CompletionResult> Result = Index.complete(Req);
+  if (!Result) {
+    // FIXME(ioeric): use logger to output error message.
+    llvm::consumeError(Result.takeError());
+    // FIXME(ioeric): consider falling back to sema code completion.
+    return;
+  }
+  for (unsigned int i = 0; i < Result->Symbols.size(); ++i)
+    Items->items.push_back(completionSymbolToCompletionItem(
+        Result->Symbols[i], StringRef(WrittenSS).startswith("::"),
+        S.getSourceManager(), SS.getRange()));
+  Items->isIncomplete = true;
+}
 
 class CompletionItemsCollector : public CodeCompleteConsumer {
 public:
   CompletionItemsCollector(const CodeCompleteOptions &CodeCompleteOpts,
-                           CompletionList &Items)
+                           CompletionList &Items,
+                           const SymbolIndex *Index = nullptr)
       : CodeCompleteConsumer(CodeCompleteOpts.getClangCompleteOpts(),
                              /*OutputIsBinary=*/false),
         ClangdOpts(CodeCompleteOpts), Items(Items),
         Allocator(std::make_shared<clang::GlobalCodeCompletionAllocator>()),
-        CCTUInfo(Allocator) {}
+        CCTUInfo(Allocator), Index(Index) {}
 
   void ProcessCodeCompleteResults(Sema &S, CodeCompletionContext Context,
                                   CodeCompletionResult *Results,
                                   unsigned NumResults) override final {
     StringRef Filter = S.getPreprocessor().getCodeCompletionFilter();
+    if (Index) {
+      if (auto OptSS = Context.getCXXScopeSpecifier()) {
+        // FIXME: output a warning to logger if there are results from sema.
+        return qualifiedIdCompletionWithIndex(*Index, S, **OptSS, &Items);
+      }
+    }
     std::priority_queue<CompletionCandidate> Candidates;
     for (unsigned I = 0; I < NumResults; ++I) {
       auto &Result = Results[I];
@@ -336,7 +472,9 @@
   CompletionList &Items;
   std::shared_ptr<clang::GlobalCodeCompletionAllocator> Allocator;
   CodeCompletionTUInfo CCTUInfo;
-
+  // If set, use the index-based global code completion; otherwise, use sema
+  // code completion by default.
+  const SymbolIndex *Index;
 }; // CompletionItemsCollector
 
 bool isInformativeQualifierChunk(CodeCompletionString::Chunk const &Chunk) {
@@ -349,8 +487,9 @@
 
 public:
   PlainTextCompletionItemsCollector(const CodeCompleteOptions &CodeCompleteOpts,
-                                    CompletionList &Items)
-      : CompletionItemsCollector(CodeCompleteOpts, Items) {}
+                                    CompletionList &Items,
+                                    const SymbolIndex *Index)
+      : CompletionItemsCollector(CodeCompleteOpts, Items, Index) {}
 
 private:
   void ProcessChunks(const CodeCompletionString &CCS,
@@ -385,8 +524,9 @@
 
 public:
   SnippetCompletionItemsCollector(const CodeCompleteOptions &CodeCompleteOpts,
-                                  CompletionList &Items)
-      : CompletionItemsCollector(CodeCompleteOpts, Items) {}
+                                  CompletionList &Items,
+                                  const SymbolIndex *Index)
+      : CompletionItemsCollector(CodeCompleteOpts, Items, Index) {}
 
 private:
   void ProcessChunks(const CodeCompletionString &CCS,
@@ -655,6 +795,7 @@
   Result.IncludeCodePatterns = EnableSnippets && IncludeCodePatterns;
   Result.IncludeMacros = IncludeMacros;
   Result.IncludeGlobals = IncludeGlobals;
+  Result.IncludeNamespaceLevelDecls = IncludeNamespaceLevelDecls;
   Result.IncludeBriefComments = IncludeBriefComments;
 
   return Result;
@@ -666,15 +807,16 @@
                             StringRef Contents, Position Pos,
                             IntrusiveRefCntPtr<vfs::FileSystem> VFS,
                             std::shared_ptr<PCHContainerOperations> PCHs,
-                            CodeCompleteOptions Opts, Logger &Logger) {
+                            CodeCompleteOptions Opts, Logger &Logger,
+                            const SymbolIndex *Index) {
   CompletionList Results;
   std::unique_ptr<CodeCompleteConsumer> Consumer;
   if (Opts.EnableSnippets) {
-    Consumer =
-        llvm::make_unique<SnippetCompletionItemsCollector>(Opts, Results);
+    Consumer = llvm::make_unique<SnippetCompletionItemsCollector>(Opts, Results,
+                                                                  Index);
   } else {
-    Consumer =
-        llvm::make_unique<PlainTextCompletionItemsCollector>(Opts, Results);
+    Consumer = llvm::make_unique<PlainTextCompletionItemsCollector>(
+        Opts, Results, Index);
   }
   invokeCodeComplete(std::move(Consumer), Opts.getClangCompleteOpts(), FileName,
                      Command, Preamble, Contents, Pos, std::move(VFS),
Index: clangd/ClangdUnitStore.h
===================================================================
--- clangd/ClangdUnitStore.h
+++ clangd/ClangdUnitStore.h
@@ -12,6 +12,7 @@
 
 #include <mutex>
 
+#include "ClangdIndex.h"
 #include "ClangdUnit.h"
 #include "GlobalCompilationDatabase.h"
 #include "Path.h"
@@ -25,11 +26,14 @@
 /// Thread-safe mapping from FileNames to CppFile.
 class CppFileCollection {
 public:
+  explicit CppFileCollection(ASTIndexSourcer *IndexSourcer)
+      : IndexSourcer(IndexSourcer) {}
+
   std::shared_ptr<CppFile>
   getOrCreateFile(PathRef File, PathRef ResourceDir,
                   GlobalCompilationDatabase &CDB, bool StorePreamblesInMemory,
                   std::shared_ptr<PCHContainerOperations> PCHs,
-                  clangd::Logger &Logger) {
+                  clangd::Logger &Logger, ASTIndexSourcer *IndexSourcer) {
     std::lock_guard<std::mutex> Lock(Mutex);
 
     auto It = OpenedFiles.find(File);
@@ -39,7 +43,8 @@
       It = OpenedFiles
                .try_emplace(File, CppFile::Create(File, std::move(Command),
                                                   StorePreamblesInMemory,
-                                                  std::move(PCHs), Logger))
+                                                  std::move(PCHs), Logger,
+                                                  IndexSourcer))
                .first;
     }
     return It->second;
@@ -86,6 +91,7 @@
 
   std::mutex Mutex;
   llvm::StringMap<std::shared_ptr<CppFile>> OpenedFiles;
+  ASTIndexSourcer *IndexSourcer;
 };
 } // namespace clangd
 } // namespace clang
Index: clangd/ClangdUnitStore.cpp
===================================================================
--- clangd/ClangdUnitStore.cpp
+++ clangd/ClangdUnitStore.cpp
@@ -23,6 +23,9 @@
 
   std::shared_ptr<CppFile> Result = It->second;
   OpenedFiles.erase(It);
+  if (IndexSourcer) {
+    IndexSourcer->remove(File);
+  }
   return Result;
 }
 
@@ -42,14 +45,15 @@
     It = OpenedFiles
              .try_emplace(File, CppFile::Create(File, std::move(NewCommand),
                                                 StorePreamblesInMemory,
-                                                std::move(PCHs), Logger))
+                                                std::move(PCHs), Logger,
+                                                IndexSourcer))
              .first;
   } else if (!compileCommandsAreEqual(It->second->getCompileCommand(),
                                       NewCommand)) {
     Result.RemovedFile = std::move(It->second);
     It->second =
         CppFile::Create(File, std::move(NewCommand), StorePreamblesInMemory,
-                        std::move(PCHs), Logger);
+                        std::move(PCHs), Logger, IndexSourcer);
   }
   Result.FileInCollection = It->second;
   return Result;
Index: clangd/ClangdUnit.h
===================================================================
--- clangd/ClangdUnit.h
+++ clangd/ClangdUnit.h
@@ -10,6 +10,7 @@
 #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_CLANGDUNIT_H
 #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_CLANGDUNIT_H
 
+#include "ClangdIndex.h"
 #include "Function.h"
 #include "Path.h"
 #include "Protocol.h"
@@ -81,6 +82,7 @@
 
   Preprocessor &getPreprocessor();
   const Preprocessor &getPreprocessor() const;
+  std::shared_ptr<Preprocessor> getPreprocessorPtr();
 
   /// This function returns all top-level decls, including those that come
   /// from Preamble. Decls, coming from Preamble, have to be deserialized, so
@@ -146,12 +148,14 @@
   static std::shared_ptr<CppFile>
   Create(PathRef FileName, tooling::CompileCommand Command,
          bool StorePreamblesInMemory,
-         std::shared_ptr<PCHContainerOperations> PCHs, clangd::Logger &Logger);
+         std::shared_ptr<PCHContainerOperations> PCHs, clangd::Logger &Logger,
+         ASTIndexSourcer *IndexSourcer);
 
 private:
   CppFile(PathRef FileName, tooling::CompileCommand Command,
           bool StorePreamblesInMemory,
-          std::shared_ptr<PCHContainerOperations> PCHs, clangd::Logger &Logger);
+          std::shared_ptr<PCHContainerOperations> PCHs, clangd::Logger &Logger,
+          ASTIndexSourcer *IndexSourcer);
 
 public:
   CppFile(CppFile const &) = delete;
@@ -254,8 +258,9 @@
   std::shared_ptr<PCHContainerOperations> PCHs;
   /// Used for logging various messages.
   clangd::Logger &Logger;
-};
 
+  ASTIndexSourcer *IndexSourcer;
+};
 
 /// Get the beginning SourceLocation at a specified \p Pos.
 SourceLocation getBeginningOfIdentifier(ParsedAST &Unit, const Position &Pos,
Index: clangd/ClangdUnit.cpp
===================================================================
--- clangd/ClangdUnit.cpp
+++ clangd/ClangdUnit.cpp
@@ -382,6 +382,10 @@
 
 Preprocessor &ParsedAST::getPreprocessor() { return Clang->getPreprocessor(); }
 
+std::shared_ptr<Preprocessor> ParsedAST::getPreprocessorPtr() {
+  return Clang->getPreprocessorPtr();
+}
+
 const Preprocessor &ParsedAST::getPreprocessor() const {
   return Clang->getPreprocessor();
 }
@@ -424,19 +428,20 @@
 CppFile::Create(PathRef FileName, tooling::CompileCommand Command,
                 bool StorePreamblesInMemory,
                 std::shared_ptr<PCHContainerOperations> PCHs,
-                clangd::Logger &Logger) {
-  return std::shared_ptr<CppFile>(new CppFile(FileName, std::move(Command),
-                                              StorePreamblesInMemory,
-                                              std::move(PCHs), Logger));
+                clangd::Logger &Logger, ASTIndexSourcer *IndexSourcer) {
+  return std::shared_ptr<CppFile>(
+      new CppFile(FileName, std::move(Command), StorePreamblesInMemory,
+                  std::move(PCHs), Logger, IndexSourcer));
 }
 
 CppFile::CppFile(PathRef FileName, tooling::CompileCommand Command,
                  bool StorePreamblesInMemory,
                  std::shared_ptr<PCHContainerOperations> PCHs,
-                 clangd::Logger &Logger)
+                 clangd::Logger &Logger, ASTIndexSourcer *IndexSourcer)
     : FileName(FileName), Command(std::move(Command)),
       StorePreamblesInMemory(StorePreamblesInMemory), RebuildCounter(0),
-      RebuildInProgress(false), PCHs(std::move(PCHs)), Logger(Logger) {
+      RebuildInProgress(false), PCHs(std::move(PCHs)), Logger(Logger),
+      IndexSourcer(IndexSourcer) {
   Logger.log("Opened file " + FileName + " with command [" +
              this->Command.Directory + "] " +
              llvm::join(this->Command.CommandLine, " "));
@@ -639,6 +644,10 @@
     if (NewAST) {
       Diagnostics.insert(Diagnostics.end(), NewAST->getDiagnostics().begin(),
                          NewAST->getDiagnostics().end());
+      if (That->IndexSourcer)
+        That->IndexSourcer->update(That->FileName, NewAST->getASTContext(),
+                                   NewAST->getPreprocessorPtr(),
+                                   NewAST->getTopLevelDecls());
     } else {
       // Don't report even Preamble diagnostics if we coulnd't build AST.
       Diagnostics.clear();
Index: clangd/ClangdServer.h
===================================================================
--- clangd/ClangdServer.h
+++ clangd/ClangdServer.h
@@ -10,6 +10,7 @@
 #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_CLANGDSERVER_H
 #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_CLANGDSERVER_H
 
+#include "ClangdIndex.h"
 #include "ClangdUnitStore.h"
 #include "DraftStore.h"
 #include "GlobalCompilationDatabase.h"
@@ -207,12 +208,15 @@
   /// clangd are stored in-memory or on disk.
   ///
   /// Various messages are logged using \p Logger.
-  ClangdServer(GlobalCompilationDatabase &CDB,
-               DiagnosticsConsumer &DiagConsumer,
-               FileSystemProvider &FSProvider, unsigned AsyncThreadsCount,
-               bool StorePreamblesInMemory,
-               clangd::Logger &Logger,
-               llvm::Optional<StringRef> ResourceDir = llvm::None);
+  ClangdServer(
+      GlobalCompilationDatabase &CDB, DiagnosticsConsumer &DiagConsumer,
+      FileSystemProvider &FSProvider, unsigned AsyncThreadsCount,
+      bool StorePreamblesInMemory, clangd::Logger &Logger,
+      bool EnableIndexBasedCodeCompletion = false,
+      std::vector<
+          std::pair<llvm::StringRef, CombinedSymbolIndex::WeightedIndex>>
+          AdditionalIndexes = {},
+      llvm::Optional<StringRef> ResourceDir = llvm::None);
 
   /// Set the root path of the workspace.
   void setRootPath(PathRef RootPath);
@@ -323,7 +327,10 @@
   DiagnosticsConsumer &DiagConsumer;
   FileSystemProvider &FSProvider;
   DraftStore DraftMgr;
+
+  std::unique_ptr<ASTIndexSourcer> IndexSourcer;
   CppFileCollection Units;
+
   std::string ResourceDir;
   // If set, this represents the workspace path.
   llvm::Optional<std::string> RootPath;
@@ -339,6 +346,8 @@
   // called before all other members to stop the worker thread that references
   // ClangdServer
   ClangdScheduler WorkScheduler;
+
+  std::unique_ptr<CombinedSymbolIndex> CombinedIndex;
 };
 
 } // namespace clangd
Index: clangd/ClangdServer.cpp
===================================================================
--- clangd/ClangdServer.cpp
+++ clangd/ClangdServer.cpp
@@ -169,18 +169,35 @@
     Worker.join();
 }
 
-ClangdServer::ClangdServer(GlobalCompilationDatabase &CDB,
-                           DiagnosticsConsumer &DiagConsumer,
-                           FileSystemProvider &FSProvider,
-                           unsigned AsyncThreadsCount,
-                           bool StorePreamblesInMemory, clangd::Logger &Logger,
-                           llvm::Optional<StringRef> ResourceDir)
+ClangdServer::ClangdServer(
+    GlobalCompilationDatabase &CDB, DiagnosticsConsumer &DiagConsumer,
+    FileSystemProvider &FSProvider, unsigned AsyncThreadsCount,
+    bool StorePreamblesInMemory, clangd::Logger &Logger,
+    bool EnableIndexBasedCodeCompletion,
+    std::vector<std::pair<llvm::StringRef, CombinedSymbolIndex::WeightedIndex>>
+        AdditionalIndexes,
+    llvm::Optional<StringRef> ResourceDir)
     : Logger(Logger), CDB(CDB), DiagConsumer(DiagConsumer),
       FSProvider(FSProvider),
+      IndexSourcer(EnableIndexBasedCodeCompletion ? new ASTIndexSourcer()
+                                                  : nullptr),
+      Units(IndexSourcer.get()),
       ResourceDir(ResourceDir ? ResourceDir->str() : getStandardResourceDir()),
       PCHs(std::make_shared<PCHContainerOperations>()),
       StorePreamblesInMemory(StorePreamblesInMemory),
-      WorkScheduler(AsyncThreadsCount) {}
+      WorkScheduler(AsyncThreadsCount) {
+  if (EnableIndexBasedCodeCompletion) {
+    assert(IndexSourcer.get() && "IndexSourcer must be set when index-based "
+                                 "code completion is enabled.");
+    CombinedSymbolIndex::WeightedIndex WeightedASTIndex(
+        llvm::make_unique<SimpleSymbolIndex>(IndexSourcer.get()));
+    WeightedASTIndex.Weight = 10;
+    CombinedIndex.reset(new CombinedSymbolIndex());
+    CombinedIndex->addSymbolIndex("AST", std::move(WeightedASTIndex));
+    for (auto &Index : AdditionalIndexes)
+      CombinedIndex->addSymbolIndex(Index.first, std::move(Index.second));
+  }
+}
 
 void ClangdServer::setRootPath(PathRef RootPath) {
   std::string NewRootPath = llvm::sys::path::convert_to_slash(
@@ -193,8 +210,9 @@
   DocVersion Version = DraftMgr.updateDraft(File, Contents);
 
   auto TaggedFS = FSProvider.getTaggedFileSystem(File);
-  std::shared_ptr<CppFile> Resources = Units.getOrCreateFile(
-      File, ResourceDir, CDB, StorePreamblesInMemory, PCHs, Logger);
+  std::shared_ptr<CppFile> Resources =
+      Units.getOrCreateFile(File, ResourceDir, CDB, StorePreamblesInMemory,
+                            PCHs, Logger, IndexSourcer.get());
   return scheduleReparseAndDiags(File, VersionedDraft{Version, Contents.str()},
                                  std::move(Resources), std::move(TaggedFS));
 }
@@ -289,7 +307,7 @@
         CompletionList Result = clangd::codeComplete(
             File, Resources->getCompileCommand(),
             Preamble ? &Preamble->Preamble : nullptr, Contents, Pos,
-            TaggedFS.Value, PCHs, CodeCompleteOpts, Logger);
+            TaggedFS.Value, PCHs, CodeCompleteOpts, Logger, CombinedIndex.get());
 
         Callback(make_tagged(std::move(Result), std::move(TaggedFS.Tag)));
       };
Index: clangd/ClangdLSPServer.h
===================================================================
--- clangd/ClangdLSPServer.h
+++ clangd/ClangdLSPServer.h
@@ -30,11 +30,15 @@
   /// If \p CompileCommandsDir has a value, compile_commands.json will be
   /// loaded only from \p CompileCommandsDir. Otherwise, clangd will look
   /// for compile_commands.json in all parent directories of each file.
-  ClangdLSPServer(JSONOutput &Out, unsigned AsyncThreadsCount,
-                  bool StorePreamblesInMemory,
-                  const clangd::CodeCompleteOptions &CCOpts,
-                  llvm::Optional<StringRef> ResourceDir,
-                  llvm::Optional<Path> CompileCommandsDir);
+  ClangdLSPServer(
+      JSONOutput &Out, unsigned AsyncThreadsCount, bool StorePreamblesInMemory,
+      const clangd::CodeCompleteOptions &CCOpts,
+      llvm::Optional<StringRef> ResourceDir,
+      llvm::Optional<Path> CompileCommandsDir,
+      bool EnableIndexBasedCodeCompletion,
+      std::vector<
+          std::pair<llvm::StringRef, CombinedSymbolIndex::WeightedIndex>>
+          AdditionalIndexes);
 
   /// Run LSP server loop, receiving input for it from \p In. \p In must be
   /// opened in binary mode. Output will be written using Out variable passed to
Index: clangd/ClangdLSPServer.cpp
===================================================================
--- clangd/ClangdLSPServer.cpp
+++ clangd/ClangdLSPServer.cpp
@@ -235,15 +235,19 @@
   C.reply(Result ? URI::fromFile(*Result).uri : "");
 }
 
-ClangdLSPServer::ClangdLSPServer(JSONOutput &Out, unsigned AsyncThreadsCount,
-                                 bool StorePreamblesInMemory,
-                                 const clangd::CodeCompleteOptions &CCOpts,
-                                 llvm::Optional<StringRef> ResourceDir,
-                                 llvm::Optional<Path> CompileCommandsDir)
+ClangdLSPServer::ClangdLSPServer(
+    JSONOutput &Out, unsigned AsyncThreadsCount, bool StorePreamblesInMemory,
+    const clangd::CodeCompleteOptions &CCOpts,
+    llvm::Optional<StringRef> ResourceDir,
+    llvm::Optional<Path> CompileCommandsDir,
+    bool EnableIndexBasedCodeCompletion,
+    std::vector<std::pair<llvm::StringRef, CombinedSymbolIndex::WeightedIndex>>
+        AdditionalIndexes)
     : Out(Out), CDB(/*Logger=*/Out, std::move(CompileCommandsDir)),
       CCOpts(CCOpts), Server(CDB, /*DiagConsumer=*/*this, FSProvider,
                              AsyncThreadsCount, StorePreamblesInMemory,
-                             /*Logger=*/Out, ResourceDir) {}
+                             /*Logger=*/Out, EnableIndexBasedCodeCompletion,
+                             std::move(AdditionalIndexes), ResourceDir) {}
 
 bool ClangdLSPServer::run(std::istream &In) {
   assert(!IsDone && "Run was called before");
Index: clangd/ClangdIndex.h
===================================================================
--- /dev/null
+++ clangd/ClangdIndex.h
@@ -0,0 +1,111 @@
+//===--- ClangdIndex.h - Symbol indexes for clangd.---------------*- C++-*-===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_CLANGDINDEX_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_CLANGDINDEX_H
+
+#include "Path.h"
+#include "Symbol.h"
+#include "SymbolIndex.h"
+#include "llvm/ADT/StringMap.h"
+#include "llvm/Support/Error.h"
+
+namespace clang {
+namespace clangd {
+
+/// \brief This combines multiple indexes. It is responsible for merging and
+/// (re-)scoring symbols from different indexes.
+class CombinedSymbolIndex : public SymbolIndex {
+public:
+  CombinedSymbolIndex() = default;
+
+  struct WeightedIndex {
+    explicit WeightedIndex(std::unique_ptr<SymbolIndex> Index)
+        : Index(std::move(Index)) {}
+
+    std::unique_ptr<SymbolIndex> Index;
+    double Weight;
+  };
+
+  void addSymbolIndex(llvm::StringRef Label, WeightedIndex Index);
+
+  llvm::Expected<CompletionResult>
+  complete(const CompletionRequest &Req) const override;
+
+private:
+  /// A list of <label, weighted index> pairs managed by the combined index,
+  /// sorted by weight - the highest comes first.
+  std::vector<std::pair<std::string, WeightedIndex>> Indexes;
+};
+
+/// \brief An interface for in-memory symbol sources that can be used to build
+/// indexes. All symbols should be able to fit into memory, e.g. AST symbol or
+// relatively small symbol table built offline.
+class InMemoryIndexSourcer {
+public:
+  virtual ~InMemoryIndexSourcer() = default;
+
+  /// \brief True if it is safe to take a reference of the symbol table.
+  virtual bool safeToReferenceSymbols() const = 0;
+
+  /// \brief Returns a copy of the symbol table.
+  virtual std::set<Symbol> symbols() const = 0;
+
+  /// \brief Returns a reference to the symbol table.
+  virtual const std::set<Symbol> &symbolsReference() const = 0;
+};
+
+/// \brief This sources all symbols for AST index.
+class ASTIndexSourcer : public InMemoryIndexSourcer {
+ public:
+   ASTIndexSourcer() = default;
+
+   /// \brief Updates symbols in the AST corresponding to `Path`.
+   /// \param TopLevelDecls Only symbols in these declarations will be
+   /// collected.
+   void update(PathRef Path, ASTContext &Context,
+               std::shared_ptr<Preprocessor> PP,
+               llvm::ArrayRef<const Decl *> TopLevelDecls);
+
+   /// \brief Removes all symbols corresponding to `Path`
+   void remove(PathRef Path);
+
+   /// \brief Since multiple threads can read and update symbols, a reference is
+   /// not safe.
+   bool safeToReferenceSymbols() const override { return false; }
+
+   std::set<Symbol> symbols() const override;
+
+   const std::set<Symbol> &symbolsReference() const override {
+     llvm_unreachable("ASTIndexSourcer does not support getting reference to "
+                      "the symbol index.");
+   }
+
+ private:
+   llvm::StringMap<std::set<Symbol>> FileToSymbols;
+   mutable std::mutex Mutex;
+};
+
+/// \brief A simple SymbolIndex implementation which iterates over all symbols
+/// and performs simple substring matching.
+class SimpleSymbolIndex : public SymbolIndex {
+public:
+  SimpleSymbolIndex(InMemoryIndexSourcer *Sourcer) : Sourcer(Sourcer) {}
+
+  llvm::Expected<CompletionResult>
+  complete(const CompletionRequest &Req) const override;
+
+private:
+  InMemoryIndexSourcer *Sourcer;
+};
+
+} // namespace clangd
+} // namespace clang
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANGD_CLANGDINDEX_H
Index: clangd/ClangdIndex.cpp
===================================================================
--- /dev/null
+++ clangd/ClangdIndex.cpp
@@ -0,0 +1,127 @@
+//===--- ClangdIndex.cpp - Symbol indexes for clangd. ------------*- C++-*-===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===-------------------------------------------------------------------===//
+
+#include "ClangdIndex.h"
+#include "SymbolIndex.h"
+#include "clang/Index/IndexingAction.h"
+
+namespace clang {
+namespace clangd {
+
+void CombinedSymbolIndex::addSymbolIndex(
+    llvm::StringRef Label, CombinedSymbolIndex::WeightedIndex Index) {
+  // Keep the indexes sorted by weight.
+  auto I = Indexes.begin();
+  for (auto E = Indexes.end(); I != E; ++I) {
+    if (I->second.Weight < Index.Weight)
+      break;
+  }
+  Indexes.insert(I, std::make_pair(Label.str(), std::move(Index)));
+}
+
+llvm::Expected<CompletionResult>
+CombinedSymbolIndex::complete(const CompletionRequest &Req) const {
+  // Add results from indexes that have highest weight first and ignore
+  // duplications in indexes with lower weight.
+  std::set<std::string> AddedUSRs;
+  CompletionResult Result;
+  Result.AllMatched = true;
+  for (const auto &Pair : Indexes) {
+    const WeightedIndex &Index = Pair.second;
+    auto Res = Index.Index->complete(Req);
+    if (!Res) {
+      // FIXME: use logger.
+      llvm::errs() << "Failed to complete request " << Req.Query << " in index "
+                   << Pair.first << "\n";
+      continue;
+    }
+    // FIXME: do something about `AllMatched`. If an index didn't match all
+    // candidates, we might need to keep an internal state for this request so
+    // that we could know which indexes to query when users ask for more
+    // results with the same request.
+    Result.AllMatched &= Res->AllMatched;
+
+    for (auto &Sym : Res->Symbols) {
+      if (AddedUSRs.find(Sym.USR) == AddedUSRs.end()) {
+        AddedUSRs.insert(Sym.USR);
+        // FIXME(ioeric): consider ranking signals.
+        Sym.SymbolScore = Index.Weight;
+        Result.Symbols.push_back(std::move(Sym));
+      }
+    }
+  }
+  return Result;
+}
+
+void ASTIndexSourcer::remove(PathRef Path) {
+  std::lock_guard<std::mutex> Lock(Mutex);
+  FileToSymbols.erase(Path);
+}
+
+void ASTIndexSourcer::update(PathRef Path, ASTContext &Ctx,
+                             std::shared_ptr<Preprocessor> PP,
+                             llvm::ArrayRef<const Decl *> TopLevelDecls) {
+  auto Collector = std::make_shared<SymbolCollector>();
+  Collector->setPreprocessor(std::move(PP));
+  index::IndexingOptions IndexOpts;
+  IndexOpts.SystemSymbolFilter =
+      index::IndexingOptions::SystemSymbolFilterKind::All;
+  IndexOpts.IndexFunctionLocals = false;
+
+  {
+    std::lock_guard<std::mutex> Lock(Mutex);
+
+    index::indexTopLevelDecls(Ctx, TopLevelDecls, Collector, IndexOpts);
+    FileToSymbols[Path.str()] = Collector->getSymbols();
+  }
+}
+
+std::set<Symbol> ASTIndexSourcer::symbols() const {
+  std::set<Symbol> Symbols;
+  {
+    std::lock_guard<std::mutex> Lock(Mutex);
+    for (const auto &FileAndSymbols : FileToSymbols)
+      Symbols.insert(FileAndSymbols.second.begin(),
+                     FileAndSymbols.second.end());
+  }
+  return Symbols;
+}
+
+/// FIXME: improve the indexing and matching algorithm.
+/// FIXME: we might want to return only the next immediate segment of name. For
+/// example, given query "a::", only return symbols "a::*" but "a::*::*". For
+/// we simply do substring match on qualified names.
+llvm::Expected<CompletionResult>
+SimpleSymbolIndex::complete(const CompletionRequest &Req) const {
+  std::set<Symbol> CopiedSymbols;
+  if (!Sourcer->safeToReferenceSymbols())
+    CopiedSymbols = Sourcer->symbols();
+  const auto &SymbolsRef = Sourcer->safeToReferenceSymbols()
+                               ? Sourcer->symbolsReference()
+                               : CopiedSymbols;
+
+  std::string LoweredQuery = llvm::StringRef(Req.Query).lower();
+  CompletionResult Result;
+  for (const auto &Symbol : SymbolsRef) {
+    if (StringRef(StringRef(Symbol.QualifiedName).lower())
+            .contains(LoweredQuery)) {
+      CompletionSymbol CS;
+      CS.QualifiedName = Symbol.QualifiedName;
+      CS.USR = Symbol.Identifier;
+      CS.CompletionInfo = Symbol.CompletionInfo;
+      CS.Kind = Symbol.SymInfo.Kind;
+      Result.Symbols.push_back(std::move(CS));
+    }
+  }
+  Result.AllMatched = true;
+  return Result;
+}
+
+} // namespace clangd
+} // namespace clang
Index: clangd/CMakeLists.txt
===================================================================
--- clangd/CMakeLists.txt
+++ clangd/CMakeLists.txt
@@ -3,6 +3,7 @@
   )
 
 add_clang_library(clangDaemon
+  ClangdIndex.cpp
   ClangdLSPServer.cpp
   ClangdServer.cpp
   ClangdUnit.cpp
@@ -17,6 +18,8 @@
   Logger.cpp
   Protocol.cpp
   ProtocolHandlers.cpp
+  Symbol.cpp
+  SymbolCompletionInfo.cpp
   Trace.cpp
 
   LINK_LIBS
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to