mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-24 21:20:11 +01:00
Add hover cards and jump to declaration / definition / reference for the formatted SemIR that appears in check tests. This is done by adding a heuristic "parser" for SemIR to the language server. The cross-references are strictly best-effort, since this is just a tool for Carbon developers, not a user-facing facility. A couple of other changes made along the way: * file_test tests with an AUTOUPDATE-SPLIT no longer look for CHECK: lines outside that split. This was motivated by the tests for this new facility including CHECK: lines as part of the test input. * An agent skill for working on the language server, tracking some things that cost Claude time when working on this. Assisted-by: Claude via Antigravity
155 lines
6.0 KiB
C++
155 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", &HandleDeclaration);
|
|
AddCallHandler("textDocument/definition", &HandleDefinition);
|
|
AddCallHandler("textDocument/documentSymbol", &HandleDocumentSymbol);
|
|
AddCallHandler("textDocument/formatting", &HandleFormatting);
|
|
AddCallHandler("textDocument/hover", &HandleHover);
|
|
AddCallHandler("textDocument/implementation", &HandleImplementation);
|
|
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
|