language-server: Support simple semantic queries. (#7639)

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
This commit is contained in:
Richard Smith
2026-08-19 14:35:05 +00:00
committed by GitHub
parent a872123a73
commit c78751338b
35 changed files with 1256 additions and 51 deletions
+3
View File
@@ -86,6 +86,9 @@ class CompilationUnit {
auto parse_tree_and_subtrees() const -> const Parse::TreeAndSubtrees& {
return GetParseTreeAndSubtrees();
}
// Only present once the check phase has run.
auto has_sem_ir() const -> bool { return sem_ir_.has_value(); }
auto sem_ir() const -> const SemIR::File& { return *sem_ir_; }
private:
// Do codegen. Returns true on success.
+41
View File
@@ -27,11 +27,29 @@ cc_library(
],
)
cc_library(
name = "sem_ir_index",
srcs = ["sem_ir_index.cpp"],
hdrs = ["sem_ir_index.h"],
deps = [
"//common:check",
"//toolchain/lex:token_index",
"//toolchain/lex:token_kind",
"//toolchain/lex:tokenized_buffer",
"//toolchain/parse:node_kind",
"//toolchain/parse:tree",
"//toolchain/sem_ir:file",
"//toolchain/sem_ir:typed_insts",
"@llvm-project//llvm:Support",
],
)
cc_library(
name = "context",
srcs = ["context.cpp"],
hdrs = ["context.h"],
deps = [
":sem_ir_index",
"//common:check",
"//common:map",
"//common:raw_string_ostream",
@@ -55,13 +73,33 @@ cc_library(
],
)
cc_library(
name = "position",
srcs = ["position.cpp"],
hdrs = ["position.h"],
deps = [
":context",
":sem_ir_index",
"//toolchain/lex:token_index",
"//toolchain/lex:tokenized_buffer",
"//toolchain/parse:tree",
"//toolchain/sem_ir:file",
"//toolchain/sem_ir:typed_insts",
"@llvm-project//clang-tools-extra/clangd:ClangDaemon",
"@llvm-project//llvm:Support",
],
)
cc_library(
name = "handle",
srcs = glob(["handle_*"]),
hdrs = ["handle.h"],
deps = [
":context",
":position",
":sem_ir_index",
"//common:check",
"//common:raw_string_ostream",
"//toolchain/base:shared_value_stores",
"//toolchain/lex",
"//toolchain/lex:token_index",
@@ -69,6 +107,9 @@ cc_library(
"//toolchain/parse",
"//toolchain/parse:node_kind",
"//toolchain/parse:tree",
"//toolchain/sem_ir:file",
"//toolchain/sem_ir:stringify",
"//toolchain/sem_ir:typed_insts",
"//toolchain/source:source_buffer",
"@llvm-project//clang-tools-extra/clangd:ClangDaemon",
"@llvm-project//llvm:Support",
+12
View File
@@ -205,10 +205,22 @@ Context::Context(const InstallPaths* installation,
vfs_ = vfs;
}
auto Context::File::sem_ir_index() const -> const SemIRIndex* {
if (!sem_ir_index_) {
const auto* sem_ir = this->sem_ir();
if (!sem_ir) {
return nullptr;
}
sem_ir_index_.emplace(*sem_ir, tree_and_subtrees());
}
return &*sem_ir_index_;
}
auto Context::File::SetText(Context& context, std::optional<int64_t> version,
llvm::StringRef text) -> void {
// Clear state dependent on the source text.
compile_driver_.reset();
sem_ir_index_.reset();
text_ = text.str();
+33 -3
View File
@@ -18,6 +18,7 @@
#include "toolchain/driver/codegen_options.h"
#include "toolchain/driver/compile_driver.h"
#include "toolchain/driver/compile_options.h"
#include "toolchain/language_server/sem_ir_index.h"
#include "toolchain/lex/tokenized_buffer.h"
#include "toolchain/parse/tree_and_subtrees.h"
#include "toolchain/sem_ir/file.h"
@@ -39,16 +40,42 @@ class Context {
auto SetText(Context& context, std::optional<int64_t> version,
llvm::StringRef text) -> void;
auto uri() const -> const clang::clangd::URIForFile& { return uri_; }
auto filename() const -> llvm::StringRef { return filename_; }
auto text() const -> llvm::StringRef { return text_; }
auto tree_and_subtrees() const -> const Parse::TreeAndSubtrees& {
CARBON_CHECK(compile_driver_);
return compile_driver_->units()[compile_driver_->first_input_index()]
->parse_tree_and_subtrees();
return unit().parse_tree_and_subtrees();
}
auto tokens() const -> const Lex::TokenizedBuffer& {
return unit().tokens();
}
// Returns the checked IR, or null if checking didn't get far enough to
// produce one.
auto sem_ir() const -> const SemIR::File* {
const auto& compilation_unit = unit();
return compilation_unit.has_sem_ir() ? &compilation_unit.sem_ir()
: nullptr;
}
// Returns an index of this file's instructions by token, building it if
// this is the first query since the text last changed. Returns null if
// there's no checked IR to index.
//
// This is deliberately not built by `SetText`: most text changes are
// followed by another text change rather than by a query, and the work
// would land on the path that produces diagnostics, which is the latency
// users actually notice.
auto sem_ir_index() const -> const SemIRIndex*;
private:
auto unit() const -> const CompilationUnit& {
CARBON_CHECK(compile_driver_);
return *compile_driver_->units()[compile_driver_->first_input_index()];
}
// The filename, stable across instances.
clang::clangd::URIForFile uri_;
std::string filename_;
@@ -59,6 +86,9 @@ class Context {
CodegenOptions codegen_options_;
CompileOptions options_;
std::unique_ptr<CompileDriver> compile_driver_;
// Built on demand by `sem_ir_index()`, and discarded by `SetText`.
mutable std::optional<SemIRIndex> sem_ir_index_;
};
// `vlog_stream` is optional; other parameters are required.
+27
View File
@@ -10,6 +10,13 @@
namespace Carbon::LanguageServer {
// Locates the entity named at a position.
auto HandleDefinition(
Context& context, const clang::clangd::TextDocumentPositionParams& params,
llvm::function_ref<
auto(llvm::Expected<std::vector<clang::clangd::Location>>)->void>
on_done) -> void;
// Stores the content of newly-opened documents.
auto HandleDidChangeTextDocument(
Context& context, const clang::clangd::DidChangeTextDocumentParams& params)
@@ -37,6 +44,12 @@ auto HandleDocumentSymbol(
auto(llvm::Expected<std::vector<clang::clangd::DocumentSymbol>>)->void>
on_done) -> void;
// Provides the type of the entity at a position.
auto HandleHover(
Context& context, const clang::clangd::TextDocumentPositionParams& params,
llvm::function_ref<auto(llvm::Expected<clang::clangd::Hover>)->void>
on_done) -> void;
// Tells the client what features are supported, and negotiates the position
// encoding.
auto HandleInitialize(
@@ -48,6 +61,13 @@ auto HandleInitialize(
auto HandleInitialized(Context& context, const clang::clangd::NoParams& params)
-> void;
// Finds references to the entity named at a position, within this file only.
auto HandleReferences(
Context& context, const clang::clangd::ReferenceParams& params,
llvm::function_ref<
auto(llvm::Expected<std::vector<clang::clangd::Location>>)->void>
on_done) -> void;
// Prepares LSP for shutdown.
auto HandleShutdown(
Context& /*context*/,
@@ -55,6 +75,13 @@ auto HandleShutdown(
llvm::function_ref<auto(llvm::Expected<std::nullptr_t>)->void> on_done)
-> void;
// Locates the type of the entity named at a position.
auto HandleTypeDefinition(
Context& context, const clang::clangd::TextDocumentPositionParams& params,
llvm::function_ref<
auto(llvm::Expected<std::vector<clang::clangd::Location>>)->void>
on_done) -> void;
} // namespace Carbon::LanguageServer
#endif // CARBON_TOOLCHAIN_LANGUAGE_SERVER_HANDLE_H_
@@ -30,9 +30,14 @@ auto HandleInitialize(
auto encoding = NegotiatePositionEncoding(params.capabilities);
context.SetPositionEncoding(encoding);
llvm::json::Object capabilities{{"documentSymbolProvider", true},
llvm::json::Object capabilities{{"declarationProvider", true},
{"definitionProvider", true},
{"documentSymbolProvider", true},
{"hoverProvider", true},
{"positionEncoding", encoding},
{"referencesProvider", true},
{"textDocumentSync", /*Incremental=*/2},
{"positionEncoding", encoding}};
{"typeDefinitionProvider", true}};
llvm::json::Object reply{{"capabilities", std::move(capabilities)}};
on_done(reply);
}
@@ -0,0 +1,194 @@
// 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
@@ -73,7 +73,12 @@ auto IncomingMessages::AddNotificationHandler(
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/hover", &HandleHover);
AddCallHandler("textDocument/references", &HandleReferences);
AddCallHandler("textDocument/typeDefinition", &HandleTypeDefinition);
AddCallHandler("initialize", &HandleInitialize);
AddCallHandler("shutdown", &HandleShutdown);
AddNotificationHandler("initialized", &HandleInitialized);
+134
View File
@@ -0,0 +1,134 @@
// 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/position.h"
#include <algorithm>
#include <iterator>
#include <optional>
#include <utility>
#include "llvm/ADT/STLExtras.h"
#include "toolchain/language_server/sem_ir_index.h"
#include "toolchain/parse/tree.h"
#include "toolchain/sem_ir/file.h"
#include "toolchain/sem_ir/typed_insts.h"
namespace Carbon::LanguageServer {
auto GetTokenRange(const Lex::TokenizedBuffer& tokens, Lex::TokenIndex start,
Lex::TokenIndex end) -> clang::clangd::Range {
auto start_line = tokens.GetLine(start);
auto start_col = tokens.GetColumnNumber(start);
auto [end_line, end_col] = tokens.GetEndLoc(end);
return clang::clangd::Range{
.start = {.line = start_line.index, .character = start_col - 1},
.end = {.line = end_line.index, .character = end_col - 1},
};
}
// Returns the 0-based (line, column) where a token starts, for comparison
// against an LSP position.
static auto GetTokenStart(const Lex::TokenizedBuffer& tokens,
Lex::TokenIndex token) -> std::pair<int, int> {
return {tokens.GetLine(token).index, tokens.GetColumnNumber(token) - 1};
}
auto FindToken(const Lex::TokenizedBuffer& tokens,
const clang::clangd::Position& position) -> Lex::TokenIndex {
std::pair<int, int> target = {position.line, position.character};
// Tokens are in source order, so find the first one starting after
// `position`; only the token before that can contain it. Searching on
// (line, column) avoids converting the position to a byte offset, which would
// mean rescanning the text for line boundaries.
//
// `tokens()` excludes the recovery tokens added after lexing finished, which
// matters here: those are appended rather than inserted in source order, so
// including them would break the ordering this search relies on.
auto range = tokens.tokens();
auto after = llvm::partition_point(range, [&](Lex::TokenIndex token) {
return GetTokenStart(tokens, token) <= target;
});
if (after == range.begin()) {
return Lex::TokenIndex::None;
}
// The position is in that token only if it's also before the token's end;
// otherwise it falls in whitespace or a comment.
auto token = *std::prev(after);
auto [end_line, end_col] = tokens.GetEndLoc(token);
if (target < std::pair<int, int>(end_line.index, end_col - 1)) {
return token;
}
return Lex::TokenIndex::None;
}
auto FindPositionInfo(const Context::File& file,
const clang::clangd::Position& position) -> PositionInfo {
const auto* sem_ir = file.sem_ir();
const auto* index = file.sem_ir_index();
if (!sem_ir || !index) {
return {};
}
auto token = FindToken(file.tokens(), position);
if (!token.has_value()) {
return {};
}
PositionInfo info = {.file = &file, .token = token};
auto insts = index->InstsForToken(token);
for (auto inst_id : insts) {
// Prefer a name reference: a token such as the name in `fn F()` also has
// instructions for the declaration itself, but a request at a name is about
// the name.
if (sem_ir->insts().Is<SemIR::NameRef>(inst_id)) {
info.inst_id = inst_id;
return info;
}
}
if (!insts.empty()) {
info.inst_id = insts.front();
}
return info;
}
auto GetReferencedInst(const SemIR::File& sem_ir, SemIR::InstId inst_id)
-> SemIR::InstId {
if (auto name_ref = sem_ir.insts().TryGetAs<SemIR::NameRef>(inst_id)) {
return name_ref->value_id;
}
return inst_id;
}
auto GetInstNameToken(const Context::File& file, SemIR::InstId inst_id)
-> Lex::TokenIndex {
const auto* sem_ir = file.sem_ir();
if (!sem_ir || !inst_id.has_value()) {
return Lex::TokenIndex::None;
}
auto loc_id = sem_ir->insts().GetCanonicalLocId(inst_id);
if (loc_id.kind() != SemIR::LocId::Kind::NodeId) {
// Imported from another file, which we can't yet name a location in.
return Lex::TokenIndex::None;
}
auto node_id = loc_id.node_id();
if (!node_id.has_value()) {
return Lex::TokenIndex::None;
}
return GetNameToken(file.tree_and_subtrees(), node_id);
}
auto GetInstLocation(const Context::File& file, SemIR::InstId inst_id)
-> std::optional<clang::clangd::Location> {
auto token = GetInstNameToken(file, inst_id);
if (!token.has_value()) {
return std::nullopt;
}
return clang::clangd::Location{.uri = file.uri(),
.range = GetTokenRange(file.tokens(), token)};
}
} // namespace Carbon::LanguageServer
+75
View File
@@ -0,0 +1,75 @@
// 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_POSITION_H_
#define CARBON_TOOLCHAIN_LANGUAGE_SERVER_POSITION_H_
#include "clang-tools-extra/clangd/Protocol.h"
#include "toolchain/language_server/context.h"
#include "toolchain/lex/token_index.h"
#include "toolchain/lex/tokenized_buffer.h"
#include "toolchain/sem_ir/ids.h"
namespace Carbon::LanguageServer {
// Returns the range covering a closed interval of tokens.
auto GetTokenRange(const Lex::TokenizedBuffer& tokens, Lex::TokenIndex start,
Lex::TokenIndex end) -> clang::clangd::Range;
// Returns the range covering a single token.
inline auto GetTokenRange(const Lex::TokenizedBuffer& tokens,
Lex::TokenIndex token) -> clang::clangd::Range {
return GetTokenRange(tokens, token, token);
}
// Returns the token containing `position`, or `None` if the position isn't
// within a token. Positions in whitespace and comments produce `None`, which is
// why hovering over blank space yields no result rather than the nearest token.
auto FindToken(const Lex::TokenizedBuffer& tokens,
const clang::clangd::Position& position) -> Lex::TokenIndex;
// What a request at a source position refers to.
struct PositionInfo {
// The file that the request points into.
const Context::File* file = nullptr;
// The token at the position, or `None` if there isn't one.
Lex::TokenIndex token = Lex::TokenIndex::None;
// The instruction to answer the request from, or `None` if the position has
// no instruction. A position can have several instructions; this is the one
// that names something, if any, because that's what these requests are about.
SemIR::InstId inst_id = SemIR::InstId::None;
auto has_inst() const -> bool { return inst_id.has_value(); }
};
// Resolves a position to the token and instruction it refers to. Returns an
// empty result if the file has no checked IR, or the position isn't in a token,
// or the token produced no instructions.
auto FindPositionInfo(const Context::File& file,
const clang::clangd::Position& position) -> PositionInfo;
// Returns the instruction that `inst_id` names, which for a name reference is
// the referenced entity and otherwise is `inst_id` itself. This is what
// `definition` and `references` are both anchored on: it gives every mention of
// an entity, including its declaration, the same identity.
auto GetReferencedInst(const SemIR::File& sem_ir, SemIR::InstId inst_id)
-> SemIR::InstId;
// Returns the token naming `inst_id`, or `None` if it isn't located in this
// file. Two instructions naming the same token denote the same entity, which is
// how a declaration and the references to it are matched up: they don't share
// an instruction, but they do share a name.
auto GetInstNameToken(const Context::File& file, SemIR::InstId inst_id)
-> Lex::TokenIndex;
// Returns the location of `inst_id` in `file`, or nullopt if it isn't located
// in this file. Instructions imported from elsewhere have no location here.
auto GetInstLocation(const Context::File& file, SemIR::InstId inst_id)
-> std::optional<clang::clangd::Location>;
} // namespace Carbon::LanguageServer
#endif // CARBON_TOOLCHAIN_LANGUAGE_SERVER_POSITION_H_
+101
View File
@@ -0,0 +1,101 @@
// 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/sem_ir_index.h"
#include "common/check.h"
#include "toolchain/lex/token_kind.h"
#include "toolchain/parse/node_kind.h"
#include "toolchain/parse/tree.h"
#include "toolchain/sem_ir/ids.h"
namespace Carbon::LanguageServer {
auto GetNameToken(const Parse::TreeAndSubtrees& tree_and_subtrees,
Parse::NodeId node_id) -> Lex::TokenIndex {
const auto& tree = tree_and_subtrees.tree();
const auto& tokens = tree.tokens();
for (auto child : tree_and_subtrees.children(node_id)) {
switch (tree.node_kind(child)) {
case Parse::NodeKind::IdentifierNameMaybeBeforeSignature:
case Parse::NodeKind::IdentifierNameNotBeforeSignature: {
auto token = tree.node_token(child);
if (tokens.GetKind(token) == Lex::TokenKind::Identifier) {
return token;
}
break;
}
default:
break;
}
}
return tree.node_token(node_id);
}
// Returns the token that `inst_id` was checked from, or `None` if it has no
// location in this file. Instructions imported from another file are located by
// an `ImportIRInstId`, and desugared instructions by the instruction they were
// desugared from, so only a `NodeId` location refers to this file's tokens.
static auto GetTokenForInst(const SemIR::File& sem_ir,
const Parse::TreeAndSubtrees& tree_and_subtrees,
SemIR::InstId inst_id) -> Lex::TokenIndex {
auto loc_id = sem_ir.insts().GetCanonicalLocId(inst_id);
if (loc_id.kind() != SemIR::LocId::Kind::NodeId) {
return Lex::TokenIndex::None;
}
auto node_id = loc_id.node_id();
if (!node_id.has_value()) {
return Lex::TokenIndex::None;
}
return GetNameToken(tree_and_subtrees, node_id);
}
SemIRIndex::SemIRIndex(const SemIR::File& sem_ir,
const Parse::TreeAndSubtrees& tree_and_subtrees) {
const auto& tokens = tree_and_subtrees.tree().tokens();
// Count the instructions per token, leaving a leading zero so that the counts
// can be turned into start offsets in place.
token_starts_.assign(tokens.size() + 1, 0);
int32_t total = 0;
for (auto [inst_id, inst] : sem_ir.insts().enumerate()) {
auto token = GetTokenForInst(sem_ir, tree_and_subtrees, inst_id);
if (!token.has_value()) {
continue;
}
++token_starts_[token.index + 1];
++total;
}
// Turn the counts into start offsets.
for (size_t i = 1; i < token_starts_.size(); ++i) {
token_starts_[i] += token_starts_[i - 1];
}
CARBON_CHECK(token_starts_.back() == total);
// Fill each token's group. `next` tracks the next free slot per token, and
// ends up equal to the following token's start, so the offsets stay valid.
insts_.resize(total, SemIR::InstId::None);
llvm::SmallVector<int32_t> next(token_starts_.begin(), token_starts_.end());
for (auto [inst_id, inst] : sem_ir.insts().enumerate()) {
auto token = GetTokenForInst(sem_ir, tree_and_subtrees, inst_id);
if (!token.has_value()) {
continue;
}
insts_[next[token.index]++] = inst_id;
}
}
auto SemIRIndex::InstsForToken(Lex::TokenIndex token) const
-> llvm::ArrayRef<SemIR::InstId> {
if (!token.has_value()) {
return {};
}
CARBON_CHECK(static_cast<size_t>(token.index) + 1 < token_starts_.size(),
"Token {0} is not from the indexed file", token.index);
int32_t start = token_starts_[token.index];
int32_t end = token_starts_[token.index + 1];
return llvm::ArrayRef(insts_).slice(start, end - start);
}
} // namespace Carbon::LanguageServer
+69
View File
@@ -0,0 +1,69 @@
// 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_SEM_IR_INDEX_H_
#define CARBON_TOOLCHAIN_LANGUAGE_SERVER_SEM_IR_INDEX_H_
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/SmallVector.h"
#include "toolchain/lex/token_index.h"
#include "toolchain/lex/tokenized_buffer.h"
#include "toolchain/parse/node_ids.h"
#include "toolchain/parse/tree_and_subtrees.h"
#include "toolchain/sem_ir/file.h"
namespace Carbon::LanguageServer {
// Returns the token that identifies `node_id`: the name it declares, if it
// declares one, and otherwise the node's own token.
//
// A declaration's own token is wherever the parser finished it -- the `{` of a
// function body, or the `:` of a binding -- which is not what a user points at
// to mean that declaration. Requests are answered at the name instead, so both
// the index and the locations we report are keyed on it.
auto GetNameToken(const Parse::TreeAndSubtrees& tree_and_subtrees,
Parse::NodeId node_id) -> Lex::TokenIndex;
// Maps each token to the instructions that were checked from it.
//
// Position-based requests such as `hover` and `definition` all start from a
// source position, so this is keyed by token rather than by parse node: the
// caller has to find the token for a position anyway, and the parse node is
// recoverable from an instruction's `LocId`. Instructions imported from other
// files are excluded, because their locations refer to another file's parse
// tree rather than to a token in this one.
//
// Build this lazily, on the first query after the file's text changes. Building
// costs the same single pass over the instructions that an unindexed scan
// would, and most text changes are never followed by a query, so building
// eagerly would only add work to the latency-sensitive path that produces
// diagnostics.
class SemIRIndex {
public:
explicit SemIRIndex(const SemIR::File& sem_ir,
const Parse::TreeAndSubtrees& tree_and_subtrees);
// Returns the instructions checked from `token`, in `InstId` order. Returns
// an empty list for a token that produced no instructions, which is common:
// punctuation and keywords usually contribute to an enclosing instruction
// rather than producing one of their own.
auto InstsForToken(Lex::TokenIndex token) const
-> llvm::ArrayRef<SemIR::InstId>;
private:
// Instructions grouped by token, in the compressed-sparse-row layout: the
// group for token `i` is `insts_[token_starts_[i] .. token_starts_[i + 1])`.
// `token_starts_` therefore has one more entry than there are tokens.
//
// Token indices are dense, so this is built by counting sort in a single pass
// over the instructions, and looked up in constant time. A hash map would
// need to handle the many-instructions-per-token case explicitly; here it
// falls out of the layout.
llvm::SmallVector<SemIR::InstId, 0> insts_;
llvm::SmallVector<int32_t, 0> token_starts_;
};
} // namespace Carbon::LanguageServer
#endif // CARBON_TOOLCHAIN_LANGUAGE_SERVER_SEM_IR_INDEX_H_
@@ -15,16 +15,21 @@
// CHECK:STDERR: error: Input/output error [LanguageServerTransportError]
// CHECK:STDERR:
// CHECK:STDOUT: Content-Length: 182{{\r}}
// CHECK:STDOUT: Content-Length: 352{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 1,
// CHECK:STDOUT: "jsonrpc": "2.0",
// CHECK:STDOUT: "result": {
// CHECK:STDOUT: "capabilities": {
// CHECK:STDOUT: "declarationProvider": true,
// CHECK:STDOUT: "definitionProvider": true,
// CHECK:STDOUT: "documentSymbolProvider": true,
// CHECK:STDOUT: "hoverProvider": true,
// CHECK:STDOUT: "positionEncoding": "utf-16",
// CHECK:STDOUT: "textDocumentSync": 2
// CHECK:STDOUT: "referencesProvider": true,
// CHECK:STDOUT: "textDocumentSync": 2,
// CHECK:STDOUT: "typeDefinitionProvider": true
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT: }Content-Length: 51{{\r}}
+21 -6
View File
@@ -20,40 +20,55 @@
// --- AUTOUPDATE-SPLIT
// CHECK:STDOUT: Content-Length: 181{{\r}}
// CHECK:STDOUT: Content-Length: 351{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 1,
// CHECK:STDOUT: "jsonrpc": "2.0",
// CHECK:STDOUT: "result": {
// CHECK:STDOUT: "capabilities": {
// CHECK:STDOUT: "declarationProvider": true,
// CHECK:STDOUT: "definitionProvider": true,
// CHECK:STDOUT: "documentSymbolProvider": true,
// CHECK:STDOUT: "hoverProvider": true,
// CHECK:STDOUT: "positionEncoding": "utf-8",
// CHECK:STDOUT: "textDocumentSync": 2
// CHECK:STDOUT: "referencesProvider": true,
// CHECK:STDOUT: "textDocumentSync": 2,
// CHECK:STDOUT: "typeDefinitionProvider": true
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT: }Content-Length: 182{{\r}}
// CHECK:STDOUT: }Content-Length: 352{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 2,
// CHECK:STDOUT: "jsonrpc": "2.0",
// CHECK:STDOUT: "result": {
// CHECK:STDOUT: "capabilities": {
// CHECK:STDOUT: "declarationProvider": true,
// CHECK:STDOUT: "definitionProvider": true,
// CHECK:STDOUT: "documentSymbolProvider": true,
// CHECK:STDOUT: "hoverProvider": true,
// CHECK:STDOUT: "positionEncoding": "utf-16",
// CHECK:STDOUT: "textDocumentSync": 2
// CHECK:STDOUT: "referencesProvider": true,
// CHECK:STDOUT: "textDocumentSync": 2,
// CHECK:STDOUT: "typeDefinitionProvider": true
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT: }Content-Length: 182{{\r}}
// CHECK:STDOUT: }Content-Length: 352{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 3,
// CHECK:STDOUT: "jsonrpc": "2.0",
// CHECK:STDOUT: "result": {
// CHECK:STDOUT: "capabilities": {
// CHECK:STDOUT: "declarationProvider": true,
// CHECK:STDOUT: "definitionProvider": true,
// CHECK:STDOUT: "documentSymbolProvider": true,
// CHECK:STDOUT: "hoverProvider": true,
// CHECK:STDOUT: "positionEncoding": "utf-16",
// CHECK:STDOUT: "textDocumentSync": 2
// CHECK:STDOUT: "referencesProvider": true,
// CHECK:STDOUT: "textDocumentSync": 2,
// CHECK:STDOUT: "typeDefinitionProvider": true
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT: }Content-Length: 51{{\r}}
@@ -19,16 +19,21 @@
// CHECK:STDERR: warning: -32602: in call to `textDocument/didOpen`, JSON parse failed: missing value at (root).textDocument [LanguageServerNotificationParseError]
// CHECK:STDERR:
// CHECK:STDOUT: Content-Length: 182{{\r}}
// CHECK:STDOUT: Content-Length: 352{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 1,
// CHECK:STDOUT: "jsonrpc": "2.0",
// CHECK:STDOUT: "result": {
// CHECK:STDOUT: "capabilities": {
// CHECK:STDOUT: "declarationProvider": true,
// CHECK:STDOUT: "definitionProvider": true,
// CHECK:STDOUT: "documentSymbolProvider": true,
// CHECK:STDOUT: "hoverProvider": true,
// CHECK:STDOUT: "positionEncoding": "utf-16",
// CHECK:STDOUT: "textDocumentSync": 2
// CHECK:STDOUT: "referencesProvider": true,
// CHECK:STDOUT: "textDocumentSync": 2,
// CHECK:STDOUT: "typeDefinitionProvider": true
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT: }Content-Length: 51{{\r}}
@@ -36,16 +36,21 @@
// --- AUTOUPDATE-SPLIT
// CHECK:STDOUT: Content-Length: 182{{\r}}
// CHECK:STDOUT: Content-Length: 352{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 1,
// CHECK:STDOUT: "jsonrpc": "2.0",
// CHECK:STDOUT: "result": {
// CHECK:STDOUT: "capabilities": {
// CHECK:STDOUT: "declarationProvider": true,
// CHECK:STDOUT: "definitionProvider": true,
// CHECK:STDOUT: "documentSymbolProvider": true,
// CHECK:STDOUT: "hoverProvider": true,
// CHECK:STDOUT: "positionEncoding": "utf-16",
// CHECK:STDOUT: "textDocumentSync": 2
// CHECK:STDOUT: "referencesProvider": true,
// CHECK:STDOUT: "textDocumentSync": 2,
// CHECK:STDOUT: "typeDefinitionProvider": true
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT: }Content-Length: 145{{\r}}
@@ -30,16 +30,21 @@ choice Colors {
[[@LSP-CALL:shutdown]]
[[@LSP-NOTIFY:exit]]
// CHECK:STDOUT: Content-Length: 182{{\r}}
// CHECK:STDOUT: Content-Length: 352{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 1,
// CHECK:STDOUT: "jsonrpc": "2.0",
// CHECK:STDOUT: "result": {
// CHECK:STDOUT: "capabilities": {
// CHECK:STDOUT: "declarationProvider": true,
// CHECK:STDOUT: "definitionProvider": true,
// CHECK:STDOUT: "documentSymbolProvider": true,
// CHECK:STDOUT: "hoverProvider": true,
// CHECK:STDOUT: "positionEncoding": "utf-16",
// CHECK:STDOUT: "textDocumentSync": 2
// CHECK:STDOUT: "referencesProvider": true,
// CHECK:STDOUT: "textDocumentSync": 2,
// CHECK:STDOUT: "typeDefinitionProvider": true
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT: }Content-Length: 504{{\r}}
@@ -33,16 +33,21 @@ fn G() {}
[[@LSP-CALL:shutdown]]
[[@LSP-NOTIFY:exit]]
// CHECK:STDOUT: Content-Length: 182{{\r}}
// CHECK:STDOUT: Content-Length: 352{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 1,
// CHECK:STDOUT: "jsonrpc": "2.0",
// CHECK:STDOUT: "result": {
// CHECK:STDOUT: "capabilities": {
// CHECK:STDOUT: "declarationProvider": true,
// CHECK:STDOUT: "definitionProvider": true,
// CHECK:STDOUT: "documentSymbolProvider": true,
// CHECK:STDOUT: "hoverProvider": true,
// CHECK:STDOUT: "positionEncoding": "utf-16",
// CHECK:STDOUT: "textDocumentSync": 2
// CHECK:STDOUT: "referencesProvider": true,
// CHECK:STDOUT: "textDocumentSync": 2,
// CHECK:STDOUT: "typeDefinitionProvider": true
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT: }Content-Length: 877{{\r}}
@@ -29,16 +29,21 @@ fn Builtin() = "int.make_type_32";
[[@LSP-CALL:shutdown]]
[[@LSP-NOTIFY:exit]]
// CHECK:STDOUT: Content-Length: 182{{\r}}
// CHECK:STDOUT: Content-Length: 352{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 1,
// CHECK:STDOUT: "jsonrpc": "2.0",
// CHECK:STDOUT: "result": {
// CHECK:STDOUT: "capabilities": {
// CHECK:STDOUT: "declarationProvider": true,
// CHECK:STDOUT: "definitionProvider": true,
// CHECK:STDOUT: "documentSymbolProvider": true,
// CHECK:STDOUT: "hoverProvider": true,
// CHECK:STDOUT: "positionEncoding": "utf-16",
// CHECK:STDOUT: "textDocumentSync": 2
// CHECK:STDOUT: "referencesProvider": true,
// CHECK:STDOUT: "textDocumentSync": 2,
// CHECK:STDOUT: "typeDefinitionProvider": true
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT: }Content-Length: 482{{\r}}
@@ -27,16 +27,21 @@ class Incomplete {
[[@LSP-CALL:shutdown]]
[[@LSP-NOTIFY:exit]]
// CHECK:STDOUT: Content-Length: 182{{\r}}
// CHECK:STDOUT: Content-Length: 352{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 1,
// CHECK:STDOUT: "jsonrpc": "2.0",
// CHECK:STDOUT: "result": {
// CHECK:STDOUT: "capabilities": {
// CHECK:STDOUT: "declarationProvider": true,
// CHECK:STDOUT: "definitionProvider": true,
// CHECK:STDOUT: "documentSymbolProvider": true,
// CHECK:STDOUT: "hoverProvider": true,
// CHECK:STDOUT: "positionEncoding": "utf-16",
// CHECK:STDOUT: "textDocumentSync": 2
// CHECK:STDOUT: "referencesProvider": true,
// CHECK:STDOUT: "textDocumentSync": 2,
// CHECK:STDOUT: "typeDefinitionProvider": true
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT: }Content-Length: 1552{{\r}}
@@ -20,16 +20,21 @@
// CHECK:STDERR: /test.cpp: warning: non-Carbon file requested [LanguageServerFileUnsupported]
// CHECK:STDERR:
// CHECK:STDOUT: Content-Length: 182{{\r}}
// CHECK:STDOUT: Content-Length: 352{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 1,
// CHECK:STDOUT: "jsonrpc": "2.0",
// CHECK:STDOUT: "result": {
// CHECK:STDOUT: "capabilities": {
// CHECK:STDOUT: "declarationProvider": true,
// CHECK:STDOUT: "definitionProvider": true,
// CHECK:STDOUT: "documentSymbolProvider": true,
// CHECK:STDOUT: "hoverProvider": true,
// CHECK:STDOUT: "positionEncoding": "utf-16",
// CHECK:STDOUT: "textDocumentSync": 2
// CHECK:STDOUT: "referencesProvider": true,
// CHECK:STDOUT: "textDocumentSync": 2,
// CHECK:STDOUT: "typeDefinitionProvider": true
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT: }Content-Length: 51{{\r}}
@@ -28,16 +28,21 @@ fn Bar() {}
[[@LSP-CALL:shutdown]]
[[@LSP-NOTIFY:exit]]
// CHECK:STDOUT: Content-Length: 182{{\r}}
// CHECK:STDOUT: Content-Length: 352{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 1,
// CHECK:STDOUT: "jsonrpc": "2.0",
// CHECK:STDOUT: "result": {
// CHECK:STDOUT: "capabilities": {
// CHECK:STDOUT: "declarationProvider": true,
// CHECK:STDOUT: "definitionProvider": true,
// CHECK:STDOUT: "documentSymbolProvider": true,
// CHECK:STDOUT: "hoverProvider": true,
// CHECK:STDOUT: "positionEncoding": "utf-16",
// CHECK:STDOUT: "textDocumentSync": 2
// CHECK:STDOUT: "referencesProvider": true,
// CHECK:STDOUT: "textDocumentSync": 2,
// CHECK:STDOUT: "typeDefinitionProvider": true
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT: }Content-Length: 149{{\r}}
@@ -20,16 +20,21 @@
// CHECK:STDERR: /test.carbon: warning: unknown file requested [LanguageServerFileUnknown]
// CHECK:STDERR:
// CHECK:STDOUT: Content-Length: 182{{\r}}
// CHECK:STDOUT: Content-Length: 352{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 1,
// CHECK:STDOUT: "jsonrpc": "2.0",
// CHECK:STDOUT: "result": {
// CHECK:STDOUT: "capabilities": {
// CHECK:STDOUT: "declarationProvider": true,
// CHECK:STDOUT: "definitionProvider": true,
// CHECK:STDOUT: "documentSymbolProvider": true,
// CHECK:STDOUT: "hoverProvider": true,
// CHECK:STDOUT: "positionEncoding": "utf-16",
// CHECK:STDOUT: "textDocumentSync": 2
// CHECK:STDOUT: "referencesProvider": true,
// CHECK:STDOUT: "textDocumentSync": 2,
// CHECK:STDOUT: "typeDefinitionProvider": true
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT: }Content-Length: 51{{\r}}
@@ -0,0 +1,131 @@
// 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
//
// AUTOUPDATE
// TIP: To test this file alone, run:
// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/language_server/testdata/position/bad_params.carbon
// TIP: To dump output, run:
// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/language_server/testdata/position/bad_params.carbon
// CHECK:STDERR: /other_file.carbon: warning: unknown file requested [LanguageServerFileUnknown]
// CHECK:STDERR:
// CHECK:STDERR: /other_file.carbon: warning: unknown file requested [LanguageServerFileUnknown]
// CHECK:STDERR:
// CHECK:STDERR: /other_file.carbon: warning: unknown file requested [LanguageServerFileUnknown]
// CHECK:STDERR:
// CHECK:STDERR: /other_file.carbon: warning: unknown file requested [LanguageServerFileUnknown]
// CHECK:STDERR:
// CHECK:STDERR: /other_file.carbon: warning: unknown file requested [LanguageServerFileUnknown]
// CHECK:STDERR:
// --- STDIN
[[@LSP-CALL:initialize:"capabilities": {}]]
[[@LSP-NOTIFY:textDocument/didOpen:
"textDocument": {
"uri": "file:/some_file.carbon",
"languageId": "carbon",
"text": ""
}
]]
[[@LSP-CALL:textDocument/hover:
"textDocument": {"uri": "file:/other_file.carbon"},
"position": {"line": 4, "character": 2}
]]
[[@LSP-CALL:textDocument/definition:
"textDocument": {"uri": "file:/other_file.carbon"},
"position": {"line": 4, "character": 2}
]]
[[@LSP-CALL:textDocument/definition:
"textDocument": {"uri": "file:/other_file.carbon"},
"position": {"line": 4, "character": 6}
]]
[[@LSP-CALL:textDocument/references:
"textDocument": {"uri": "file:/other_file.carbon"},
"position": {"line": 3, "character": 6},
"context": {"includeDeclaration": true}
]]
[[@LSP-CALL:textDocument/typeDefinition:
"textDocument": {"uri": "file:/other_file.carbon"},
"position": {"line": 4, "character": 6}
]]
[[@LSP-CALL:shutdown]]
[[@LSP-NOTIFY:exit]]
// CHECK:STDOUT: Content-Length: 352{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 1,
// CHECK:STDOUT: "jsonrpc": "2.0",
// CHECK:STDOUT: "result": {
// CHECK:STDOUT: "capabilities": {
// CHECK:STDOUT: "declarationProvider": true,
// CHECK:STDOUT: "definitionProvider": true,
// CHECK:STDOUT: "documentSymbolProvider": true,
// CHECK:STDOUT: "hoverProvider": true,
// CHECK:STDOUT: "positionEncoding": "utf-16",
// CHECK:STDOUT: "referencesProvider": true,
// CHECK:STDOUT: "textDocumentSync": 2,
// CHECK:STDOUT: "typeDefinitionProvider": true
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT: }Content-Length: 149{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "jsonrpc": "2.0",
// CHECK:STDOUT: "method": "textDocument/publishDiagnostics",
// CHECK:STDOUT: "params": {
// CHECK:STDOUT: "diagnostics": [],
// CHECK:STDOUT: "uri": "file:///some_file.carbon"
// CHECK:STDOUT: }
// CHECK:STDOUT: }Content-Length: 130{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "error": {
// CHECK:STDOUT: "code": -32602,
// CHECK:STDOUT: "message": "Unknown textDocument `/other_file.carbon`"
// CHECK:STDOUT: },
// CHECK:STDOUT: "id": 2,
// CHECK:STDOUT: "jsonrpc": "2.0"
// CHECK:STDOUT: }Content-Length: 130{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "error": {
// CHECK:STDOUT: "code": -32602,
// CHECK:STDOUT: "message": "Unknown textDocument `/other_file.carbon`"
// CHECK:STDOUT: },
// CHECK:STDOUT: "id": 3,
// CHECK:STDOUT: "jsonrpc": "2.0"
// CHECK:STDOUT: }Content-Length: 130{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "error": {
// CHECK:STDOUT: "code": -32602,
// CHECK:STDOUT: "message": "Unknown textDocument `/other_file.carbon`"
// CHECK:STDOUT: },
// CHECK:STDOUT: "id": 4,
// CHECK:STDOUT: "jsonrpc": "2.0"
// CHECK:STDOUT: }Content-Length: 130{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "error": {
// CHECK:STDOUT: "code": -32602,
// CHECK:STDOUT: "message": "Unknown textDocument `/other_file.carbon`"
// CHECK:STDOUT: },
// CHECK:STDOUT: "id": 5,
// CHECK:STDOUT: "jsonrpc": "2.0"
// CHECK:STDOUT: }Content-Length: 130{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "error": {
// CHECK:STDOUT: "code": -32602,
// CHECK:STDOUT: "message": "Unknown textDocument `/other_file.carbon`"
// CHECK:STDOUT: },
// CHECK:STDOUT: "id": 6,
// CHECK:STDOUT: "jsonrpc": "2.0"
// CHECK:STDOUT: }Content-Length: 51{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 7,
// CHECK:STDOUT: "jsonrpc": "2.0",
// CHECK:STDOUT: "result": null
// CHECK:STDOUT: }
@@ -0,0 +1,263 @@
// 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
//
// AUTOUPDATE
// TIP: To test this file alone, run:
// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/language_server/testdata/position/hover_and_goto.carbon
// TIP: To dump output, run:
// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/language_server/testdata/position/hover_and_goto.carbon
// Requests are answered at the name a user points at, not at the token the
// parser happened to finish a declaration on, so `definition` from the call on
// line 4 lands on `Abs` on line 0 rather than on the `{` of its body.
// `references` matches on that same name, which is why asking at the
// declaration of `x` still finds its use.
// --- position.carbon
fn Abs(n: i32) -> i32 { return n; }
fn Run() {
var x: i32 = 1;
Abs(x);
}
// --- STDIN
[[@LSP-CALL:initialize:"capabilities": {}]]
[[@LSP-NOTIFY:textDocument/didOpen:
"textDocument": {
"uri": "file:/position.carbon",
"languageId": "carbon",
"text": "FROM_FILE_SPLIT"
}
]]
[[@LSP-CALL:textDocument/hover:
"textDocument": {"uri": "file:/position.carbon"},
"position": {"line": 4, "character": 2}
]]
[[@LSP-CALL:textDocument/definition:
"textDocument": {"uri": "file:/position.carbon"},
"position": {"line": 4, "character": 2}
]]
[[@LSP-CALL:textDocument/definition:
"textDocument": {"uri": "file:/position.carbon"},
"position": {"line": 4, "character": 6}
]]
[[@LSP-CALL:textDocument/references:
"textDocument": {"uri": "file:/position.carbon"},
"position": {"line": 3, "character": 6},
"context": {"includeDeclaration": true}
]]
[[@LSP-CALL:textDocument/typeDefinition:
"textDocument": {"uri": "file:/position.carbon"},
"position": {"line": 4, "character": 6}
]]
[[@LSP-CALL:textDocument/hover:
"textDocument": {"uri": "file:/position.carbon"},
"position": {"line": 2, "character": 0}
]]
[[@LSP-CALL:shutdown]]
[[@LSP-NOTIFY:exit]]
// CHECK:STDOUT: Content-Length: 352{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 1,
// CHECK:STDOUT: "jsonrpc": "2.0",
// CHECK:STDOUT: "result": {
// CHECK:STDOUT: "capabilities": {
// CHECK:STDOUT: "declarationProvider": true,
// CHECK:STDOUT: "definitionProvider": true,
// CHECK:STDOUT: "documentSymbolProvider": true,
// CHECK:STDOUT: "hoverProvider": true,
// CHECK:STDOUT: "positionEncoding": "utf-16",
// CHECK:STDOUT: "referencesProvider": true,
// CHECK:STDOUT: "textDocumentSync": 2,
// CHECK:STDOUT: "typeDefinitionProvider": true
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT: }Content-Length: 1570{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "jsonrpc": "2.0",
// CHECK:STDOUT: "method": "textDocument/publishDiagnostics",
// CHECK:STDOUT: "params": {
// CHECK:STDOUT: "diagnostics": [
// CHECK:STDOUT: {
// CHECK:STDOUT: "message": "`Core.Int` implicitly referenced here, but package `Core` not found",
// CHECK:STDOUT: "range": {
// CHECK:STDOUT: "end": {
// CHECK:STDOUT: "character": 13,
// CHECK:STDOUT: "line": 0
// CHECK:STDOUT: },
// CHECK:STDOUT: "start": {
// CHECK:STDOUT: "character": 10,
// CHECK:STDOUT: "line": 0
// CHECK:STDOUT: }
// CHECK:STDOUT: },
// CHECK:STDOUT: "severity": 1,
// CHECK:STDOUT: "source": "carbon"
// CHECK:STDOUT: },
// CHECK:STDOUT: {
// CHECK:STDOUT: "message": "`Core.Int` implicitly referenced here, but package `Core` not found",
// CHECK:STDOUT: "range": {
// CHECK:STDOUT: "end": {
// CHECK:STDOUT: "character": 21,
// CHECK:STDOUT: "line": 0
// CHECK:STDOUT: },
// CHECK:STDOUT: "start": {
// CHECK:STDOUT: "character": 18,
// CHECK:STDOUT: "line": 0
// CHECK:STDOUT: }
// CHECK:STDOUT: },
// CHECK:STDOUT: "severity": 1,
// CHECK:STDOUT: "source": "carbon"
// CHECK:STDOUT: },
// CHECK:STDOUT: {
// CHECK:STDOUT: "message": "`Core.Int` implicitly referenced here, but package `Core` not found",
// CHECK:STDOUT: "range": {
// CHECK:STDOUT: "end": {
// CHECK:STDOUT: "character": 12,
// CHECK:STDOUT: "line": 3
// CHECK:STDOUT: },
// CHECK:STDOUT: "start": {
// CHECK:STDOUT: "character": 9,
// CHECK:STDOUT: "line": 3
// CHECK:STDOUT: }
// CHECK:STDOUT: },
// CHECK:STDOUT: "severity": 1,
// CHECK:STDOUT: "source": "carbon"
// CHECK:STDOUT: },
// CHECK:STDOUT: {
// CHECK:STDOUT: "message": "`Core.Destroy` implicitly referenced here, but package `Core` not found",
// CHECK:STDOUT: "range": {
// CHECK:STDOUT: "end": {
// CHECK:STDOUT: "character": 12,
// CHECK:STDOUT: "line": 3
// CHECK:STDOUT: },
// CHECK:STDOUT: "start": {
// CHECK:STDOUT: "character": 2,
// CHECK:STDOUT: "line": 3
// CHECK:STDOUT: }
// CHECK:STDOUT: },
// CHECK:STDOUT: "severity": 1,
// CHECK:STDOUT: "source": "carbon"
// CHECK:STDOUT: }
// CHECK:STDOUT: ],
// CHECK:STDOUT: "uri": "file:///position.carbon"
// CHECK:STDOUT: }
// CHECK:STDOUT: }Content-Length: 309{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 2,
// CHECK:STDOUT: "jsonrpc": "2.0",
// CHECK:STDOUT: "result": {
// CHECK:STDOUT: "contents": {
// CHECK:STDOUT: "kind": "markdown",
// CHECK:STDOUT: "value": "```carbon\nAbs: <type of Abs>\n```"
// CHECK:STDOUT: },
// CHECK:STDOUT: "range": {
// CHECK:STDOUT: "end": {
// CHECK:STDOUT: "character": 5,
// CHECK:STDOUT: "line": 4
// CHECK:STDOUT: },
// CHECK:STDOUT: "start": {
// CHECK:STDOUT: "character": 2,
// CHECK:STDOUT: "line": 4
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT: }Content-Length: 278{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 3,
// CHECK:STDOUT: "jsonrpc": "2.0",
// CHECK:STDOUT: "result": [
// CHECK:STDOUT: {
// CHECK:STDOUT: "range": {
// CHECK:STDOUT: "end": {
// CHECK:STDOUT: "character": 6,
// CHECK:STDOUT: "line": 0
// CHECK:STDOUT: },
// CHECK:STDOUT: "start": {
// CHECK:STDOUT: "character": 3,
// CHECK:STDOUT: "line": 0
// CHECK:STDOUT: }
// CHECK:STDOUT: },
// CHECK:STDOUT: "uri": "file:///position.carbon"
// CHECK:STDOUT: }
// CHECK:STDOUT: ]
// CHECK:STDOUT: }Content-Length: 278{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 4,
// CHECK:STDOUT: "jsonrpc": "2.0",
// CHECK:STDOUT: "result": [
// CHECK:STDOUT: {
// CHECK:STDOUT: "range": {
// CHECK:STDOUT: "end": {
// CHECK:STDOUT: "character": 7,
// CHECK:STDOUT: "line": 3
// CHECK:STDOUT: },
// CHECK:STDOUT: "start": {
// CHECK:STDOUT: "character": 6,
// CHECK:STDOUT: "line": 3
// CHECK:STDOUT: }
// CHECK:STDOUT: },
// CHECK:STDOUT: "uri": "file:///position.carbon"
// CHECK:STDOUT: }
// CHECK:STDOUT: ]
// CHECK:STDOUT: }Content-Length: 505{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 5,
// CHECK:STDOUT: "jsonrpc": "2.0",
// CHECK:STDOUT: "result": [
// CHECK:STDOUT: {
// CHECK:STDOUT: "range": {
// CHECK:STDOUT: "end": {
// CHECK:STDOUT: "character": 7,
// CHECK:STDOUT: "line": 3
// CHECK:STDOUT: },
// CHECK:STDOUT: "start": {
// CHECK:STDOUT: "character": 6,
// CHECK:STDOUT: "line": 3
// CHECK:STDOUT: }
// CHECK:STDOUT: },
// CHECK:STDOUT: "uri": "file:///position.carbon"
// CHECK:STDOUT: },
// CHECK:STDOUT: {
// CHECK:STDOUT: "range": {
// CHECK:STDOUT: "end": {
// CHECK:STDOUT: "character": 7,
// CHECK:STDOUT: "line": 4
// CHECK:STDOUT: },
// CHECK:STDOUT: "start": {
// CHECK:STDOUT: "character": 6,
// CHECK:STDOUT: "line": 4
// CHECK:STDOUT: }
// CHECK:STDOUT: },
// CHECK:STDOUT: "uri": "file:///position.carbon"
// CHECK:STDOUT: }
// CHECK:STDOUT: ]
// CHECK:STDOUT: }Content-Length: 49{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 6,
// CHECK:STDOUT: "jsonrpc": "2.0",
// CHECK:STDOUT: "result": []
// CHECK:STDOUT: }Content-Length: 73{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 7,
// CHECK:STDOUT: "jsonrpc": "2.0",
// CHECK:STDOUT: "result": {
// CHECK:STDOUT: "contents": null
// CHECK:STDOUT: }
// CHECK:STDOUT: }Content-Length: 51{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 8,
// CHECK:STDOUT: "jsonrpc": "2.0",
// CHECK:STDOUT: "result": null
// CHECK:STDOUT: }
@@ -21,16 +21,21 @@
// CHECK:STDERR: /test.carbon: warning: unknown file requested [LanguageServerFileUnknown]
// CHECK:STDERR:
// CHECK:STDOUT: Content-Length: 182{{\r}}
// CHECK:STDOUT: Content-Length: 352{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 1,
// CHECK:STDOUT: "jsonrpc": "2.0",
// CHECK:STDOUT: "result": {
// CHECK:STDOUT: "capabilities": {
// CHECK:STDOUT: "declarationProvider": true,
// CHECK:STDOUT: "definitionProvider": true,
// CHECK:STDOUT: "documentSymbolProvider": true,
// CHECK:STDOUT: "hoverProvider": true,
// CHECK:STDOUT: "positionEncoding": "utf-16",
// CHECK:STDOUT: "textDocumentSync": 2
// CHECK:STDOUT: "referencesProvider": true,
// CHECK:STDOUT: "textDocumentSync": 2,
// CHECK:STDOUT: "typeDefinitionProvider": true
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT: }Content-Length: 51{{\r}}
@@ -20,16 +20,21 @@
// CHECK:STDERR: /test.carbon: warning: tried closing unknown file; ignoring request [LanguageServerCloseUnknownFile]
// CHECK:STDERR:
// CHECK:STDOUT: Content-Length: 182{{\r}}
// CHECK:STDOUT: Content-Length: 352{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 1,
// CHECK:STDOUT: "jsonrpc": "2.0",
// CHECK:STDOUT: "result": {
// CHECK:STDOUT: "capabilities": {
// CHECK:STDOUT: "declarationProvider": true,
// CHECK:STDOUT: "definitionProvider": true,
// CHECK:STDOUT: "documentSymbolProvider": true,
// CHECK:STDOUT: "hoverProvider": true,
// CHECK:STDOUT: "positionEncoding": "utf-16",
// CHECK:STDOUT: "textDocumentSync": 2
// CHECK:STDOUT: "referencesProvider": true,
// CHECK:STDOUT: "textDocumentSync": 2,
// CHECK:STDOUT: "typeDefinitionProvider": true
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT: }Content-Length: 51{{\r}}
@@ -26,16 +26,21 @@
// --- AUTOUPDATE-SPLIT
// CHECK:STDOUT: Content-Length: 182{{\r}}
// CHECK:STDOUT: Content-Length: 352{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 1,
// CHECK:STDOUT: "jsonrpc": "2.0",
// CHECK:STDOUT: "result": {
// CHECK:STDOUT: "capabilities": {
// CHECK:STDOUT: "declarationProvider": true,
// CHECK:STDOUT: "definitionProvider": true,
// CHECK:STDOUT: "documentSymbolProvider": true,
// CHECK:STDOUT: "hoverProvider": true,
// CHECK:STDOUT: "positionEncoding": "utf-16",
// CHECK:STDOUT: "textDocumentSync": 2
// CHECK:STDOUT: "referencesProvider": true,
// CHECK:STDOUT: "textDocumentSync": 2,
// CHECK:STDOUT: "typeDefinitionProvider": true
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT: }Content-Length: 1164{{\r}}
@@ -28,16 +28,21 @@
// --- AUTOUPDATE-SPLIT
// CHECK:STDOUT: Content-Length: 182{{\r}}
// CHECK:STDOUT: Content-Length: 352{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 1,
// CHECK:STDOUT: "jsonrpc": "2.0",
// CHECK:STDOUT: "result": {
// CHECK:STDOUT: "capabilities": {
// CHECK:STDOUT: "declarationProvider": true,
// CHECK:STDOUT: "definitionProvider": true,
// CHECK:STDOUT: "documentSymbolProvider": true,
// CHECK:STDOUT: "hoverProvider": true,
// CHECK:STDOUT: "positionEncoding": "utf-16",
// CHECK:STDOUT: "textDocumentSync": 2
// CHECK:STDOUT: "referencesProvider": true,
// CHECK:STDOUT: "textDocumentSync": 2,
// CHECK:STDOUT: "typeDefinitionProvider": true
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT: }Content-Length: 162{{\r}}
@@ -107,16 +107,21 @@
// --- AUTOUPDATE-SPLIT
// CHECK:STDOUT: Content-Length: 182{{\r}}
// CHECK:STDOUT: Content-Length: 352{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 1,
// CHECK:STDOUT: "jsonrpc": "2.0",
// CHECK:STDOUT: "result": {
// CHECK:STDOUT: "capabilities": {
// CHECK:STDOUT: "declarationProvider": true,
// CHECK:STDOUT: "definitionProvider": true,
// CHECK:STDOUT: "documentSymbolProvider": true,
// CHECK:STDOUT: "hoverProvider": true,
// CHECK:STDOUT: "positionEncoding": "utf-16",
// CHECK:STDOUT: "textDocumentSync": 2
// CHECK:STDOUT: "referencesProvider": true,
// CHECK:STDOUT: "textDocumentSync": 2,
// CHECK:STDOUT: "typeDefinitionProvider": true
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT: }Content-Length: 144{{\r}}
@@ -35,16 +35,21 @@
// --- AUTOUPDATE-SPLIT
// CHECK:STDOUT: Content-Length: 182{{\r}}
// CHECK:STDOUT: Content-Length: 352{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 1,
// CHECK:STDOUT: "jsonrpc": "2.0",
// CHECK:STDOUT: "result": {
// CHECK:STDOUT: "capabilities": {
// CHECK:STDOUT: "declarationProvider": true,
// CHECK:STDOUT: "definitionProvider": true,
// CHECK:STDOUT: "documentSymbolProvider": true,
// CHECK:STDOUT: "hoverProvider": true,
// CHECK:STDOUT: "positionEncoding": "utf-16",
// CHECK:STDOUT: "textDocumentSync": 2
// CHECK:STDOUT: "referencesProvider": true,
// CHECK:STDOUT: "textDocumentSync": 2,
// CHECK:STDOUT: "typeDefinitionProvider": true
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT: }Content-Length: 144{{\r}}
@@ -25,16 +25,21 @@
// --- AUTOUPDATE-SPLIT
// CHECK:STDOUT: Content-Length: 182{{\r}}
// CHECK:STDOUT: Content-Length: 352{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 1,
// CHECK:STDOUT: "jsonrpc": "2.0",
// CHECK:STDOUT: "result": {
// CHECK:STDOUT: "capabilities": {
// CHECK:STDOUT: "declarationProvider": true,
// CHECK:STDOUT: "definitionProvider": true,
// CHECK:STDOUT: "documentSymbolProvider": true,
// CHECK:STDOUT: "hoverProvider": true,
// CHECK:STDOUT: "positionEncoding": "utf-16",
// CHECK:STDOUT: "textDocumentSync": 2
// CHECK:STDOUT: "referencesProvider": true,
// CHECK:STDOUT: "textDocumentSync": 2,
// CHECK:STDOUT: "typeDefinitionProvider": true
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT: }Content-Length: 161{{\r}}
@@ -26,16 +26,21 @@
// --- AUTOUPDATE-SPLIT
// CHECK:STDOUT: Content-Length: 182{{\r}}
// CHECK:STDOUT: Content-Length: 352{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 1,
// CHECK:STDOUT: "jsonrpc": "2.0",
// CHECK:STDOUT: "result": {
// CHECK:STDOUT: "capabilities": {
// CHECK:STDOUT: "declarationProvider": true,
// CHECK:STDOUT: "definitionProvider": true,
// CHECK:STDOUT: "documentSymbolProvider": true,
// CHECK:STDOUT: "hoverProvider": true,
// CHECK:STDOUT: "positionEncoding": "utf-16",
// CHECK:STDOUT: "textDocumentSync": 2
// CHECK:STDOUT: "referencesProvider": true,
// CHECK:STDOUT: "textDocumentSync": 2,
// CHECK:STDOUT: "typeDefinitionProvider": true
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT: }Content-Length: 144{{\r}}
@@ -25,16 +25,21 @@
// CHECK:STDERR: /test.carbon: warning: duplicate open file request; updating content [LanguageServerOpenDuplicateFile]
// CHECK:STDERR:
// CHECK:STDOUT: Content-Length: 182{{\r}}
// CHECK:STDOUT: Content-Length: 352{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 1,
// CHECK:STDOUT: "jsonrpc": "2.0",
// CHECK:STDOUT: "result": {
// CHECK:STDOUT: "capabilities": {
// CHECK:STDOUT: "declarationProvider": true,
// CHECK:STDOUT: "definitionProvider": true,
// CHECK:STDOUT: "documentSymbolProvider": true,
// CHECK:STDOUT: "hoverProvider": true,
// CHECK:STDOUT: "positionEncoding": "utf-16",
// CHECK:STDOUT: "textDocumentSync": 2
// CHECK:STDOUT: "referencesProvider": true,
// CHECK:STDOUT: "textDocumentSync": 2,
// CHECK:STDOUT: "typeDefinitionProvider": true
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT: }Content-Length: 144{{\r}}
@@ -22,16 +22,21 @@
// --- AUTOUPDATE-SPLIT
// CHECK:STDOUT: Content-Length: 182{{\r}}
// CHECK:STDOUT: Content-Length: 352{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 1,
// CHECK:STDOUT: "jsonrpc": "2.0",
// CHECK:STDOUT: "result": {
// CHECK:STDOUT: "capabilities": {
// CHECK:STDOUT: "declarationProvider": true,
// CHECK:STDOUT: "definitionProvider": true,
// CHECK:STDOUT: "documentSymbolProvider": true,
// CHECK:STDOUT: "hoverProvider": true,
// CHECK:STDOUT: "positionEncoding": "utf-16",
// CHECK:STDOUT: "textDocumentSync": 2
// CHECK:STDOUT: "referencesProvider": true,
// CHECK:STDOUT: "textDocumentSync": 2,
// CHECK:STDOUT: "typeDefinitionProvider": true
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT: }Content-Length: 464{{\r}}