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 ¶ms) {
+ 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 ¶ms) 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 ¶ms) 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 ¶ms) {
- // 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();
}