verilog: tools: ls: verilog_ls: Wrapped implementation of Language Server in a class Signed-off-by: Grzegorz Latosinski <glatosinski@antmicro.com>
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(); }