sammccall created this revision.
Herald added subscribers: usaxena95, kadircet, arphaman, mgorny.
sammccall requested review of this revision.
Herald added subscribers: cfe-commits, MaskRay, ilya-biryukov.
Herald added a project: clang.

This is related to the ideas in D96244 <https://reviews.llvm.org/D96244>, but a 
bit more expansive...

Core ideas:

- the bulk of the features (though not everything) can be moved into modules, 
breaking up the Clangd[LSP]Server monoliths and allowing for out-of-tree 
features
- group together the LSP, C++ bound-to-server API, and free-function APIs for 
features. We no longer separate the first two in simple cases.
- allow modules to expose public C++ APIs, where we want to expose something 
richer than LSP

Caveats:

- this demo just pulls out formatting, which won't uncover all issues.
- Lit tests pass (so clangd works), but unit tests don't.
- Passing DraftStore into ClangdServer is a hack, need a real solution.
- I'm concerned whether richer C++ APIs will have a quite different style than 
the LSP-based ones. e.g. foo(Path, Position, Options) vs foo(FooParams).


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D96252

Files:
  clang-tools-extra/clangd/CMakeLists.txt
  clang-tools-extra/clangd/ClangdLSPServer.cpp
  clang-tools-extra/clangd/ClangdLSPServer.h
  clang-tools-extra/clangd/ClangdServer.cpp
  clang-tools-extra/clangd/ClangdServer.h
  clang-tools-extra/clangd/Format.cpp
  clang-tools-extra/clangd/Format.h
  clang-tools-extra/clangd/Module.h
  clang-tools-extra/clangd/ModuleDefaults.cpp
  clang-tools-extra/clangd/Protocol.cpp
  clang-tools-extra/clangd/Protocol.h
  clang-tools-extra/clangd/test/crash-non-added-files.test
  clang-tools-extra/clangd/tool/ClangdMain.cpp

Index: clang-tools-extra/clangd/tool/ClangdMain.cpp
===================================================================
--- clang-tools-extra/clangd/tool/ClangdMain.cpp
+++ clang-tools-extra/clangd/tool/ClangdMain.cpp
@@ -706,6 +706,8 @@
     log("{0}: {1}", FlagsEnvVar, *EnvFlags);
 
   ClangdLSPServer::Options Opts;
+  auto Modules = ModuleSet::defaults();
+  Opts.Modules = &Modules;
   Opts.UseDirBasedCDB = (CompileArgsFrom == FilesystemCompileArgs);
 
   // If --compile-commands-dir arg was invoked, check value and override default
Index: clang-tools-extra/clangd/test/crash-non-added-files.test
===================================================================
--- clang-tools-extra/clangd/test/crash-non-added-files.test
+++ clang-tools-extra/clangd/test/crash-non-added-files.test
@@ -11,21 +11,21 @@
 {"jsonrpc":"2.0","id":3,"method":"textDocument/rangeFormatting","params":{"textDocument":{"uri":"test:///foo.c"},"range":{"start":{"line":1,"character":4},"end":{"line":1,"character":12}},"options":{"tabSize":4,"insertSpaces":true}}}
 #      CHECK:  "error": {
 # CHECK-NEXT:    "code": -32602
-# CHECK-NEXT:    "message": "onDocumentRangeFormatting called for non-added file"
+# CHECK-NEXT:    "message": "formatting non-added file"
 # CHECK-NEXT:  },
 # CHECK-NEXT:  "id": 3,
 ---
 {"jsonrpc":"2.0","id":4,"method":"textDocument/formatting","params":{"textDocument":{"uri":"test:///foo.c"},"options":{"tabSize":4,"insertSpaces":true}}}
 #      CHECK:  "error": {
 # CHECK-NEXT:    "code": -32602
-# CHECK-NEXT:    "message": "onDocumentFormatting called for non-added file"
+# CHECK-NEXT:    "message": "formatting non-added file"
 # CHECK-NEXT:  },
 # CHECK-NEXT:  "id": 4,
 ---
 {"jsonrpc":"2.0","id":5,"method":"textDocument/onTypeFormatting","params":{"textDocument":{"uri":"test:///foo.c"},"position":{"line":3,"character":1},"ch":"}","options":{"tabSize":4,"insertSpaces":true}}}
 #      CHECK:  "error": {
 # CHECK-NEXT:    "code": -32602
-# CHECK-NEXT:    "message": "onDocumentOnTypeFormatting called for non-added file"
+# CHECK-NEXT:    "message": "formatting non-added file"
 # CHECK-NEXT:  },
 # CHECK-NEXT:  "id": 5,
 ---
Index: clang-tools-extra/clangd/Protocol.h
===================================================================
--- clang-tools-extra/clangd/Protocol.h
+++ clang-tools-extra/clangd/Protocol.h
@@ -745,44 +745,6 @@
 bool fromJSON(const llvm::json::Value &, DidChangeConfigurationParams &,
               llvm::json::Path);
 
-// Note: we do not parse FormattingOptions for *FormattingParams.
-// In general, we use a clang-format style detected from common mechanisms
-// (.clang-format files and the -fallback-style flag).
-// It would be possible to override these with FormatOptions, but:
-//  - the protocol makes FormatOptions mandatory, so many clients set them to
-//    useless values, and we can't tell when to respect them
-// - we also format in other places, where FormatOptions aren't available.
-
-struct DocumentRangeFormattingParams {
-  /// The document to format.
-  TextDocumentIdentifier textDocument;
-
-  /// The range to format
-  Range range;
-};
-bool fromJSON(const llvm::json::Value &, DocumentRangeFormattingParams &,
-              llvm::json::Path);
-
-struct DocumentOnTypeFormattingParams {
-  /// The document to format.
-  TextDocumentIdentifier textDocument;
-
-  /// The position at which this request was sent.
-  Position position;
-
-  /// The character that has been typed.
-  std::string ch;
-};
-bool fromJSON(const llvm::json::Value &, DocumentOnTypeFormattingParams &,
-              llvm::json::Path);
-
-struct DocumentFormattingParams {
-  /// The document to format.
-  TextDocumentIdentifier textDocument;
-};
-bool fromJSON(const llvm::json::Value &, DocumentFormattingParams &,
-              llvm::json::Path);
-
 struct DocumentSymbolParams {
   // The text document to find symbols in.
   TextDocumentIdentifier textDocument;
Index: clang-tools-extra/clangd/Protocol.cpp
===================================================================
--- clang-tools-extra/clangd/Protocol.cpp
+++ clang-tools-extra/clangd/Protocol.cpp
@@ -539,25 +539,6 @@
          O.map("text", R.text);
 }
 
-bool fromJSON(const llvm::json::Value &Params, DocumentRangeFormattingParams &R,
-              llvm::json::Path P) {
-  llvm::json::ObjectMapper O(Params, P);
-  return O && O.map("textDocument", R.textDocument) && O.map("range", R.range);
-}
-
-bool fromJSON(const llvm::json::Value &Params,
-              DocumentOnTypeFormattingParams &R, llvm::json::Path P) {
-  llvm::json::ObjectMapper O(Params, P);
-  return O && O.map("textDocument", R.textDocument) &&
-         O.map("position", R.position) && O.map("ch", R.ch);
-}
-
-bool fromJSON(const llvm::json::Value &Params, DocumentFormattingParams &R,
-              llvm::json::Path P) {
-  llvm::json::ObjectMapper O(Params, P);
-  return O && O.map("textDocument", R.textDocument);
-}
-
 bool fromJSON(const llvm::json::Value &Params, DocumentSymbolParams &R,
               llvm::json::Path P) {
   llvm::json::ObjectMapper O(Params, P);
Index: clang-tools-extra/clangd/ModuleDefaults.cpp
===================================================================
--- /dev/null
+++ clang-tools-extra/clangd/ModuleDefaults.cpp
@@ -0,0 +1,16 @@
+#include "Format.h"
+#include "Module.h"
+
+namespace clang {
+namespace clangd {
+
+// This could use registration magic if we wanted linked-in modules to
+// automatically be included without source changes.
+ModuleSet ModuleSet::defaults() {
+  ModuleSet All;
+  All.add(std::make_unique<FormatModule>());
+  return All;
+}
+
+} // namespace clangd
+} // namespace clang
Index: clang-tools-extra/clangd/Module.h
===================================================================
--- /dev/null
+++ clang-tools-extra/clangd/Module.h
@@ -0,0 +1,196 @@
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_MODULE_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_MODULE_H
+
+#include "DraftStore.h"
+#include "Protocol.h"
+#include "TUScheduler.h"
+#include "support/Function.h"
+#include "support/Logger.h"
+#include "support/ThreadsafeFS.h"
+#include "llvm/ADT/Sequence.h"
+#include "llvm/ADT/StringMap.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/ADT/iterator.h"
+#include "llvm/Support/JSON.h"
+#include <type_traits>
+
+namespace clang {
+namespace clangd {
+
+/// A Module contributes a vertical feature to clangd. This consists of:
+///
+///  - LSP bindings (e.g. setting capabilities, implementing methods)
+///  - a public interface to access the functionality from C++ embedders
+///    ClangdServer::getModule<FooModule>()->foo(...)
+///
+/// The lifetime of a module is roughly:
+///  - modules are created before the LSP server, in ClangdMain.cpp
+///  - when ClangdLSPServer is created, initializeLSP is called to bind methods
+///    FIXME: this should be later, during the initialize LSP call, and have
+///    access to capabilities.
+///  - when ClangdServer is created, it fills in the ServerFacilities
+///  - module hooks and LSP method handlers may be called at this point
+///  - ClangdServer will not be destroyed until all requests are done
+///    FIXME: I guess we need a way to to block server shutdown in case modules
+///    have background work.
+///  - ServerFacilities are removed before ClangdServer is destroyed.
+///    There are no more calls after this point.
+///  - The module is destroyed sometime after ClangdLSPServer
+///
+/// Conventionally, standard modules live in the `clangd` namespace, and other
+/// exposed details live in a sub-namespace.
+/// clang::clangd::
+///   FormatModule
+///   format::
+///     FormattingRequest
+///     formatIncremental() // exposed for testing
+class Module {
+public:
+  virtual ~Module() = default;
+
+protected: // hooks for subclasses to implement (called by module container)
+  class MethodBinder;
+  // Register handlers for LSP methods and notifications.
+  virtual void initializeLSP(MethodBinder &){};
+
+protected: // API for subclasses to use
+  const DraftStore &drafts() { return Facilities->Drafts; }
+  TUScheduler &scheduler() { return Facilities->Scheduler; }
+  const ThreadsafeFS &fs() { return Facilities->FS; }
+
+private: // State is set up by module container, which must be a friend.
+  struct ServerFacilities {
+    const DraftStore &Drafts;
+    TUScheduler &Scheduler;
+    const ThreadsafeFS &FS;
+  };
+  llvm::Optional<ServerFacilities> Facilities;
+  friend class ClangdServer;
+  friend class ClangdLSPServer;
+  friend class ModuleTesting;
+};
+
+class Module::MethodBinder {
+public:
+  // Binds an LSP method to a module method which handles it. e.g:
+  //   void FooModule::foo(const FooParams &, Callback<FooResult>);
+  //   Bind.method("textDocument/foo", this, &FooModule::foo)
+  template <typename Subclass, typename Param, typename Result>
+  void method(const char *Method, Subclass *This,
+              void (Subclass::*Handler)(const Param &, Callback<Result>));
+  // Binds an LSP notification to a module method which handles it. e.g:
+  //   void FooModule::ping(const PingParams &);
+  //   Bind.method("textDocument/onPing", this, &FooModule::ping)
+  template <typename Subclass, typename Param>
+  void notification(const char *Method, Subclass *This,
+                    void (Subclass::*Handler)(const Param &));
+
+private:
+  template <typename T> using HandlerMap = llvm::StringMap<std::function<T>>;
+  using Method = void(llvm::json::Value, Callback<llvm::json::Value>);
+  using Notification = void(llvm::json::Value);
+  HandlerMap<Method> Methods;
+  HandlerMap<Notification> Notifications;
+
+  template <typename T>
+  static llvm::Expected<T> parse(const llvm::json::Value &Raw,
+                                 llvm::StringRef PayloadName,
+                                 llvm::StringRef PayloadKind);
+
+  friend class ClangdLSPServer; // pulls out the results.
+};
+
+/// A collection of Modules, with type-based lookup.
+/// This allows modules to be used generically through the Module/LSP interface,
+/// but also to expose a public API to C++ callers.
+class ModuleSet {
+  std::vector<Module *> Vec;
+
+public:
+  /// The standard set of built-in modules.
+  static ModuleSet defaults();
+
+  using iterator = llvm::pointee_iterator<decltype(Vec)::iterator>;
+  using const_iterator = llvm::pointee_iterator<decltype(Vec)::const_iterator>;
+
+  iterator begin() { return iterator(Vec.begin()); }
+  iterator end() { return iterator(Vec.end()); }
+  const_iterator begin() const { return const_iterator(Vec.begin()); }
+  const_iterator end() const { return const_iterator(Vec.end()); }
+
+  template <typename T> const T *get() const {
+    static_assert(std::is_base_of<Module, T>(), "Can only get Modules!");
+    return static_cast<T *>(Map.lookup(&Key<T>));
+  }
+
+  template <typename T> bool add(std::unique_ptr<T> M) {
+    static_assert(std::is_base_of<Module, T>(), "Can only add Modules!");
+    static_assert(std::is_final<T>(), "Modules must be final!");
+    auto R = Map.try_emplace(&Key<T>, std::unique_ptr<Module>(std::move(M)));
+    if (R.second)
+      Vec.push_back(R.first->second.get());
+    return R.second;
+  }
+
+private:
+  template <typename T> static int Key;
+  llvm::DenseMap<int *, std::unique_ptr<Module>> Map;
+};
+
+/// Tedious template implementation stuff lives below.
+
+template <typename T> int ModuleSet::Key;
+
+template <typename Subclass, typename Param, typename Result>
+void Module::MethodBinder::method(const char *Method, Subclass *This,
+                                  void (Subclass::*Handler)(const Param &,
+                                                            Callback<Result>)) {
+  Methods[Method] = [Method, Handler, This](llvm::json::Value RawParams,
+                                            Callback<llvm::json::Value> Reply) {
+    llvm::Expected<Param> P = parse<Param>(RawParams, Method, "request");
+    if (!P)
+      return Reply(P.takeError());
+    (This->*Handler)(*P, std::move(Reply));
+  };
+}
+
+template <typename Subclass, typename Param>
+void Module::MethodBinder::notification(
+    const char *Method, Subclass *This,
+    void (Subclass::*Handler)(const Param &)) {
+  Notifications[Method] = [Method, Handler, This](llvm::json::Value RawParams) {
+    llvm::Expected<Param> P = parse<Param>(RawParams, Method, "request");
+    if (!P)
+      return llvm::consumeError(P.takeError());
+    // XXX trace latency
+    (This->*Handler)(*P);
+  };
+}
+
+template <typename T>
+llvm::Expected<T> Module::MethodBinder::parse(const llvm::json::Value &Raw,
+                                              llvm::StringRef PayloadName,
+                                              llvm::StringRef PayloadKind) {
+  T Result;
+  llvm::json::Path::Root Root;
+  if (!fromJSON(Raw, Result, Root)) {
+    elog("Failed to decode {0} {1}: {2}", PayloadName, PayloadKind,
+         Root.getError());
+    // Dump the relevant parts of the broken message.
+    std::string Context;
+    llvm::raw_string_ostream OS(Context);
+    Root.printErrorContext(Raw, OS);
+    vlog("{0}", OS.str());
+    // Report the error (e.g. to the client).
+    return llvm::make_error<LSPError>(
+        llvm::formatv("failed to decode {0} {1}: {2}", PayloadName, PayloadKind,
+                      fmt_consume(Root.getError())),
+        ErrorCode::InvalidParams);
+  }
+  return std::move(Result);
+}
+
+} // namespace clangd
+} // namespace clang
+
+#endif
Index: clang-tools-extra/clangd/Format.h
===================================================================
--- clang-tools-extra/clangd/Format.h
+++ clang-tools-extra/clangd/Format.h
@@ -13,13 +13,14 @@
 #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_FORMAT_H
 #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_FORMAT_H
 
+#include "Module.h"
 #include "Protocol.h"
 #include "clang/Format/Format.h"
-#include "clang/Tooling/Core/Replacement.h"
 #include "llvm/ADT/StringRef.h"
 
 namespace clang {
 namespace clangd {
+namespace format {
 
 /// Applies limited formatting around new \p InsertedText.
 /// The \p Code already contains the updated text before \p Cursor, and may have
@@ -41,7 +42,8 @@
 /// would merge these, and thus lose information about cursor position.
 std::vector<tooling::Replacement>
 formatIncremental(llvm::StringRef Code, unsigned Cursor,
-                  llvm::StringRef InsertedText, format::FormatStyle Style);
+                  llvm::StringRef InsertedText,
+                  clang::format::FormatStyle Style);
 
 /// Determine the new cursor position after applying \p Replacements.
 /// Analogue of tooling::Replacements::getShiftedCodePosition().
@@ -49,6 +51,68 @@
 transformCursorPosition(unsigned Offset,
                         const std::vector<tooling::Replacement> &Replacements);
 
+// Note: we do not parse FormattingOptions for *FormattingParams.
+// In general, we use a clang-format style detected from common mechanisms
+// (.clang-format files and the -fallback-style flag).
+// It would be possible to override these with FormatOptions, but:
+//  - the protocol makes FormatOptions mandatory, so many clients set them to
+//    useless values, and we can't tell when to respect them
+// - we also format in other places, where FormatOptions aren't available.
+
+struct DocumentRangeFormattingParams {
+  /// The document to format.
+  TextDocumentIdentifier textDocument;
+
+  /// The range to format
+  Range range;
+};
+bool fromJSON(const llvm::json::Value &, DocumentRangeFormattingParams &,
+              llvm::json::Path);
+
+struct DocumentOnTypeFormattingParams {
+  /// The document to format.
+  TextDocumentIdentifier textDocument;
+
+  /// The position at which this request was sent.
+  Position position;
+
+  /// The character that has been typed.
+  std::string ch;
+};
+bool fromJSON(const llvm::json::Value &, DocumentOnTypeFormattingParams &,
+              llvm::json::Path);
+
+struct DocumentFormattingParams {
+  /// The document to format.
+  TextDocumentIdentifier textDocument;
+};
+bool fromJSON(const llvm::json::Value &, DocumentFormattingParams &,
+              llvm::json::Path);
+
+} // namespace format
+
+class FormatModule final : public Module {
+public:
+  /// The document range formatting request is sent from the client to the
+  /// server to format a given range in a document.
+  void rangeFormatting(const format::DocumentRangeFormattingParams &,
+                       Callback<std::vector<TextEdit>>);
+  /// The document formatting request is sent from the client to the server to
+  /// format a whole document.
+  void formatting(const format::DocumentFormattingParams &,
+                  Callback<std::vector<TextEdit>>);
+  /// The document on type formatting request is sent from the client to the
+  /// server to format parts of the document during typing.
+  void onTypeFormatting(const format::DocumentOnTypeFormattingParams &,
+                        Callback<std::vector<TextEdit>>);
+
+private:
+  void formatImpl(llvm::StringRef Name, llvm::StringRef Code,
+                  tooling::Replacements Range, Callback<std::vector<TextEdit>>);
+
+  void initializeLSP(MethodBinder &MB) override;
+};
+
 } // namespace clangd
 } // namespace clang
 
Index: clang-tools-extra/clangd/Format.cpp
===================================================================
--- clang-tools-extra/clangd/Format.cpp
+++ clang-tools-extra/clangd/Format.cpp
@@ -5,8 +5,14 @@
 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 //
 //===----------------------------------------------------------------------===//
+
+// I wonder whether we should split this file into FormatModule.cpp and
+// Format.cpp, as they have pretty different sets of implementation deps...
+
 #include "Format.h"
+#include "SourceCode.h"
 #include "support/Logger.h"
+#include "support/ThreadsafeFS.h"
 #include "clang/Basic/FileManager.h"
 #include "clang/Basic/SourceManager.h"
 #include "clang/Format/Format.h"
@@ -16,18 +22,110 @@
 
 namespace clang {
 namespace clangd {
+
+void FormatModule::initializeLSP(MethodBinder &Bind) {
+  Bind.method("textDocument/formatting", this, &FormatModule::formatting);
+  Bind.method("textDocument/rangeFormatting", this,
+              &FormatModule::rangeFormatting);
+  Bind.method("textDocument/onTypeFormatting", this,
+              &FormatModule::onTypeFormatting);
+}
+
+static llvm::Expected<std::vector<TextEdit>>
+formatCode(PathRef File, llvm::StringRef Code,
+           llvm::ArrayRef<tooling::Range> Ranges, const ThreadsafeFS &FS) {
+  auto Style = getFormatStyleForFile(File, Code, FS);
+  tooling::Replacements IncludeReplaces =
+      clang::format::sortIncludes(Style, Code, Ranges, File);
+  auto Changed = tooling::applyAllReplacements(Code, IncludeReplaces);
+  if (!Changed)
+    return Changed.takeError();
+  auto Replacements = IncludeReplaces.merge(clang::format::reformat(
+      Style, *Changed,
+      tooling::calculateRangesAfterReplacements(IncludeReplaces, Ranges),
+      File));
+  return replacementsToEdits(Code, Replacements);
+};
+
+void FormatModule::rangeFormatting(
+    const format::DocumentRangeFormattingParams &Params,
+    Callback<std::vector<TextEdit>> Reply) {
+  auto Draft = drafts().getDraft(Params.textDocument.uri.file());
+  if (!Draft)
+    return Reply(llvm::make_error<LSPError>("formatting non-added file",
+                                            ErrorCode::InvalidParams));
+  llvm::StringRef Code = Draft->Contents;
+  llvm::Expected<size_t> Begin = positionToOffset(Code, Params.range.start);
+  if (!Begin)
+    return Reply(Begin.takeError());
+  llvm::Expected<size_t> End = positionToOffset(Code, Params.range.end);
+  if (!End)
+    return Reply(End.takeError());
+
+  scheduler().runQuick("rangeFormatting", Params.textDocument.uri.file(),
+                       [File(std::string(Params.textDocument.uri.file())),
+                        Code(std::string(Code)), Begin(*Begin), End(*End),
+                        Reply(std::move(Reply)), FS(&fs())]() mutable {
+                         Reply(formatCode(File, Code,
+                                          {tooling::Range(Begin, End - Begin)},
+                                          *FS));
+                       });
+}
+
+void FormatModule::formatting(const format::DocumentFormattingParams &Params,
+                              Callback<std::vector<TextEdit>> Reply) {
+  auto Draft = drafts().getDraft(Params.textDocument.uri.file());
+  if (!Draft)
+    return Reply(llvm::make_error<LSPError>("formatting non-added file",
+                                            ErrorCode::InvalidParams));
+  scheduler().runQuick(
+      "formatting", Params.textDocument.uri.file(),
+      [File(std::string(Params.textDocument.uri.file())),
+       Code(std::string(Draft->Contents)), Reply(std::move(Reply)),
+       FS(&fs())]() mutable {
+        Reply(formatCode(File, Code, {tooling::Range(0, Code.size())}, *FS));
+      });
+}
+
+void FormatModule::onTypeFormatting(
+    const format::DocumentOnTypeFormattingParams &Params,
+    Callback<std::vector<TextEdit>> Reply) {
+  auto Draft = drafts().getDraft(Params.textDocument.uri.file());
+  if (!Draft)
+    return Reply(llvm::make_error<LSPError>("formatting non-added file",
+                                            ErrorCode::InvalidParams));
+  llvm::StringRef Code = Draft->Contents;
+  llvm::Expected<size_t> CursorPos = positionToOffset(Code, Params.position);
+  if (!CursorPos)
+    return Reply(CursorPos.takeError());
+
+  scheduler().runQuick(
+      "onTypeFormatting", Params.textDocument.uri.file(),
+      [File(std::string(Params.textDocument.uri.file())), CursorPos(*CursorPos),
+       TriggerText(Params.ch), Code(std::string(Code)), FS(&fs()),
+       Reply(std::move(Reply))]() mutable {
+        auto Style = getFormatStyleForFile(File, Code, *FS);
+        std::vector<TextEdit> Result;
+        for (const tooling::Replacement &R :
+             format::formatIncremental(Code, CursorPos, TriggerText, Style))
+          Result.push_back(replacementToEdit(Code, R));
+        return Reply(Result);
+      });
+}
+
+namespace format {
 namespace {
 
 /// Append closing brackets )]} to \p Code to make it well-formed.
 /// Clang-format conservatively refuses to format files with unmatched brackets
 /// as it isn't sure where the errors are and so can't correct.
 /// When editing, it's reasonable to assume code before the cursor is complete.
-void closeBrackets(std::string &Code, const format::FormatStyle &Style) {
+void closeBrackets(std::string &Code, const clang::format::FormatStyle &Style) {
   SourceManagerForFile FileSM("dummy.cpp", Code);
   auto &SM = FileSM.get();
   FileID FID = SM.getMainFileID();
   Lexer Lex(FID, SM.getBufferOrFake(FID), SM,
-            format::getFormattingLangOpts(Style));
+            clang::format::getFormattingLangOpts(Style));
   Token Tok;
   std::vector<char> Brackets;
   while (!Lex.LexFromRawLexer(Tok)) {
@@ -246,7 +344,8 @@
 // all the regions we want to format, and discard changes in them.
 std::vector<tooling::Replacement>
 formatIncremental(llvm::StringRef OriginalCode, unsigned OriginalCursor,
-                  llvm::StringRef InsertedText, format::FormatStyle Style) {
+                  llvm::StringRef InsertedText,
+                  clang::format::FormatStyle Style) {
   IncrementalChanges Incremental =
       getIncrementalChanges(OriginalCode, OriginalCursor, InsertedText);
   // Never *remove* lines in response to pressing enter! This annoys users.
@@ -286,8 +385,8 @@
 
   // Run clang-format, and truncate changes at FormatLimit.
   tooling::Replacements FormattingChanges;
-  format::FormattingAttemptStatus Status;
-  for (const tooling::Replacement &R : format::reformat(
+  clang::format::FormattingAttemptStatus Status;
+  for (const tooling::Replacement &R : clang::format::reformat(
            Style, CodeToFormat, RangesToFormat, Filename, &Status)) {
     if (R.getOffset() + R.getLength() <= FormatLimit) // Before limit.
       cantFail(FormattingChanges.add(R));
@@ -375,5 +474,25 @@
   return Offset;
 }
 
+bool fromJSON(const llvm::json::Value &Params, DocumentRangeFormattingParams &R,
+              llvm::json::Path P) {
+  llvm::json::ObjectMapper O(Params, P);
+  return O && O.map("textDocument", R.textDocument) && O.map("range", R.range);
+}
+
+bool fromJSON(const llvm::json::Value &Params,
+              DocumentOnTypeFormattingParams &R, llvm::json::Path P) {
+  llvm::json::ObjectMapper O(Params, P);
+  return O && O.map("textDocument", R.textDocument) &&
+         O.map("position", R.position) && O.map("ch", R.ch);
+}
+
+bool fromJSON(const llvm::json::Value &Params, DocumentFormattingParams &R,
+              llvm::json::Path P) {
+  llvm::json::ObjectMapper O(Params, P);
+  return O && O.map("textDocument", R.textDocument);
+}
+
+} // namespace format
 } // namespace clangd
 } // namespace clang
Index: clang-tools-extra/clangd/ClangdServer.h
===================================================================
--- clang-tools-extra/clangd/ClangdServer.h
+++ clang-tools-extra/clangd/ClangdServer.h
@@ -14,6 +14,7 @@
 #include "ConfigProvider.h"
 #include "GlobalCompilationDatabase.h"
 #include "Hover.h"
+#include "Module.h"
 #include "Protocol.h"
 #include "SemanticHighlighting.h"
 #include "TUScheduler.h"
@@ -95,6 +96,8 @@
     /// AST caching policy. The default is to keep up to 3 ASTs in memory.
     ASTRetentionPolicy RetentionPolicy;
 
+    ModuleSet *Modules = nullptr;
+
     /// Cached preambles are potentially large. If false, store them on disk.
     bool StorePreamblesInMemory = true;
 
@@ -165,6 +168,7 @@
   /// those arguments for subsequent reparses. However, ClangdServer will check
   /// if compilation arguments changed on calls to forceReparse().
   ClangdServer(const GlobalCompilationDatabase &CDB, const ThreadsafeFS &TFS,
+               const DraftStore &Drafts, // XXX
                const Options &Opts, Callbacks *Callbacks = nullptr);
 
   /// Add a \p File to the list of tracked C++ files or update the contents if
@@ -249,19 +253,6 @@
   void findReferences(PathRef File, Position Pos, uint32_t Limit,
                       Callback<ReferencesResult> CB);
 
-  /// Run formatting for \p Rng inside \p File with content \p Code.
-  void formatRange(PathRef File, StringRef Code, Range Rng,
-                   Callback<tooling::Replacements> CB);
-
-  /// Run formatting for the whole \p File with content \p Code.
-  void formatFile(PathRef File, StringRef Code,
-                  Callback<tooling::Replacements> CB);
-
-  /// Run formatting after \p TriggerText was typed at \p Pos in \p File with
-  /// content \p Code.
-  void formatOnType(PathRef File, StringRef Code, Position Pos,
-                    StringRef TriggerText, Callback<std::vector<TextEdit>> CB);
-
   /// Test the validity of a rename operation.
   ///
   /// If NewName is provided, it performs a name validation.
@@ -345,6 +336,7 @@
 
   const GlobalCompilationDatabase &CDB;
   const ThreadsafeFS &TFS;
+  ModuleSet *Modules;
 
   Path ResourceDir;
   // The index used to look up symbols. This could be:
Index: clang-tools-extra/clangd/ClangdServer.cpp
===================================================================
--- clang-tools-extra/clangd/ClangdServer.cpp
+++ clang-tools-extra/clangd/ClangdServer.cpp
@@ -9,6 +9,7 @@
 #include "ClangdServer.h"
 #include "CodeComplete.h"
 #include "Config.h"
+#include "DraftStore.h"
 #include "DumpAST.h"
 #include "FindSymbols.h"
 #include "Format.h"
@@ -32,7 +33,6 @@
 #include "support/MemoryTree.h"
 #include "support/ThreadsafeFS.h"
 #include "support/Trace.h"
-#include "clang/Format/Format.h"
 #include "clang/Frontend/CompilerInstance.h"
 #include "clang/Frontend/CompilerInvocation.h"
 #include "clang/Lex/Preprocessor.h"
@@ -136,9 +136,10 @@
 }
 
 ClangdServer::ClangdServer(const GlobalCompilationDatabase &CDB,
-                           const ThreadsafeFS &TFS, const Options &Opts,
-                           Callbacks *Callbacks)
-    : CDB(CDB), TFS(TFS),
+                           const ThreadsafeFS &TFS,
+                           const DraftStore &Drafts, // XXX
+                           const Options &Opts, Callbacks *Callbacks)
+    : CDB(CDB), TFS(TFS), Modules(Opts.Modules),
       DynamicIdx(Opts.BuildDynamicSymbolIndex ? new FileIndex() : nullptr),
       ClangTidyProvider(Opts.ClangTidyProvider),
       WorkspaceRoot(Opts.WorkspaceRoot),
@@ -179,6 +180,16 @@
   }
   if (DynamicIdx)
     AddIndex(DynamicIdx.get());
+
+  if (Modules) {
+    Module::ServerFacilities Facilities{
+        Drafts,
+        WorkScheduler,
+        TFS,
+    };
+    for (auto &Mod : *Modules)
+      Mod.Facilities.emplace(Facilities);
+  }
 }
 
 void ClangdServer::addDocument(PathRef File, llvm::StringRef Contents,
@@ -362,48 +373,6 @@
                                 std::move(Action));
 }
 
-void ClangdServer::formatRange(PathRef File, llvm::StringRef Code, Range Rng,
-                               Callback<tooling::Replacements> CB) {
-  llvm::Expected<size_t> Begin = positionToOffset(Code, Rng.start);
-  if (!Begin)
-    return CB(Begin.takeError());
-  llvm::Expected<size_t> End = positionToOffset(Code, Rng.end);
-  if (!End)
-    return CB(End.takeError());
-  formatCode(File, Code, {tooling::Range(*Begin, *End - *Begin)},
-             std::move(CB));
-}
-
-void ClangdServer::formatFile(PathRef File, llvm::StringRef Code,
-                              Callback<tooling::Replacements> CB) {
-  // Format everything.
-  formatCode(File, Code, {tooling::Range(0, Code.size())}, std::move(CB));
-}
-
-void ClangdServer::formatOnType(PathRef File, llvm::StringRef Code,
-                                Position Pos, StringRef TriggerText,
-                                Callback<std::vector<TextEdit>> CB) {
-  llvm::Expected<size_t> CursorPos = positionToOffset(Code, Pos);
-  if (!CursorPos)
-    return CB(CursorPos.takeError());
-  auto Action = [File = File.str(), Code = Code.str(),
-                 TriggerText = TriggerText.str(), CursorPos = *CursorPos,
-                 CB = std::move(CB), this]() mutable {
-    auto Style = format::getStyle(format::DefaultFormatStyle, File,
-                                  format::DefaultFallbackStyle, Code,
-                                  TFS.view(/*CWD=*/llvm::None).get());
-    if (!Style)
-      return CB(Style.takeError());
-
-    std::vector<TextEdit> Result;
-    for (const tooling::Replacement &R :
-         formatIncremental(Code, CursorPos, TriggerText, *Style))
-      Result.push_back(replacementToEdit(Code, R));
-    return CB(Result);
-  };
-  WorkScheduler.runQuick("FormatOnType", File, std::move(Action));
-}
-
 void ClangdServer::prepareRename(PathRef File, Position Pos,
                                  llvm::Optional<std::string> NewName,
                                  const RenameOptions &RenameOpts,
@@ -566,8 +535,7 @@
       // Tweaks don't apply clang-format, do that centrally here.
       for (auto &It : (*Effect)->ApplyEdits) {
         Edit &E = It.second;
-        format::FormatStyle Style =
-            getFormatStyleForFile(File, E.InitialCode, TFS);
+        auto Style = getFormatStyleForFile(File, E.InitialCode, TFS);
         if (llvm::Error Err = reformatEdit(E, Style))
           elog("Failed to format {0}: {1}", It.first(), std::move(Err));
       }
@@ -611,27 +579,6 @@
   WorkScheduler.runWithAST("SwitchHeaderSource", Path, std::move(Action));
 }
 
-void ClangdServer::formatCode(PathRef File, llvm::StringRef Code,
-                              llvm::ArrayRef<tooling::Range> Ranges,
-                              Callback<tooling::Replacements> CB) {
-  // Call clang-format.
-  auto Action = [File = File.str(), Code = Code.str(), Ranges = Ranges.vec(),
-                 CB = std::move(CB), this]() mutable {
-    format::FormatStyle Style = getFormatStyleForFile(File, Code, TFS);
-    tooling::Replacements IncludeReplaces =
-        format::sortIncludes(Style, Code, Ranges, File);
-    auto Changed = tooling::applyAllReplacements(Code, IncludeReplaces);
-    if (!Changed)
-      return CB(Changed.takeError());
-
-    CB(IncludeReplaces.merge(format::reformat(
-        Style, *Changed,
-        tooling::calculateRangesAfterReplacements(IncludeReplaces, Ranges),
-        File)));
-  };
-  WorkScheduler.runQuick("Format", File, std::move(Action));
-}
-
 void ClangdServer::findDocumentHighlights(
     PathRef File, Position Pos, Callback<std::vector<DocumentHighlight>> CB) {
   auto Action =
@@ -651,8 +598,8 @@
                  this](llvm::Expected<InputsAndAST> InpAST) mutable {
     if (!InpAST)
       return CB(InpAST.takeError());
-    format::FormatStyle Style = getFormatStyleForFile(
-        File, InpAST->Inputs.Contents, *InpAST->Inputs.TFS);
+    auto Style = getFormatStyleForFile(File, InpAST->Inputs.Contents,
+                                       *InpAST->Inputs.TFS);
     CB(clangd::getHover(InpAST->AST, Pos, std::move(Style), Index));
   };
 
Index: clang-tools-extra/clangd/ClangdLSPServer.h
===================================================================
--- clang-tools-extra/clangd/ClangdLSPServer.h
+++ clang-tools-extra/clangd/ClangdLSPServer.h
@@ -100,12 +100,6 @@
   void onDocumentDidClose(const DidCloseTextDocumentParams &);
   void onDocumentDidSave(const DidSaveTextDocumentParams &);
   void onAST(const ASTParams &, Callback<llvm::Optional<ASTNode>>);
-  void onDocumentOnTypeFormatting(const DocumentOnTypeFormattingParams &,
-                                  Callback<std::vector<TextEdit>>);
-  void onDocumentRangeFormatting(const DocumentRangeFormattingParams &,
-                                 Callback<std::vector<TextEdit>>);
-  void onDocumentFormatting(const DocumentFormattingParams &,
-                            Callback<std::vector<TextEdit>>);
   // The results are serialized 'vector<DocumentSymbol>' if
   // SupportsHierarchicalDocumentSymbol is true and 'vector<SymbolInformation>'
   // otherwise.
Index: clang-tools-extra/clangd/ClangdLSPServer.cpp
===================================================================
--- clang-tools-extra/clangd/ClangdLSPServer.cpp
+++ clang-tools-extra/clangd/ClangdLSPServer.cpp
@@ -267,6 +267,16 @@
     };
   }
 
+  void bindAll(ModuleSet &Mods) {
+    Module::MethodBinder Binder;
+    for (auto &Mod : Mods)
+      Mod.initializeLSP(Binder);
+    for (auto &M : Binder.Methods)
+      Calls.try_emplace(M.first(), std::move(M.second));
+    for (auto &M : Binder.Notifications)
+      Notifications.try_emplace(M.first(), std::move(M.second));
+  }
+
   // Bind a reply callback to a request. The callback will be invoked when
   // clangd receives the reply from the LSP client.
   // Return a call id of the request.
@@ -547,7 +557,7 @@
     llvm::Optional<WithContextValue> WithOffsetEncoding;
     if (Opts.Encoding)
       WithOffsetEncoding.emplace(kCurrentOffsetEncoding, *Opts.Encoding);
-    Server.emplace(*CDB, TFS, Opts,
+    Server.emplace(*CDB, TFS, DraftMgr, Opts,
                    static_cast<ClangdServer::Callbacks *>(this));
   }
   applyConfiguration(Params.initializationOptions.ConfigSettings);
@@ -605,6 +615,7 @@
                  {"change", (int)TextDocumentSyncKind::Incremental},
                  {"save", true},
              }},
+            // XXX move into format module
             {"documentFormattingProvider", true},
             {"documentRangeFormattingProvider", true},
             {"documentOnTypeFormattingProvider",
@@ -906,61 +917,6 @@
   publishDiagnostics(Notification);
 }
 
-void ClangdLSPServer::onDocumentOnTypeFormatting(
-    const DocumentOnTypeFormattingParams &Params,
-    Callback<std::vector<TextEdit>> Reply) {
-  auto File = Params.textDocument.uri.file();
-  auto Code = DraftMgr.getDraft(File);
-  if (!Code)
-    return Reply(llvm::make_error<LSPError>(
-        "onDocumentOnTypeFormatting called for non-added file",
-        ErrorCode::InvalidParams));
-
-  Server->formatOnType(File, Code->Contents, Params.position, Params.ch,
-                       std::move(Reply));
-}
-
-void ClangdLSPServer::onDocumentRangeFormatting(
-    const DocumentRangeFormattingParams &Params,
-    Callback<std::vector<TextEdit>> Reply) {
-  auto File = Params.textDocument.uri.file();
-  auto Code = DraftMgr.getDraft(File);
-  if (!Code)
-    return Reply(llvm::make_error<LSPError>(
-        "onDocumentRangeFormatting called for non-added file",
-        ErrorCode::InvalidParams));
-
-  Server->formatRange(
-      File, Code->Contents, Params.range,
-      [Code = Code->Contents, Reply = std::move(Reply)](
-          llvm::Expected<tooling::Replacements> Result) mutable {
-        if (Result)
-          Reply(replacementsToEdits(Code, Result.get()));
-        else
-          Reply(Result.takeError());
-      });
-}
-
-void ClangdLSPServer::onDocumentFormatting(
-    const DocumentFormattingParams &Params,
-    Callback<std::vector<TextEdit>> Reply) {
-  auto File = Params.textDocument.uri.file();
-  auto Code = DraftMgr.getDraft(File);
-  if (!Code)
-    return Reply(llvm::make_error<LSPError>(
-        "onDocumentFormatting called for non-added file",
-        ErrorCode::InvalidParams));
-
-  Server->formatFile(File, Code->Contents,
-                     [Code = Code->Contents, Reply = std::move(Reply)](
-                         llvm::Expected<tooling::Replacements> Result) mutable {
-                       if (Result)
-                         Reply(replacementsToEdits(Code, Result.get()));
-                       else
-                         Reply(Result.takeError());
-                     });
-}
-
 /// The functions constructs a flattened view of the DocumentSymbol hierarchy.
 /// Used by the clients that do not support the hierarchical view.
 static std::vector<SymbolInformation>
@@ -1516,9 +1472,6 @@
   MsgHandler->bind("initialized", &ClangdLSPServer::onInitialized);
   MsgHandler->bind("shutdown", &ClangdLSPServer::onShutdown);
   MsgHandler->bind("sync", &ClangdLSPServer::onSync);
-  MsgHandler->bind("textDocument/rangeFormatting", &ClangdLSPServer::onDocumentRangeFormatting);
-  MsgHandler->bind("textDocument/onTypeFormatting", &ClangdLSPServer::onDocumentOnTypeFormatting);
-  MsgHandler->bind("textDocument/formatting", &ClangdLSPServer::onDocumentFormatting);
   MsgHandler->bind("textDocument/codeAction", &ClangdLSPServer::onCodeAction);
   MsgHandler->bind("textDocument/completion", &ClangdLSPServer::onCompletion);
   MsgHandler->bind("textDocument/signatureHelp", &ClangdLSPServer::onSignatureHelp);
@@ -1555,6 +1508,8 @@
   if (Opts.FoldingRanges)
     MsgHandler->bind("textDocument/foldingRange", &ClangdLSPServer::onFoldingRange);
   // clang-format on
+  if (Opts.Modules)
+    MsgHandler->bindAll(*Opts.Modules);
 }
 
 ClangdLSPServer::~ClangdLSPServer() {
Index: clang-tools-extra/clangd/CMakeLists.txt
===================================================================
--- clang-tools-extra/clangd/CMakeLists.txt
+++ clang-tools-extra/clangd/CMakeLists.txt
@@ -74,6 +74,7 @@
   Hover.cpp
   IncludeFixer.cpp
   JSONTransport.cpp
+  ModuleDefaults.cpp
   PathMapping.cpp
   Protocol.cpp
   Quality.cpp
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to