Files
carbon-lang/toolchain/language_server/incoming_messages.cpp
T
DavidLoftus dc2ee5bd5e Implement lsp/formatting for Carbon::LanguageServer (#7688)
Implements formatting support within LSP. Current implementation
performs while file formatting, we just replace file with output of
Carbon::Format()

https://github.com/carbon-language/carbon-lang/pull/7687 independently
improves formatting so that this output is somewhat decent.

Assisted-With: Gemini / Antigravity
2026-08-31 16:38:38 +00:00

154 lines
6.0 KiB
C++

// 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 <utility>
#include "common/ostream.h"
#include "common/raw_string_ostream.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 <typename ParamsT>
inline auto Parse(llvm::StringRef name, const llvm::json::Value& raw_params)
-> llvm::Expected<ParamsT> {
ParamsT params;
llvm::json::Path::Root root;
if (!clang::clangd::fromJSON(raw_params, params, root)) {
return llvm::make_error<clang::clangd::LSPError>(
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 <typename ParamsT, typename ResultT>
auto IncomingMessages::AddCallHandler(
llvm::StringRef name,
auto (*handler)(Context&, const ParamsT&,
llvm::function_ref<auto(llvm::Expected<ResultT>)->void>)
->void) -> void {
CallHandler parsing_handler =
[name, handler](
Context& context, llvm::json::Value raw_params,
llvm::function_ref<auto(llvm::Expected<llvm::json::Value>)->void>
on_done) -> void {
auto params = Parse<ParamsT>(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 <typename ParamsT>
auto IncomingMessages::AddNotificationHandler(
llvm::StringRef name, auto (*handler)(Context&, const ParamsT&)->void)
-> void {
NotificationHandler parsing_handler =
[name, handler](Context& context, llvm::json::Value raw_params) -> void {
auto params = Parse<ParamsT>(name, raw_params);
if (!params) {
CARBON_DIAGNOSTIC(LanguageServerNotificationParseError, Warning, "{0}",
std::string);
context.no_loc_emitter().Emit(LanguageServerNotificationParseError,
llvm::toString(params.takeError()));
return;
}
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/declaration", &HandleDefinition);
AddCallHandler("textDocument/definition", &HandleDefinition);
AddCallHandler("textDocument/documentSymbol", &HandleDocumentSymbol);
AddCallHandler("textDocument/formatting", &HandleFormatting);
AddCallHandler("textDocument/hover", &HandleHover);
AddCallHandler("textDocument/references", &HandleReferences);
AddCallHandler("textDocument/typeDefinition", &HandleTypeDefinition);
AddCallHandler("initialize", &HandleInitialize);
AddCallHandler("shutdown", &HandleShutdown);
AddNotificationHandler("initialized", &HandleInitialized);
AddNotificationHandler("textDocument/didChange",
&HandleDidChangeTextDocument);
AddNotificationHandler("textDocument/didClose", &HandleDidCloseTextDocument);
AddNotificationHandler("textDocument/didOpen", &HandleDidOpenTextDocument);
AddNotificationHandler("textDocument/didSave", &HandleDidSaveTextDocument);
}
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<llvm::json::Value> reply) {
transport_->reply(id, std::move(reply));
});
} else {
transport_->reply(id, llvm::make_error<clang::clangd::LSPError>(
llvm::formatv("unsupported call `{0}`", name),
clang::clangd::ErrorCode::MethodNotFound));
}
return true;
}
auto IncomingMessages::onNotify(llvm::StringRef name, llvm::json::Value value)
-> bool {
if (name == "exit") {
return false;
}
// Notifications prefixed with `$/` are implementation dependent, and LSP says
// a recipient is free to ignore ones it doesn't implement. Note this doesn't
// apply to calls, which must be answered with `MethodNotFound`; `onCall`
// already does that for anything unhandled.
// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#dollarRequests
if (name.starts_with("$/")) {
return true;
}
if (auto result = notification_handlers_.Lookup(name)) {
(result.value())(*context_, std::move(value));
} else {
CARBON_DIAGNOSTIC(LanguageServerUnsupportedNotification, Warning,
"unsupported notification `{0}`", std::string);
context_->no_loc_emitter().Emit(LanguageServerUnsupportedNotification,
name.str());
}
return true;
}
auto IncomingMessages::onReply(llvm::json::Value id,
llvm::Expected<llvm::json::Value> result)
-> bool {
RawStringOstream id_str;
id_str << id;
RawStringOstream result_str;
if (result) {
result_str << result.get();
} else {
result_str << result.takeError();
}
CARBON_DIAGNOSTIC(LanguageServerUnexpectedReply, Warning,
"unexpected reply to request ID {0}: {1}", std::string,
std::string);
context_->no_loc_emitter().Emit(LanguageServerUnexpectedReply,
id_str.TakeStr(), result_str.TakeStr());
return true;
}
} // namespace Carbon::LanguageServer