[clangd] Add code completion support
authorKrasimir Georgiev <krasimir@google.com>
Tue, 4 Apr 2017 09:46:39 +0000 (09:46 +0000)
committerKrasimir Georgiev <krasimir@google.com>
Tue, 4 Apr 2017 09:46:39 +0000 (09:46 +0000)
Summary: Adds code completion support to clangd.

Reviewers: bkramer, malaperle-ericsson

Reviewed By: bkramer, malaperle-ericsson

Subscribers: stanionascu, malaperle-ericsson, cfe-commits

Differential Revision: https://reviews.llvm.org/D31328

llvm-svn: 299421

clang-tools-extra/clangd/ASTManager.cpp
clang-tools-extra/clangd/ASTManager.h
clang-tools-extra/clangd/ClangDMain.cpp
clang-tools-extra/clangd/Protocol.cpp
clang-tools-extra/clangd/Protocol.h
clang-tools-extra/clangd/ProtocolHandlers.cpp
clang-tools-extra/clangd/ProtocolHandlers.h
clang-tools-extra/test/clangd/completion.test [new file with mode: 0644]
clang-tools-extra/test/clangd/formatting.test

index 7353d13..4e0b3f5 100644 (file)
@@ -83,50 +83,54 @@ void ASTManager::runWorker() {
 }
 
 void ASTManager::parseFileAndPublishDiagnostics(StringRef File) {
-  auto &Unit = ASTs[File]; // Only one thread can access this at a time.
-
-  if (!Unit) {
-    Unit = createASTUnitForFile(File, this->Store);
-  } else {
-    // Do a reparse if this wasn't the first parse.
-    // FIXME: This might have the wrong working directory if it changed in the
-    // meantime.
-    Unit->Reparse(PCHs, getRemappedFiles(this->Store));
-  }
+  DiagnosticToReplacementMap LocalFixIts; // Temporary storage
+  std::string Diagnostics;
+  {
+    std::lock_guard<std::mutex> ASTGuard(ASTLock);
+    auto &Unit = ASTs[File]; // Only one thread can access this at a time.
 
-  if (!Unit)
-    return;
+    if (!Unit) {
+      Unit = createASTUnitForFile(File, this->Store);
+    } else {
+      // Do a reparse if this wasn't the first parse.
+      // FIXME: This might have the wrong working directory if it changed in the
+      // meantime.
+      Unit->Reparse(PCHs, getRemappedFiles(this->Store));
+    }
 
-  // Send the diagnotics to the editor.
-  // FIXME: If the diagnostic comes from a different file, do we want to
-  // show them all? Right now we drop everything not coming from the
-  // main file.
-  std::string Diagnostics;
-  DiagnosticToReplacementMap LocalFixIts; // Temporary storage
-  for (ASTUnit::stored_diag_iterator D = Unit->stored_diag_begin(),
-                                     DEnd = Unit->stored_diag_end();
-       D != DEnd; ++D) {
-    if (!D->getLocation().isValid() ||
-        !D->getLocation().getManager().isInMainFile(D->getLocation()))
-      continue;
-    Position P;
-    P.line = D->getLocation().getSpellingLineNumber() - 1;
-    P.character = D->getLocation().getSpellingColumnNumber();
-    Range R = {P, P};
-    Diagnostics +=
-        R"({"range":)" + Range::unparse(R) +
-        R"(,"severity":)" + std::to_string(getSeverity(D->getLevel())) +
-        R"(,"message":")" + llvm::yaml::escape(D->getMessage()) +
-        R"("},)";
-
-    // We convert to Replacements to become independent of the SourceManager.
-    clangd::Diagnostic Diag = {R, getSeverity(D->getLevel()), D->getMessage()};
-    auto &FixItsForDiagnostic = LocalFixIts[Diag];
-    for (const FixItHint &Fix : D->getFixIts()) {
-      FixItsForDiagnostic.push_back(clang::tooling::Replacement(
-          Unit->getSourceManager(), Fix.RemoveRange, Fix.CodeToInsert));
+    if (!Unit)
+      return;
+
+    // Send the diagnotics to the editor.
+    // FIXME: If the diagnostic comes from a different file, do we want to
+    // show them all? Right now we drop everything not coming from the
+    // main file.
+    for (ASTUnit::stored_diag_iterator D = Unit->stored_diag_begin(),
+                                       DEnd = Unit->stored_diag_end();
+         D != DEnd; ++D) {
+      if (!D->getLocation().isValid() ||
+          !D->getLocation().getManager().isInMainFile(D->getLocation()))
+        continue;
+      Position P;
+      P.line = D->getLocation().getSpellingLineNumber() - 1;
+      P.character = D->getLocation().getSpellingColumnNumber();
+      Range R = {P, P};
+      Diagnostics +=
+          R"({"range":)" + Range::unparse(R) +
+          R"(,"severity":)" + std::to_string(getSeverity(D->getLevel())) +
+          R"(,"message":")" + llvm::yaml::escape(D->getMessage()) +
+          R"("},)";
+
+      // We convert to Replacements to become independent of the SourceManager.
+      clangd::Diagnostic Diag = {R, getSeverity(D->getLevel()),
+                                 D->getMessage()};
+      auto &FixItsForDiagnostic = LocalFixIts[Diag];
+      for (const FixItHint &Fix : D->getFixIts()) {
+        FixItsForDiagnostic.push_back(clang::tooling::Replacement(
+            Unit->getSourceManager(), Fix.RemoveRange, Fix.CodeToInsert));
+      }
     }
-  }
+  } // unlock ASTLock
 
   // Put FixIts into place.
   {
@@ -232,3 +236,79 @@ ASTManager::getFixIts(const clangd::Diagnostic &D) {
     return I->second;
   return {};
 }
+
+namespace {
+
+class CompletionItemsCollector : public CodeCompleteConsumer {
+  std::vector<CompletionItem> *Items;
+  std::shared_ptr<clang::GlobalCodeCompletionAllocator> Allocator;
+  CodeCompletionTUInfo CCTUInfo;
+
+public:
+  CompletionItemsCollector(std::vector<CompletionItem> *Items,
+                           const CodeCompleteOptions &CodeCompleteOpts)
+      : CodeCompleteConsumer(CodeCompleteOpts, /*OutputIsBinary=*/false),
+        Items(Items),
+        Allocator(std::make_shared<clang::GlobalCodeCompletionAllocator>()),
+        CCTUInfo(Allocator) {}
+
+  void ProcessCodeCompleteResults(Sema &S, CodeCompletionContext Context,
+                                  CodeCompletionResult *Results,
+                                  unsigned NumResults) override {
+    for (unsigned I = 0; I != NumResults; ++I) {
+      CodeCompletionString *CCS = Results[I].CreateCodeCompletionString(
+          S, Context, *Allocator, CCTUInfo,
+          CodeCompleteOpts.IncludeBriefComments);
+      if (CCS) {
+        CompletionItem Item;
+        assert(CCS->getTypedText());
+        Item.label = CCS->getTypedText();
+        if (CCS->getBriefComment())
+          Item.documentation = CCS->getBriefComment();
+        Items->push_back(std::move(Item));
+      }
+    }
+  }
+
+  GlobalCodeCompletionAllocator &getAllocator() override { return *Allocator; }
+
+  CodeCompletionTUInfo &getCodeCompletionTUInfo() override { return CCTUInfo; }
+};
+
+} // namespace
+
+std::vector<CompletionItem>
+ASTManager::codeComplete(StringRef Uri, unsigned Line, unsigned Column) {
+  CodeCompleteOptions CCO;
+  CCO.IncludeBriefComments = 1;
+  // This is where code completion stores dirty buffers. Need to free after
+  // completion.
+  SmallVector<const llvm::MemoryBuffer *, 4> OwnedBuffers;
+  SmallVector<StoredDiagnostic, 4> StoredDiagnostics;
+  IntrusiveRefCntPtr<DiagnosticsEngine> DiagEngine(
+      new DiagnosticsEngine(new DiagnosticIDs, new DiagnosticOptions));
+  std::vector<CompletionItem> Items;
+  CompletionItemsCollector Collector(&Items, CCO);
+  std::lock_guard<std::mutex> Guard(ASTLock);
+  auto &Unit = ASTs[Uri];
+  if (!Unit)
+    Unit = createASTUnitForFile(Uri, this->Store);
+  if (!Unit)
+    return {};
+  IntrusiveRefCntPtr<SourceManager> SourceMgr(
+      new SourceManager(*DiagEngine, Unit->getFileManager()));
+  StringRef File(Uri);
+  File.consume_front("file://");
+  // CodeComplete seems to require fresh LangOptions.
+  LangOptions LangOpts = Unit->getLangOpts();
+  // The language server protocol uses zero-based line and column numbers.
+  // The clang code completion uses one-based numbers.
+  Unit->CodeComplete(File, Line + 1, Column + 1, getRemappedFiles(this->Store),
+                     CCO.IncludeMacros, CCO.IncludeCodePatterns,
+                     CCO.IncludeBriefComments, Collector, PCHs, *DiagEngine,
+                     LangOpts, *SourceMgr, Unit->getFileManager(),
+                     StoredDiagnostics, OwnedBuffers);
+  for (const llvm::MemoryBuffer *Buffer : OwnedBuffers)
+    delete Buffer;
+  return Items;
+}
index 48539fe..ea379bc 100644 (file)
@@ -36,7 +36,13 @@ public:
 
   void onDocumentAdd(StringRef Uri) override;
   // FIXME: Implement onDocumentRemove
-  // FIXME: Implement codeComplete
+
+  /// Get code completions at a specified \p Line and \p Column in \p File.
+  ///
+  /// This function is thread-safe and returns completion items that own the
+  /// data they contain.
+  std::vector<CompletionItem> codeComplete(StringRef File, unsigned Line,
+                                           unsigned Column);
 
   /// Get the fixes associated with a certain diagnostic as replacements.
   ///
@@ -59,7 +65,7 @@ private:
   /// database is cached for subsequent accesses.
   clang::tooling::CompilationDatabase *
   getOrCreateCompilationDatabaseForFile(StringRef Uri);
-  // Craetes a new ASTUnit for the document at Uri.
+  // Creates a new ASTUnit for the document at Uri.
   // FIXME: This calls chdir internally, which is thread unsafe.
   std::unique_ptr<clang::ASTUnit>
   createASTUnitForFile(StringRef Uri, const DocumentStore &Docs);
@@ -68,7 +74,17 @@ private:
   void parseFileAndPublishDiagnostics(StringRef File);
 
   /// Clang objects.
+
+  /// A map from Uri-s to ASTUnit-s. Guarded by \c ASTLock. ASTUnit-s are used
+  /// for generating diagnostics and fix-it-s asynchronously by the worker
+  /// thread and synchronously for code completion.
+  ///
+  /// TODO(krasimir): code completion should always have priority over parsing
+  /// for diagnostics.
   llvm::StringMap<std::unique_ptr<clang::ASTUnit>> ASTs;
+  /// A lock for access to the map \c ASTs.
+  std::mutex ASTLock;
+
   llvm::StringMap<std::unique_ptr<clang::tooling::CompilationDatabase>>
       CompilationDatabases;
   std::shared_ptr<clang::PCHContainerOperations> PCHs;
index b23f6e2..8eefd27 100644 (file)
@@ -61,6 +61,8 @@ int main(int argc, char *argv[]) {
       llvm::make_unique<TextDocumentFormattingHandler>(Out, Store));
   Dispatcher.registerHandler("textDocument/codeAction",
                              llvm::make_unique<CodeActionHandler>(Out, AST));
+  Dispatcher.registerHandler("textDocument/completion",
+                             llvm::make_unique<CompletionHandler>(Out, AST));
 
   while (std::cin.good()) {
     // A Language Server Protocol message starts with a HTTP header, delimited
index 2814a54..163d982 100644 (file)
@@ -570,3 +570,75 @@ CodeActionParams::parse(llvm::yaml::MappingNode *Params) {
   }
   return Result;
 }
+
+llvm::Optional<TextDocumentPositionParams>
+TextDocumentPositionParams::parse(llvm::yaml::MappingNode *Params) {
+  TextDocumentPositionParams Result;
+  for (auto &NextKeyValue : *Params) {
+    auto *KeyString = dyn_cast<llvm::yaml::ScalarNode>(NextKeyValue.getKey());
+    if (!KeyString)
+      return llvm::None;
+
+    llvm::SmallString<10> KeyStorage;
+    StringRef KeyValue = KeyString->getValue(KeyStorage);
+    auto *Value =
+        dyn_cast_or_null<llvm::yaml::MappingNode>(NextKeyValue.getValue());
+    if (!Value)
+      return llvm::None;
+
+    llvm::SmallString<10> Storage;
+    if (KeyValue == "textDocument") {
+      auto Parsed = TextDocumentIdentifier::parse(Value);
+      if (!Parsed)
+        return llvm::None;
+      Result.textDocument = std::move(*Parsed);
+    } else if (KeyValue == "position") {
+      auto Parsed = Position::parse(Value);
+      if (!Parsed)
+        return llvm::None;
+      Result.position = std::move(*Parsed);
+    } else {
+      return llvm::None;
+    }
+  }
+  return Result;
+}
+
+std::string CompletionItem::unparse(const CompletionItem &CI) {
+  std::string Result = "{";
+  llvm::raw_string_ostream Os(Result);
+  assert(!CI.label.empty() && "completion item label is required");
+  Os << R"("label":")" << llvm::yaml::escape(CI.label) << R"(",)";
+  if (CI.kind != CompletionItemKind::Missing)
+    Os << R"("kind":)" << static_cast<int>(CI.kind) << R"(",)";
+  if (!CI.detail.empty())
+    Os << R"("detail":")" << llvm::yaml::escape(CI.detail) << R"(",)";
+  if (!CI.documentation.empty())
+    Os << R"("documentation":")" << llvm::yaml::escape(CI.documentation)
+       << R"(",)";
+  if (!CI.sortText.empty())
+    Os << R"("sortText":")" << llvm::yaml::escape(CI.sortText) << R"(",)";
+  if (!CI.filterText.empty())
+    Os << R"("filterText":")" << llvm::yaml::escape(CI.filterText) << R"(",)";
+  if (!CI.insertText.empty())
+    Os << R"("insertText":")" << llvm::yaml::escape(CI.insertText) << R"(",)";
+  if (CI.insertTextFormat != InsertTextFormat::Missing) {
+    Os << R"("insertTextFormat":")" << static_cast<int>(CI.insertTextFormat)
+       << R"(",)";
+  }
+  if (CI.textEdit)
+    Os << R"("textEdit":)" << TextEdit::unparse(*CI.textEdit) << ',';
+  if (!CI.additionalTextEdits.empty()) {
+    Os << R"("additionalTextEdits":[)";
+    for (const auto &Edit : CI.additionalTextEdits)
+      Os << TextEdit::unparse(Edit) << ",";
+    Os.flush();
+    // The list additionalTextEdits is guaranteed nonempty at this point.
+    // Replace the trailing comma with right brace.
+    Result.back() = ']';
+  }
+  Os.flush();
+  // Label is required, so Result is guaranteed to have a trailing comma.
+  Result.back() = '}';
+  return Result;
+}
index 5a32bed..7525d10 100644 (file)
@@ -233,6 +233,113 @@ struct CodeActionParams {
   parse(llvm::yaml::MappingNode *Params);
 };
 
+struct TextDocumentPositionParams {
+  /// The text document.
+  TextDocumentIdentifier textDocument;
+
+  /// The position inside the text document.
+  Position position;
+
+  static llvm::Optional<TextDocumentPositionParams>
+  parse(llvm::yaml::MappingNode *Params);
+};
+
+/// The kind of a completion entry.
+enum class CompletionItemKind {
+  Missing = 0,
+  Text = 1,
+  Method = 2,
+  Function = 3,
+  Constructor = 4,
+  Field = 5,
+  Variable = 6,
+  Class = 7,
+  Interface = 8,
+  Module = 9,
+  Property = 10,
+  Unit = 11,
+  Value = 12,
+  Enum = 13,
+  Keyword = 14,
+  Snippet = 15,
+  Color = 16,
+  File = 17,
+  Reference = 18,
+};
+
+/// Defines whether the insert text in a completion item should be interpreted
+/// as plain text or a snippet.
+enum class InsertTextFormat {
+  Missing = 0,
+  /// The primary text to be inserted is treated as a plain string.
+  PlainText = 1,
+  /// The primary text to be inserted is treated as a snippet.
+  ///
+  /// A snippet can define tab stops and placeholders with `$1`, `$2`
+  /// and `${3:foo}`. `$0` defines the final tab stop, it defaults to the end
+  /// of the snippet. Placeholders with equal identifiers are linked, that is
+  /// typing in one will update others too.
+  ///
+  /// See also:
+  /// https//github.com/Microsoft/vscode/blob/master/src/vs/editor/contrib/snippet/common/snippet.md
+  Snippet = 2,
+};
+
+struct CompletionItem {
+  /// The label of this completion item. By default also the text that is
+  /// inserted when selecting this completion.
+  std::string label;
+
+  /// The kind of this completion item. Based of the kind an icon is chosen by
+  /// the editor.
+  CompletionItemKind kind = CompletionItemKind::Missing;
+
+  /// A human-readable string with additional information about this item, like
+  /// type or symbol information.
+  std::string detail;
+
+  /// A human-readable string that represents a doc-comment.
+  std::string documentation;
+
+  /// A string that should be used when comparing this item with other items.
+  /// When `falsy` the label is used.
+  std::string sortText;
+
+  /// A string that should be used when filtering a set of completion items.
+  /// When `falsy` the label is used.
+  std::string filterText;
+
+  /// A string that should be inserted to a document when selecting this
+  /// completion. When `falsy` the label is used.
+  std::string insertText;
+
+  /// The format of the insert text. The format applies to both the `insertText`
+  /// property and the `newText` property of a provided `textEdit`.
+  InsertTextFormat insertTextFormat = InsertTextFormat::Missing;
+
+  /// An edit which is applied to a document when selecting this completion.
+  /// When an edit is provided `insertText` is ignored.
+  ///
+  /// Note: The range of the edit must be a single line range and it must
+  /// contain the position at which completion has been requested.
+  llvm::Optional<TextEdit> textEdit;
+
+  /// An optional array of additional text edits that are applied when selecting
+  /// this completion. Edits must not overlap with the main edit nor with
+  /// themselves.
+  std::vector<TextEdit> additionalTextEdits;
+  
+  // TODO(krasimir): The following optional fields defined by the language
+  // server protocol are unsupported:
+  //
+  // command?: Command - An optional command that is executed *after* inserting
+  //                     this completion.
+  //
+  // data?: any - A data entry field that is preserved on a completion item
+  //              between a completion and a completion resolve request.
+  static std::string unparse(const CompletionItem &P);
+};
+
 } // namespace clangd
 } // namespace clang
 
index 8edd6c5..43a98b8 100644 (file)
@@ -178,3 +178,25 @@ void CodeActionHandler::handleMethod(llvm::yaml::MappingNode *Params,
       R"(, "result": [)" + Commands +
       R"(]})");
 }
+
+void CompletionHandler::handleMethod(llvm::yaml::MappingNode *Params,
+                                     StringRef ID) {
+  auto TDPP = TextDocumentPositionParams::parse(Params);
+  if (!TDPP) {
+    Output.log("Failed to decode TextDocumentPositionParams!\n");
+    return;
+  }
+
+  auto Items = AST.codeComplete(TDPP->textDocument.uri, TDPP->position.line,
+                                TDPP->position.character);
+  std::string Completions;
+  for (const auto &Item : Items) {
+    Completions += CompletionItem::unparse(Item);
+    Completions += ",";
+  }
+  if (!Completions.empty())
+    Completions.pop_back();
+  writeMessage(
+      R"({"jsonrpc":"2.0","id":)" + ID.str() +
+      R"(,"result":[)" + Completions + R"(]})");
+}
index 8acce25..4b22655 100644 (file)
@@ -36,7 +36,8 @@ struct InitializeHandler : Handler {
           "documentFormattingProvider": true,
           "documentRangeFormattingProvider": true,
           "documentOnTypeFormattingProvider": {"firstTriggerCharacter":"}","moreTriggerCharacter":[]},
-          "codeActionProvider": true
+          "codeActionProvider": true,
+          "completionProvider": {"resolveProvider": false, "triggerCharacters": [".",">"]}
         }}})");
   }
 };
@@ -114,6 +115,16 @@ private:
   ASTManager &AST;
 };
 
+struct CompletionHandler : Handler {
+  CompletionHandler(JSONOutput &Output, ASTManager &AST)
+      : Handler(Output), AST(AST) {}
+
+  void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override;
+
+ private:
+  ASTManager &AST;
+};
+
 } // namespace clangd
 } // namespace clang
 
diff --git a/clang-tools-extra/test/clangd/completion.test b/clang-tools-extra/test/clangd/completion.test
new file mode 100644 (file)
index 0000000..05c4a4d
--- /dev/null
@@ -0,0 +1,69 @@
+# RUN: clangd -run-synchronously < %s | FileCheck %s\r
+# It is absolutely vital that this file has CRLF line endings.\r
+#\r
+Content-Length: 125\r
+\r
+{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}}\r
+\r
+Content-Length: 208\r
+\r
+{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///main.cpp","languageId":"cpp","version":1,"text":"#include <vector>\nint main() {\n  std::vector<int> v;\n  v.\n}\n"}}}\r
+\r
+Content-Length: 148\r
+\r
+{"jsonrpc":"2.0","id":1,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":3,"character":4}}}\r
+# The order of results returned by ASTUnit CodeComplete seems to be\r
+# nondeterministic, so we check regardless of order.\r
+#\r
+# CHECK: {"jsonrpc":"2.0","id":1,"result":[\r
+# CHECK-DAG: {"label":"_M_allocate"}\r
+# CHECK-DAG: {"label":"_M_allocate_and_copy"}\r
+# CHECK-DAG: {"label":"_M_assign_aux"}\r
+# CHECK-DAG: {"label":"_M_assign_dispatch"}\r
+# CHECK-DAG: {"label":"_M_check_len"}\r
+# CHECK-DAG: {"label":"_M_create_storage"\r
+# CHECK-DAG: {"label":"_M_deallocate"}\r
+# CHECK-DAG: {"label":"_M_erase_at_end"}\r
+# CHECK-DAG: {"label":"_M_fill_assign"}\r
+# CHECK-DAG: {"label":"_M_fill_initialize"}\r
+# CHECK-DAG: {"label":"_M_fill_insert"}\r
+# CHECK-DAG: {"label":"_M_get_Tp_allocator"}\r
+# CHECK-DAG: {"label":"_M_impl"}\r
+# CHECK-DAG: {"label":"_M_initialize_dispatch"}\r
+# CHECK-DAG: {"label":"_M_insert_aux"}\r
+# CHECK-DAG: {"label":"_M_insert_dispatch"}\r
+# CHECK-DAG: {"label":"_M_range_check"}\r
+# CHECK-DAG: {"label":"_M_range_initialize"}\r
+# CHECK-DAG: {"label":"_M_range_insert"}\r
+# CHECK-DAG: {"label":"_Vector_base"}\r
+# CHECK-DAG: {"label":"assign"}\r
+# CHECK-DAG: {"label":"at"}\r
+# CHECK-DAG: {"label":"back"}\r
+# CHECK-DAG: {"label":"begin"}\r
+# CHECK-DAG: {"label":"capacity"}\r
+# CHECK-DAG: {"label":"clear"}\r
+# CHECK-DAG: {"label":"data"}\r
+# CHECK-DAG: {"label":"empty"}\r
+# CHECK-DAG: {"label":"end"}\r
+# CHECK-DAG: {"label":"erase"}\r
+# CHECK-DAG: {"label":"front"}\r
+# CHECK-DAG: {"label":"get_allocator"}\r
+# CHECK-DAG: {"label":"insert"}\r
+# CHECK-DAG: {"label":"max_size"}\r
+# CHECK-DAG: {"label":"operator="}\r
+# CHECK-DAG: {"label":"operator[]"}\r
+# CHECK-DAG: {"label":"pop_back"}\r
+# CHECK-DAG: {"label":"push_back"}\r
+# CHECK-DAG: {"label":"rbegin"}\r
+# CHECK-DAG: {"label":"rend"}\r
+# CHECK-DAG: {"label":"reserve"}\r
+# CHECK-DAG: {"label":"resize"}\r
+# CHECK-DAG: {"label":"size"}\r
+# CHECK-DAG: {"label":"swap"}\r
+# CHECK-DAG: {"label":"vector"}\r
+# CHECK-DAG: {"label":"~_Vector_base"}\r
+# CHECK-DAG: {"label":"~vector"}\r
+# CHECK: ]}\r
+Content-Length: 44\r
+\r
+{"jsonrpc":"2.0","id":3,"method":"shutdown"}\r
index 0834767..d0a0cd5 100644 (file)
@@ -4,13 +4,14 @@
 Content-Length: 125\r
 \r
 {"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}}\r
-# CHECK: Content-Length: 332\r
+# CHECK: Content-Length: 424\r
 # CHECK: {"jsonrpc":"2.0","id":0,"result":{"capabilities":{\r
 # CHECK:   "textDocumentSync": 1,\r
 # CHECK:   "documentFormattingProvider": true,\r
 # CHECK:   "documentRangeFormattingProvider": true,\r
 # CHECK:   "documentOnTypeFormattingProvider": {"firstTriggerCharacter":"}","moreTriggerCharacter":[]},\r
-# CHECK:   "codeActionProvider": true\r
+# CHECK:   "codeActionProvider": true,\r
+# CHECK:   "completionProvider": {"resolveProvider": false, "triggerCharacters": [".",">"]}\r
 # CHECK: }}}\r
 #\r
 Content-Length: 193\r