From c78751338b76d199bf598c9f12aeb65f2eb6a7a1 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Wed, 19 Aug 2026 14:35:05 +0000 Subject: [PATCH] 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 "", but is a starting point for richer information. Assisted-by: Claude Code --- toolchain/driver/compile_driver.h | 3 + toolchain/language_server/BUILD | 41 +++ toolchain/language_server/context.cpp | 12 + toolchain/language_server/context.h | 36 ++- toolchain/language_server/handle.h | 27 ++ .../language_server/handle_initialize.cpp | 9 +- toolchain/language_server/handle_position.cpp | 194 +++++++++++++ .../language_server/incoming_messages.cpp | 5 + toolchain/language_server/position.cpp | 134 +++++++++ toolchain/language_server/position.h | 75 +++++ toolchain/language_server/sem_ir_index.cpp | 101 +++++++ toolchain/language_server/sem_ir_index.h | 69 +++++ .../basics/fail_shutdown_without_exit.carbon | 9 +- .../testdata/basics/initialize.carbon | 27 +- .../testdata/basics/notify_parse_error.carbon | 9 +- .../testdata/document_symbol/basics.carbon | 9 +- .../testdata/document_symbol/choice.carbon | 9 +- .../decl_with_parse_error.carbon | 9 +- .../document_symbol/fn_definition.carbon | 9 +- .../document_symbol/incomplete.carbon | 9 +- .../testdata/document_symbol/language.carbon | 9 +- .../testdata/document_symbol/namespace.carbon | 9 +- .../testdata/document_symbol/unknown.carbon | 9 +- .../testdata/position/bad_params.carbon | 131 +++++++++ .../testdata/position/hover_and_goto.carbon | 263 ++++++++++++++++++ .../text_document/change_unknown.carbon | 9 +- .../text_document/close_unknown.carbon | 9 +- .../testdata/text_document/diagnostics.carbon | 9 +- .../text_document/import_prelude.carbon | 9 +- .../text_document/incremental_sync.carbon | 9 +- .../incremental_sync_multiline.carbon | 9 +- .../text_document/multiple_files.carbon | 9 +- .../text_document/open_change_close.carbon | 9 +- .../text_document/open_duplicate.carbon | 9 +- .../open_with_cpp_nonexistent.carbon | 9 +- 35 files changed, 1256 insertions(+), 51 deletions(-) create mode 100644 toolchain/language_server/handle_position.cpp create mode 100644 toolchain/language_server/position.cpp create mode 100644 toolchain/language_server/position.h create mode 100644 toolchain/language_server/sem_ir_index.cpp create mode 100644 toolchain/language_server/sem_ir_index.h create mode 100644 toolchain/language_server/testdata/position/bad_params.carbon create mode 100644 toolchain/language_server/testdata/position/hover_and_goto.carbon diff --git a/toolchain/driver/compile_driver.h b/toolchain/driver/compile_driver.h index cdb1d163b5d6..a63e16016d85 100644 --- a/toolchain/driver/compile_driver.h +++ b/toolchain/driver/compile_driver.h @@ -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. diff --git a/toolchain/language_server/BUILD b/toolchain/language_server/BUILD index c39f04123f7c..454dfb5764bb 100644 --- a/toolchain/language_server/BUILD +++ b/toolchain/language_server/BUILD @@ -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", diff --git a/toolchain/language_server/context.cpp b/toolchain/language_server/context.cpp index 28d26f85800b..3b4b11917aca 100644 --- a/toolchain/language_server/context.cpp +++ b/toolchain/language_server/context.cpp @@ -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 version, llvm::StringRef text) -> void { // Clear state dependent on the source text. compile_driver_.reset(); + sem_ir_index_.reset(); text_ = text.str(); diff --git a/toolchain/language_server/context.h b/toolchain/language_server/context.h index 0cd700e9ad94..676d242d9a99 100644 --- a/toolchain/language_server/context.h +++ b/toolchain/language_server/context.h @@ -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 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 compile_driver_; + + // Built on demand by `sem_ir_index()`, and discarded by `SetText`. + mutable std::optional sem_ir_index_; }; // `vlog_stream` is optional; other parameters are required. diff --git a/toolchain/language_server/handle.h b/toolchain/language_server/handle.h index 16d58ed07a4b..9964d4c5e634 100644 --- a/toolchain/language_server/handle.h +++ b/toolchain/language_server/handle.h @@ -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>)->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>)->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)->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>)->void> + on_done) -> void; + // Prepares LSP for shutdown. auto HandleShutdown( Context& /*context*/, @@ -55,6 +75,13 @@ auto HandleShutdown( llvm::function_ref)->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>)->void> + on_done) -> void; + } // namespace Carbon::LanguageServer #endif // CARBON_TOOLCHAIN_LANGUAGE_SERVER_HANDLE_H_ diff --git a/toolchain/language_server/handle_initialize.cpp b/toolchain/language_server/handle_initialize.cpp index 9e4d0672bf66..0c48b70542ed 100644 --- a/toolchain/language_server/handle_initialize.cpp +++ b/toolchain/language_server/handle_initialize.cpp @@ -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); } diff --git a/toolchain/language_server/handle_position.cpp b/toolchain/language_server/handle_position.cpp new file mode 100644 index 000000000000..891b81434e0a --- /dev/null +++ b/toolchain/language_server/handle_position.cpp @@ -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 +#include +#include + +#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 +// `` for a function and `` 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 +static auto FindInstAtPositionOrFail( + Context& context, const clang::clangd::TextDocumentPositionParams& params, + llvm::function_ref)->void> on_done) + -> PositionInfo { + auto* file = context.LookupFile(params.textDocument.uri.file()); + if (!file) { + on_done(llvm::make_error( + 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)->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>)->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()); + return; + } + target_id = sem_ir.types().GetTypeInstId(type_id); + } + + std::vector 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>)->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>)->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>)->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()); + 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 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(); + 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 diff --git a/toolchain/language_server/incoming_messages.cpp b/toolchain/language_server/incoming_messages.cpp index d13bc5ce56de..36420077dca2 100644 --- a/toolchain/language_server/incoming_messages.cpp +++ b/toolchain/language_server/incoming_messages.cpp @@ -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); diff --git a/toolchain/language_server/position.cpp b/toolchain/language_server/position.cpp new file mode 100644 index 000000000000..e151e25a9f09 --- /dev/null +++ b/toolchain/language_server/position.cpp @@ -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 +#include +#include +#include + +#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 { + return {tokens.GetLine(token).index, tokens.GetColumnNumber(token) - 1}; +} + +auto FindToken(const Lex::TokenizedBuffer& tokens, + const clang::clangd::Position& position) -> Lex::TokenIndex { + std::pair 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(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(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(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 { + 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 diff --git a/toolchain/language_server/position.h b/toolchain/language_server/position.h new file mode 100644 index 000000000000..a0cdde5aac64 --- /dev/null +++ b/toolchain/language_server/position.h @@ -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; + +} // namespace Carbon::LanguageServer + +#endif // CARBON_TOOLCHAIN_LANGUAGE_SERVER_POSITION_H_ diff --git a/toolchain/language_server/sem_ir_index.cpp b/toolchain/language_server/sem_ir_index.cpp new file mode 100644 index 000000000000..95f6b7d42542 --- /dev/null +++ b/toolchain/language_server/sem_ir_index.cpp @@ -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 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 { + if (!token.has_value()) { + return {}; + } + CARBON_CHECK(static_cast(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 diff --git a/toolchain/language_server/sem_ir_index.h b/toolchain/language_server/sem_ir_index.h new file mode 100644 index 000000000000..4d3dcb06ee4b --- /dev/null +++ b/toolchain/language_server/sem_ir_index.h @@ -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; + + 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 insts_; + llvm::SmallVector token_starts_; +}; + +} // namespace Carbon::LanguageServer + +#endif // CARBON_TOOLCHAIN_LANGUAGE_SERVER_SEM_IR_INDEX_H_ diff --git a/toolchain/language_server/testdata/basics/fail_shutdown_without_exit.carbon b/toolchain/language_server/testdata/basics/fail_shutdown_without_exit.carbon index 03279e5268f8..0ce55800a706 100644 --- a/toolchain/language_server/testdata/basics/fail_shutdown_without_exit.carbon +++ b/toolchain/language_server/testdata/basics/fail_shutdown_without_exit.carbon @@ -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}} diff --git a/toolchain/language_server/testdata/basics/initialize.carbon b/toolchain/language_server/testdata/basics/initialize.carbon index 51192556513f..ee6a2286fe7a 100644 --- a/toolchain/language_server/testdata/basics/initialize.carbon +++ b/toolchain/language_server/testdata/basics/initialize.carbon @@ -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}} diff --git a/toolchain/language_server/testdata/basics/notify_parse_error.carbon b/toolchain/language_server/testdata/basics/notify_parse_error.carbon index 4e51e3215369..671edae02112 100644 --- a/toolchain/language_server/testdata/basics/notify_parse_error.carbon +++ b/toolchain/language_server/testdata/basics/notify_parse_error.carbon @@ -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}} diff --git a/toolchain/language_server/testdata/document_symbol/basics.carbon b/toolchain/language_server/testdata/document_symbol/basics.carbon index e84d12f127fd..f8244340c091 100644 --- a/toolchain/language_server/testdata/document_symbol/basics.carbon +++ b/toolchain/language_server/testdata/document_symbol/basics.carbon @@ -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}} diff --git a/toolchain/language_server/testdata/document_symbol/choice.carbon b/toolchain/language_server/testdata/document_symbol/choice.carbon index e9e3c301f8a9..43b070672bc7 100644 --- a/toolchain/language_server/testdata/document_symbol/choice.carbon +++ b/toolchain/language_server/testdata/document_symbol/choice.carbon @@ -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}} diff --git a/toolchain/language_server/testdata/document_symbol/decl_with_parse_error.carbon b/toolchain/language_server/testdata/document_symbol/decl_with_parse_error.carbon index 67348b21d56c..c1736a0201ca 100644 --- a/toolchain/language_server/testdata/document_symbol/decl_with_parse_error.carbon +++ b/toolchain/language_server/testdata/document_symbol/decl_with_parse_error.carbon @@ -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}} diff --git a/toolchain/language_server/testdata/document_symbol/fn_definition.carbon b/toolchain/language_server/testdata/document_symbol/fn_definition.carbon index 2f1bdbfbd0b8..97a523be93cd 100644 --- a/toolchain/language_server/testdata/document_symbol/fn_definition.carbon +++ b/toolchain/language_server/testdata/document_symbol/fn_definition.carbon @@ -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}} diff --git a/toolchain/language_server/testdata/document_symbol/incomplete.carbon b/toolchain/language_server/testdata/document_symbol/incomplete.carbon index 2559dd9999ff..3825ca74ca34 100644 --- a/toolchain/language_server/testdata/document_symbol/incomplete.carbon +++ b/toolchain/language_server/testdata/document_symbol/incomplete.carbon @@ -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}} diff --git a/toolchain/language_server/testdata/document_symbol/language.carbon b/toolchain/language_server/testdata/document_symbol/language.carbon index 7859c6ea777e..b62f6ec855aa 100644 --- a/toolchain/language_server/testdata/document_symbol/language.carbon +++ b/toolchain/language_server/testdata/document_symbol/language.carbon @@ -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}} diff --git a/toolchain/language_server/testdata/document_symbol/namespace.carbon b/toolchain/language_server/testdata/document_symbol/namespace.carbon index 7099a706cc08..a5826a4d4a68 100644 --- a/toolchain/language_server/testdata/document_symbol/namespace.carbon +++ b/toolchain/language_server/testdata/document_symbol/namespace.carbon @@ -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}} diff --git a/toolchain/language_server/testdata/document_symbol/unknown.carbon b/toolchain/language_server/testdata/document_symbol/unknown.carbon index ce0366b14a81..4b6ba8713354 100644 --- a/toolchain/language_server/testdata/document_symbol/unknown.carbon +++ b/toolchain/language_server/testdata/document_symbol/unknown.carbon @@ -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}} diff --git a/toolchain/language_server/testdata/position/bad_params.carbon b/toolchain/language_server/testdata/position/bad_params.carbon new file mode 100644 index 000000000000..c7a763af292d --- /dev/null +++ b/toolchain/language_server/testdata/position/bad_params.carbon @@ -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: } diff --git a/toolchain/language_server/testdata/position/hover_and_goto.carbon b/toolchain/language_server/testdata/position/hover_and_goto.carbon new file mode 100644 index 000000000000..8edfd5181706 --- /dev/null +++ b/toolchain/language_server/testdata/position/hover_and_goto.carbon @@ -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: \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: } diff --git a/toolchain/language_server/testdata/text_document/change_unknown.carbon b/toolchain/language_server/testdata/text_document/change_unknown.carbon index 1d1669f2649e..3c1817730fb1 100644 --- a/toolchain/language_server/testdata/text_document/change_unknown.carbon +++ b/toolchain/language_server/testdata/text_document/change_unknown.carbon @@ -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}} diff --git a/toolchain/language_server/testdata/text_document/close_unknown.carbon b/toolchain/language_server/testdata/text_document/close_unknown.carbon index c51ee122f7b0..20b219daa1ea 100644 --- a/toolchain/language_server/testdata/text_document/close_unknown.carbon +++ b/toolchain/language_server/testdata/text_document/close_unknown.carbon @@ -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}} diff --git a/toolchain/language_server/testdata/text_document/diagnostics.carbon b/toolchain/language_server/testdata/text_document/diagnostics.carbon index 1239ef13af94..51cc196acb79 100644 --- a/toolchain/language_server/testdata/text_document/diagnostics.carbon +++ b/toolchain/language_server/testdata/text_document/diagnostics.carbon @@ -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}} diff --git a/toolchain/language_server/testdata/text_document/import_prelude.carbon b/toolchain/language_server/testdata/text_document/import_prelude.carbon index dc329b97e969..84297a4c262c 100644 --- a/toolchain/language_server/testdata/text_document/import_prelude.carbon +++ b/toolchain/language_server/testdata/text_document/import_prelude.carbon @@ -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}} diff --git a/toolchain/language_server/testdata/text_document/incremental_sync.carbon b/toolchain/language_server/testdata/text_document/incremental_sync.carbon index be4833a8b440..3eb27fe5243c 100644 --- a/toolchain/language_server/testdata/text_document/incremental_sync.carbon +++ b/toolchain/language_server/testdata/text_document/incremental_sync.carbon @@ -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}} diff --git a/toolchain/language_server/testdata/text_document/incremental_sync_multiline.carbon b/toolchain/language_server/testdata/text_document/incremental_sync_multiline.carbon index 80bb135626c0..a86eba23fd8d 100644 --- a/toolchain/language_server/testdata/text_document/incremental_sync_multiline.carbon +++ b/toolchain/language_server/testdata/text_document/incremental_sync_multiline.carbon @@ -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}} diff --git a/toolchain/language_server/testdata/text_document/multiple_files.carbon b/toolchain/language_server/testdata/text_document/multiple_files.carbon index a1d0b8b57ff9..8716a5cffaaf 100644 --- a/toolchain/language_server/testdata/text_document/multiple_files.carbon +++ b/toolchain/language_server/testdata/text_document/multiple_files.carbon @@ -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}} diff --git a/toolchain/language_server/testdata/text_document/open_change_close.carbon b/toolchain/language_server/testdata/text_document/open_change_close.carbon index 44d558db715a..0c3751a01dd6 100644 --- a/toolchain/language_server/testdata/text_document/open_change_close.carbon +++ b/toolchain/language_server/testdata/text_document/open_change_close.carbon @@ -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}} diff --git a/toolchain/language_server/testdata/text_document/open_duplicate.carbon b/toolchain/language_server/testdata/text_document/open_duplicate.carbon index 359137644c78..6768a2f1f272 100644 --- a/toolchain/language_server/testdata/text_document/open_duplicate.carbon +++ b/toolchain/language_server/testdata/text_document/open_duplicate.carbon @@ -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}} diff --git a/toolchain/language_server/testdata/text_document/open_with_cpp_nonexistent.carbon b/toolchain/language_server/testdata/text_document/open_with_cpp_nonexistent.carbon index 09ed859e869e..4519f9e1db5a 100644 --- a/toolchain/language_server/testdata/text_document/open_with_cpp_nonexistent.carbon +++ b/toolchain/language_server/testdata/text_document/open_with_cpp_nonexistent.carbon @@ -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}}