From 96d836f965fcda4426d7010b908545f499679e8d Mon Sep 17 00:00:00 2001 From: Jon Ross-Perkins Date: Tue, 7 Jan 2025 22:01:51 -0800 Subject: [PATCH] Refactor the language server structure. (#4721) I'm trying to make the LSP look more like the rest of the toolchain. I'm trying to separate the handlers from the transport layer, and remove the multiple inheritance aspect. Also fixing some style issues, switching to `Map`, and removing an unnecessary copt. I'm using `Context` for the central object for consistency with other portions of the toolchain. In order to get the `handle_*` files working, I'm using LLVM's registry class. It has a quirk that I can't register two registries in the same cpp file, so there are two one-line cpp files. In order to help show the delta (or lack thereof) for actual implementation, I've copied server.cpp over handle_* and undone that in two commits. See the third and fourth commits on the PR history for that. --- toolchain/language_server/BUILD | 52 +++++- toolchain/language_server/context.h | 26 +++ toolchain/language_server/handle.h | 38 ++++ .../handle_document_symbol.cpp | 92 +++++++++ .../language_server/handle_initialize.cpp | 20 ++ .../language_server/handle_text_document.cpp | 25 +++ .../language_server/incoming_messages.cpp | 107 +++++++++++ toolchain/language_server/incoming_messages.h | 76 ++++++++ toolchain/language_server/language_server.cpp | 27 ++- toolchain/language_server/outgoing_messages.h | 38 ++++ toolchain/language_server/server.cpp | 174 ------------------ toolchain/language_server/server.h | 90 --------- 12 files changed, 489 insertions(+), 276 deletions(-) create mode 100644 toolchain/language_server/context.h create mode 100644 toolchain/language_server/handle.h create mode 100644 toolchain/language_server/handle_document_symbol.cpp create mode 100644 toolchain/language_server/handle_initialize.cpp create mode 100644 toolchain/language_server/handle_text_document.cpp create mode 100644 toolchain/language_server/incoming_messages.cpp create mode 100644 toolchain/language_server/incoming_messages.h create mode 100644 toolchain/language_server/outgoing_messages.h delete mode 100644 toolchain/language_server/server.cpp delete mode 100644 toolchain/language_server/server.h diff --git a/toolchain/language_server/BUILD b/toolchain/language_server/BUILD index f9acb5376361..e149c61e0c95 100644 --- a/toolchain/language_server/BUILD +++ b/toolchain/language_server/BUILD @@ -8,26 +8,60 @@ package(default_visibility = ["//visibility:public"]) cc_library( name = "language_server", - srcs = [ - "language_server.cpp", - "server.cpp", - "server.h", - ], + srcs = ["language_server.cpp"], hdrs = ["language_server.h"], - # Some parameters are unused in clangd headers. - copts = ["-Wno-unused-parameter"], deps = [ + ":context", + ":incoming_messages", + ":outgoing_messages", "//common:error", "//common:ostream", + "@llvm-project//clang-tools-extra/clangd:ClangDaemon", + ], +) + +cc_library( + name = "context", + hdrs = ["context.h"], + deps = [ + "//common:map", + "@llvm-project//clang-tools-extra/clangd:ClangDaemon", + ], +) + +cc_library( + name = "handle", + srcs = glob(["handle_*"]), + hdrs = ["handle.h"], + deps = [ + ":context", "//toolchain/base:shared_value_stores", "//toolchain/diagnostics:null_diagnostics", "//toolchain/lex", - "//toolchain/lex:tokenized_buffer", "//toolchain/parse", "//toolchain/parse:node_kind", "//toolchain/parse:tree", "//toolchain/source:source_buffer", + ], +) + +cc_library( + name = "incoming_messages", + srcs = ["incoming_messages.cpp"], + hdrs = ["incoming_messages.h"], + deps = [ + ":context", + ":handle", + "//common:check", + "//common:map", "@llvm-project//clang-tools-extra/clangd:ClangDaemon", - "@llvm-project//llvm:Support", + ], +) + +cc_library( + name = "outgoing_messages", + hdrs = ["outgoing_messages.h"], + deps = [ + "@llvm-project//clang-tools-extra/clangd:ClangDaemon", ], ) diff --git a/toolchain/language_server/context.h b/toolchain/language_server/context.h new file mode 100644 index 000000000000..ba240f78f874 --- /dev/null +++ b/toolchain/language_server/context.h @@ -0,0 +1,26 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef CARBON_TOOLCHAIN_LANGUAGE_SERVER_CONTEXT_H_ +#define CARBON_TOOLCHAIN_LANGUAGE_SERVER_CONTEXT_H_ + +#include "clang-tools-extra/clangd/Protocol.h" +#include "clang-tools-extra/clangd/support/Function.h" +#include "common/map.h" + +namespace Carbon::LanguageServer { + +// Context for LSP call handling. +class Context { + public: + auto files() -> Map& { return files_; } + + private: + // Content of files managed by the language client. + Map files_; +}; + +} // namespace Carbon::LanguageServer + +#endif // CARBON_TOOLCHAIN_LANGUAGE_SERVER_CONTEXT_H_ diff --git a/toolchain/language_server/handle.h b/toolchain/language_server/handle.h new file mode 100644 index 000000000000..80f3eca5e630 --- /dev/null +++ b/toolchain/language_server/handle.h @@ -0,0 +1,38 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef CARBON_TOOLCHAIN_LANGUAGE_SERVER_HANDLE_H_ +#define CARBON_TOOLCHAIN_LANGUAGE_SERVER_HANDLE_H_ + +#include "toolchain/language_server/context.h" + +namespace Carbon::LanguageServer { + +// Stores the content of newly-opened documents. +auto HandleDidChangeTextDocument( + Context& context, const clang::clangd::DidChangeTextDocumentParams& params) + -> void; + +// Updates the content of already-open documents. +auto HandleDidOpenTextDocument( + Context& context, const clang::clangd::DidOpenTextDocumentParams& params) + -> void; + +// Provides information about document symbols. +auto HandleDocumentSymbol( + Context& context, const clang::clangd::DocumentSymbolParams& params, + llvm::function_ref< + void(llvm::Expected>)> + on_done) -> void; + +// Tells the client what features are supported. +auto HandleInitialize( + Context& /*context*/, + const clang::clangd::NoParams& /*client_capabilities*/, + llvm::function_ref)> on_done) + -> void; + +} // namespace Carbon::LanguageServer + +#endif // CARBON_TOOLCHAIN_LANGUAGE_SERVER_HANDLE_H_ diff --git a/toolchain/language_server/handle_document_symbol.cpp b/toolchain/language_server/handle_document_symbol.cpp new file mode 100644 index 000000000000..dc86cd838981 --- /dev/null +++ b/toolchain/language_server/handle_document_symbol.cpp @@ -0,0 +1,92 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include "toolchain/base/shared_value_stores.h" +#include "toolchain/diagnostics/null_diagnostics.h" +#include "toolchain/language_server/handle.h" +#include "toolchain/lex/lex.h" +#include "toolchain/parse/node_kind.h" +#include "toolchain/parse/parse.h" +#include "toolchain/parse/tree_and_subtrees.h" +#include "toolchain/source/source_buffer.h" + +namespace Carbon::LanguageServer { + +// Returns the text of first child of kind Parse::NodeKind::IdentifierName. +static auto GetIdentifierName(const SharedValueStores& value_stores, + const Lex::TokenizedBuffer& tokens, + const Parse::TreeAndSubtrees& tree_and_subtrees, + Parse::NodeId node) + -> std::optional { + for (auto child : tree_and_subtrees.children(node)) { + if (tree_and_subtrees.tree().node_kind(child) == + Parse::NodeKind::IdentifierName) { + auto token = tree_and_subtrees.tree().node_token(child); + if (tokens.GetKind(token) == Lex::TokenKind::Identifier) { + return value_stores.identifiers().Get(tokens.GetIdentifier(token)); + } + } + } + return std::nullopt; +} + +auto HandleDocumentSymbol( + Context& context, const clang::clangd::DocumentSymbolParams& params, + llvm::function_ref< + void(llvm::Expected>)> + on_done) -> void { + SharedValueStores value_stores; + llvm::vfs::InMemoryFileSystem vfs; + auto lookup = context.files().Lookup(params.textDocument.uri.file()); + CARBON_CHECK(lookup); + vfs.addFile(lookup.key(), /*mtime=*/0, + llvm::MemoryBuffer::getMemBufferCopy(lookup.value())); + + auto source = + SourceBuffer::MakeFromFile(vfs, lookup.key(), NullDiagnosticConsumer()); + auto tokens = Lex::Lex(value_stores, *source, NullDiagnosticConsumer()); + auto tree = Parse::Parse(tokens, NullDiagnosticConsumer(), nullptr); + Parse::TreeAndSubtrees tree_and_subtrees(tokens, tree); + std::vector result; + for (const auto& node : tree.postorder()) { + clang::clangd::SymbolKind symbol_kind; + switch (tree.node_kind(node)) { + case Parse::NodeKind::FunctionDecl: + case Parse::NodeKind::FunctionDefinitionStart: + symbol_kind = clang::clangd::SymbolKind::Function; + break; + case Parse::NodeKind::Namespace: + symbol_kind = clang::clangd::SymbolKind::Namespace; + break; + case Parse::NodeKind::InterfaceDefinitionStart: + case Parse::NodeKind::NamedConstraintDefinitionStart: + symbol_kind = clang::clangd::SymbolKind::Interface; + break; + case Parse::NodeKind::ClassDefinitionStart: + symbol_kind = clang::clangd::SymbolKind::Class; + break; + default: + continue; + } + + if (auto name = + GetIdentifierName(value_stores, tokens, tree_and_subtrees, node)) { + auto token = tree.node_token(node); + clang::clangd::Position pos{tokens.GetLineNumber(token) - 1, + tokens.GetColumnNumber(token) - 1}; + + clang::clangd::DocumentSymbol symbol{ + .name = std::string(*name), + .kind = symbol_kind, + .range = {.start = pos, .end = pos}, + .selectionRange = {.start = pos, .end = pos}, + }; + + result.push_back(symbol); + } + } + on_done(result); +} + +} // namespace Carbon::LanguageServer diff --git a/toolchain/language_server/handle_initialize.cpp b/toolchain/language_server/handle_initialize.cpp new file mode 100644 index 000000000000..77884820853b --- /dev/null +++ b/toolchain/language_server/handle_initialize.cpp @@ -0,0 +1,20 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include "toolchain/language_server/handle.h" + +namespace Carbon::LanguageServer { + +auto HandleInitialize( + Context& /*context*/, + const clang::clangd::NoParams& /*client_capabilities*/, + llvm::function_ref)> on_done) + -> void { + llvm::json::Object capabilities{{"documentSymbolProvider", true}, + {"textDocumentSync", /*Full=*/1}}; + llvm::json::Object reply{{"capabilities", std::move(capabilities)}}; + on_done(reply); +} + +} // namespace Carbon::LanguageServer diff --git a/toolchain/language_server/handle_text_document.cpp b/toolchain/language_server/handle_text_document.cpp new file mode 100644 index 000000000000..9faa92af7365 --- /dev/null +++ b/toolchain/language_server/handle_text_document.cpp @@ -0,0 +1,25 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include "toolchain/language_server/handle.h" + +namespace Carbon::LanguageServer { + +auto HandleDidOpenTextDocument( + Context& context, const clang::clangd::DidOpenTextDocumentParams& params) + -> void { + context.files().Update(params.textDocument.uri.file(), + params.textDocument.text); +} + +auto HandleDidChangeTextDocument( + Context& context, const clang::clangd::DidChangeTextDocumentParams& params) + -> void { + // Full text is sent if full sync is specified in capabilities. + CARBON_CHECK(params.contentChanges.size() == 1); + context.files().Update(params.textDocument.uri.file(), + params.contentChanges[0].text); +} + +} // namespace Carbon::LanguageServer diff --git a/toolchain/language_server/incoming_messages.cpp b/toolchain/language_server/incoming_messages.cpp new file mode 100644 index 000000000000..f628a43c9957 --- /dev/null +++ b/toolchain/language_server/incoming_messages.cpp @@ -0,0 +1,107 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include "toolchain/language_server/incoming_messages.h" + +#include "toolchain/language_server/handle.h" + +namespace Carbon::LanguageServer { + +// Parses a JSON value into a specific parameter type. The name of the method is +// used when producing errors. +template +inline auto Parse(llvm::StringRef name, const llvm::json::Value& raw_params) + -> llvm::Expected { + ParamsT params; + llvm::json::Path::Root root; + if (!clang::clangd::fromJSON(raw_params, params, root)) { + return llvm::make_error( + llvm::formatv("in call to `{0}`, JSON parse failed: {1}", name, + llvm::fmt_consume(root.getError())), + clang::clangd::ErrorCode::InvalidParams); + } + return std::move(params); +} + +template +auto IncomingMessages::AddCallHandler( + llvm::StringRef name, + void (*handler)(Context&, const ParamsT&, + llvm::function_ref)>)) + -> void { + CallHandler parsing_handler = + [name, handler]( + Context& context, llvm::json::Value raw_params, + llvm::function_ref)> on_done) + -> void { + auto params = Parse(name, raw_params); + if (!params) { + on_done(params.takeError()); + return; + } + handler(context, *params, on_done); + }; + auto result = call_handlers_.Insert(name, parsing_handler); + CARBON_CHECK(result.is_inserted(), "Duplicate handler: {0}", name); +} + +template +auto IncomingMessages::AddNotificationHandler(llvm::StringRef name, + void (*handler)(Context&, + const ParamsT&)) + -> void { + NotificationHandler parsing_handler = + [name, handler](Context& context, llvm::json::Value raw_params) -> void { + auto params = Parse(name, raw_params); + if (!params) { + // TODO: Maybe we should do something more with this error? + llvm::consumeError(params.takeError()); + } + handler(context, *params); + }; + auto result = notification_handlers_.Insert(name, parsing_handler); + CARBON_CHECK(result.is_inserted(), "Duplicate handler: {0}", name); +} + +IncomingMessages::IncomingMessages(clang::clangd::Transport* transport, + Context* context) + : transport_(transport), context_(context) { + AddCallHandler("textDocument/documentSymbol", &HandleDocumentSymbol); + AddCallHandler("initialize", &HandleInitialize); + AddNotificationHandler("textDocument/didChange", + &HandleDidChangeTextDocument); + AddNotificationHandler("textDocument/didOpen", &HandleDidOpenTextDocument); +} + +auto IncomingMessages::onCall(llvm::StringRef name, llvm::json::Value params, + llvm::json::Value id) -> bool { + if (auto result = call_handlers_.Lookup(name)) { + (result.value())(*context_, std::move(params), + [&](llvm::Expected reply) { + transport_->reply(id, std::move(reply)); + }); + } else { + transport_->reply(id, llvm::make_error( + llvm::formatv("call `{0}` not found", name), + clang::clangd::ErrorCode::MethodNotFound)); + } + + return true; +} + +auto IncomingMessages::onNotify(llvm::StringRef name, llvm::json::Value value) + -> bool { + if (name == "exit") { + return false; + } + if (auto result = notification_handlers_.Lookup(name)) { + (result.value())(*context_, std::move(value)); + } else { + clang::clangd::log("notification `{0}` not found", name); + } + + return true; +} + +} // namespace Carbon::LanguageServer diff --git a/toolchain/language_server/incoming_messages.h b/toolchain/language_server/incoming_messages.h new file mode 100644 index 000000000000..fcf90a71109a --- /dev/null +++ b/toolchain/language_server/incoming_messages.h @@ -0,0 +1,76 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef CARBON_TOOLCHAIN_LANGUAGE_SERVER_INCOMING_MESSAGES_H_ +#define CARBON_TOOLCHAIN_LANGUAGE_SERVER_INCOMING_MESSAGES_H_ + +#include "clang-tools-extra/clangd/LSPBinder.h" +#include "clang-tools-extra/clangd/Transport.h" +#include "common/check.h" +#include "common/map.h" +#include "toolchain/language_server/context.h" + +namespace Carbon::LanguageServer { + +// Handles LSP messages from the client (IDE extension) by forwarding them to +// `handlers_`. +// +// Handlers can return false to indicate server shutdown, although that's only +// used for the `exit` notification. +// +// TODO: Consider adding multithreading support for calls. +class IncomingMessages : public clang::clangd::Transport::MessageHandler { + public: + explicit IncomingMessages(clang::clangd::Transport* transport, + Context* context); + + // Dispatches calls to the appropriate entry in `call_handlers_`. + auto onCall(llvm::StringRef name, llvm::json::Value params, + llvm::json::Value id) -> bool override; + + // Dispatches notifications to the appropriate entry in + // `notification_handlers_`, except for `exit` which directly returns false. + auto onNotify(llvm::StringRef name, llvm::json::Value value) -> bool override; + + // Handles replies. + // TODO: Implement when needed. + auto onReply(llvm::json::Value /*id*/, + llvm::Expected /*result*/) -> bool override { + return true; + } + + private: + // These are the signatures expected for handlers. + using CallHandler = std::function)> on_done)>; + using NotificationHandler = + std::function; + + template + auto AddCallHandler( + llvm::StringRef name, + void (*handler)(Context&, const ParamsT&, + llvm::function_ref)>)) + -> void; + template + auto AddNotificationHandler(llvm::StringRef name, + void (*handler)(Context&, const ParamsT&)) + -> void; + + // The connection to the client. + clang::clangd::Transport* transport_; + // The context for handlers. + Context* context_; + + // Handlers for LSP calls. + Map call_handlers_; + + // Handlers for LSP notifications. + Map notification_handlers_; +}; + +} // namespace Carbon::LanguageServer + +#endif // CARBON_TOOLCHAIN_LANGUAGE_SERVER_INCOMING_MESSAGES_H_ diff --git a/toolchain/language_server/language_server.cpp b/toolchain/language_server/language_server.cpp index 64a31424e9a3..285a30ee8c75 100644 --- a/toolchain/language_server/language_server.cpp +++ b/toolchain/language_server/language_server.cpp @@ -4,14 +4,35 @@ #include "toolchain/language_server/language_server.h" -#include "toolchain/language_server/server.h" +#include "clang-tools-extra/clangd/LSPBinder.h" +#include "clang-tools-extra/clangd/Transport.h" +#include "toolchain/language_server/context.h" +#include "toolchain/language_server/incoming_messages.h" +#include "toolchain/language_server/outgoing_messages.h" namespace Carbon::LanguageServer { auto Run(std::FILE* input_stream, llvm::raw_ostream& output_stream) -> ErrorOr { - Server server(input_stream, output_stream); - return server.Run(); + // Set up the connection. + std::unique_ptr transport( + clang::clangd::newJSONTransport(input_stream, output_stream, + /*InMirror=*/nullptr, + /*Pretty=*/true)); + Context context; + IncomingMessages incoming(transport.get(), &context); + OutgoingMessages outgoing(transport.get()); + + // Run the server loop. + llvm::Error err = transport->loop(incoming); + if (err) { + std::string str; + llvm::raw_string_ostream out(str); + out << err; + return Error(str); + } else { + return Success(); + } } } // namespace Carbon::LanguageServer diff --git a/toolchain/language_server/outgoing_messages.h b/toolchain/language_server/outgoing_messages.h new file mode 100644 index 000000000000..4111fe2f35b9 --- /dev/null +++ b/toolchain/language_server/outgoing_messages.h @@ -0,0 +1,38 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef CARBON_TOOLCHAIN_LANGUAGE_SERVER_OUTGOING_MESSAGES_H_ +#define CARBON_TOOLCHAIN_LANGUAGE_SERVER_OUTGOING_MESSAGES_H_ + +#include "clang-tools-extra/clangd/LSPBinder.h" +#include "clang-tools-extra/clangd/Transport.h" + +namespace Carbon::LanguageServer { + +// Handles sending LSP messages to the client (IDE extension). +class OutgoingMessages : public clang::clangd::LSPBinder::RawOutgoing { + public: + explicit OutgoingMessages(clang::clangd::Transport* transport) + : transport_(transport) {} + + // Calls a method on the client. + // TODO: Implement when needed. + auto callMethod(llvm::StringRef /*method*/, llvm::json::Value /*params*/, + clang::clangd::Callback /*reply*/) + -> void override {} + + // Sets a notification to the client. + auto notify(llvm::StringRef method, llvm::json::Value params) + -> void override { + transport_->notify(method, params); + } + + private: + // The connection to the client. + clang::clangd::Transport* transport_; +}; + +} // namespace Carbon::LanguageServer + +#endif // CARBON_TOOLCHAIN_LANGUAGE_SERVER_OUTGOING_MESSAGES_H_ diff --git a/toolchain/language_server/server.cpp b/toolchain/language_server/server.cpp deleted file mode 100644 index b2553992c226..000000000000 --- a/toolchain/language_server/server.cpp +++ /dev/null @@ -1,174 +0,0 @@ -// Part of the Carbon Language project, under the Apache License v2.0 with LLVM -// Exceptions. See /LICENSE for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - -#include "toolchain/language_server/server.h" - -#include "toolchain/base/shared_value_stores.h" -#include "toolchain/diagnostics/null_diagnostics.h" -#include "toolchain/lex/lex.h" -#include "toolchain/parse/node_kind.h" -#include "toolchain/parse/parse.h" -#include "toolchain/parse/tree_and_subtrees.h" -#include "toolchain/source/source_buffer.h" - -namespace Carbon::LanguageServer { - -Server::Server(std::FILE* input_stream, llvm::raw_ostream& output_stream) - : transport_(clang::clangd::newJSONTransport(input_stream, output_stream, - /*InMirror=*/nullptr, - /*Pretty=*/true)), - binder_(handlers_, *this) { - binder_.notification("textDocument/didOpen", this, - &Server::OnDidOpenTextDocument); - binder_.notification("textDocument/didChange", this, - &Server::OnDidChangeTextDocument); - binder_.method("initialize", this, &Server::OnInitialize); - binder_.method("textDocument/documentSymbol", this, - &Server::OnDocumentSymbol); -} - -auto Server::Run() -> ErrorOr { - llvm::Error err = transport_->loop(*this); - if (err) { - std::string str; - llvm::raw_string_ostream out(str); - out << err; - return Error(str); - } else { - return Success(); - } -} - -void Server::OnDidOpenTextDocument( - clang::clangd::DidOpenTextDocumentParams const& params) { - files_.emplace(params.textDocument.uri.file(), params.textDocument.text); -} - -void Server::OnDidChangeTextDocument( - clang::clangd::DidChangeTextDocumentParams const& params) { - // Full text is sent if full sync is specified in capabilities. - CARBON_CHECK(params.contentChanges.size() == 1); - std::string file = params.textDocument.uri.file().str(); - files_[file] = params.contentChanges[0].text; -} - -void Server::OnInitialize( - clang::clangd::NoParams const& /*client_capabilities*/, - clang::clangd::Callback cb) { - llvm::json::Object capabilities{{"documentSymbolProvider", true}, - {"textDocumentSync", /*Full=*/1}}; - - llvm::json::Object reply{{"capabilities", std::move(capabilities)}}; - cb(reply); -} - -auto Server::onNotify(llvm::StringRef method, llvm::json::Value value) -> bool { - if (method == "exit") { - return false; - } - if (auto handler = handlers_.NotificationHandlers.find(method); - handler != handlers_.NotificationHandlers.end()) { - handler->second(std::move(value)); - } else { - clang::clangd::log("unhandled notification {0}", method); - } - - return true; -} - -auto Server::onCall(llvm::StringRef method, llvm::json::Value params, - llvm::json::Value id) -> bool { - if (auto handler = handlers_.MethodHandlers.find(method); - handler != handlers_.MethodHandlers.end()) { - // TODO: Improve this if add threads. - handler->second(std::move(params), - [&](llvm::Expected reply) { - transport_->reply(id, std::move(reply)); - }); - } else { - transport_->reply( - id, llvm::make_error( - "method not found", clang::clangd::ErrorCode::MethodNotFound)); - } - - return true; -} - -auto Server::onReply(llvm::json::Value /*id*/, - llvm::Expected /*result*/) -> bool { - return true; -} - -// Returns the text of first child of kind Parse::NodeKind::IdentifierName. -static auto GetIdentifierName(const SharedValueStores& value_stores, - const Lex::TokenizedBuffer& tokens, - const Parse::TreeAndSubtrees& p, - Parse::NodeId node) - -> std::optional { - for (auto ch : p.children(node)) { - if (p.tree().node_kind(ch) == Parse::NodeKind::IdentifierName) { - auto token = p.tree().node_token(ch); - if (tokens.GetKind(token) == Lex::TokenKind::Identifier) { - return value_stores.identifiers().Get(tokens.GetIdentifier(token)); - } - } - } - return std::nullopt; -} - -void Server::OnDocumentSymbol( - clang::clangd::DocumentSymbolParams const& params, - clang::clangd::Callback> cb) { - SharedValueStores value_stores; - llvm::vfs::InMemoryFileSystem vfs; - auto file = params.textDocument.uri.file().str(); - vfs.addFile(file, /*mtime=*/0, - llvm::MemoryBuffer::getMemBufferCopy(files_.at(file))); - - auto buf = SourceBuffer::MakeFromFile(vfs, file, NullDiagnosticConsumer()); - auto lexed = Lex::Lex(value_stores, *buf, NullDiagnosticConsumer()); - auto parsed = Parse::Parse(lexed, NullDiagnosticConsumer(), nullptr); - Parse::TreeAndSubtrees tree_and_subtrees(lexed, parsed); - std::vector result; - for (const auto& node : parsed.postorder()) { - clang::clangd::SymbolKind symbol_kind; - switch (parsed.node_kind(node)) { - case Parse::NodeKind::FunctionDecl: - case Parse::NodeKind::FunctionDefinitionStart: - symbol_kind = clang::clangd::SymbolKind::Function; - break; - case Parse::NodeKind::Namespace: - symbol_kind = clang::clangd::SymbolKind::Namespace; - break; - case Parse::NodeKind::InterfaceDefinitionStart: - case Parse::NodeKind::NamedConstraintDefinitionStart: - symbol_kind = clang::clangd::SymbolKind::Interface; - break; - case Parse::NodeKind::ClassDefinitionStart: - symbol_kind = clang::clangd::SymbolKind::Class; - break; - default: - continue; - } - - if (auto name = - GetIdentifierName(value_stores, lexed, tree_and_subtrees, node)) { - auto tok = parsed.node_token(node); - clang::clangd::Position pos{lexed.GetLineNumber(tok) - 1, - lexed.GetColumnNumber(tok) - 1}; - - clang::clangd::DocumentSymbol symbol{ - .name = std::string(*name), - .kind = symbol_kind, - .range = {.start = pos, .end = pos}, - .selectionRange = {.start = pos, .end = pos}, - }; - - result.push_back(symbol); - } - } - cb(result); -} - -} // namespace Carbon::LanguageServer diff --git a/toolchain/language_server/server.h b/toolchain/language_server/server.h deleted file mode 100644 index 08afe932e65c..000000000000 --- a/toolchain/language_server/server.h +++ /dev/null @@ -1,90 +0,0 @@ -// Part of the Carbon Language project, under the Apache License v2.0 with LLVM -// Exceptions. See /LICENSE for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - -#ifndef CARBON_TOOLCHAIN_LANGUAGE_SERVER_SERVER_H_ -#define CARBON_TOOLCHAIN_LANGUAGE_SERVER_SERVER_H_ - -#include -#include -#include - -#include "clang-tools-extra/clangd/LSPBinder.h" -#include "clang-tools-extra/clangd/Protocol.h" -#include "clang-tools-extra/clangd/Transport.h" -#include "common/error.h" - -namespace Carbon::LanguageServer { - -// Provides a LSP implementation for Carbon. -class Server : public clang::clangd::Transport::MessageHandler, - public clang::clangd::LSPBinder::RawOutgoing { - public: - // Prepares the server to run on the provided streams. - explicit Server(std::FILE* input_stream, llvm::raw_ostream& output_stream); - - // Runs the server in a loop, returning the result. Currently this always - // returns an error when the input stream is closed. - auto Run() -> ErrorOr; - - // Transport::MessageHandler - // Handlers returns true to keep processing messages, or false to shut down. - - // Handler called on notification by client. - auto onNotify(llvm::StringRef method, llvm::json::Value value) - -> bool override; - // Handler called on method call by client. - auto onCall(llvm::StringRef method, llvm::json::Value params, - llvm::json::Value id) -> bool override; - // Handler called on response of Transport::call. - auto onReply(llvm::json::Value id, llvm::Expected result) - -> bool override; - - // LSPBinder::RawOutgoing - - // Send method call to client - auto callMethod(llvm::StringRef method, llvm::json::Value params, - clang::clangd::Callback reply) - -> void override { - // TODO: Implement when needed. - } - - // Send notification to client - auto notify(llvm::StringRef method, llvm::json::Value params) - -> void override { - transport_->notify(method, params); - } - - private: - // Typed handlers for notifications and method calls by client. - - // Client opened a document. - auto OnDidOpenTextDocument( - clang::clangd::DidOpenTextDocumentParams const& params) -> void; - - // Client updated content of a document. - auto OnDidChangeTextDocument( - clang::clangd::DidChangeTextDocumentParams const& params) -> void; - - // Capabilities negotiation - auto OnInitialize(clang::clangd::NoParams const& client_capabilities, - clang::clangd::Callback cb) -> void; - - // Code outline - auto OnDocumentSymbol( - clang::clangd::DocumentSymbolParams const& params, - clang::clangd::Callback> cb) - -> void; - - const std::unique_ptr transport_; - // content of files managed by the language client. - std::unordered_map files_; - // handlers for client methods and notifications - clang::clangd::LSPBinder::RawHandlers handlers_; - // Binds client calls to member methods. - clang::clangd::LSPBinder binder_; -}; - -} // namespace Carbon::LanguageServer - -#endif // CARBON_TOOLCHAIN_LANGUAGE_SERVER_SERVER_H_