Merge pull request #1560 from antmicro/verilog-ls-class

[LSP] Wrap implementation of the Language Server in a class
diff --git a/verilog/tools/ls/BUILD b/verilog/tools/ls/BUILD
index fe67b55..8e9260f 100644
--- a/verilog/tools/ls/BUILD
+++ b/verilog/tools/ls/BUILD
@@ -62,16 +62,23 @@
     ],
 )
 
+cc_library(
+    name = "verilog-language-server",
+    srcs = ["verilog-language-server.cc"],
+    hdrs = ["verilog-language-server.h"],
+    visibility = ["//visibility:public"],
+    deps = [
+        ":verible-lsp-adapter",
+        "//common/lsp:message-stream-splitter",
+    ],
+)
+
 cc_binary(
     name = "verible-verilog-ls",
     srcs = ["verilog_ls.cc"],
     visibility = ["//visibility:public"],
     deps = [
-        ":lsp-parse-buffer",
-        ":verible-lsp-adapter",
-        "//common/lsp:json-rpc-dispatcher",
-        "//common/lsp:lsp-protocol",
-        "//common/lsp:message-stream-splitter",
+        ":verilog-language-server",
         "//common/util:init_command_line",
     ],
 )
diff --git a/verilog/tools/ls/verilog-language-server.cc b/verilog/tools/ls/verilog-language-server.cc
new file mode 100644
index 0000000..8422e55
--- /dev/null
+++ b/verilog/tools/ls/verilog-language-server.cc
@@ -0,0 +1,165 @@
+// Copyright 2021-2022 The Verible Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "verilog/tools/ls/verilog-language-server.h"
+
+namespace verilog {
+
+VerilogLanguageServer::VerilogLanguageServer(const WriteFun &write_fun)
+    : dispatcher_(write_fun), buffers_(&dispatcher_) {
+  // All bodies the stream splitter extracts are pushed to the json dispatcher
+  stream_splitter_.SetMessageProcessor(
+      [this](absl::string_view header, absl::string_view body) {
+        return dispatcher_.DispatchMessage(body);
+      });
+
+  // Whenever there is a new parse result ready, use that as an opportunity
+  // to send diagnostics to the client.
+  buffers_.SetChangeListener(parsed_buffers_.GetSubscriptionCallback());
+  parsed_buffers_.SetChangeListener(
+      [this](const std::string &uri,
+             const verilog::BufferTracker &buffer_tracker) {
+        SendDiagnostics(uri, buffer_tracker);
+      });
+  SetRequestHandlers();
+}
+
+void VerilogLanguageServer::SetRequestHandlers() {
+  // Exchange of capabilities.
+  dispatcher_.AddRequestHandler("initialize",
+                                [this](const nlohmann::json &params) {
+                                  return InitializeRequestHandler(params);
+                                });
+
+  dispatcher_.AddRequestHandler(  // Provide diagnostics on request
+      "textDocument/diagnostic",
+      [this](const verible::lsp::DocumentDiagnosticParams &p) {
+        return verilog::GenerateDiagnosticReport(
+            parsed_buffers_.FindBufferTrackerOrNull(p.textDocument.uri), p);
+      });
+
+  dispatcher_.AddRequestHandler(  // Provide autofixes
+      "textDocument/codeAction",
+      [this](const verible::lsp::CodeActionParams &p) {
+        return verilog::GenerateLinterCodeActions(
+            parsed_buffers_.FindBufferTrackerOrNull(p.textDocument.uri), p);
+      });
+
+  dispatcher_.AddRequestHandler(  // Provide document outline/index
+      "textDocument/documentSymbol",
+      [this](const verible::lsp::DocumentSymbolParams &p) {
+        return verilog::CreateDocumentSymbolOutline(
+            parsed_buffers_.FindBufferTrackerOrNull(p.textDocument.uri), p);
+      });
+
+  dispatcher_.AddRequestHandler(  // Highlight related symbols under cursor
+      "textDocument/documentHighlight",
+      [this](const verible::lsp::DocumentHighlightParams &p) {
+        return verilog::CreateHighlightRanges(
+            parsed_buffers_.FindBufferTrackerOrNull(p.textDocument.uri), p);
+      });
+
+  dispatcher_.AddRequestHandler(  // format range of file
+      "textDocument/rangeFormatting",
+      [this](const verible::lsp::DocumentFormattingParams &p) {
+        return verilog::FormatRange(
+            parsed_buffers_.FindBufferTrackerOrNull(p.textDocument.uri), p);
+      });
+  dispatcher_.AddRequestHandler(  // format entire file
+      "textDocument/formatting",
+      [this](const verible::lsp::DocumentFormattingParams &p) {
+        return verilog::FormatRange(
+            parsed_buffers_.FindBufferTrackerOrNull(p.textDocument.uri), p);
+      });
+  // The client sends a request to shut down. Use that to exit our loop.
+  dispatcher_.AddRequestHandler("shutdown", [this](const nlohmann::json &) {
+    shutdown_requested_ = true;
+    return nullptr;
+  });
+}
+
+absl::Status VerilogLanguageServer::Run(const ReadFun &read_fun) {
+  shutdown_requested_ = false;
+  absl::Status status = absl::OkStatus();
+  while (status.ok() && !shutdown_requested_) {
+    status = stream_splitter_.PullFrom(read_fun);
+  }
+  return status;
+}
+
+void VerilogLanguageServer::PrintStatistics() const {
+  if (shutdown_requested_) {
+    std::cerr << "Shutting down due to shutdown request." << std::endl;
+  }
+
+  std::cerr << "Statistics" << std::endl;
+  std::cerr << "Largest message seen: "
+            << stream_splitter_.StatLargestBodySeen() / 1024 << " kiB "
+            << std::endl;
+  for (const auto &stats : dispatcher_.GetStatCounters()) {
+    fprintf(stderr, "%30s %9d\n", stats.first.c_str(), stats.second);
+  }
+}
+
+verible::lsp::InitializeResult VerilogLanguageServer::InitializeRequestHandler(
+    const nlohmann::json &params) const {
+  // Ignore passed client capabilities from params right now,
+  // just announce what we do.
+  verible::lsp::InitializeResult result;
+  result.serverInfo = {
+      .name = "Verible Verilog language server.",
+      .version = GetVersionNumber(),
+  };
+  result.capabilities = {
+      {
+          "textDocumentSync",
+          {
+              {"openClose", true},  // Want open/close events
+              {"change", 2},        // Incremental updates
+          },
+      },
+      {"codeActionProvider", true},               // Autofixes for lint errors
+      {"documentSymbolProvider", true},           // Symbol-outline of file
+      {"documentRangeFormattingProvider", true},  // Format selection
+      {"documentFormattingProvider", true},       // Full file format
+      {"documentHighlightProvider", true},        // Highlight same symbol
+      {"diagnosticProvider",                      // Pull model of diagnostics.
+       {
+           {"interFileDependencies", false},
+           {"workspaceDiagnostics", false},
+       }},
+  };
+
+  return result;
+}
+
+void VerilogLanguageServer::SendDiagnostics(
+    const std::string &uri, const verilog::BufferTracker &buffer_tracker) {
+  // TODO(hzeller): Cache result and rate-limit.
+  // This should not send anything if the diagnostics we're about to
+  // send would be exactly the same as last time.
+  verible::lsp::PublishDiagnosticsParams params;
+
+  // For the diagnostic notification (that we send somewhat unsolicited), we
+  // limit the number of diagnostic messages. In the
+  // textDocument/diagnostic RPC request, we send all of them.
+  // Arbitrary limit here. Maybe set with flag ?
+  static constexpr int kDiagnosticLimit = 500;
+  params.uri = uri;
+  params.diagnostics =
+      verilog::CreateDiagnostics(buffer_tracker, kDiagnosticLimit);
+  dispatcher_.SendNotification("textDocument/publishDiagnostics", params);
+}
+
+};  // namespace verilog
diff --git a/verilog/tools/ls/verilog-language-server.h b/verilog/tools/ls/verilog-language-server.h
new file mode 100644
index 0000000..7e7e32c
--- /dev/null
+++ b/verilog/tools/ls/verilog-language-server.h
@@ -0,0 +1,75 @@
+// Copyright 2021-2022 The Verible Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef VERILOG_TOOLS_LS_LS_WRAPPER_H
+#define VERILOG_TOOLS_LS_LS_WRAPPER_H
+
+#include <functional>
+
+#include "absl/status/status.h"
+#include "absl/strings/string_view.h"
+#include "common/lsp/json-rpc-dispatcher.h"
+#include "common/lsp/lsp-protocol.h"
+#include "common/lsp/message-stream-splitter.h"
+#include "verilog/tools/ls/verible-lsp-adapter.h"
+
+namespace verilog {
+
+// Class implementing the Language Server for Verilog
+class VerilogLanguageServer {
+ public:
+  using ReadFun = verible::lsp::MessageStreamSplitter::ReadFun;
+  using WriteFun = verible::lsp::JsonRpcDispatcher::WriteFun;
+
+  static std::string GetVersionNumber() { return "0.0 alpha"; }
+
+  // Constructor preparing the callbacks for Language Server requests
+  VerilogLanguageServer(const WriteFun &write_fun);
+
+  // Runs the Language Server
+  absl::Status Run(const ReadFun &read_fun);
+
+  // Prints statistics of the current Language Server session
+  void PrintStatistics() const;
+
+ private:
+  // Creates callbacks for requests from Language Server Client
+  void SetRequestHandlers();
+
+  // The "initialize" method requests server capabilities.
+  verible::lsp::InitializeResult InitializeRequestHandler(
+      const nlohmann::json &params) const;
+
+  // Publish a diagnostic sent to the server.
+  void SendDiagnostics(const std::string &uri,
+                       const verilog::BufferTracker &buffer_tracker);
+
+  // Stream splitter splits the input stream into messages (header/body).
+  verible::lsp::MessageStreamSplitter stream_splitter_;
+
+  // Parser for JSON messages from LS client
+  verible::lsp::JsonRpcDispatcher dispatcher_;
+
+  // Object for keeping track of updates in opened buffers on client's side
+  verible::lsp::BufferCollection buffers_;
+
+  // Tracks changes in buffers from BufferCollection and parses their contents
+  verilog::BufferTrackerContainer parsed_buffers_;
+
+  // A flag for indicating "shutdown" request
+  bool shutdown_requested_ = false;
+};
+
+};      // namespace verilog
+#endif  // VERILOG_TOOLS_LS_LS_WRAPPER_H
diff --git a/verilog/tools/ls/verilog_ls.cc b/verilog/tools/ls/verilog_ls.cc
index 3656850..0702a3e 100644
--- a/verilog/tools/ls/verilog_ls.cc
+++ b/verilog/tools/ls/verilog_ls.cc
@@ -1,4 +1,4 @@
-// Copyright 2021 The Verible Authors.
+// Copyright 2021-2022 The Verible Authors.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -13,17 +13,13 @@
 // limitations under the License.
 //
 
+#include <functional>
 #include <iomanip>  // Only needed for json debugging right now
 #include <iostream>
 
-#include "common/lsp/json-rpc-dispatcher.h"
-#include "common/lsp/lsp-protocol.h"
-#include "common/lsp/message-stream-splitter.h"
 #include "common/util/init_command_line.h"
-#include "verilog/tools/ls/lsp-parse-buffer.h"
-#include "verilog/tools/ls/verible-lsp-adapter.h"
+#include "verilog/tools/ls/verilog-language-server.h"
 
-// Windows specific implementation of read()
 #ifndef _WIN32
 #include <unistd.h>
 #else
@@ -34,187 +30,32 @@
 #define read(fd, buf, size) _read(fd, buf, size)
 #endif
 
-using nlohmann::json;
-using verible::lsp::BufferCollection;
-using verible::lsp::InitializeResult;
-using verible::lsp::JsonRpcDispatcher;
-using verible::lsp::MessageStreamSplitter;
-
-static std::string GetVersionNumber() {
-  return "0.0 alpha";  // TODO(hzeller): once ready, extract from build version
-}
-
-// The "initialize" method requests server capabilities.
-static InitializeResult InitializeServer(const nlohmann::json &params) {
-  // Ignore passed client capabilities from params right now,
-  // just announce what we do.
-  InitializeResult result;
-  result.serverInfo = {
-      .name = "Verible Verilog language server.",
-      .version = GetVersionNumber(),
-  };
-  result.capabilities = {
-      {
-          "textDocumentSync",
-          {
-              {"openClose", true},  // Want open/close events
-              {"change", 2},        // Incremental updates
-          },
-      },
-      {"codeActionProvider", true},               // Autofixes for lint errors
-      {"documentSymbolProvider", true},           // Symbol-outline of file
-      {"documentRangeFormattingProvider", true},  // Format selection
-      {"documentFormattingProvider", true},       // Full file format
-      {"documentHighlightProvider", true},        // Highlight same symbol
-      {"diagnosticProvider",                      // Pull model of diagnostics.
-       {
-           {"interFileDependencies", false},
-           {"workspaceDiagnostics", false},
-       }},
-  };
-
-  return result;
-}
-
-// Publish a diagnostic sent to the server.
-static void SendDiagnostics(const std::string &uri,
-                            const verilog::BufferTracker &buffer_tracker,
-                            JsonRpcDispatcher *dispatcher) {
-  // TODO(hzeller): Cache result and rate-limit.
-  // This should not send anything if the diagnostics we're about to
-  // send would be exactly the same as last time.
-  verible::lsp::PublishDiagnosticsParams params;
-
-  // For the diagnostic notification (that we send somewhat unsolicited), we
-  // limit the number of diagnostic messages. In the
-  // textDocument/diagnostic RPC request, we send all of them.
-  // Arbitrary limit here. Maybe set with flag ?
-  static constexpr int kDiagnosticLimit = 500;
-  params.uri = uri;
-  params.diagnostics =
-      verilog::CreateDiagnostics(buffer_tracker, kDiagnosticLimit);
-  dispatcher->SendNotification("textDocument/publishDiagnostics", params);
+static void FormatHeaderBodyReply(absl::string_view reply) {
+  // Output formatting as header/body chunk as required by LSP spec.
+  std::cout << "Content-Length: " << reply.size() << "\r\n\r\n";
+  std::cout << reply << std::flush;
 }
 
 int main(int argc, char *argv[]) {
   verible::InitCommandLine(argv[0], &argc, &argv);
 
+  std::cerr << "Verible Alpha Language Server "
+            << verilog::VerilogLanguageServer::GetVersionNumber() << std::endl;
+
+  // Input and output is stdin and stdout
+  constexpr int in_fd = 0;  // STDIN_FILENO
+
 #ifdef _WIN32
   _setmode(_fileno(stdin), _O_BINARY);
   _setmode(_fileno(stdout), _O_BINARY);
 #endif
 
-  std::cerr << "Verible Alpha Language Server " << GetVersionNumber()
-            << std::endl;
+  verilog::VerilogLanguageServer server(FormatHeaderBodyReply);
 
-  // Input and output is stdin and stdout
-  static constexpr int in_fd = 0;  // STDIN_FILENO
-  JsonRpcDispatcher::WriteFun write_fun = [](absl::string_view reply) {
-    // Output formatting as header/body chunk as required by LSP spec.
-    std::cout << "Content-Length: " << reply.size() << "\r\n\r\n";
-    std::cout << reply << std::flush;
-  };
-
-  // Stream splitter splits the input stream into messages (header/body).
-  MessageStreamSplitter stream_splitter;
-  JsonRpcDispatcher dispatcher(write_fun);
-
-  // All bodies the stream splitter extracts are pushed to the json dispatcher
-  stream_splitter.SetMessageProcessor(
-      [&dispatcher](absl::string_view /*header*/, absl::string_view body) {
-        return dispatcher.DispatchMessage(body);
-      });
-
-  // The buffer collection keeps track of all the buffers opened in the editor.
-  // It registers callbacks to receive the relevant events on the dispatcher.
-  BufferCollection buffers(&dispatcher);
-
-  // The parsed buffers convert the raw text into the SystemVerilog parsed
-  // representation, basis for all high-level features we provide from it.
-  // Subscribe them to text edit updates to trigger parse.
-  verilog::BufferTrackerContainer parsed_buffers;
-  buffers.SetChangeListener(parsed_buffers.GetSubscriptionCallback());
-
-  // Whenever there is a new parse result ready, use that as an opportunity
-  // to send diagnostics to the client.
-  parsed_buffers.SetChangeListener(
-      [&dispatcher](const std::string &uri,
-                    const verilog::BufferTracker &buffer_tracker) {
-        SendDiagnostics(uri, buffer_tracker, &dispatcher);
-      });
-
-  // -- Register JSON RPC callbacks
-
-  // Exchange of capabilities.
-  dispatcher.AddRequestHandler("initialize", InitializeServer);
-
-  dispatcher.AddRequestHandler(  // Provide diagnostics on request
-      "textDocument/diagnostic",
-      [&parsed_buffers](const verible::lsp::DocumentDiagnosticParams &p) {
-        return verilog::GenerateDiagnosticReport(
-            parsed_buffers.FindBufferTrackerOrNull(p.textDocument.uri), p);
-      });
-
-  dispatcher.AddRequestHandler(  // Provide autofixes
-      "textDocument/codeAction",
-      [&parsed_buffers](const verible::lsp::CodeActionParams &p) {
-        return verilog::GenerateLinterCodeActions(
-            parsed_buffers.FindBufferTrackerOrNull(p.textDocument.uri), p);
-      });
-
-  dispatcher.AddRequestHandler(  // Provide document outline/index
-      "textDocument/documentSymbol",
-      [&parsed_buffers](const verible::lsp::DocumentSymbolParams &p) {
-        return verilog::CreateDocumentSymbolOutline(
-            parsed_buffers.FindBufferTrackerOrNull(p.textDocument.uri), p);
-      });
-
-  dispatcher.AddRequestHandler(  // Highlight related symbols under cursor
-      "textDocument/documentHighlight",
-      [&parsed_buffers](const verible::lsp::DocumentHighlightParams &p) {
-        return verilog::CreateHighlightRanges(
-            parsed_buffers.FindBufferTrackerOrNull(p.textDocument.uri), p);
-      });
-
-  dispatcher.AddRequestHandler(  // format range of file
-      "textDocument/rangeFormatting",
-      [&parsed_buffers](const verible::lsp::DocumentFormattingParams &p) {
-        return verilog::FormatRange(
-            parsed_buffers.FindBufferTrackerOrNull(p.textDocument.uri), p);
-      });
-  dispatcher.AddRequestHandler(  // format entire file
-      "textDocument/formatting",
-      [&parsed_buffers](const verible::lsp::DocumentFormattingParams &p) {
-        return verilog::FormatRange(
-            parsed_buffers.FindBufferTrackerOrNull(p.textDocument.uri), p);
-      });
-
-  // The client sends a request to shut down. Use that to exit our loop.
-  bool shutdown_requested = false;
-  dispatcher.AddRequestHandler("shutdown",
-                               [&shutdown_requested](const nlohmann::json &) {
-                                 shutdown_requested = true;
-                                 return nullptr;
-                               });
-
-  absl::Status status = absl::OkStatus();
-  while (status.ok() && !shutdown_requested) {
-    status = stream_splitter.PullFrom([](char *buf, int size) -> int {  //
-      return read(in_fd, buf, size);
-    });
-  }
+  absl::Status status = server.Run(
+      [](char *buf, int size) -> int { return read(in_fd, buf, size); });
 
   std::cerr << status.message() << std::endl;
 
-  if (shutdown_requested) {
-    std::cerr << "Shutting down due to shutdown request." << std::endl;
-  }
-
-  std::cerr << "Statistics" << std::endl;
-  std::cerr << "Largest message seen: "
-            << stream_splitter.StatLargestBodySeen() / 1024 << " kiB "
-            << std::endl;
-  for (const auto &stats : dispatcher.GetStatCounters()) {
-    fprintf(stderr, "%30s %9d\n", stats.first.c_str(), stats.second);
-  }
+  server.PrintStatistics();
 }