mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-24 21:30:12 +01:00
Add support for "jump to declaration", "find references", type information on hover. This support is strictly single-file for now; only references and declarations within the same file are found. We could go a bit beyond that, but to properly handle cross-file references we'll need to build an index and a compilation database, which is beyond the scope of this change. On hover, we provide the type information for the instruction under the cursor as-is. This is frequently not very useful, as the type of a function F is simply "<type of F>", but is a starting point for richer information. Assisted-by: Claude Code
195 lines
7.4 KiB
C++
195 lines
7.4 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 <optional>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "common/raw_string_ostream.h"
|
|
#include "toolchain/language_server/handle.h"
|
|
#include "toolchain/language_server/position.h"
|
|
#include "toolchain/language_server/sem_ir_index.h"
|
|
#include "toolchain/sem_ir/file.h"
|
|
#include "toolchain/sem_ir/stringify.h"
|
|
#include "toolchain/sem_ir/typed_insts.h"
|
|
|
|
namespace Carbon::LanguageServer {
|
|
|
|
// Returns the type of `inst_id` rendered as Carbon source, or an empty string
|
|
// if it has no type. Instructions that aren't values, such as declarations of
|
|
// namespaces, have no type to show.
|
|
//
|
|
// TODO: `StringifyConstantInst` renders some types as placeholders such as
|
|
// `<type of F>` for a function and `<pattern for i32>` for a binding pattern,
|
|
// which is unhelpful as hover text. Show the signature for a function, and the
|
|
// bound type rather than the pattern type for a binding.
|
|
static auto StringifyTypeOfInst(const SemIR::File& sem_ir,
|
|
SemIR::InstId inst_id) -> std::string {
|
|
auto type_id = sem_ir.insts().Get(inst_id).type_id();
|
|
if (!type_id.has_value()) {
|
|
return "";
|
|
}
|
|
return SemIR::StringifyConstantInst(sem_ir,
|
|
sem_ir.types().GetTypeInstId(type_id));
|
|
}
|
|
|
|
// Given a position-based query, returns the corresponding position information.
|
|
// If the request is invalid or there is no instruction at that position,
|
|
// produces a response to the query: an InvalidParams error or a
|
|
// default-constructed reply, as appropriate.
|
|
//
|
|
// TODO: If a default-constructed response is not the correct way to handle an
|
|
// invalid location, we will need to extend this function to accept a fallback
|
|
// value. For now, it's right for all the queries we support.
|
|
template <typename ResponseType>
|
|
static auto FindInstAtPositionOrFail(
|
|
Context& context, const clang::clangd::TextDocumentPositionParams& params,
|
|
llvm::function_ref<auto(llvm::Expected<ResponseType>)->void> on_done)
|
|
-> PositionInfo {
|
|
auto* file = context.LookupFile(params.textDocument.uri.file());
|
|
if (!file) {
|
|
on_done(llvm::make_error<clang::clangd::LSPError>(
|
|
llvm::formatv("Unknown textDocument `{0}`",
|
|
params.textDocument.uri.file()),
|
|
clang::clangd::ErrorCode::InvalidParams));
|
|
return {};
|
|
}
|
|
|
|
auto info = FindPositionInfo(*file, params.position);
|
|
if (!info.has_inst()) {
|
|
on_done(ResponseType());
|
|
}
|
|
|
|
return info;
|
|
}
|
|
|
|
// Implements `textDocument/hover`:
|
|
// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_hover
|
|
auto HandleHover(
|
|
Context& context, const clang::clangd::TextDocumentPositionParams& params,
|
|
llvm::function_ref<auto(llvm::Expected<clang::clangd::Hover>)->void>
|
|
on_done) -> void {
|
|
auto info = FindInstAtPositionOrFail(context, params, on_done);
|
|
if (!info.has_inst()) {
|
|
return;
|
|
}
|
|
|
|
const auto& sem_ir = *info.file->sem_ir();
|
|
RawStringOstream text;
|
|
text << "```carbon\n" << info.file->tokens().GetTokenText(info.token);
|
|
if (auto type = StringifyTypeOfInst(sem_ir, info.inst_id); !type.empty()) {
|
|
text << ": " << type;
|
|
}
|
|
text << "\n```";
|
|
|
|
on_done(clang::clangd::Hover{
|
|
.contents = {.kind = clang::clangd::MarkupKind::Markdown,
|
|
.value = text.TakeStr()},
|
|
.range = GetTokenRange(info.file->tokens(), info.token)});
|
|
}
|
|
|
|
// Shared implementation of the goto-style requests, which differ only in which
|
|
// instruction they resolve to.
|
|
static auto HandleGoto(
|
|
Context& context, const clang::clangd::TextDocumentPositionParams& params,
|
|
bool use_type,
|
|
llvm::function_ref<
|
|
auto(llvm::Expected<std::vector<clang::clangd::Location>>)->void>
|
|
on_done) -> void {
|
|
auto info = FindInstAtPositionOrFail(context, params, on_done);
|
|
if (!info.has_inst()) {
|
|
return;
|
|
}
|
|
|
|
const auto& sem_ir = *info.file->sem_ir();
|
|
auto target_id = GetReferencedInst(sem_ir, info.inst_id);
|
|
if (use_type) {
|
|
auto type_id = sem_ir.insts().Get(target_id).type_id();
|
|
if (!type_id.has_value()) {
|
|
on_done(std::vector<clang::clangd::Location>());
|
|
return;
|
|
}
|
|
target_id = sem_ir.types().GetTypeInstId(type_id);
|
|
}
|
|
|
|
std::vector<clang::clangd::Location> locations;
|
|
if (auto location = GetInstLocation(*info.file, target_id)) {
|
|
locations.push_back(*location);
|
|
}
|
|
on_done(std::move(locations));
|
|
}
|
|
|
|
// Implements `textDocument/definition` and `textDocument/declaration`:
|
|
// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_definition
|
|
//
|
|
// Carbon separates declaration from definition, but SemIR resolves a name to a
|
|
// single entity instruction, so both requests currently answer the same way.
|
|
// TODO: Point `definition` at the definition when an entity is declared in one
|
|
// place and defined in another.
|
|
auto HandleDefinition(
|
|
Context& context, const clang::clangd::TextDocumentPositionParams& params,
|
|
llvm::function_ref<
|
|
auto(llvm::Expected<std::vector<clang::clangd::Location>>)->void>
|
|
on_done) -> void {
|
|
HandleGoto(context, params, /*use_type=*/false, on_done);
|
|
}
|
|
|
|
// Implements `textDocument/typeDefinition`:
|
|
// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_typeDefinition
|
|
auto HandleTypeDefinition(
|
|
Context& context, const clang::clangd::TextDocumentPositionParams& params,
|
|
llvm::function_ref<
|
|
auto(llvm::Expected<std::vector<clang::clangd::Location>>)->void>
|
|
on_done) -> void {
|
|
HandleGoto(context, params, /*use_type=*/true, on_done);
|
|
}
|
|
|
|
// Implements `textDocument/references`:
|
|
// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_references
|
|
//
|
|
// Only finds references within the file being edited. Without a project-wide
|
|
// index there's no way to see other files, so results may be incomplete.
|
|
auto HandleReferences(
|
|
Context& context, const clang::clangd::ReferenceParams& params,
|
|
llvm::function_ref<
|
|
auto(llvm::Expected<std::vector<clang::clangd::Location>>)->void>
|
|
on_done) -> void {
|
|
auto info = FindInstAtPositionOrFail(context, params, on_done);
|
|
if (!info.has_inst()) {
|
|
return;
|
|
}
|
|
|
|
const auto& sem_ir = *info.file->sem_ir();
|
|
auto target_token =
|
|
GetInstNameToken(*info.file, GetReferencedInst(sem_ir, info.inst_id));
|
|
if (!target_token.has_value()) {
|
|
on_done(std::vector<clang::clangd::Location>());
|
|
return;
|
|
}
|
|
|
|
// This is the one request the token index can't serve: it needs every
|
|
// instruction referring to an entity, which is the opposite direction from
|
|
// the index. A scan is inherent, and cheap next to the compile that produced
|
|
// the IR.
|
|
std::vector<clang::clangd::Location> locations;
|
|
if (params.context.includeDeclaration) {
|
|
locations.push_back(
|
|
{.uri = info.file->uri(),
|
|
.range = GetTokenRange(info.file->tokens(), target_token)});
|
|
}
|
|
for (auto [inst_id, inst] : sem_ir.insts().enumerate()) {
|
|
auto name_ref = inst.TryAs<SemIR::NameRef>();
|
|
if (!name_ref ||
|
|
GetInstNameToken(*info.file, name_ref->value_id) != target_token) {
|
|
continue;
|
|
}
|
|
if (auto location = GetInstLocation(*info.file, inst_id)) {
|
|
locations.push_back(*location);
|
|
}
|
|
}
|
|
on_done(std::move(locations));
|
|
}
|
|
|
|
} // namespace Carbon::LanguageServer
|